Files
infix/src/webui/static/css/style.css
T
Joachim Wiberg f2f52f8518 webui: sidebar fixes, content sectioning, and theme fixes
Drop the useless 768–1024px icon-only sidebar mode; everything up to
1024px now uses the hamburger overlay and full-width sidebar.

Fix the user dropdown on touch devices: click now toggles aria-expanded
so CSS-driven visibility works without hover.  A document click listener
closes the menu when tapping outside.

Give the sidebar proper light/dark tokens instead of hardcoded dark
values. Introduce --sidebar-hover-fg (primary in light, white in dark)
so nav-link hover text is legible in both themes.

Fix dropdown hover background using --border instead of --border-subtle,
which had zero contrast against the surface in dark mode.

Keep active nav link in sync with the current URL via JS so it updates
correctly after htmx navigation instead of staying on the initial
server-rendered page.  Also, add YouTube-style progress when laoding
pages that take time, e.g., Dashboard.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:25 +02:00

1318 lines
32 KiB
CSS

/* ==========================================================================
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; }
}