<?php
$user = Auth::user();
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);

 
$storageAlerts = [];
try {
    $alertStorages = Database::fetchAll("SELECT id, name, driver, health_status, health_message, status, disk_total, disk_used, max_usage_pct FROM media_storages WHERE health_status IN ('error','warning') OR (status = 'active' AND disk_total > 0 AND disk_used > 0 AND (disk_used / disk_total * 100) >= max_usage_pct)");
    foreach ($alertStorages as $as) {
         
         
        $asDisk = MediaStorage::liveDiskUsage($as);
        if (($as['health_status'] ?? '') === 'error') {
            $storageAlerts[] = ['id' => (int)$as['id'], 'name' => $as['name'], 'type' => 'error', 'msg' => $as['health_message'] ?: 'Connection error'];
        } elseif ($asDisk['disk_total'] > 0) {
            $pct = round($asDisk['disk_used'] / $asDisk['disk_total'] * 100);
            if ($pct >= (int)$as['max_usage_pct']) {
                $storageAlerts[] = ['id' => (int)$as['id'], 'name' => $as['name'], 'type' => 'warning', 'msg' => "Disk {$pct}% full (threshold: {$as['max_usage_pct']}%)"];
            }
        } elseif (($as['health_status'] ?? '') === 'warning') {
            $storageAlerts[] = ['id' => (int)$as['id'], 'name' => $as['name'], 'type' => 'warning', 'msg' => $as['health_message'] ?: 'Warning'];
        }
    }
} catch (\Throwable) {}

 
try {
    if (MediaStorage::isAllFull()) {
        $storageAlerts[] = ['id' => 0, 'name' => 'All Storage Servers', 'type' => 'error', 'msg' => 'All storage is full — imports are paused. Add more space or a new storage server.', 'link' => '/admin/settings?tab=storage'];
    }
} catch (\Throwable) {}

 
try {
    $downServers = Database::fetchAll("SELECT id, name, status, last_error FROM transcode_servers WHERE is_active = 1 AND status = 'error'");
    foreach ($downServers as $ds) {
        $storageAlerts[] = ['id' => 0, 'name' => $ds['name'], 'type' => 'error', 'msg' => $ds['last_error'] ?: 'Server unreachable', 'link' => '/admin/settings?tab=transcode&sub=servers'];
    }
} catch (\Throwable) {}

 
try {
    if (Setting::get('entity_autotranslate_blocked', '0') === '1') {
        $storageAlerts[] = ['id' => 0, 'name' => 'Auto-translation paused', 'type' => 'warning', 'msg' => Setting::get('entity_autotranslate_blocked_msg', 'Insufficient credits — top up to resume.'), 'link' => '/admin/import'];
    }
} catch (\Throwable) {}

$unreadMessages = ContactMessage::countUnread();
$pendingReports = Report::countPending();
$_updateCount = CatalogueClient::isConfigured() ? UpdateManager::countUpdates() : 0;
$_updateData = ($_updateCount > 0) ? json_decode(Setting::get('update_check_data', '{}'), true) : [];
$_pluginUpdates = count($_updateData['plugins'] ?? []);
$_themeUpdates = count($_updateData['themes'] ?? []);
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="<?= Router::csrfToken() ?>">
    <title><?= htmlspecialchars($pageTitle ?? 'Admin') ?> - TubePress</title>
<?php $_adminFavicon = Setting::get('site_favicon', ''); ?>
<?php if ($_adminFavicon !== ''): ?>
    <link rel="icon" href="/uploads/branding/<?= htmlspecialchars($_adminFavicon) ?>">
<?php else: ?>
     
     
    <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%232563eb'/%3E%3Cpath d='M6.2 4.6l5 3.4-5 3.4z' fill='%23fff'/%3E%3C/svg%3E">
<?php endif; ?>
     
     
    <link rel="preload" as="font" type="font/woff2" crossorigin
          href="/assets/fontawesomepro/webfonts/fa-solid-900.woff2">
     
     
    <link rel="stylesheet" href="<?= AdminAssets::url('flag-icons/flag-icons.css') ?>">
    <link rel="stylesheet" href="/assets/fontawesomepro/css/all.min.css">
    <style>
        select {
            appearance: none;
            -webkit-appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 10px center;
            background-size: 16px;
            padding-right: 36px !important;
        }
        .scrollbar-hide::-webkit-scrollbar { display: none; }
        .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }

         
        .admin-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; -ms-overflow-style: none; }
        .admin-tabs::-webkit-scrollbar { display: none; }
        .admin-tabs a, .admin-tabs button { white-space: nowrap; flex-shrink: 0; }
        @media (max-width: 1023px) {
            nav.flex, #vfTabNav { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
            nav.flex::-webkit-scrollbar, #vfTabNav::-webkit-scrollbar { display: none; }
            nav.flex > a, nav.flex > button, #vfTabNav > button { white-space: nowrap; flex-shrink: 0; }
        }
        @media (max-width: 639px) {
            .hide-mobile { display: none !important; }
            .admin-tabs > a, .admin-tabs > button { padding: 8px 10px !important; font-size: 12px !important; }
        }
        .admin-tabs--wrap { flex-wrap: wrap; overflow-x: visible; }
        .admin-tabs--wrap > a { flex-shrink: 0; }
        @media (max-width: 639px) {
            .admin-tabs--wrap > a { padding: 6px 8px !important; font-size: 11px !important; }
        }
        #sidebar { scrollbar-width: thin; scrollbar-color: #374151 transparent; }
        #sidebar::-webkit-scrollbar { width: 6px; }
        #sidebar::-webkit-scrollbar-track { background: transparent; }
        #sidebar::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
        #sidebar::-webkit-scrollbar-thumb:hover { background: #4b5563; }
    </style>
     
     
     
     
    <link rel="stylesheet" href="<?= AdminAssets::url('tailwind.css') ?>">
</head>
<body class="bg-gray-50 font-sans text-gray-800 text-sm leading-relaxed">
    <div class="flex min-h-screen">
        
        <div id="sidebarOverlay" class="fixed inset-0 z-40 lg:hidden hidden" style="background:rgba(0,0,0,.5)" onclick="closeSidebar()"></div>

        
        <aside id="sidebar" class="w-60 bg-gray-900 text-white fixed top-0 left-0 bottom-0 flex flex-col z-50 overflow-y-auto transform -translate-x-full lg:translate-x-0 transition-transform duration-200" style="background:#111827">
            <div class="px-5 py-5 border-b border-gray-700 relative flex items-center justify-center">
                <a href="/admin" class="no-underline flex items-center justify-center"><img src="/templates/admin/img/logo.webp?v=2" alt="TubePress" style="height:26px;width:auto;"></a>
                <button onclick="closeSidebar()" class="lg:hidden w-9 h-9 flex items-center justify-center rounded-lg text-gray-300 hover:text-white hover:bg-gray-700 absolute right-3 top-1/2 -translate-y-1/2 transition-colors">
                    <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
                </button>
            </div>
            <nav class="flex-1 py-3">
                <?php if ($_updateCount > 0): ?>
                <?php $_updatesActive = ($activeMenu ?? '') === 'updates'; ?>
                <a href="/admin/updates" class="flex items-center gap-3 mx-3 mb-2 px-3 py-2.5 no-underline rounded-lg transition-all duration-150 <?= $_updatesActive ? 'bg-orange-600 text-white shadow-lg shadow-orange-900/40' : 'bg-orange-500/10 text-orange-200 ring-1 ring-inset ring-orange-500/30 hover:bg-orange-500/20 hover:ring-orange-500/50 hover:text-orange-100' ?>">
                    <span class="flex items-center justify-center w-9 h-9 rounded-lg shrink-0 <?= $_updatesActive ? 'bg-white/20' : 'bg-orange-500/20' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] <?= $_updatesActive ? '' : 'animate-pulse' ?>" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
                    </span>
                    <span class="flex-1 min-w-0 leading-tight">
                        <span class="block text-sm font-semibold"><?= $_updateCount > 1 ? 'Updates available' : 'Update available' ?></span>
                        <span class="block text-[11px] <?= $_updatesActive ? 'text-orange-100/90' : 'text-orange-300/70' ?>">Review &amp; install</span>
                    </span>
                    <span class="inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 text-[11px] font-bold rounded-full shrink-0 <?= $_updatesActive ? 'bg-white text-orange-700' : 'bg-orange-500 text-white' ?>"><?= $_updateCount ?></span>
                </a>
                <div class="h-px bg-gray-700 mx-5 mb-1"></div>
                <?php endif; ?>
                <a href="/admin" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'dashboard' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
                    Dashboard
                </a>
                <?php $videoSubs = in_array($activeMenu ?? '', ['videos','categories','tags','performers','channels']); ?>
                <div class="flex items-center transition-all duration-150 <?= ($activeMenu ?? '') === 'videos' ? 'bg-primary-600' : ($videoSubs ? 'bg-gray-800' : 'hover:bg-gray-800') ?>">
                    <a href="/admin/videos" class="flex-1 flex items-center gap-2.5 pl-5 py-2.5 text-sm no-underline <?= $videoSubs ? 'text-white' : 'text-gray-400 hover:text-white' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
                        Videos
                    </a>
                    <button onclick="document.getElementById('videoSubMenu').classList.toggle('hidden');this.querySelector('svg').classList.toggle('rotate-90')" class="px-3 py-2.5 text-gray-400 hover:text-white shrink-0">
                        <svg class="w-4 h-4 transition-transform <?= $videoSubs ? 'rotate-90' : '' ?>" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
                    </button>
                </div>
                <div id="videoSubMenu" class="ml-[38px] border-l border-gray-700 <?= $videoSubs ? '' : 'hidden' ?>">
                    <a href="/admin/categories" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'categories' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Categories</a>
                    <a href="/admin/tags" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'tags' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Tags</a>
                    <a href="/admin/performers" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'performers' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Pornstars</a>
                    <a href="/admin/channels" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'channels' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Channels</a>
                </div>
                <?php $albumSubs = in_array($activeMenu ?? '', ['albums','album_categories','album_tags','album_performers','album_channels']); ?>
                <div class="flex items-center transition-all duration-150 <?= ($activeMenu ?? '') === 'albums' ? 'bg-primary-600' : ($albumSubs ? 'bg-gray-800' : 'hover:bg-gray-800') ?>">
                    <a href="/admin/albums" class="flex-1 flex items-center gap-2.5 pl-5 py-2.5 text-sm no-underline <?= $albumSubs ? 'text-white' : 'text-gray-400 hover:text-white' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
                        Albums
                    </a>
                    <button onclick="document.getElementById('albumSubMenu').classList.toggle('hidden');this.querySelector('svg').classList.toggle('rotate-90')" class="px-3 py-2.5 text-gray-400 hover:text-white shrink-0">
                        <svg class="w-4 h-4 transition-transform <?= $albumSubs ? 'rotate-90' : '' ?>" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
                    </button>
                </div>
                <div id="albumSubMenu" class="ml-[38px] border-l border-gray-700 <?= $albumSubs ? '' : 'hidden' ?>">
                    <a href="/admin/albums/categories" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'album_categories' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Categories</a>
                    <a href="/admin/albums/tags" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'album_tags' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Tags</a>
                    <a href="/admin/albums/performers" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'album_performers' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Pornstars</a>
                    <a href="/admin/albums/channels" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'album_channels' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Channels</a>
                </div>
                <a href="/admin/users" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'users' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
                    Users
                </a>
                <?php if (Setting::get('comments_enabled', '1') !== '0'): ?>
                <a href="/admin/comments" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'comments' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
                    Comments
                </a>
                <?php endif; ?>
                <?php $inboxBadge = $unreadMessages + $pendingReports; ?>
                <a href="/admin/inbox" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'inbox' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
                    Inbox
                    <?php if ($inboxBadge > 0): ?>
                    <span class="ml-auto inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 text-[11px] font-bold text-white bg-red-500 rounded-full"><?= $inboxBadge ?></span>
                    <?php endif; ?>
                </a>
                <a href="/admin/pages" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'pages' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                    Pages
                </a>
                <?php
                    $importSubs = ($activeMenu ?? '') === 'import';
                    $_hasDownloads = (bool) Database::fetchOne("SELECT 1 FROM catalogue_downloads LIMIT 1");
                ?>
                <?php if ($_hasDownloads): ?>
                <div class="flex items-center transition-all duration-150 <?= $importSubs && ($activeSubMenu ?? '') !== 'downloads' ? 'bg-primary-600' : ($importSubs ? 'bg-gray-800' : 'hover:bg-gray-800') ?>">
                    <a href="/admin/import" class="flex-1 flex items-center gap-2.5 pl-5 py-2.5 text-sm no-underline <?= $importSubs ? 'text-white' : 'text-gray-400 hover:text-white' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>
                        Import
                    </a>
                    <button onclick="document.getElementById('importSubMenu').classList.toggle('hidden');this.querySelector('svg').classList.toggle('rotate-90')" class="px-3 py-2.5 text-gray-400 hover:text-white shrink-0">
                        <svg class="w-4 h-4 transition-transform <?= $importSubs ? 'rotate-90' : '' ?>" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
                    </button>
                </div>
                <div id="importSubMenu" class="ml-[38px] border-l border-gray-700 <?= $importSubs ? '' : 'hidden' ?>">
                    <a href="/admin/import/downloads" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeSubMenu ?? '') === 'downloads' ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Download Queue</a>
                </div>
                <?php else: ?>
                <a href="/admin/import" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= $importSubs ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>
                    Import
                </a>
                <?php endif; ?>
                <?php HookSystem::doAction('admin.sidebar.menu'); ?>
                <div class="h-px bg-gray-700 mx-5 my-2"></div>
                <?php
                    ob_start();
                    HookSystem::doAction('admin.sidebar.plugin_submenu', $activeMenu ?? '');
                    $_pluginSubmenuHtml = trim(ob_get_clean());
                    $_pluginSubs = str_starts_with($activeMenu ?? '', 'plugin_');
                     
                     
                     
                    $_pluginExpanded = $_pluginSubs || ($activeMenu ?? '') === 'plugins';
                ?>
                <?php if ($_pluginSubmenuHtml !== ''): ?>
                <div class="flex items-center transition-all duration-150 <?= ($activeMenu ?? '') === 'plugins' ? 'bg-primary-600' : ($_pluginSubs ? 'bg-gray-800' : 'hover:bg-gray-800') ?>">
                    <a href="/admin/plugins" class="flex-1 flex items-center gap-2.5 pl-5 py-2.5 text-sm no-underline <?= ($activeMenu ?? '') === 'plugins' || $_pluginSubs ? 'text-white' : 'text-gray-400 hover:text-white' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="5"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
                        Plugins
                        <?php if ($_pluginUpdates > 0): ?>
                        <span class="ml-auto w-2.5 h-2.5 rounded-full bg-orange-500 shrink-0 mr-1"></span>
                        <?php endif; ?>
                    </a>
                    <button onclick="document.getElementById('pluginSubMenu').classList.toggle('hidden');this.querySelector('svg').classList.toggle('rotate-90')" class="px-3 py-2.5 text-gray-400 hover:text-white shrink-0">
                        <svg class="w-4 h-4 transition-transform <?= $_pluginExpanded ? 'rotate-90' : '' ?>" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
                    </button>
                </div>
                <div id="pluginSubMenu" class="ml-[38px] border-l border-gray-700 <?= $_pluginExpanded ? '' : 'hidden' ?>">
                    <?= $_pluginSubmenuHtml ?>
                </div>
                <?php else: ?>
                <a href="/admin/plugins" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'plugins' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="5"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
                    Plugins
                    <?php if ($_pluginUpdates > 0): ?>
                    <span class="ml-auto w-2.5 h-2.5 rounded-full bg-orange-500 shrink-0 mr-1"></span>
                    <?php endif; ?>
                </a>
                <?php endif; ?>
                <?php $themeSubs = ($activeMenu ?? '') === 'themes'; ?>
                <div class="flex items-center transition-all duration-150 <?= $themeSubs && ($activeSubMenu ?? '') !== 'editor' ? 'bg-primary-600' : ($themeSubs ? 'bg-gray-800' : 'hover:bg-gray-800') ?>">
                    <a href="/admin/themes" class="flex-1 flex items-center gap-2.5 pl-5 py-2.5 text-sm no-underline <?= $themeSubs ? 'text-white' : 'text-gray-400 hover:text-white' ?>">
                        <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
                        Themes
                        <?php if ($_themeUpdates > 0): ?>
                        <span class="ml-auto w-2.5 h-2.5 rounded-full bg-orange-500 shrink-0 mr-1"></span>
                        <?php endif; ?>
                    </a>
                    <button onclick="document.getElementById('themeSubMenu').classList.toggle('hidden');this.querySelector('svg').classList.toggle('rotate-90')" class="px-3 py-2.5 text-gray-400 hover:text-white shrink-0">
                        <svg class="w-4 h-4 transition-transform <?= $themeSubs ? 'rotate-90' : '' ?>" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
                    </button>
                </div>
                <div id="themeSubMenu" class="ml-[38px] border-l border-gray-700 <?= $themeSubs ? '' : 'hidden' ?>">
                    <a href="/admin/themes/editor" class="block px-4 py-1.5 text-[13px] no-underline transition-all duration-150 <?= ($activeSubMenu ?? '') === 'editor' && $themeSubs ? 'text-white' : 'text-gray-500 hover:text-gray-300' ?>">Edit Code</a>
                </div>
                <?php HookSystem::doAction('admin.sidebar_menu', $activeMenu ?? ''); ?>
                <a href="/admin/languages" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'languages' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
                    Languages
                </a>
                <a href="/admin/settings" class="flex items-center gap-2.5 px-5 py-2.5 text-sm no-underline transition-all duration-150 <?= ($activeMenu ?? '') === 'settings' ? 'text-white bg-primary-600' : 'text-gray-400 hover:text-white hover:bg-gray-800' ?>">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
                    Settings
                </a>
            </nav>
            <div class="mx-3 my-2 flex items-stretch gap-1.5">
                <a href="#" id="tpsidebar-help" onclick="var b=document.getElementById('tphelp-bubble'); if (b) b.click(); return false;" class="flex-1 flex items-center justify-center gap-2 px-4 py-1.5 text-sm font-semibold no-underline rounded-lg transition-all duration-150 text-white bg-red-600 hover:bg-red-700">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
                    Help Center
                </a>
                <a href="#" id="tpsidebar-chat" title="Support — open your tickets" onclick="var b=document.getElementById('tpsup-bubble'); if (b) b.click(); return false;" class="flex items-center justify-center px-2.5 no-underline rounded-lg transition-all duration-150 text-white">
                    <svg viewBox="0 0 24 24" class="w-[18px] h-[18px] shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
                    <span class="tpsidebar-chat-chip" id="tpsidebar-chat-chip">1</span>
                </a>
            </div>
            <div class="border-t border-gray-700 px-3 py-2.5 flex items-center gap-2">
                <a href="/" target="_blank" class="flex-1 flex items-center justify-center gap-2 py-2 text-xs text-gray-400 no-underline rounded-lg border border-gray-700 transition-all duration-150 hover:text-white hover:border-gray-500 hover:bg-gray-800">
                    <svg viewBox="0 0 24 24" class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
                    View Site
                </a>
                <a href="<?= htmlspecialchars(AdminController::loginUrl() . '/logout') ?>" class="flex items-center justify-center gap-2 py-2 px-3 text-xs no-underline rounded-lg transition-all duration-150 text-red-300 bg-red-950/40 border border-red-900/50 hover:text-white hover:bg-red-700 hover:border-red-600">
                    <svg viewBox="0 0 24 24" class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
                </a>
            </div>
        </aside>

        
        <main class="flex-1 lg:ml-60 min-h-screen min-w-0 overflow-x-hidden">
            <?php
             
            $loadAvg = function_exists('sys_getloadavg') ? sys_getloadavg() : false;
            $cpuCores = function_exists('shell_exec') ? ((int) @trim(shell_exec('nproc 2>/dev/null') ?: '1') ?: 1) : 1;
             
             
             
             
             
            $diskRoot = defined('APP_ROOT') ? APP_ROOT : '/';
            $diskTotal = @disk_total_space($diskRoot);
            $diskFree = @disk_free_space($diskRoot);
            if ($diskTotal === false || $diskFree === false) {
                $diskTotal = @disk_total_space('/');
                $diskFree = @disk_free_space('/');
            }
            $diskPct = ($diskTotal && $diskTotal > 0) ? round((($diskTotal - $diskFree) / $diskTotal) * 100) : false;
            $ramPct = false;
            $memInfo = @file_get_contents('/proc/meminfo');
            if ($memInfo) {
                preg_match('/MemTotal:\s+(\d+)/', $memInfo, $mt);
                preg_match('/MemAvailable:\s+(\d+)/', $memInfo, $ma);
                if (!empty($mt[1]) && !empty($ma[1])) {
                    $ramPct = round((1 - (int) $ma[1] / (int) $mt[1]) * 100);
                }
            }
            $laPct = ($loadAvg !== false) ? round(($loadAvg[0] / $cpuCores) * 100) : false;
            $statusImport = false; $statusEncoding = false; $statusDeleting = false;
             
            try {
                $csvJob = Database::fetchOne("SELECT processed_rows, total_rows FROM import_jobs WHERE status IN ('processing','pending') ORDER BY status = 'processing' DESC, id ASC LIMIT 1");
                if ($csvJob) {
                    $statusImport = ['pct' => round(((int) $csvJob['processed_rows'] / max(1, (int) $csvJob['total_rows'])) * 100), 'href' => '/admin/import'];
                }
            } catch (\Throwable $e) {}
             
            if (!$statusImport) {
                try {
                    $dlStats = Database::fetchOne("SELECT COUNT(*) as total, SUM(CASE WHEN status IN ('completed') THEN 1 ELSE 0 END) as done FROM catalogue_downloads WHERE batch_id = (SELECT batch_id FROM catalogue_downloads WHERE status IN ('pending','downloading') ORDER BY id DESC LIMIT 1)");
                    if ($dlStats && (int) $dlStats['total'] > 0 && (int) $dlStats['done'] < (int) $dlStats['total']) {
                        $statusImport = ['pct' => round(((int) $dlStats['done'] / (int) $dlStats['total']) * 100), 'href' => '/admin/import/downloads'];
                    }
                } catch (\Throwable $e) {}
            }
             
            try {
                $tcStats = Database::fetchOne("SELECT COUNT(*) as total, AVG(progress) as avg_progress FROM transcode_jobs WHERE status IN ('pending','processing')");
                if ($tcStats && (int) $tcStats['total'] > 0) {
                    $statusEncoding = ['pct' => round((float) $tcStats['avg_progress']), 'count' => (int) $tcStats['total']];
                }
            } catch (\Throwable $e) {}
             
            try {
                $bdJob = Database::fetchOne("SELECT deleted_count, total_count FROM bulk_delete_jobs WHERE status IN ('pending','processing') ORDER BY id DESC LIMIT 1");
                if ($bdJob && (int) $bdJob['total_count'] > 0) {
                    $statusDeleting = ['pct' => round(((int) $bdJob['deleted_count'] / max(1, (int) $bdJob['total_count'])) * 100)];
                }
            } catch (\Throwable $e) {}
            $hasActivity = $statusImport || $statusEncoding || $statusDeleting;
             
            function _statColor(int $pct, int $warn = 80, int $crit = 90): string { return $pct < $warn ? '#22c55e' : ($pct < $crit ? '#eab308' : '#ef4444'); }
            function _statColorClass(int $pct, int $warn = 80, int $crit = 90): string { return $pct < $warn ? 'text-green-500' : ($pct < $crit ? 'text-yellow-500' : 'text-red-500'); }
            function _formatBytes(float $bytes): string {
                if ($bytes >= 1099511627776) return round($bytes / 1099511627776, 1) . ' TB';
                if ($bytes >= 1073741824) return round($bytes / 1073741824, 1) . ' GB';
                return round($bytes / 1048576) . ' MB';
            }
            ?>
            <?php require __DIR__ . '/_announcement_bar.php'; ?>
            <div class="sticky top-0 z-30 text-[11px] px-4 sm:px-6 border-b border-gray-200" style="background:#f8fafc">
                <div class="flex items-center h-7 overflow-x-auto">
                    <?php if ($diskPct !== false): ?>
                    <div class="flex items-center gap-1.5 shrink-0 pr-4" title="<?= _formatBytes($diskTotal - $diskFree) ?> / <?= _formatBytes($diskTotal) ?>">
                        <svg class="w-3.5 h-3.5 text-gray-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18"/><circle cx="7" cy="7" r="0.5" fill="currentColor" stroke="none"/><circle cx="10" cy="7" r="0.5" fill="currentColor" stroke="none"/></svg>
                        <span class="text-gray-500 font-semibold">HDD</span>
                        <span class="font-bold tabular-nums <?= _statColorClass($diskPct, 80, 90) ?>"><?= $diskPct ?>%</span>
                    </div>
                    <?php endif; ?>
                    <?php if ($ramPct !== false): ?>
                    <div class="flex items-center gap-1.5 shrink-0 border-l border-gray-300 pl-4 pr-4" title="RAM usage">
                        <svg class="w-3.5 h-3.5 text-gray-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 4V2m6 2V2m-6 20v-2m6 2v-2M4 9H2m2 6H2m20-6h-2m2 6h-2"/><rect x="8" y="8" width="8" height="8" rx="1"/></svg>
                        <span class="text-gray-500 font-semibold">RAM</span>
                        <span class="font-bold tabular-nums <?= _statColorClass($ramPct, 70, 90) ?>"><?= $ramPct ?>%</span>
                    </div>
                    <?php endif; ?>
                    <?php if ($laPct !== false): ?>
                    <div class="flex items-center gap-1.5 shrink-0 border-l border-gray-300 pl-4 pr-4" title="<?= number_format($loadAvg[0], 2) ?> / <?= number_format($loadAvg[1], 2) ?> / <?= number_format($loadAvg[2], 2) ?>">
                        <svg class="w-3.5 h-3.5 text-gray-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
                        <span class="text-gray-500 font-semibold">L/A</span>
                        <span class="font-bold tabular-nums <?= _statColorClass($laPct, 70, 100) ?>"><?= $laPct ?>%</span>
                    </div>
                    <?php endif; ?>
                    <?php if ($statusImport): ?>
                    <a href="<?= $statusImport['href'] ?>" class="flex items-center gap-1.5 shrink-0 border-l border-gray-300 pl-4 pr-4 no-underline hover:opacity-80">
                        <svg class="w-3.5 h-3.5 text-blue-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
                        <span class="text-blue-600 font-semibold">Import</span>
                        <span class="font-bold tabular-nums text-blue-600"><?= $statusImport['pct'] ?>%</span>
                    </a>
                    <?php endif; ?>
                    <?php if ($statusEncoding): ?>
                    <a href="/admin/settings?tab=transcode&sub=queue" class="flex items-center gap-1.5 shrink-0 border-l border-gray-300 pl-4 pr-4 no-underline hover:opacity-80">
                        <svg class="w-3.5 h-3.5 text-purple-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
                        <span class="text-purple-600 font-semibold">Encoding</span>
                        <span class="font-bold tabular-nums text-purple-600"><?= $statusEncoding['pct'] ?>%</span>
                    </a>
                    <?php endif; ?>
                    <?php if ($statusDeleting): ?>
                    <a href="/admin/videos" class="flex items-center gap-1.5 shrink-0 border-l border-gray-300 pl-4 pr-4 no-underline hover:opacity-80">
                        <svg class="w-3.5 h-3.5 text-red-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
                        <span class="text-red-600 font-semibold">Deleting</span>
                        <span class="font-bold tabular-nums text-red-600"><?= $statusDeleting['pct'] ?>%</span>
                    </a>
                    <?php endif; ?>
                </div>
            </div>
            <header class="h-[60px] bg-white border-b border-gray-200 flex items-center justify-between px-4 sm:px-6 sticky top-7 z-30">
                <div class="flex items-center gap-3">
                    <button onclick="openSidebar()" class="lg:hidden text-gray-500 hover:text-gray-700 p-1 -ml-1">
                        <svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
                    </button>
                    <h1 class="text-lg font-semibold text-gray-900"><?= htmlspecialchars($pageTitle ?? 'Dashboard') ?></h1>
                </div>
                <div class="flex items-center gap-3">
                    <?php if (!empty($headerExtra)) echo $headerExtra; ?>
                    <?php if (!empty($headerAction)): ?>
                        <a href="<?= htmlspecialchars($headerAction['url']) ?>" class="inline-flex items-center gap-1.5 px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors duration-150"><?= htmlspecialchars($headerAction['label']) ?></a>
                    <?php endif; ?>
                </div>
            </header>

            <?php if ($flash): ?>
                <?php
                    $flashColors = [
                        'success' => 'bg-green-50 text-green-700 border border-green-200',
                        'error'   => 'bg-red-50 text-red-700 border border-red-200',
                        'warning' => 'bg-yellow-50 text-yellow-700 border border-yellow-200',
                        'info'    => 'bg-blue-50 text-blue-700 border border-blue-200',
                    ];
                    $flashClass = $flashColors[$flash['type']] ?? $flashColors['info'];
                ?>
                <div class="mx-4 sm:mx-6 mt-4 px-4 py-3 rounded-lg text-sm <?= $flashClass ?>">
                    <?= htmlspecialchars($flash['message']) ?>
                </div>
            <?php endif; ?>

            <?php
            $licNotice = class_exists('LicenseGuard') ? LicenseGuard::adminNotice() : null;
            if ($licNotice):
                $licHost = LicenseGuard::host();
                $licBase = preg_replace('#/v\d+/?$#', '', rtrim((string) Setting::get('catalogue_api_url', 'https://tubepress.io/v1'), '/'));
                foreach ($licNotice as $ln):
                    $licBlocked = ($ln['status'] ?? '') === 'blocked';
                    $licLabel = (($ln['type'] ?? '') === 'theme' ? 'Theme' : 'Plugin') . ' &laquo;' . htmlspecialchars($ln['slug'] ?? '') . '&raquo;';
                    $licBound = !empty($ln['bound']) ? htmlspecialchars(implode(', ', $ln['bound'])) : '';
            ?>
                <div class="mx-4 sm:mx-6 mt-4 px-4 py-3 rounded-lg text-sm <?= $licBlocked ? 'bg-red-50 text-red-700 border border-red-200' : 'bg-yellow-50 text-yellow-700 border border-yellow-200' ?>">
                    <i class="fa-solid fa-lock mr-1.5"></i>
                    <strong><?= $licLabel ?></strong> —
                    <?php if ($licBlocked): ?>
                        disabled on this domain. This licence is for a single domain<?= $licBound !== '' ? ' (<strong>' . $licBound . '</strong>)' : '' ?> and does not cover this site<?= $licHost !== '' ? ' <strong>' . htmlspecialchars($licHost) . '</strong>' : '' ?>.
                    <?php else: ?>
                        this licence does not cover this domain<?= $licHost !== '' ? ' <strong>' . htmlspecialchars($licHost) . '</strong>' : '' ?> and it will be disabled soon.
                    <?php endif; ?>
                    Move or upgrade your licence at <a href="<?= htmlspecialchars($licBase) ?>/dashboard/catalogue" target="_blank" rel="noopener" class="underline font-medium">tubepress.io/dashboard</a>.
                </div>
            <?php endforeach; endif; ?>

            <div class="p-4 sm:p-6">
                <?php
                if (!empty($contentPath)) {
                    require $contentPath;
                } else {
                    require APP_ROOT . '/templates/admin/' . $contentTemplate . '.php';
                }
                ?>
            </div>
        </main>
<?php if (!empty($storageAlerts)): ?>
<div class="fixed bottom-0 right-0 left-0 lg:left-60 z-40">
<?php foreach ($storageAlerts as $alert):
    $isError = $alert['type'] === 'error';
?>
    <?php $alertLink = $alert['link'] ?? '/admin/storage/' . $alert['id'] . '/edit'; ?>
    <a href="<?= htmlspecialchars($alertLink) ?>" class="flex items-center gap-3 px-4 sm:px-6 py-2.5 <?= $isError ? 'bg-red-600' : 'bg-amber-500' ?> text-white text-sm hover:brightness-110 transition-all border-t <?= $isError ? 'border-red-700' : 'border-amber-600' ?>">
        <i class="fa-solid <?= $isError ? 'fa-circle-exclamation' : 'fa-triangle-exclamation' ?>" style="font-size:13px"></i>
        <span class="flex-1"><strong><?= htmlspecialchars($alert['name']) ?></strong> — <?= htmlspecialchars($alert['msg']) ?></span>
        <span class="text-xs opacity-75 shrink-0">Fix →</span>
    </a>
<?php endforeach; ?>
</div>
<?php endif; ?>
    </div>

<script>
function openSidebar() {
    document.getElementById('sidebar').classList.remove('-translate-x-full');
    document.getElementById('sidebarOverlay').classList.remove('hidden');
}
function closeSidebar() {
    document.getElementById('sidebar').classList.add('-translate-x-full');
    document.getElementById('sidebarOverlay').classList.add('hidden');
}


(function(){
    var table = document.querySelector('table[data-bulk-delete]');
    if (!table) return;
    var bar = document.getElementById('bulkBar');
    var countEl = document.getElementById('bulkCount');
    var selectAll = table.querySelector('[data-bulk-select-all]');
    var selectAllBtn = document.getElementById('bulkSelectAllBtn');
    var actionsEl = document.getElementById('bulkActions');
    var progressEl = document.getElementById('bulkProgress');
    var endpoint = table.getAttribute('data-bulk-delete') || '';
    var isAsync = table.hasAttribute('data-bulk-async');
    var noun = table.getAttribute('data-bulk-noun') || 'item';
    var nounPlural = table.getAttribute('data-bulk-noun-plural') || (noun + 's');
    var _activeJobId = null;
    var _startTime = null;

    function csrf() {
        var el = document.querySelector('meta[name="csrf-token"]');
        return el ? el.content : '<?= Router::csrfToken() ?>';
    }
    function getCheckboxes() { return table.querySelectorAll('[data-bulk-id]'); }
    function getCheckedIds() {
        var ids = [];
        getCheckboxes().forEach(function(cb) { if (cb.checked) ids.push(cb.getAttribute('data-bulk-id')); });
        return ids;
    }
    function updateBar() {
        var ids = getCheckedIds();
        var count = (typeof _bulkSelectAll !== 'undefined' && _bulkSelectAll) ? _bulkTotal : ids.length;
        if (count > 0) {
            bar.classList.remove('hidden');
            countEl.textContent = count.toLocaleString();
        } else {
            bar.classList.add('hidden');
            if (typeof _bulkSelectAll !== 'undefined') _bulkSelectAll = false;
        }
        if (selectAll) {
            var boxes = getCheckboxes();
            var allChecked = boxes.length > 0 && ids.length === boxes.length;
            selectAll.checked = allChecked;
            selectAll.indeterminate = ids.length > 0 && !allChecked;
            
            if (selectAllBtn && allChecked && !_bulkSelectAll) {
                selectAllBtn.style.display = '';
            } else if (selectAllBtn) {
                selectAllBtn.style.display = 'none';
            }
        }
    }
    if (selectAll) {
        selectAll.addEventListener('change', function() {
            var checked = this.checked;
            if (!checked && typeof _bulkSelectAll !== 'undefined') _bulkSelectAll = false;
            getCheckboxes().forEach(function(cb) {
                if (cb.closest('tr').style.display !== 'none') cb.checked = checked;
            });
            updateBar();
        });
    }
    table.addEventListener('change', function(e) {
        if (e.target.hasAttribute('data-bulk-id')) {
            if (typeof _bulkSelectAll !== 'undefined') _bulkSelectAll = false;
            updateBar();
        }
    });

    window.bulkSelectAllMatching = function() {
        _bulkSelectAll = true;
        updateBar();
    };

    window.bulkDeleteSelected = function() {
        var ids = getCheckedIds();
        var useSelectAll = (typeof _bulkSelectAll !== 'undefined' && _bulkSelectAll);
        var count = useSelectAll ? _bulkTotal : ids.length;
        if (count === 0) return;
        var label = count > 1 ? nounPlural : noun;
        var msg = 'Delete ' + count.toLocaleString() + ' ' + label + '? This cannot be undone.';
        if (!confirm(msg)) return;

        
        
        if (!isAsync) {
            fetch(endpoint, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf() },
                body: JSON.stringify({ ids: ids }),
                credentials: 'same-origin'
            }).then(function(r) {
                return r.json().catch(function() { return {}; }).then(function(d) { return { ok: r.ok, data: d }; });
            }).then(function(res) {
                if (!res.ok || (res.data && res.data.error)) {
                    alert((res.data && res.data.error) || 'Bulk deletion failed.');
                    return;
                }
                window.location.reload();
            }).catch(function() { alert('Bulk deletion failed.'); });
            return;
        }

        
        var body = {};
        if (useSelectAll) {
            body.select_all = true;
            
            var f = {};
            for (var k in _bulkFilters) {
                if (_bulkFilters[k] !== '' && _bulkFilters[k] !== 0 && _bulkFilters[k] !== '0') f[k] = _bulkFilters[k];
            }
            body.filters = f;
        } else {
            body.ids = ids;
        }

        
        fetch(endpoint + '-start', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf() },
            body: JSON.stringify(body),
            credentials: 'same-origin'
        }).then(function(r) { return r.json(); }).then(function(data) {
            if (data.error) { alert(data.error); return; }
            _activeJobId = data.job_id;
            _startTime = Date.now();
            if (actionsEl) actionsEl.classList.add('hidden');
            if (progressEl) progressEl.classList.remove('hidden');
            var bt = document.getElementById('bulkTotal');
            if (bt) bt.textContent = count.toLocaleString();
            pollStatus();
        }).catch(function() { alert('Failed to start batch deletion.'); });
    };

    function pollStatus() {
        if (!_activeJobId) return;
        
        
        fetch(endpoint + '-chunk?job_id=' + _activeJobId, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf() },
            body: JSON.stringify({ job_id: _activeJobId }),
            credentials: 'same-origin'
        }).then(function(r) { return r.json(); }).then(function(data) {
            var del = data.deleted_count || 0;
            var tot = data.total_count || 1;
            var pct = Math.min(100, Math.round((del / tot) * 100));
            document.getElementById('bulkDeleted').textContent = del.toLocaleString();
            document.getElementById('bulkPct').textContent = pct;
            document.getElementById('bulkProgressBar').style.width = pct + '%';
            var elapsed = (Date.now() - _startTime) / 1000;
            if (del > 0 && pct < 100) {
                var rate = del / elapsed;
                var remaining = Math.round((tot - del) / rate);
                var mm = Math.floor(remaining / 60);
                var ss = remaining % 60;
                document.getElementById('bulkEta').textContent = 'ETA: ' + (mm > 0 ? mm + 'm ' : '') + ss + 's';
            }
            if (data.done) {
                finishJob();
            } else {
                setTimeout(pollStatus, 350);
            }
        }).catch(function() {
            setTimeout(pollStatus, 3000);
        });
    }

    function finishJob() {
        _activeJobId = null;
        window.location.reload();
    }

    window.bulkDeleteCancelJob = function() {
        if (!_activeJobId) return;
        fetch(endpoint + '-cancel', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf() },
            body: JSON.stringify({ job_id: _activeJobId }),
            credentials: 'same-origin'
        }).then(function() {
            _activeJobId = null;
            window.location.reload();
        });
    };

    window.bulkClearAll = function() {
        if (typeof _bulkSelectAll !== 'undefined') _bulkSelectAll = false;
        getCheckboxes().forEach(function(cb) { cb.checked = false; });
        if (selectAll) { selectAll.checked = false; selectAll.indeterminate = false; }
        bar.classList.add('hidden');
    };
})();


(function(){
    var zoomEl = null;
    document.addEventListener('mouseenter', function(e) {
        var src = e.target.getAttribute && e.target.getAttribute('data-zoom-src');
        if (!src) return;
        if (zoomEl) { zoomEl.remove(); zoomEl = null; }
        zoomEl = document.createElement('div');
        zoomEl.style.cssText = 'position:fixed;z-index:9999;pointer-events:none;border-radius:8px;box-shadow:0 8px 30px rgba(0,0,0,.25);overflow:hidden;transition:opacity .15s;opacity:0;background:#fff;';
        var img = document.createElement('img');
        img.src = src;
        img.style.cssText = 'display:block;max-width:280px;max-height:200px;object-fit:contain;';
        zoomEl.appendChild(img);
        document.body.appendChild(zoomEl);
        var rect = e.target.getBoundingClientRect();
        var left = rect.right + 12;
        var top = rect.top;
        if (left + 300 > window.innerWidth) left = rect.left - 300;
        if (top + 220 > window.innerHeight) top = window.innerHeight - 220;
        if (top < 8) top = 8;
        zoomEl.style.left = left + 'px';
        zoomEl.style.top = top + 'px';
        requestAnimationFrame(function(){ if(zoomEl) zoomEl.style.opacity='1'; });
    }, true);
    document.addEventListener('mouseleave', function(e) {
        if (e.target.getAttribute && e.target.getAttribute('data-zoom-src') && zoomEl) {
            zoomEl.remove(); zoomEl = null;
        }
    }, true);
})();

document.querySelectorAll('[data-search-table]').forEach(input => {
    const tableId = input.getAttribute('data-search-table');
    const table = document.getElementById(tableId);
    if (!table) return;
    const rows = table.querySelectorAll('tbody tr[data-searchable]');
    const counter = document.getElementById(tableId + '-count');
    input.addEventListener('input', function() {
        const q = this.value.toLowerCase().trim();
        let visible = 0;
        rows.forEach(row => {
            const text = row.textContent.toLowerCase();
            const match = !q || text.includes(q);
            row.style.display = match ? '' : 'none';
            if (match) visible++;
        });
        if (counter) counter.textContent = visible + ' result' + (visible !== 1 ? 's' : '');
    });
});
</script>
<?php require APP_ROOT . '/templates/admin/_support_widget.php'; ?>
<?php require APP_ROOT . '/templates/admin/_help_widget.php'; ?>
<?php
session_write_close();
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
CronManager::run();
?>
</body>
</html>
