<?php
/**
 * Slate — admin shell header.
 *
 * Renders the layout:
 *   Desktop (≥768px):  dark sidebar on left, light topbar across,
 *                       light off-white canvas under main content
 *   Mobile  (<768px):  topbar across, main content full-width,
 *                       bottom tab bar pinned to bottom of viewport
 *
 * Pages call this with $pageTitle set, then render content, then
 * include footer.php.
 *
 *   require_once dirname(__DIR__).'/../config.php';
 *   Auth::require();
 *   $pageTitle = 'My Page';
 *   require __DIR__ . '/partials/header.php';
 *   // ... page content ...
 *   require __DIR__ . '/partials/footer.php';
 *
 * NAV ITEMS are dynamic. Core declares its items here; plugins extend
 * via the `admin_nav_items` filter. Items are auto-filtered by the
 * current user's permissions and sorted by 'order'. Items optionally
 * carry a 'group' key — items with the same group render under an
 * uppercase section label. Core groups: overview, content, system.
 *
 * MOBILE TAB BAR shows the first 4 items + a "More" button that opens
 * a sheet listing the rest. Desktop sidebar shows all items grouped.
 *
 * TOPBAR slots (filters):
 *   admin_topbar_search   → string of HTML for the centre-right slot
 *                            (e.g. a search input). Empty by default.
 *   admin_topbar_actions  → array of HTML strings rendered as buttons
 *                            in the top-right. The first one is the
 *                            page's primary CTA.
 */

if (!defined('SLATE_ROOT')) exit;
Auth::require();

// Admin pages must never be cached — stale renders after a deploy are
// a real problem (see the Phase 0 hotfix story). Headers must be sent
// BEFORE any output, so this is the right place. Output buffer also
// isn't started yet here.
if (!headers_sent()) {
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    header('Expires: 0');
}

require_once SLATE_ROOT . '/includes/breadcrumbs.php';

$user      = Auth::user();
$pageTitle = $pageTitle ?? 'Admin';

// ── Core nav items ───────────────────────────────────────────
// Each item: ['slug', 'label', 'href', 'icon', 'perm', 'order', 'group']
$coreNav = [
    [
        'slug'  => 'dashboard',
        'label' => __('dashboard', 'Dashboard'),
        'href'  => SLATE_URL . '/admin/index.php',
        'icon'  => 'home',
        'perm'  => null,
        'order' => 10,
        'group' => 'overview',
    ],
    [
        'slug'  => 'contact-forms',
        'label' => __('contact_forms', 'Forms'),
        'href'  => SLATE_URL . '/admin/contact_forms.php',
        'icon'  => 'mail',
        'perm'  => 'contact.view',
        'order' => 220,
        'group' => 'content',
    ],
    [
        'slug'  => 'users',
        'label' => __('users', 'Users'),
        'href'  => SLATE_URL . '/admin/users.php',
        'icon'  => 'users',
        'perm'  => 'users.view',
        'order' => 200,
        'group' => 'system',
    ],
    [
        'slug'  => 'roles',
        'label' => __('roles', 'Roles'),
        'href'  => SLATE_URL . '/admin/roles.php',
        'icon'  => 'shield',
        'perm'  => null, // super-admin via short-circuit
        'order' => 210,
        'group' => 'system',
    ],
    [
        'slug'  => 'plugins',
        'label' => __('plugins', 'Plugins'),
        'href'  => SLATE_URL . '/admin/plugins.php',
        'icon'  => 'box',
        'perm'  => 'plugins.manage',
        'order' => 800,
        'group' => 'system',
    ],
    [
        'slug'  => 'audit',
        'label' => __('audit_log', 'Audit log'),
        'href'  => SLATE_URL . '/admin/audit.php',
        'icon'  => 'clipboard-list',
        'perm'  => 'audit.view',
        'order' => 810,
        'group' => 'system',
    ],
    [
        'slug'  => 'settings',
        'label' => __('settings', 'Settings'),
        'href'  => SLATE_URL . '/admin/settings.php',
        'icon'  => 'settings',
        'perm'  => 'settings.view',
        'order' => 900,
        'group' => 'system',
    ],
];

// Plugin nav contributions
$navItems = Hook::applyFilters('admin_nav_items', $coreNav);

// Filter by permissions
$navItems = array_values(array_filter($navItems, function ($item) {
    if (!is_array($item) || empty($item['slug'])) return false;
    $perm = $item['perm'] ?? null;
    if ($perm !== null && $perm !== '' && !Auth::can($perm)) return false;
    return true;
}));

// Sort by order (stable across PHP 8+)
usort($navItems, fn($a, $b) => ($a['order'] ?? 500) <=> ($b['order'] ?? 500));

// ── Active nav slug ──────────────────────────────────────────
$currentNav = $currentNav ?? null;
if (!$currentNav) {
    $script = basename($_SERVER['SCRIPT_NAME'] ?? '');
    $map = [
        'index.php'         => 'dashboard',
        'plugins.php'       => 'plugins',
        'users.php'         => 'users',
        'roles.php'         => 'roles',
        'contact_forms.php' => 'contact-forms',
        'settings.php'      => 'settings',
        'audit.php'         => 'audit',
    ];
    $currentNav = $map[$script] ?? null;
}

// ── Site identity ────────────────────────────────────────────
$siteName     = Database::setting('site_name')     ?: 'Slate';
$brandSublabel = Database::setting('brand_sublabel') ?: __('pro_admin', 'Pro Admin');

// ── Group nav items by 'group' field (preserves first-seen order) ─
// Note: use $_navGroupKey (not $g) so we never clobber a caller's $g variable.
$navGroups = [];
foreach ($navItems as $item) {
    $_navGroupKey = $item['group'] ?? '';
    if ($_navGroupKey === '' || !is_string($_navGroupKey)) $_navGroupKey = 'plugins';
    $_navGroupKey = strtolower(preg_replace('/[^a-z0-9_-]+/i', '-', $_navGroupKey)) ?: 'plugins';
    if (!isset($navGroups[$_navGroupKey])) $navGroups[$_navGroupKey] = [];
    $navGroups[$_navGroupKey][] = $item;
}
unset($_navGroupKey, $item);

// Friendly display label for a group key.
if (!function_exists('slate_admin_group_label')) {
    function slate_admin_group_label(string $key): string {
        // First check for an i18n key (lets plugins translate their own group).
        $translated = __($key, '');
        if ($translated !== '' && $translated !== $key) {
            return strtoupper($translated);
        }
        static $defaults = [
            'overview' => 'Overview',
            'content'  => 'Content',
            'system'   => 'System',
            'plugins'  => 'Plugins',
            'shop'     => 'Shop',
            'media'    => 'Media',
        ];
        $label = $defaults[$key] ?? str_replace(['-', '_'], ' ', $key);
        return strtoupper($label);
    }
}

// ── Split nav for mobile (first 4 in tab bar, rest in "More") ─
$tabBarItems  = array_slice($navItems, 0, 4);
$moreNavItems = array_slice($navItems, 4);

// ── Topbar slots (plugin contributions) ──────────────────────
$topbarSearchHtml = (string)Hook::applyFilters('admin_topbar_search', '');
$topbarActions    = Hook::applyFilters('admin_topbar_actions', []);
if (!is_array($topbarActions)) $topbarActions = [];

// ── Notifications (topbar bell) ──────────────────────────────
$notifUnread = 0;
$notifItems  = [];
if (class_exists('Notifications')) {
    try {
        $notifUnread = Notifications::unreadCount();
        $notifItems  = Notifications::recent(10);
    } catch (\Throwable $e) { /* feed unavailable — bell still renders */ }
}

if (!function_exists('slate_admin_time_ago')) {
    function slate_admin_time_ago(string $datetime): string {
        $ts = strtotime($datetime);
        if (!$ts) return '';
        $diff = time() - $ts;
        if ($diff < 60)      return 'just now';
        if ($diff < 3600)    return floor($diff / 60) . 'm ago';
        if ($diff < 86400)   return floor($diff / 3600) . 'h ago';
        if ($diff < 604800)  return floor($diff / 86400) . 'd ago';
        return date('j M', $ts);
    }
}

/**
 * Inline SVG icon helper — line icons, 24×24 viewBox, single stroke.
 * Sized via CSS (.nav-icon).
 *
 * Defined before HTML output so the sidebar's first call resolves.
 * Unknown names fall back to `circle`.
 */
if (!function_exists('slate_admin_nav_icon')) {
    function slate_admin_nav_icon(string $name): string {
        $icons = [
            'home'           => '<path d="M3 12L12 4l9 8M5 10v10h4v-6h6v6h4V10"/>',
            'users'          => '<path d="M17 11a4 4 0 1 0-8 0 4 4 0 0 0 8 0zM3 21v-2a5 5 0 0 1 5-5h10a5 5 0 0 1 5 5v2"/>',
            'shield'         => '<path d="M12 3l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V7l8-4z"/>',
            'box'            => '<path d="M21 8l-9-5-9 5v8l9 5 9-5V8zM3 8l9 5 9-5M12 13v9"/>',
            'mail'           => '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/>',
            'settings'       => '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 0 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 0 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 0 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>',
            'star'           => '<path d="M12 2l3 7 7 .8-5.4 4.8L18 22l-6-3.5L6 22l1.4-7.4L2 9.8 9 9l3-7z"/>',
            'circle'         => '<circle cx="12" cy="12" r="9"/>',
            'logout'         => '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>',
            'more'           => '<circle cx="12" cy="12" r="1.5"/><circle cx="5" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/>',
            'search'         => '<circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/>',
            // Commerce / content
            'shopping-bag'   => '<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4zM3 6h18M16 10a4 4 0 0 1-8 0"/>',
            'clipboard-list' => '<rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M9 12h.01M9 16h.01M13 12h2M13 16h2"/>',
            'package'        => '<path d="M16.5 9.4L7.5 4.21M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16zM3.27 6.96L12 12l8.73-5.04M12 22V12"/>',
            'folder'         => '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
            'tag'            => '<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><circle cx="7" cy="7" r="1.5"/>',
            'bar-chart-2'    => '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
            // Extended set added Phase 0 (was: featureless "circle" fallback)
            'truck'          => '<path d="M1 3h15v13H1z"/><path d="M16 8h4l3 3v5h-7"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/>',
            'image'          => '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/>',
            'edit-3'         => '<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4z"/>',
            'send'           => '<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>',
            'bar-chart-3'    => '<path d="M3 3v18h18"/><path d="M7 16V9"/><path d="M11 16V5"/><path d="M15 16v-7"/><path d="M19 16v-4"/>',
            'calendar'       => '<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
            'credit-card'    => '<rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/>',
            // Booking sub-nav (were falling back to the featureless circle)
            'plus'           => '<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>',
            'list'           => '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
            'map-pin'        => '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>',
        ];
        $path = $icons[$name] ?? $icons['circle'];
        return '<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" '
             . 'stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" '
             . 'aria-hidden="true">' . $path . '</svg>';
    }
}
?>
<!DOCTYPE html>
<html lang="<?= e(I18n::currentLocale()) ?>">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <meta name="theme-color" content="#0E1117">
    <title><?= e($pageTitle) ?> — <?= e($siteName) ?></title>
    <meta name="csrf" content="<?= e(csrf_token()) ?>">
    <?php slate_ui_emit_css(); ?>
    <?php require SLATE_ROOT . '/includes/a11y_head.php'; ?>
    <style>
    /* ─── Layout ──────────────────────────────────────────── */
    body {
        min-height: 100vh;
        min-height: 100dvh;
        background: var(--bg);
    }
    .app-layout {
        display: grid;
        grid-template-columns: 1fr;
        grid-template-rows: auto 1fr;
        min-height: 100vh;
        min-height: 100dvh;
    }

    /* ─── Topbar ──────────────────────────────────────────── */
    .topbar {
        position: sticky;
        top: 0;
        z-index: 30;
        height: var(--topbar-height);
        background: rgba(245, 244, 241, 0.82);
        backdrop-filter: saturate(180%) blur(12px);
        -webkit-backdrop-filter: saturate(180%) blur(12px);
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 0 20px 0 24px;
    }
    /* Mobile-only current PAGE TITLE in the topbar (sidebar hidden < 768px).
       Hidden on desktop, where the breadcrumb fills the topbar instead. */
    .topbar-page-title-m {
        display: none;
        flex: 1 1 auto; min-width: 0;
        font-family: var(--font-display);
        font-size: 16px; font-weight: 600;
        color: var(--text); letter-spacing: -0.015em;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    /* Breadcrumb relocated into the topbar (desktop). Hidden until populated
       and on mobile (where the breadcrumb stays in content). */
    .topbar-bc-slot { display: none; min-width: 0; flex: 1 1 auto; align-items: center; }
    .topbar-bc-slot.is-filled { display: flex; }
    .topbar-bc-slot .slate-bc { margin: 0; }
    .topbar-bc-slot .slate-bc-item { font-size: 12.5px; }
    .topbar-bc-slot .slate-bc-current { color: var(--text); }
    .topbar-search-slot {
        flex: 0 1 auto;
        min-width: 0;
        display: flex;
        justify-content: center;
    }
    .topbar-search-slot:empty { flex: 1 1 auto; }
    .topbar-search {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 6px 10px 6px 11px;
        border-radius: var(--radius);
        background: var(--surface);
        border: 1px solid var(--border);
        width: 100%;
        max-width: 360px;
        color: var(--muted);
        transition: border-color 140ms ease, box-shadow 140ms ease;
    }
    .topbar-search:focus-within {
        border-color: var(--accent);
        box-shadow: 0 0 0 3px var(--ring);
    }
    .topbar-search input {
        border: 0;
        background: transparent;
        outline: none;
        flex: 1;
        font-size: 13px;
        color: var(--text);
        font-family: inherit;
        min-width: 0;
    }
    .topbar-search input::placeholder { color: var(--subtle); }
    .topbar-search kbd {
        font-family: var(--font-mono);
        font-size: 10.5px;
        padding: 1px 5px;
        background: var(--surface-2);
        color: var(--muted);
        border-radius: 4px;
        border: 1px solid var(--border);
    }
    .topbar-actions {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        flex-shrink: 0;
    }
    .topbar-icon-btn {
        width: 32px;
        height: 32px;
        border-radius: var(--radius-sm);
        background: transparent;
        border: 0;
        color: var(--muted);
        display: grid;
        place-items: center;
        cursor: pointer;
        position: relative;
        transition: background 140ms ease, color 140ms ease;
    }
    .topbar-icon-btn:hover { background: var(--surface-2); color: var(--text); }
    .topbar-icon-btn-dot {
        position: absolute;
        top: 6px; right: 6px;
        width: 6px; height: 6px;
        border-radius: 999px;
        background: var(--danger);
        box-shadow: 0 0 0 2px var(--bg);
    }
    .topbar-user {
        display: flex;
        align-items: center;
        gap: 8px;
        color: var(--muted);
        font-size: 12.5px;
    }
    .topbar-user-name {
        font-weight: 500;
        color: var(--text-2);
    }
    .topbar-avatar {
        width: 30px; height: 30px;
        border-radius: 999px;
        background: linear-gradient(135deg, #F59E0B, #EC4899);
        color: #fff;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-weight: 600;
        font-size: 12px;
        letter-spacing: 0.01em;
        flex-shrink: 0;
        box-shadow: 0 1px 2px rgba(15,17,23,0.12);
    }

    /* ─── Topbar menus (notifications + user) ────────────── */
    .topbar-menu { position: relative; display: inline-flex; }
    .topbar-badge {
        position: absolute; top: 1px; right: 0;
        min-width: 15px; height: 15px; padding: 0 4px;
        border-radius: 999px; background: var(--danger); color: #fff;
        font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
        box-shadow: 0 0 0 2px var(--bg); pointer-events: none;
    }
    .topbar-user-btn {
        display: inline-flex; align-items: center; gap: 7px;
        padding: 4px 4px 4px 11px; border: 1px solid transparent;
        border-radius: 999px; background: transparent; cursor: pointer;
        color: var(--text-2); font: inherit; transition: background .12s, border-color .12s;
    }
    .topbar-user-btn .topbar-user-name { font-size: 13px; font-weight: 500; color: var(--text-2); }
    .topbar-user-btn:hover { background: var(--surface-2); border-color: var(--border); }
    .topbar-user-caret { color: var(--subtle); }
    .topbar-pop {
        position: absolute; top: calc(100% + 8px); right: 0;
        min-width: 260px; max-width: 320px;
        background: var(--surface); border: 1px solid var(--border);
        border-radius: var(--radius-lg);
        box-shadow: 0 12px 32px rgba(15,17,23,0.14);
        z-index: 60; overflow: hidden;
        animation: topbar-pop-in .12s ease;
    }
    @keyframes topbar-pop-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
    .topbar-pop-head {
        display: flex; align-items: center; justify-content: space-between;
        padding: 11px 14px; border-bottom: 1px solid var(--border);
        font-size: 12.5px; font-weight: 600; color: var(--text);
    }
    .topbar-pop-link { background: none; border: 0; padding: 0; cursor: pointer;
        font: inherit; font-size: 11.5px; font-weight: 500; color: var(--accent); }
    .topbar-pop-link:hover { text-decoration: underline; }
    .topbar-pop-body { max-height: 360px; overflow-y: auto; }
    .topbar-pop-empty { padding: 22px 14px; text-align: center; color: var(--muted); font-size: 12.5px; }
    .topbar-notif {
        display: flex; gap: 9px; padding: 11px 14px; text-decoration: none;
        color: var(--text); border-bottom: 1px solid var(--border);
        transition: background .1s;
    }
    .topbar-notif:last-child { border-bottom: 0; }
    a.topbar-notif:hover { background: var(--surface-2); }
    .topbar-notif-dot { width: 7px; height: 7px; border-radius: 999px; background: transparent; margin-top: 5px; flex: none; }
    .topbar-notif.is-unread .topbar-notif-dot { background: var(--accent); }
    .topbar-notif-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
    .topbar-notif-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
    .topbar-notif-body { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
    .topbar-notif-time { font-size: 11px; color: var(--subtle); margin-top: 2px; }
    .topbar-pop-userhead { padding: 12px 14px; border-bottom: 1px solid var(--border); }
    .topbar-pop-username { font-size: 13px; font-weight: 600; color: var(--text); }
    .topbar-pop-userrole { font-size: 11.5px; color: var(--accent); margin-top: 1px; }
    .topbar-pop-useremail { font-size: 11.5px; color: var(--muted); margin-top: 2px; overflow-wrap: anywhere; }
    .topbar-pop-item {
        display: block; padding: 9px 14px; text-decoration: none;
        color: var(--text-2); font-size: 12.5px; transition: background .1s;
    }
    .topbar-pop-item:hover { background: var(--surface-2); color: var(--text); }
    .topbar-pop-item-danger { color: var(--danger); }
    .topbar-pop-item-danger:hover { background: var(--danger-soft); color: var(--danger); }

    main.content {
        padding: 16px 14px 28px;
        /* clear the fixed bottom tab bar (its height + safe area + a little room) */
        padding-bottom: calc(20px + var(--tabbar-height) + env(safe-area-inset-bottom, 0));
        max-width: var(--content-max);
        width: 100%;
        min-width: 0;
    }

    /* ─── Sidebar (desktop only) ──────────────────────────── */
    .sidebar { display: none; }

    @media (min-width: 768px) {
        .app-layout {
            grid-template-columns: var(--sidebar-width) 1fr;
            grid-template-rows: auto 1fr;
            grid-template-areas:
                "sidebar topbar"
                "sidebar main";
        }
        .topbar { grid-area: topbar; }
        /* Desktop: breadcrumb fills the topbar; the page title shows in the
           content page-header (the mobile-only topbar title is hidden). */
        .topbar-page-title-m { display: none; }
        .topbar-search-slot { justify-content: flex-start; }
        main.content {
            grid-area: main;
            padding: 24px 32px 56px;
        }
        .sidebar {
            grid-area: sidebar;
            display: flex; flex-direction: column;
            background: var(--sidebar-bg);
            color: var(--sidebar-text);
            padding: 14px 10px 10px;
            position: sticky;
            top: 0;
            height: 100vh;
            height: 100dvh;
            overflow-y: auto;
        }

        /* Brand row */
        .sidebar-brand {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 4px 6px 14px;
            margin-bottom: 8px;
            border-bottom: 1px solid var(--sidebar-border);
            color: #fff;
            text-decoration: none;
        }
        .sidebar-brand:hover { color: #fff; text-decoration: none; }
        .sidebar-brand-mark {
            width: 30px;
            height: 30px;
            border-radius: var(--radius);
            background: var(--accent);
            color: var(--on-accent);
            display: grid;
            place-items: center;
            font-family: var(--font-display);
            font-size: 14px;
            font-weight: 700;
            letter-spacing: -0.04em;
            flex-shrink: 0;
            box-shadow: 0 1px 2px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.18);
        }
        .sidebar-brand-text {
            min-width: 0;
            flex: 1;
            line-height: 1.15;
        }
        .sidebar-brand-name {
            display: block;
            font-family: var(--font-display);
            font-size: 15px;
            font-weight: 700;
            letter-spacing: -0.02em;
            color: #fff;
        }
        .sidebar-brand-sub {
            display: block;
            font-size: 10.5px;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--sidebar-subtle);
            font-weight: 600;
            margin-top: 2px;
        }

        /* Section labels */
        .nav-section-label {
            font-size: 11px;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            color: var(--sidebar-subtle);
            padding: 14px 10px 6px;
            font-weight: 600;
        }
        .nav-section-label:first-of-type { padding-top: 6px; }

        .sidebar-nav { list-style: none; padding: 0; margin: 0 0 2px; }
        .sidebar-item a {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 7px 10px;
            color: var(--sidebar-text);
            text-decoration: none;
            font-size: 13px;
            font-weight: 500;
            border-radius: var(--radius-sm);
            transition: background 0.12s ease, color 0.12s ease;
        }
        .sidebar-item a:hover {
            background: var(--sidebar-hover);
            color: #fff;
            text-decoration: none;
        }
        .sidebar-item.is-active a {
            background: var(--sidebar-active);
            color: #fff;
        }
        .sidebar-item.is-active a .nav-icon { color: #fff; }
        .nav-icon {
            width: 17px; height: 17px;
            flex-shrink: 0;
            color: var(--sidebar-muted);
            transition: color 0.12s ease;
        }
        .sidebar-item a:hover .nav-icon { color: #fff; }

        .sidebar-footer {
            margin-top: auto;
            padding: 10px 8px 4px;
            border-top: 1px solid var(--sidebar-border);
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .sidebar-footer .sidebar-user-info { min-width: 0; flex: 1; }
        .sidebar-footer .sidebar-user-name {
            font-size: 13px;
            font-weight: 600;
            color: #fff;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            line-height: 1.2;
        }
        .sidebar-footer .sidebar-user-role {
            font-size: 11px;
            color: var(--sidebar-muted);
            line-height: 1.3;
        }
        .sidebar-footer-logout {
            width: 32px;
            height: 32px;
            border-radius: var(--radius-sm);
            color: var(--sidebar-muted);
            display: grid;
            place-items: center;
            text-decoration: none;
            transition: background 0.12s, color 0.12s;
        }
        .sidebar-footer-logout:hover { background: var(--sidebar-hover); color: #fff; }
    }

    /* ─── Bottom tab bar (mobile only) — plain fixed edge-to-edge nav ─── */
    .tabbar {
        position: fixed;
        bottom: 0; left: 0; right: 0;
        height: var(--tabbar-height);
        padding-bottom: env(safe-area-inset-bottom, 0);
        background: var(--surface);
        border-top: 1px solid var(--border);
        box-shadow: 0 -1px 0 var(--border);
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        align-items: stretch;
        z-index: 30;
        overflow: hidden;
    }
    /* "More" trigger — a flat tab cell, same as the others (no raised FAB). */
    .tabbar-item,
    .tabbar-fab {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 5px;
        color: var(--subtle);
        text-decoration: none;
        font-size: 10px;
        font-weight: 600;
        line-height: 1;
        padding: 8px 4px;
        border: none;
        background: transparent;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
        transition: color 0.16s ease;
    }
    .tabbar-item:hover,
    .tabbar-fab:hover { color: var(--text-2); text-decoration: none; }
    .tabbar-item.is-active { color: var(--accent); }
    .tabbar-item .nav-icon,
    .tabbar-fab svg {
        width: 23px; height: 23px; color: var(--subtle);
        transition: color .16s ease, transform .2s ease;
    }
    .tabbar-item.is-active .nav-icon { color: var(--accent); }
    /* The More "+" tints + rotates to a × when its sheet is open. */
    .tabbar-fab[aria-expanded="true"] { color: var(--accent); }
    .tabbar-fab[aria-expanded="true"] svg { color: var(--accent); transform: rotate(135deg); }
    .tabbar-fab:active svg { transform: scale(0.9); }
    .tabbar-item-label {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 100%;
    }

    @media (min-width: 768px) {
        .tabbar { display: none; }
        main.content { padding-bottom: 56px; }
    }
    @media (max-width: 767px) {
        /* Keep the mobile topbar minimal & uncluttered: brand · bell · avatar.
           Breadcrumb stays in the content; gear + name are dropped here. */
        .topbar { height: 54px; background: rgba(255,255,255,0.92); padding: 0 12px; gap: 8px; }
        .topbar-page-title-m { display: block; }
        .topbar-bc-slot { display: none !important; }
        /* Title lives in the topbar on mobile — drop the big content H1 so it
           isn't duplicated. Breadcrumb, sub-text and actions stay. */
        main.content .page-header h1 { display: none; }
        .topbar-search-slot { display: none; }
        .topbar-gear { display: none; }
        .topbar-user-name { display: none; }
        .topbar-user-caret { display: none; }
        .topbar-actions { gap: 4px; }
        .topbar-actions .btn { padding: 7px 10px; }
        .topbar-icon-btn { width: 36px; height: 36px; }
        .topbar-user-btn { padding: 3px; gap: 0; }
        .topbar-avatar { width: 30px; height: 30px; }
        .topbar-pop { min-width: 240px; max-width: calc(100vw - 24px); }

        /* Compact action buttons in page headers / toolbars on phones so
           multi-button rows (Mark completed, No-show, Cancel, …) don't
           dominate the screen. */
        .page-header .btn:not(.btn-lg):not(.btn-block),
        .toolbar .btn:not(.btn-lg):not(.btn-block) {
            min-height: 38px; padding: 8px 12px; font-size: 12.5px;
        }
        .page-header input[type="datetime-local"],
        .page-header input[type="date"] { min-height: 38px; padding: 7px 10px; font-size: 12.5px; }
        /* Detail-page toolbars wrap each action in an inline <form>; unwrap
           them so the buttons fill the row instead of leaving empty space. */
        .page-header .toolbar { display: flex; flex-wrap: wrap; gap: 8px; width: 100%; }
        .page-header .toolbar > form { display: contents; }
        .page-header .toolbar .btn { flex: 1 1 calc(50% - 4px); justify-content: center; }
        .page-header .toolbar input[type="datetime-local"],
        .page-header .toolbar input[type="date"] { flex: 1 1 100%; }
    }

    /* ─── More sheet (mobile overflow nav) ────────────────── */
    .sheet-overlay {
        position: fixed;
        inset: 0;
        background: rgba(14, 17, 23, 0.4);
        backdrop-filter: blur(3px);
        -webkit-backdrop-filter: blur(3px);
        z-index: 40;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
    }
    .sheet-overlay.is-open {
        opacity: 1;
        pointer-events: auto;
    }
    /* Full-screen menu (covers the whole viewport, slides up from the bottom). */
    .sheet {
        position: fixed;
        inset: 0;
        display: flex; flex-direction: column;
        background: var(--surface);
        border: 0;
        border-radius: 0;
        box-shadow: none;
        z-index: 50;
        transform: translateY(100%);
        transition: transform 0.32s cubic-bezier(.22,1.2,.36,1);
        max-height: none;
        overflow: hidden;
    }
    .sheet.is-open { transform: translateY(0); }
    /* No drag handle on the full-screen menu — the × closes it. */
    .sheet-grab { display: none; }
    .sheet-handle {
        width: 40px; height: 4px;
        background: var(--faint);
        border-radius: 999px;
    }
    .sheet-topbar {
        display: flex; align-items: center; gap: 8px;
        padding: calc(env(safe-area-inset-top, 0) + 14px) 14px 14px 20px;
        border-bottom: 1px solid var(--border);
    }
    .sheet-header {
        flex: 1; min-width: 0;
        font-size: 16px;
        font-weight: 600;
        color: var(--text);
        letter-spacing: -0.01em;
        font-family: var(--font-display);
    }
    .sheet-close {
        width: 32px; height: 32px; flex: none;
        display: grid; place-items: center;
        border: 1px solid var(--border); border-radius: 999px;
        background: var(--surface-2); color: var(--muted);
        cursor: pointer; -webkit-tap-highlight-color: transparent;
        transition: background .12s ease, color .12s ease;
    }
    .sheet-close:active { background: var(--surface-sunken); color: var(--text); }
    /* Inner scroll area; the bottom fade hints there's more below the fold. */
    .sheet-scroll {
        flex: 1 1 auto; min-height: 0;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        padding-bottom: calc(env(safe-area-inset-bottom, 0) + 12px);
        -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 22px), transparent);
                mask-image: linear-gradient(180deg, #000 calc(100% - 22px), transparent);
    }
    .sheet-section-label {
        font-family: var(--font-mono);
        font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
        color: var(--subtle); font-weight: 600;
        padding: 14px 20px 7px;
    }
    /* App-style quick-menu: 2-column tile grid, grouped by section. */
    .sheet-nav {
        list-style: none; margin: 0;
        padding: 0 14px 2px;
        display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
    }
    .sheet-nav li { margin: 0; }
    .sheet-nav a {
        display: flex; align-items: center; gap: 11px;
        padding: 10px 12px;
        color: var(--text);
        text-decoration: none;
        font-size: 13.5px;
        font-weight: 500;
        border: 1px solid var(--border);
        border-radius: 13px;
        background: var(--surface-2);
        min-height: 56px;
        -webkit-tap-highlight-color: transparent;
        transition: border-color .12s ease, background .12s ease, transform .08s ease;
    }
    .sheet-nav a:active { transform: scale(0.97); }
    .sheet-nav a:hover { background: var(--surface); border-color: var(--border-stronger); text-decoration: none; }
    .sheet-nav-ico {
        width: 34px; height: 34px; flex: none;
        display: grid; place-items: center;
        border-radius: 10px;
        background: var(--surface); border: 1px solid var(--border);
        color: var(--muted);
    }
    .sheet-nav .nav-icon { width: 19px; height: 19px; color: inherit; flex: none; }
    .sheet-nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .sheet-nav a.is-active {
        background: var(--accent-soft); border-color: transparent; color: var(--accent-deep);
    }
    .sheet-nav a.is-active .sheet-nav-ico { background: var(--accent); border-color: transparent; color: #fff; }
    .sheet-nav-danger { color: var(--danger); }
    .sheet-nav-danger .sheet-nav-ico { color: var(--danger); }

    @media (max-width: 480px) {
        .topbar-user-name { display: none; }
    }
    </style>
</head>
<body>
    <a class="skip-link" href="#content"><?= __('skip_to_content', 'Skip to main content') ?></a>

    <div class="app-layout">

        <!-- ─── Sidebar (desktop) ────────────────────────── -->
        <aside class="sidebar" aria-label="<?= __('primary_navigation', 'Primary navigation') ?>">
            <a href="<?= e(SLATE_URL) ?>/admin/" class="sidebar-brand">
                <span class="sidebar-brand-mark" aria-hidden="true"><?= e(mb_strtoupper(mb_substr($siteName, 0, 1))) ?></span>
                <span class="sidebar-brand-text">
                    <span class="sidebar-brand-name"><?= e($siteName) ?></span>
                    <span class="sidebar-brand-sub"><?= e($brandSublabel) ?></span>
                </span>
            </a>

            <?php foreach ($navGroups as $groupKey => $groupItems): ?>
                <div class="nav-section-label"><?= e(slate_admin_group_label((string)$groupKey)) ?></div>
                <ul class="sidebar-nav">
                    <?php foreach ($groupItems as $item):
                        $isActive = ($currentNav === ($item['slug'] ?? null));
                    ?>
                        <li class="sidebar-item <?= $isActive ? 'is-active' : '' ?>">
                            <a href="<?= e($item['href']) ?>"
                               <?= $isActive ? 'aria-current="page"' : '' ?>>
                                <?= slate_admin_nav_icon($item['icon'] ?? 'circle') ?>
                                <span><?= e($item['label']) ?></span>
                            </a>
                        </li>
                    <?php endforeach; ?>
                </ul>
            <?php endforeach; ?>

            <div class="sidebar-footer">
                <span class="topbar-avatar" aria-hidden="true">
                    <?= e(mb_strtoupper(mb_substr($user['name'] ?? 'A', 0, 1))) ?>
                </span>
                <div class="sidebar-user-info">
                    <div class="sidebar-user-name"><?= e($user['name']) ?></div>
                    <div class="sidebar-user-role"><?= e($user['role'] ?? __('administrator', 'Administrator')) ?></div>
                </div>
                <a href="<?= e(SLATE_URL) ?>/admin/logout.php"
                   class="sidebar-footer-logout"
                   title="<?= __('logout', 'Log out') ?>"
                   aria-label="<?= __('logout', 'Log out') ?>">
                    <?= slate_admin_nav_icon('logout') ?>
                </a>
            </div>
        </aside>

        <!-- ─── Topbar ───────────────────────────────────── -->
        <header class="topbar">
            <!-- Desktop: breadcrumb is relocated into the slot below (topbar
                 carries context). Mobile: the sidebar is hidden, so the topbar
                 shows the current PAGE TITLE here (and the content's big H1 is
                 hidden on mobile to avoid duplication). -->
            <div class="topbar-page-title-m"><?= e($pageTitle) ?></div>
            <!-- The page breadcrumb is relocated here on desktop (see footer JS),
                 filling the topbar and freeing vertical space in the content. -->
            <div class="topbar-bc-slot" id="topbar-bc-slot"></div>
            <div class="topbar-search-slot"><?= $topbarSearchHtml ?></div>
            <div class="topbar-actions">
                <?php foreach ($topbarActions as $actionHtml): ?>
                    <?= $actionHtml ?>
                <?php endforeach; ?>

                <!-- Notifications -->
                <div class="topbar-menu" data-topbar-menu>
                    <button type="button" class="topbar-icon-btn" data-topbar-toggle
                            aria-haspopup="true" aria-expanded="false"
                            aria-label="Notifications<?= $notifUnread > 0 ? ' (' . (int)$notifUnread . ' unread)' : '' ?>">
                        <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
                             stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
                            <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
                        </svg>
                        <?php if ($notifUnread > 0): ?>
                            <span class="topbar-badge" aria-hidden="true"><?= $notifUnread > 99 ? '99+' : (int)$notifUnread ?></span>
                        <?php endif; ?>
                    </button>
                    <div class="topbar-pop topbar-pop-notif" data-topbar-pop hidden role="menu">
                        <div class="topbar-pop-head">
                            <span>Notifications</span>
                            <?php if (!empty($notifItems)): ?>
                                <button type="button" class="topbar-pop-link" data-notif-readall>Mark all read</button>
                            <?php endif; ?>
                        </div>
                        <div class="topbar-pop-body">
                            <?php if (empty($notifItems)): ?>
                                <div class="topbar-pop-empty">You're all caught up.</div>
                            <?php else: foreach ($notifItems as $n):
                                $nUrl = (string)($n['url'] ?? '');
                                $tag  = $nUrl !== '' ? 'a' : 'div';
                                ?>
                                <<?= $tag ?> <?= $nUrl !== '' ? 'href="' . e($nUrl) . '"' : '' ?>
                                    class="topbar-notif<?= empty($n['is_read']) ? ' is-unread' : '' ?>">
                                    <span class="topbar-notif-dot" aria-hidden="true"></span>
                                    <span class="topbar-notif-main">
                                        <span class="topbar-notif-title"><?= e((string)$n['title']) ?></span>
                                        <?php if (!empty($n['body'])): ?>
                                            <span class="topbar-notif-body"><?= e((string)$n['body']) ?></span>
                                        <?php endif; ?>
                                        <span class="topbar-notif-time"><?= e(slate_admin_time_ago((string)($n['created_at'] ?? ''))) ?></span>
                                    </span>
                                </<?= $tag ?>>
                            <?php endforeach; endif; ?>
                        </div>
                    </div>
                </div>

                <!-- Quick settings -->
                <a href="<?= e(SLATE_URL) ?>/admin/settings.php" class="topbar-icon-btn topbar-gear" aria-label="Settings" title="Settings">
                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
                         stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                        <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 1 1-2.83 2.83l-.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-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
                    </svg>
                </a>

                <!-- User menu -->
                <div class="topbar-menu" data-topbar-menu>
                    <button type="button" class="topbar-user-btn" data-topbar-toggle
                            aria-haspopup="true" aria-expanded="false">
                        <span class="topbar-user-name"><?= e($user['name'] ?? 'Account') ?></span>
                        <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor"
                             stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"
                             class="topbar-user-caret"><polyline points="6 9 12 15 18 9"/></svg>
                        <span class="topbar-avatar" aria-hidden="true"><?= e(mb_strtoupper(mb_substr($user['name'] ?? 'A', 0, 1))) ?></span>
                    </button>
                    <div class="topbar-pop topbar-pop-user" data-topbar-pop hidden role="menu">
                        <div class="topbar-pop-userhead">
                            <div class="topbar-pop-username"><?= e($user['name'] ?? '') ?></div>
                            <div class="topbar-pop-userrole"><?= e($user['role'] ?? __('administrator', 'Administrator')) ?></div>
                            <?php if (!empty($user['email'])): ?>
                                <div class="topbar-pop-useremail"><?= e($user['email']) ?></div>
                            <?php endif; ?>
                        </div>
                        <a href="<?= e(SLATE_URL) ?>/admin/settings.php" class="topbar-pop-item" role="menuitem">Settings</a>
                        <a href="<?= e(SLATE_URL) ?>/admin/logout.php" class="topbar-pop-item topbar-pop-item-danger" role="menuitem">Log out</a>
                    </div>
                </div>
            </div>
        </header>

        <!-- ─── Main content ─────────────────────────────── -->
        <main class="content" id="content">