/* Topbar layout, nav pills, donate/logout buttons. Extracted from style.css. Theme via var(--*) tokens. */ /* Developer-mode strip — sits flush at the very top of the viewport above the topbar when CFG_DEV_MODE is on. Above-chrome is the standard env- banner placement (Stripe test-mode, GitHub staff-mode); pushing the topbar down 36px is a deliberate signal that the environment is non- standard. Neutral glass so it reads as "system bar" not "warning"; only the icon picks up the theme accent. Body gets .has-dev-banner to bump padding-top + shift every other fixed-positioned chrome element down by the strip's height. Dismissal is local and remembered in localStorage until dev mode is toggled off + on again. */ .dev-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 1001; height: 36px; display: flex; align-items: center; justify-content: center; padding: 0 56px 0 24px; font-size: 0.85rem; font-weight: 500; color: var(--text-primary); background: rgba(var(--text-rgb), 0.04); border-bottom: 1px solid rgba(var(--text-rgb), 0.12); backdrop-filter: blur(12px) saturate(140%); -webkit-backdrop-filter: blur(12px) saturate(140%); } .dev-banner[hidden] { display: none; } .dev-banner-label { display: inline-flex; align-items: center; gap: 8px; } .dev-banner-label svg { color: var(--accent); } .dev-banner-close { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; background: transparent; border: none; border-radius: 6px; color: var(--text-primary); opacity: 0.65; cursor: pointer; transition: opacity 0.15s ease, background 0.15s ease; } .dev-banner-close:hover { opacity: 1; background: rgba(var(--text-rgb), 0.1); } body.has-dev-banner { padding-top: 96px; } /* Topbar is fixed top:0 on every viewport, so it always slides to top:36 to sit below the banner. The viewport-fitted .apps-layout is in flex flow but sizes itself off the topbar height — pull 36px more off so it doesn't overflow the bottom when the banner is on. Everything else that needs nudging (sidebar / sidebar-container / mobile-drawer) only becomes fixed at mobile widths and is handled in the media query below. */ body.has-dev-banner .topbar { top: 36px; } body.has-dev-banner .apps-layout { height: calc(100vh - 96px); } @media (max-width: 768px) { .dev-banner { padding: 0 48px 0 12px; font-size: 0.8rem; } body.has-dev-banner .sidebar { top: 96px; } body.has-dev-banner .sidebar-container { top: 96px; } body.has-dev-banner .mobile-drawer { top: 96px; height: calc(100vh - 96px); } } /* Topbar */ .topbar { display: flex; justify-content: space-between; align-items: center; padding: 0 24px; height: 60px; position: fixed; top: 0; left: 0; right: 0; z-index: 1000; background: var(--bg-primary, #1a1a1a); border-bottom: 1px solid var(--border-color, #444); backdrop-filter: blur(12px) saturate(140%); -webkit-backdrop-filter: blur(12px) saturate(140%); } .topbar-left { display: flex; align-items: center; flex: 0 0 auto; } .topbar .logo { font-size: 20px; font-weight: 600; } .topbar .donate-btn { padding: 8px 16px; border: none; border-radius: 6px; background: var(--primary-color); color: var(--text-primary); cursor: pointer; transition: background 0.2s; font-weight: 600; } .topbar .logout-btn { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border: 1px solid rgba(var(--status-danger-rgb), 0.3); border-radius: 6px; background: rgba(var(--status-danger-rgb), 0.08); color: var(--status-danger); cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: background 0.15s ease, border-color 0.15s ease; } .topbar .logout-btn:hover { background: rgba(var(--status-danger-rgb), 0.18); border-color: rgba(var(--status-danger-rgb), 0.5); } .topbar-nav { display: flex; gap: 8px; align-items: center; } .topbar-nav .nav-item { background: rgba(var(--text-rgb), 0.1); border: 1px solid rgba(var(--text-rgb), 0.2); border-radius: 8px; padding: 12px 16px; font-size: 14px; font-weight: 500; color: var(--text-muted); text-decoration: none; display: flex; align-items: center; gap: 8px; transition: all 0.2s ease; cursor: pointer; min-height: 44px; white-space: nowrap; } .topbar-nav .nav-item:hover { background: rgba(var(--text-rgb), 0.2); transform: translateY(-1px); } .topbar-nav .nav-item.active { background: var(--primary-color); color: var(--text-primary); border-color: var(--primary-color); } .topbar-nav .nav-item.nav-active { background: var(--primary-color); color: var(--text-primary); border-color: var(--primary-color); } .topbar-nav .nav-item.nav-active:hover { background: var(--accent-hover); border-color: var(--accent-hover); } .topbar-nav .nav-item svg { width: 16px; height: 16px; } /* Disabled state used while a system-wide task (e.g. config_update) is running so the user can't navigate to App Center / Config mid-flight and act on stale data. */ .topbar-nav .nav-item.nav-item-disabled { opacity: 0.45; cursor: not-allowed; pointer-events: auto; /* keep cursor styling, but the JS click handler returns early */ } .topbar-nav .nav-item.nav-item-disabled:hover { background: transparent; } .topbar-controls { display: flex; align-items: center; gap: 12px; } .mobile-drawer { display: flex; align-items: center; flex: 1; justify-content: space-between; gap: 12px; min-width: 0; } .mobile-drawer-page-section { display: none; } @media (max-width: 768px) { .topbar { padding: 0 12px; gap: 8px; justify-content: flex-start; } .topbar-left { flex: 0 0 auto; } .mobile-drawer { position: fixed; top: 60px; left: 0; width: 100vw; height: calc(100vh - 60px); flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 0; padding: 16px; background: var(--surface-bg-solid, #1a1a1a); border-right: 1px solid var(--border-color, #444); box-shadow: 6px 0 24px rgba(0, 0, 0, 0.35); overflow-y: auto; overscroll-behavior: contain; transform: translateX(-100%); transition: transform 0.3s ease; z-index: 101; } .mobile-drawer.mobile-open { transform: translateX(0); } .mobile-drawer .topbar-nav { flex-direction: column; align-items: stretch; gap: 6px; width: 100%; } .mobile-drawer .topbar-nav .nav-item { width: 100%; justify-content: flex-start; padding: 12px 14px; } .mobile-drawer-page-section { display: flex; flex-direction: column; gap: 4px; margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(var(--text-rgb), 0.12); } .mobile-drawer-page-section:empty { display: none; } .mobile-drawer .topbar-controls { flex-direction: column; align-items: stretch; gap: 8px; width: 100%; margin-top: auto; padding-top: 16px; padding-bottom: 16px; border-top: 1px solid rgba(var(--text-rgb), 0.12); position: sticky; bottom: -16px; flex-shrink: 0; background: var(--surface-bg-solid, #1a1a1a); z-index: 1; } .mobile-drawer .topbar-controls .custom-select, .mobile-drawer .topbar-controls .donate-btn, .mobile-drawer .topbar-controls .logout-btn { width: 100%; justify-content: center; } .mobile-drawer .topbar-controls .custom-select-button { width: 100%; justify-content: space-between; } } /* Compact custom-select for the topbar theme switcher. The default .custom-select-button is form-input sized (12px 16px padding, 14px font), too tall for the 60px topbar. */ .topbar-controls .custom-select { width: auto; min-width: 110px; } .topbar-controls .custom-select-button { padding: 6px 12px; font-size: 13px; border-radius: 6px; min-height: 0; line-height: 1.2; } .custom-select-popup-topbar { min-width: 140px; }