/* ========================================================================== Design System: Primitives & Tokens ========================================================================== */ :root { /* --- Primitives: Slate (Neutral) --- */ --slate-50: #f8fafc; --slate-100: #f1f5f9; --slate-200: #e2e8f0; --slate-300: #cbd5e1; --slate-400: #94a3b8; --slate-500: #64748b; --slate-600: #475569; --slate-700: #334155; --slate-800: #1e293b; --slate-900: #0f172a; --slate-950: #020617; /* --- Primitives: Colors --- */ --blue-500: #3b82f6; --blue-600: #2563eb; --blue-700: #1d4ed8; --green-500: #22c55e; --green-600: #16a34a; --amber-500: #f59e0b; --amber-600: #d97706; --red-500: #ef4444; --red-600: #dc2626; /* --- Semantic Tokens: Light Mode (Default) --- */ --bg: var(--slate-50); --surface: #ffffff; --fg: var(--slate-900); --fg-muted: var(--slate-500); --border: var(--slate-200); --border-subtle: var(--slate-100); --primary: var(--blue-500); --primary-hover: var(--blue-600); --success: var(--green-500); --warning: var(--amber-500); --danger: var(--red-500); /* Sidebar — Light mode */ --sidebar-bg: var(--slate-100); --sidebar-fg: var(--slate-700); --sidebar-hover: rgba(0, 0, 0, 0.07); --sidebar-hover-fg: var(--primary); --sidebar-border: rgba(0, 0, 0, 0.08); --sidebar-width: 240px; /* Login page */ --dot-color: rgba(0, 0, 0, 0.055); /* Layout */ --radius: 8px; --radius-sm: 4px; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --font-mono: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; /* Legacy variable mapping */ --card-bg: var(--surface); } /* --- Dark Mode Overrides --- */ @media (prefers-color-scheme: dark) { :root { --bg: var(--slate-900); --surface: var(--slate-800); --fg: var(--slate-100); --fg-muted: var(--slate-400); --border: var(--slate-700); --border-subtle: var(--slate-800); --sidebar-bg: var(--slate-800); --sidebar-fg: var(--slate-200); --sidebar-hover: rgba(255, 255, 255, 0.1); --sidebar-hover-fg: #fff; --sidebar-border: rgba(255, 255, 255, 0.1); --dot-color: rgba(255, 255, 255, 0.045); } } /* Manual Dark Mode Toggle */ .dark { --bg: var(--slate-900); --surface: var(--slate-800); --fg: var(--slate-100); --fg-muted: var(--slate-400); --border: var(--slate-700); --border-subtle: var(--slate-800); --zebra-stripe: rgba(255,255,255,0.03); --sidebar-bg: var(--slate-800); --sidebar-fg: var(--slate-200); --sidebar-hover: rgba(255, 255, 255, 0.1); --sidebar-hover-fg: #fff; --sidebar-border: rgba(255, 255, 255, 0.1); --dot-color: rgba(255, 255, 255, 0.045); } /* Force Light Mode (override system dark preference) */ .light { --bg: var(--slate-50); --surface: #ffffff; --fg: var(--slate-900); --fg-muted: var(--slate-500); --border: var(--slate-200); --border-subtle: var(--slate-100); --sidebar-bg: var(--slate-100); --sidebar-fg: var(--slate-700); --sidebar-hover: rgba(0, 0, 0, 0.07); --sidebar-hover-fg: var(--primary); --sidebar-border: rgba(0, 0, 0, 0.08); --dot-color: rgba(0, 0, 0, 0.055); } .light .alert-error { background: #fef2f2; color: #991b1b; border-color: #fecaca; } .light .alert-info { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; } .light .health-bar-track { background-color: var(--slate-200); } .light .zone-badge { background-color: hsl(var(--zone-hue), 80%, 90%); color: hsl(var(--zone-hue), 80%, 30%); border-color: hsl(var(--zone-hue), 60%, 80%); } .light .matrix-allow { background: #dcfce7; color: #166534; } .light .matrix-deny { background: #fef2f2; color: #991b1b; } .light .matrix-self { background: var(--slate-100); color: var(--slate-400); } .light .badge-accept { background: #dcfce7; color: #166534; } .light .badge-reject { background: #fef2f2; color: #991b1b; } .light .badge-drop { background: var(--slate-100); color: var(--slate-600); } .light .badge-continue { background: #dbeafe; color: #1e40af; } .light .key-full { background: var(--slate-100); } /* ========================================================================== Reset & Base ========================================================================== */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; font-family: var(--font-sans); font-size: 15px; line-height: 1.5; color: var(--fg); background: var(--bg); -webkit-font-smoothing: antialiased; } a { color: var(--primary); text-decoration: none; transition: color 0.15s ease; } a:hover { text-decoration: underline; } /* ========================================================================== Layout ========================================================================== */ .layout { display: flex; height: 100vh; overflow: hidden; } #sidebar { width: var(--sidebar-width); background: var(--sidebar-bg); color: var(--sidebar-fg); display: flex; flex-direction: column; flex-shrink: 0; border-right: 1px solid var(--sidebar-border); overflow-y: auto; } .main-column { flex: 1; display: flex; flex-direction: column; overflow: hidden; } #content { flex: 1; padding: 2rem; overflow-y: auto; } /* ========================================================================== Sidebar Components ========================================================================== */ .sidebar-header { padding: 1.5rem 1.25rem; } .sidebar-logo { max-width: 100%; height: auto; display: block; } .sidebar-nav { flex: 1; padding: 0.5rem 0; overflow-y: auto; } /* Standalone nav link (Dashboard) */ .nav-standalone { margin: 0.25rem 0.5rem 0.5rem; } /* Top-level accordion groups (Status / Configure / Maintenance) */ details.nav-group-top { } details.nav-group-top > summary.nav-group-summary-top { list-style: none; cursor: pointer; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--sidebar-fg); opacity: 0.7; padding: 0.75rem 1rem 0.375rem; display: flex; justify-content: space-between; align-items: center; user-select: none; transition: opacity 0.15s; } details.nav-group-top > summary.nav-group-summary-top:hover { opacity: 1; } details.nav-group-top > summary.nav-group-summary-top::-webkit-details-marker { display: none; } details.nav-group-top > summary.nav-group-summary-top::marker { display: none; } details.nav-group-top > summary.nav-group-summary-top::after { content: '▸'; font-size: 0.7rem; } details[open].nav-group-top > summary.nav-group-summary-top::after { content: '▾'; } /* Static sub-section labels within top-level accordion */ .nav-section-label { font-size: 0.6rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--sidebar-fg); opacity: 0.45; padding: 0.75rem 1rem 0.2rem; user-select: none; } .nav-group-items { list-style: none; padding-bottom: 0.25rem; } .nav-icon { display: inline-block; width: 1rem; height: 1rem; background-color: currentColor; -webkit-mask-image: var(--icon); mask-image: var(--icon); -webkit-mask-size: contain; mask-size: contain; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; flex-shrink: 0; } .nav-link { display: flex; align-items: center; gap: 0.625rem; padding: 0.5rem 1rem; color: var(--sidebar-fg); text-decoration: none; font-size: 0.875rem; border-radius: 0.375rem; margin: 0 0.5rem; transition: background-color 0.15s, color 0.15s; font-weight: 500; } .nav-link:hover { background-color: var(--sidebar-hover); color: var(--sidebar-hover-fg); text-decoration: none; } .nav-link.active { background-color: var(--primary); color: #fff; text-decoration: none; } /* Button styled as a nav-link (for actions like Reboot) */ .nav-link-btn { width: 100%; background: none; border: none; cursor: pointer; text-align: left; font-family: inherit; font-size: inherit; } .nav-link-danger:hover { color: var(--danger); background-color: var(--sidebar-hover); } .sidebar-footer { padding: 1.25rem; border-top: 1px solid var(--sidebar-border); } /* ========================================================================== Buttons ========================================================================== */ .btn { display: inline-flex; align-items: center; justify-content: center; padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: var(--radius); font-size: 0.9rem; font-weight: 500; cursor: pointer; text-decoration: none; transition: all 0.15s ease; line-height: 1.25; } .btn-primary { background: var(--primary); color: #fff; } .btn-primary:hover { background: var(--primary-hover); text-decoration: none; } .btn-block { display: flex; width: 100%; } /* Sidebar Actions */ .btn-sidebar-action, .btn-logout { display: flex; width: 100%; align-items: center; justify-content: center; background: transparent; color: var(--sidebar-fg); border: 1px solid rgba(255,255,255,0.2); padding: 0.5rem 0.75rem; border-radius: var(--radius); cursor: pointer; font-size: 0.85rem; text-decoration: none; margin-bottom: 0.5rem; transition: all 0.15s ease; } .btn-sidebar-action:hover, .btn-logout:hover { background: var(--sidebar-hover); border-color: rgba(255,255,255,0.3); text-decoration: none; } .btn-danger-hover:hover { border-color: var(--danger); color: #fca5a5; background: rgba(220, 38, 38, 0.1); } /* ========================================================================== Cards & Content Containers ========================================================================== */ .info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .info-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-sm); overflow: hidden; margin-bottom: 1.5rem; display: flex; flex-direction: column; } /* Legacy support: info-card often contains direct h2 */ .info-card h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; color: var(--fg-muted); padding: 1rem 1.25rem; border-bottom: 1px solid var(--border); margin: 0; background: var(--bg); } .card-header { padding: 0.75rem 1.25rem; background: var(--bg); border-bottom: 1px solid var(--border); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; color: var(--fg-muted); } .card-body { padding: 1.25rem; } /* ========================================================================== Tables ========================================================================== */ .info-table, .disk-table, .counters-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; } .info-table th, .info-table td, .disk-table th, .disk-table td, .counters-table th, .counters-table td { padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--border); } .info-table tr:last-child th, .info-table tr:last-child td, .disk-table tbody tr:last-child td, .counters-table tbody tr:last-child td { border-bottom: none; } .info-table tr:nth-child(even), .disk-table tbody tr:nth-child(even), .counters-table tbody tr:nth-child(even) { background-color: var(--zebra-stripe, rgba(0,0,0,0.025)); } .info-table th { text-align: left; font-weight: 500; color: var(--fg-muted); width: 40%; background: transparent; } .disk-table thead th, .counters-table thead th { background: var(--bg); font-weight: 600; color: var(--fg-muted); text-align: left; font-size: 0.8rem; text-transform: uppercase; } .counters-table td { font-family: var(--font-mono); } /* New Data Table Styles */ .data-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); } .data-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; white-space: nowrap; } .data-table th, .data-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); text-align: left; } .data-table th { background: var(--bg); color: var(--fg-muted); font-weight: 600; font-size: 0.8rem; text-transform: uppercase; } .data-table tbody tr:nth-child(even) { background-color: var(--zebra-stripe, rgba(0,0,0,0.025)); } .data-table tbody tr:hover { background-color: rgba(0,0,0,0.05); } /* ========================================================================== Forms & Inputs ========================================================================== */ .form-group { margin-bottom: 1.25rem; } .form-group label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem; color: var(--fg); } .form-group input { display: block; width: 100%; padding: 0.6rem 0.8rem; background: var(--surface); color: var(--fg); border: 1px solid var(--border); border-radius: var(--radius); font-size: 0.9rem; transition: all 0.15s ease; } .form-group input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); } /* ========================================================================== Authentication ========================================================================== */ @keyframes login-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } } .login-wrapper { display: flex; align-items: center; justify-content: center; min-height: 100vh; background-color: var(--bg); background-image: radial-gradient(var(--dot-color) 1.5px, transparent 1.5px); background-size: 22px 22px; padding: 1rem; } .login-card { background: var(--surface); padding: 2.5rem; border-radius: var(--radius); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -2px rgb(0 0 0 / 0.07), 0 0 0 1px var(--border); width: 100%; max-width: 400px; border-top: 2px solid var(--primary); animation: login-rise 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; } .login-logo { display: block; max-width: 180px; height: auto; margin: 0 auto 2rem; } /* ========================================================================== Alerts & Banners ========================================================================== */ /* Top progress bar — shown during htmx page navigations */ .page-progress { position: fixed; top: 0; left: 0; height: 2px; width: 0%; background: var(--primary); z-index: 9999; opacity: 0; pointer-events: none; } .conn-banner { background: var(--danger); color: #fff; text-align: center; padding: 0.5rem; font-size: 0.9rem; font-weight: 600; position: sticky; top: 0; z-index: 50; } .alert { padding: 1rem; border-radius: var(--radius); margin-bottom: 1.5rem; font-size: 0.9rem; border: 1px solid transparent; } .alert-error { background: #fef2f2; color: var(--red-600); border-color: #fecaca; } .alert-info { background: #eff6ff; color: var(--blue-700); border-color: #bfdbfe; } /* Dark mode adjustments for alerts */ @media (prefers-color-scheme: dark) { .alert-error { background: rgba(239, 68, 68, 0.1); color: #fca5a5; border-color: rgba(239, 68, 68, 0.2); } .alert-info { background: rgba(59, 130, 246, 0.1); color: #93c5fd; border-color: rgba(59, 130, 246, 0.2); } } .dark .alert-error { background: rgba(239, 68, 68, 0.1); color: #fca5a5; border-color: rgba(239, 68, 68, 0.2); } .dark .alert-info { background: rgba(59, 130, 246, 0.1); color: #93c5fd; border-color: rgba(59, 130, 246, 0.2); } /* ========================================================================== Status Indicators ========================================================================== */ /* Legacy Status Dots */ .status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 0.5rem; vertical-align: middle; } .status-up { background: var(--success); } .status-down { background: var(--danger); } /* New Interface Status Indicators */ .iface-status { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 0.5rem; } .iface-up { background-color: var(--success); box-shadow: 0 0 0 rgba(34, 197, 94, 0.4); animation: pulse-green 2s infinite; } .iface-down { background-color: var(--danger); } .iface-lower-down { background-color: var(--warning); } @keyframes pulse-green { 0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); } 70% { box-shadow: 0 0 0 6px rgba(34, 197, 94, 0); } 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); } } /* ========================================================================== Components: Health Bar ========================================================================== */ .health-bar-track { width: 100%; height: 6px; background-color: var(--slate-200); border-radius: 99px; overflow: hidden; } .health-bar-fill { height: 100%; background-color: var(--success); border-radius: 99px; transition: width 0.5s ease; } .health-bar-fill.is-warn { background-color: var(--warning); } .health-bar-fill.is-crit { background-color: var(--danger); } /* Dark mode track */ @media (prefers-color-scheme: dark) { .health-bar-track { background-color: var(--slate-700); } } .dark .health-bar-track { background-color: var(--slate-700); } /* ========================================================================== Components: Zone Badge ========================================================================== */ .zone-badge { /* Requires --zone-hue to be set inline or via utility */ --zone-hue: 200; /* Default */ display: inline-flex; align-items: center; padding: 0.25rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: 600; background-color: hsl(var(--zone-hue), 80%, 90%); color: hsl(var(--zone-hue), 80%, 30%); border: 1px solid hsl(var(--zone-hue), 60%, 80%); } @media (prefers-color-scheme: dark) { .zone-badge { background-color: hsl(var(--zone-hue), 60%, 20%); color: hsl(var(--zone-hue), 80%, 85%); border-color: hsl(var(--zone-hue), 60%, 30%); } } .dark .zone-badge { background-color: hsl(var(--zone-hue), 60%, 20%); color: hsl(var(--zone-hue), 80%, 85%); border-color: hsl(var(--zone-hue), 60%, 30%); } /* ========================================================================== Utilities ========================================================================== */ .num { font-variant-numeric: tabular-nums; font-family: var(--font-mono); } /* ========================================================================== Domain Specific: Firewall, Keystore, Firmware ========================================================================== */ /* Zone Matrix */ .matrix-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); } .zone-matrix { border-collapse: collapse; font-size: 0.9rem; width: 100%; } .zone-matrix th, .zone-matrix td { padding: 0.75rem; text-align: center; border: 1px solid var(--border); min-width: 90px; } .zone-matrix thead th { font-weight: 600; color: var(--fg-muted); background: var(--bg); } .zone-matrix tbody th { font-weight: 600; text-align: right; background: var(--bg); color: var(--fg-muted); } .matrix-corner { color: var(--fg-muted); } .matrix-allow { background: #dcfce7; color: #166534; font-weight: 700; } .matrix-deny { background: #fef2f2; color: #991b1b; font-weight: 700; } .matrix-self { background: var(--slate-100); color: var(--slate-400); } @media (prefers-color-scheme: dark) { .matrix-allow { background: rgba(22, 163, 74, 0.2); color: #86efac; } .matrix-deny { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .matrix-self { background: var(--slate-800); color: var(--slate-600); } } .dark .matrix-allow { background: rgba(22, 163, 74, 0.2); color: #86efac; } .dark .matrix-deny { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .dark .matrix-self { background: var(--slate-800); color: var(--slate-600); } .matrix-legend { display: flex; gap: 1.5rem; font-size: 0.85rem; color: var(--fg-muted); margin-top: 1rem; } .legend-item { display: flex; align-items: center; gap: 0.5rem; } .legend-swatch { width: 16px; height: 16px; border-radius: 4px; border: 1px solid var(--border); } /* Badges */ .badge { display: inline-block; padding: 0.25rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-weight: 600; text-transform: capitalize; } .badge-accept { background: #dcfce7; color: #166534; } .badge-reject { background: #fef2f2; color: #991b1b; } .badge-drop { background: var(--slate-100); color: var(--slate-600); } .badge-continue { background: #dbeafe; color: #1e40af; } @media (prefers-color-scheme: dark) { .badge-accept { background: rgba(22, 163, 74, 0.2); color: #86efac; } .badge-reject { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .badge-drop { background: var(--slate-800); color: var(--slate-400); } .badge-continue { background: rgba(59, 130, 246, 0.2); color: #93c5fd; } } .dark .badge-accept { background: rgba(22, 163, 74, 0.2); color: #86efac; } .dark .badge-reject { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .dark .badge-drop { background: var(--slate-800); color: var(--slate-400); } .dark .badge-continue { background: rgba(59, 130, 246, 0.2); color: #93c5fd; } /* General-purpose status badges */ .badge-up { background: #dcfce7; color: #166534; } .badge-down { background: #fef2f2; color: #991b1b; } .badge-warning { background: #fef9c3; color: #854d0e; } .badge-info { background: #dbeafe; color: #1e40af; } .badge-neutral { background: var(--slate-100); color: var(--slate-600); } @media (prefers-color-scheme: dark) { .badge-up { background: rgba(22, 163, 74, 0.2); color: #86efac; } .badge-down { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .badge-warning { background: rgba(245, 158, 11, 0.2); color: #fcd34d; } .badge-info { background: rgba(59, 130, 246, 0.2); color: #93c5fd; } .badge-neutral { background: var(--slate-800); color: var(--slate-400); } } .dark .badge-up { background: rgba(22, 163, 74, 0.2); color: #86efac; } .dark .badge-down { background: rgba(220, 38, 38, 0.2); color: #fca5a5; } .dark .badge-warning { background: rgba(245, 158, 11, 0.2); color: #fcd34d; } .dark .badge-info { background: rgba(59, 130, 246, 0.2); color: #93c5fd; } .dark .badge-neutral { background: var(--slate-800); color: var(--slate-400); } .light .badge-up { background: #dcfce7; color: #166534; } .light .badge-down { background: #fef2f2; color: #991b1b; } .light .badge-warning { background: #fef9c3; color: #854d0e; } .light .badge-info { background: #dbeafe; color: #1e40af; } .light .badge-neutral { background: var(--slate-100); color: var(--slate-600); } /* Interfaces */ .iface-name { white-space: nowrap; font-weight: 500; } .iface-name a { color: var(--primary); text-decoration: none; } .iface-name a:hover { text-decoration: underline; } .tree-pipe { color: var(--slate-300); font-family: var(--font-mono); margin-right: 0.5rem; } .addr-origin { color: var(--fg-muted); font-size: 0.75rem; margin-left: 0.5rem; } .wg-peer { padding: 1rem 0; border-bottom: 1px solid var(--border); } .wg-peer:last-child { border-bottom: none; } .eth-stats { display: grid; grid-template-columns: 1fr 1fr; font-size: 0.85rem; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; } .eth-stats-row { display: flex; justify-content: space-between; padding: 0.5rem 1rem; border-bottom: 1px solid var(--border); background: var(--surface); } .eth-stats dt { color: var(--fg-muted); font-family: var(--font-mono); } .eth-stats dd { text-align: right; font-family: var(--font-mono); font-weight: 600; } .iface-detail-header { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 1rem; } .iface-detail-header h2 { font-size: 1.5rem; font-weight: 600; color: var(--fg); } .back-link { display: inline-flex; align-items: center; margin-bottom: 0.5rem; font-size: 0.9rem; } .info-card h3 { font-size: 0.85rem; font-weight: 600; text-transform: uppercase; color: var(--fg-muted); margin: 1.5rem 0 0.75rem; padding-left: 0.5rem; border-left: 3px solid var(--primary); } /* Firmware & Reboot */ .firmware-form { display: flex; gap: 1rem; align-items: flex-end; } .firmware-form .form-group { flex: 1; margin-bottom: 0; } .progress-bar-wrap { background: var(--slate-200); border-radius: var(--radius); height: 1.25rem; overflow: hidden; margin-top: 0.5rem; } .progress-bar { background: var(--primary); height: 100%; width: 0%; transition: width 0.3s ease; } .progress-text { font-size: 0.85rem; color: var(--fg-muted); margin-top: 0.5rem; text-align: center; } .reboot-overlay { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; } .reboot-spinner { width: 48px; height: 48px; border: 4px solid var(--slate-200); border-top-color: var(--primary); border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 1.5rem; } @keyframes spin { to { transform: rotate(360deg); } } .reboot-message { font-size: 1.25rem; font-weight: 600; color: var(--fg); } .reboot-status { font-size: 1rem; color: var(--fg-muted); margin-top: 0.5rem; } .reboot-status.is-error { color: var(--danger); } /* Keystore */ .empty-message { color: var(--fg-muted); font-style: italic; padding: 1rem; text-align: center; } .key-full { font-family: var(--font-mono); font-size: 0.8rem; word-break: break-all; color: var(--fg); background: var(--slate-100); padding: 0.25rem; border-radius: 4px; } @media (prefers-color-scheme: dark) { .key-full { background: var(--slate-800); } } .dark .key-full { background: var(--slate-800); } /* Survey */ .survey-chart { max-width: 100%; height: auto; display: block; } .survey-hint { font-size: 0.8rem; color: var(--fg-muted); margin-bottom: 1rem; } /* WiFi */ .wifi-radio-header { display: flex; justify-content: space-between; align-items: center; width: 100%; } .wifi-caps { display: flex; gap: 0.375rem; align-items: center; } .wifi-survey-body { padding: 1rem; display: flex; align-items: center; justify-content: center; } .wifi-survey-body .survey-chart { max-height: 220px; width: auto; } .wifi-ssid { font-weight: 400; color: var(--fg-muted); font-size: 0.85rem; margin-left: 0.5rem; font-family: var(--font-mono); } .wifi-band-list { display: flex; flex-direction: column; gap: 0.375rem; } .wifi-band-item { display: flex; align-items: center; gap: 0.5rem; } .wifi-band-caps { display: inline-flex; gap: 0.25rem; } /* Signal Strength */ .signal-excellent { color: var(--success); font-weight: 600; } .signal-good { color: var(--green-600); font-weight: 600; } .signal-ok { color: var(--warning); font-weight: 600; } .signal-poor { color: var(--danger); font-weight: 600; } /* ========================================================================== Topbar & User Menu ========================================================================== */ .topbar { display: flex; align-items: center; justify-content: flex-end; padding: 0 1rem; height: 48px; background: var(--sidebar-bg); border-bottom: 1px solid var(--sidebar-border); flex-shrink: 0; z-index: 100; } .topbar-right { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; } .hamburger-btn { background: none; border: 1px solid var(--border); border-radius: 0.375rem; padding: 0.375rem; cursor: pointer; color: var(--fg-muted); display: none; align-items: center; } .hamburger-btn:hover { color: var(--fg); border-color: var(--fg-muted); } /* User menu button */ .user-menu { position: relative; } .user-menu-btn { display: flex; align-items: center; gap: 0.4rem; background: none; border: none; border-radius: var(--radius); padding: 0.35rem 0.5rem; font-size: 0.875rem; font-weight: 500; color: var(--fg-muted); cursor: pointer; transition: color 0.15s; } .user-menu-btn:hover { color: var(--fg); } .user-menu-btn .chevron { color: var(--fg-muted); transition: transform 0.2s; } .user-menu-btn[aria-expanded="true"] .chevron { transform: rotate(180deg); } /* Dropdown panel — opens on hover */ .user-dropdown { position: absolute; top: 100%; right: 0; min-width: 190px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 0.375rem 0; z-index: 200; /* Hidden by default; shown on hover or when button is aria-expanded */ opacity: 0; pointer-events: none; transform: translateY(-4px); transition: opacity 0.15s, transform 0.15s; } .user-menu:hover .user-dropdown, .user-menu-btn[aria-expanded="true"] + .user-dropdown { opacity: 1; pointer-events: auto; transform: translateY(0); } .dropdown-section-label { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--fg-muted); padding: 0.25rem 0.875rem 0.125rem; } .dropdown-divider { border: none; border-top: 1px solid var(--border); margin: 0.375rem 0; } .dropdown-item { display: flex; align-items: center; gap: 0.5rem; width: 100%; padding: 0.45rem 0.875rem; font-size: 0.875rem; color: var(--fg-muted); background: none; border: none; cursor: pointer; text-decoration: none; text-align: left; transition: background 0.1s, color 0.1s; box-sizing: border-box; } .dropdown-item:hover, .dropdown-item:focus { background: var(--border); color: var(--fg); text-decoration: none; } .dropdown-item svg { flex-shrink: 0; color: var(--fg-muted); transition: color 0.1s; } .dropdown-item:hover svg, .dropdown-item:focus svg { color: var(--fg); } .dropdown-item-danger:hover, .dropdown-item-danger:focus { color: var(--danger); } .dropdown-item-danger:hover svg, .dropdown-item-danger:focus svg { color: var(--danger); } /* Theme checkmark — hidden by default, shown on active option */ .theme-check { margin-left: auto; flex-shrink: 0; opacity: 0; transition: opacity 0.1s; } .theme-opt.is-active .theme-check { opacity: 1; } /* Login page floating theme button */ .login-theme-btn { position: fixed; bottom: 1.25rem; right: 1.25rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 0.45rem; cursor: pointer; color: var(--fg-muted); display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-sm); transition: all 0.15s; } .login-theme-btn:hover { color: var(--fg); border-color: var(--fg-muted); } .login-header { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 1.75rem; color: var(--fg-muted); } .login-header h1 { font-size: 1.25rem; font-weight: 600; color: var(--fg); line-height: 1; } /* ========================================================================== Responsive Layout ========================================================================== */ /* Narrow (≤1024px): Sidebar hidden behind hamburger overlay */ @media (max-width: 1024px) { #sidebar { position: fixed; top: 0; left: 0; height: 100%; z-index: 300; transform: translateX(-100%); transition: transform 0.25s ease; width: var(--sidebar-width); } body.sidebar-open #sidebar { transform: translateX(0); } body.sidebar-open::after { content: ''; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 200; } #content { height: auto; min-height: 100%; } .hamburger-btn { display: flex; } } /* Desktop (>1024px): hamburger hidden, sidebar always visible */ @media (min-width: 1025px) { .hamburger-btn { display: none; } } /* Column priority hiding for data tables */ @media (max-width: 600px) { .col-priority-2 { display: none; } } @media (max-width: 480px) { .col-priority-3 { display: none; } }