/* OpenPXE web UI — Netbox-style minimal layout, fully offline. * * Theme tokens live on `:root` (dark default) and `:root[data-theme=light]`. * Both palettes share variable *names*, so component CSS uses * `var(--bg)` regardless and the toggle in the topbar just flips the * data-attribute. No JS-side recolouring, no React re-renders, no FOUC * (the inline script in index.html paints the right theme before main * CSS lands). */ :root { /* Jet-black dark palette (default). Modelled on Netbox Labs's near-black product chrome, with surfaces stepping subtly upward rather than the previous blue-tinted ramp, so the UI reads as a genuine "dark" rather than "dim navy". */ --bg: #030303; --bg-panel: #0a0a0a; --bg-panel-2: #141414; --bg-elev: #1c1c1c; --fg: #e8eaed; --fg-dim: #9aa0a6; --fg-dimmer: #6b7077; --accent: #00d4b4; /* Netbox-ish teal — kept for brand */ --accent-dim: #07a38c; --warn: #ffb347; --err: #ef6e6e; --ok: #4ade80; --border: #1f1f1f; --border-soft: #141414; --terminal-bg: #050505; --shadow-card: 0 1px 0 rgba(255,255,255,0.02), 0 8px 24px rgba(0,0,0,0.55); --radius: 6px; --radius-lg: 10px; --sidebar-w: 240px; --topbar-h: 56px; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif; } :root[data-theme="light"] { /* Light palette — high-contrast neutral, accent unchanged for brand consistency. Designed against Netbox Labs's reference screenshot: near-white surfaces, soft grey dividers, dark text. */ --bg: #f6f8fb; --bg-panel: #fbfcfe; --bg-panel-2: #f0f3f8; --bg-elev: #e6ebf2; --fg: #1c2330; --fg-dim: #5a6377; --fg-dimmer: #95a0b3; --accent: #00b89c; --accent-dim: #008b73; --warn: #b67016; --err: #c63a3a; --ok: #1f9b54; --border: #d8dde6; --border-soft: #e7eaf0; /* Light-mode terminal: the pane background and chrome track the rest of the light theme. Per-level text colours below recolour-on-light so log lines stay readable on a pale background — previously the terminal was locked to dark and looked like a stuck panel. */ --terminal-bg: #ffffff; --shadow-card: 0 1px 0 rgba(0,0,0,0.02), 0 6px 18px rgba(20,28,52,0.06); } * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; font-family: var(--sans); font-size: 14px; line-height: 1.5; background: var(--bg); color: var(--fg); transition: background 0.16s ease, color 0.16s ease; } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } code, kbd { font-family: var(--mono); font-size: 12.5px; background: var(--bg-panel-2); padding: 1px 5px; border-radius: 3px; } /* ── Shell ─────────────────────────────────────────────────────────── */ .shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; grid-template-rows: var(--topbar-h) 1fr; grid-template-areas: "sidebar topbar" "sidebar main"; height: 100vh; } .sidebar { grid-area: sidebar; background: var(--bg-panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; } /* The brand block sits flush with the topbar so the sidebar+topbar reads as one continuous bar across the top of the app, rather than a chunky 2-line logo block plus a separate (smaller-typeface) page title. The height/border-bottom match the topbar exactly so the divider runs straight across without a step. */ .sidebar .brand { display: flex; align-items: center; gap: 12px; padding: 0 18px; height: var(--topbar-h); border-bottom: 1px solid var(--border); } .sidebar .brand img { width: 26px; height: 26px; flex: none; } .sidebar .brand strong { font-size: 15px; font-weight: 600; letter-spacing: 0.2px; color: var(--fg); } .sidebar nav { padding: 10px 0; flex: 1; overflow-y: auto; } .sidebar nav a { display: flex; align-items: center; gap: 10px; padding: 8px 18px; color: var(--fg); font-size: 13.5px; border-left: 2px solid transparent; cursor: pointer; } .sidebar nav a:hover { background: var(--bg-panel-2); text-decoration: none; } .sidebar nav a.active { background: var(--bg-panel-2); border-left-color: var(--accent); color: var(--accent); } .sidebar nav a .count { margin-left: auto; background: var(--bg-elev); color: var(--fg-dim); padding: 1px 7px; font-size: 11px; border-radius: 10px; font-variant-numeric: tabular-nums; } .sidebar nav a.active .count { background: var(--accent); color: #002923; } .sidebar .footer { padding: 12px 18px; border-top: 1px solid var(--border); color: var(--fg-dimmer); font-size: 11px; display: flex; flex-direction: column; gap: 4px; } .sidebar .footer code { background: transparent; color: var(--fg-dim); padding: 0; font-size: 11px; word-break: break-all; } .sidebar .footer .status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } .sidebar .footer .status-row .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--fg-dimmer); flex: none; } .sidebar .footer .status-row .dot.ok { background: var(--ok); box-shadow: 0 0 6px color-mix(in srgb, var(--ok) 60%, transparent); } .sidebar .footer .status-row .dot.err { background: var(--err); } .sidebar .footer .status-row .dot.warn { background: var(--warn); } .sidebar .footer .status-label { color: var(--fg-dim); } .sidebar .footer .status-value { color: var(--fg); font-weight: 600; } .sidebar .footer .status-value.ok { color: var(--ok); } .sidebar .footer .status-value.err { color: var(--err); } .sidebar .footer .status-value.warn { color: var(--warn); } .sidebar .footer .footer-sub { color: var(--fg-dimmer); margin-top: 2px; } /* Persistent backend identity. Sits below the advertised URL so even when an operator has uploaded their own logo, "what is this" stays answerable from the bottom-left of every page. */ .sidebar .footer .footer-version { margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border-soft); color: var(--fg-dim); font-variant-numeric: tabular-nums; letter-spacing: 0.2px; } /* ── Top bar ───────────────────────────────────────────────────────── */ .topbar { grid-area: topbar; display: flex; align-items: center; padding: 0 20px; gap: 14px; background: var(--bg-panel); border-bottom: 1px solid var(--border); } .topbar h1 { margin: 0; font-size: 15px; font-weight: 600; color: var(--fg); letter-spacing: 0.2px; } .topbar .spacer { flex: 1; } .topbar .chip { background: var(--bg-panel-2); border: 1px solid var(--border); color: var(--fg-dim); font-size: 12px; padding: 4px 10px; border-radius: 12px; white-space: nowrap; } .topbar .chip strong { color: var(--fg); font-weight: 600; } /* Theme toggle button. Two glyphs stacked; CSS swaps which is visible based on the active theme. Keeps the layout stable when toggling. */ .theme-toggle { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 32px; background: transparent; color: var(--fg); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; padding: 0; transition: background 0.15s ease, border-color 0.15s ease; } .theme-toggle:hover { background: var(--bg-panel-2); border-color: var(--accent); } .theme-toggle .t-sun { display: none; } .theme-toggle .t-moon { display: inline; } :root[data-theme="light"] .theme-toggle .t-sun { display: inline; } :root[data-theme="light"] .theme-toggle .t-moon { display: none; } /* ── Main content ─────────────────────────────────────────────────── */ .main { grid-area: main; overflow: auto; padding: 22px 26px 40px; } .grid { display: grid; gap: 20px; } .grid.cols-3 { grid-template-columns: repeat(3, 1fr); } .grid.cols-2 { grid-template-columns: repeat(2, 1fr); } @media (max-width: 1024px) { .grid.cols-3, .grid.cols-2 { grid-template-columns: 1fr; } } /* ── Cards / panels ───────────────────────────────────────────────── */ .card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-card); } .card > header { padding: 12px 16px; background: var(--bg-panel-2); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; } .card > header h2 { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--fg); } .card > header .sub { color: var(--fg-dim); font-size: 12px; margin-left: auto; } .card .body { padding: 16px; } .stat { padding: 16px; } .stat .label { color: var(--fg-dim); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.8px; } .stat .value { font-size: 28px; font-weight: 600; line-height: 1.1; margin-top: 4px; color: var(--fg); } .stat .trend { font-size: 12px; color: var(--fg-dim); margin-top: 4px; } /* ── Tables ───────────────────────────────────────────────────────── */ table { width: 100%; border-collapse: collapse; } th, td { text-align: left; padding: 9px 16px; border-bottom: 1px solid var(--border-soft); } th { color: var(--fg-dim); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; background: var(--bg-panel-2); } tr:hover td { background: var(--bg-panel-2); } td.mono { font-family: var(--mono); font-size: 12.5px; } td.num { text-align: right; font-variant-numeric: tabular-nums; } /* ── Tags / pills ─────────────────────────────────────────────────── */ .tag { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; background: var(--bg-elev); color: var(--fg-dim); } .tag.ok { background: color-mix(in srgb, var(--ok) 22%, transparent); color: var(--ok); } .tag.warn { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); } .tag.err { background: color-mix(in srgb, var(--err) 22%, transparent); color: var(--err); } .tag.accent { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); } .tag.arch { text-transform: uppercase; } /* ── Forms ────────────────────────────────────────────────────────── */ button, .btn { background: var(--accent); color: #002923; border: 0; border-radius: var(--radius); padding: 7px 14px; font: inherit; font-weight: 600; cursor: pointer; transition: background 0.12s ease; } button:hover, .btn:hover { background: var(--accent-dim); color: #f4fffd; } button.ghost { background: transparent; color: var(--fg); border: 1px solid var(--border); } button.ghost:hover { background: var(--bg-panel-2); color: var(--fg); } button.danger { background: transparent; color: var(--err); border: 1px solid color-mix(in srgb, var(--err) 35%, transparent); } button.danger:hover { background: color-mix(in srgb, var(--err) 14%, transparent); color: var(--err); } label.field { display: grid; gap: 4px; margin-bottom: 14px; } label.field .name { color: var(--fg-dim); font-size: 12px; } label.field .hint { color: var(--fg-dimmer); font-size: 11px; } /* All single-line inputs share one chrome rule. Pre-v0.4.6 we only styled type=text/number, which left type=password fields rendering with the default browser look — visibly off vs adjacent text fields in the Account card. The negation list keeps `type=checkbox`, `type=file`, and `type=range` (none of which we use inside `label.field`) from picking up the padded-box look. */ label.field input:not([type="checkbox"]):not([type="file"]):not([type="range"]), label.field select, label.field textarea { width: 100%; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: var(--radius); padding: 7px 10px; font: inherit; /* iOS/Safari shrinks password-field text by default; clamp it so the password input matches the username input's metrics. */ font-size: 14px; line-height: 1.4; box-shadow: none; -webkit-appearance: none; appearance: none; } label.field input:focus, label.field select:focus, label.field textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent); } label.check { display: flex; gap: 10px; align-items: center; padding: 8px 10px; margin-bottom: 6px; border: 1px solid var(--border-soft); border-radius: var(--radius); } label.check input { accent-color: var(--accent); } /* ── Drop zone ────────────────────────────────────────────────────── */ .drop { border: 2px dashed var(--border); border-radius: var(--radius-lg); padding: 32px; text-align: center; color: var(--fg-dim); cursor: pointer; transition: border-color .15s, color .15s, background .15s; } .drop.hover, .drop:hover { border-color: var(--accent); color: var(--fg); background: var(--bg-panel-2); } .drop strong { color: var(--accent); } /* Plain progress bar (used for ISO uploads). */ .progress { height: 6px; background: var(--bg-panel-2); border-radius: 3px; overflow: hidden; margin-top: 12px; display: none; } .progress.active { display: block; } .progress .bar { height: 100%; width: 0%; background: var(--accent); transition: width .25s; } /* ── Imaging progress widget ─────────────────────────────────────── Animated brand mark paired with a horizontal progress bar; surfaces on Dashboard and the Queue tab. */ .queue-progress { display: flex; align-items: center; gap: 16px; padding: 16px; } .queue-progress .mark { width: 56px; height: 56px; flex: none; border-radius: 50%; background: url("/assets/loader.svg") no-repeat center / contain; filter: drop-shadow(0 0 16px rgba(255, 255, 255, 0.10)); } .queue-progress .info { flex: 1; min-width: 0; } .queue-progress .info .label { font-size: 12.5px; color: var(--fg-dim); margin-bottom: 6px; } .queue-progress .bar-track { height: 8px; background: var(--bg-elev); border-radius: 4px; overflow: hidden; position: relative; } .queue-progress .bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent-dim), var(--accent)); width: 0%; transition: width 0.4s ease; position: relative; } .queue-progress .bar-fill::after { /* Subtle moving sheen so the bar feels alive even at 0% movement. */ content: ""; position: absolute; inset: 0; background: linear-gradient( 90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.18) 50%, rgba(255,255,255,0) 100%); animation: queue-sheen 1.6s linear infinite; } @keyframes queue-sheen { from { transform: translateX(-100%); } to { transform: translateX(100%); } } .queue-progress.idle .mark { filter: grayscale(0.85) opacity(0.55); } .queue-progress.idle .bar-fill::after { animation: none; } /* ── Page-load loader ────────────────────────────────────────────── */ .loader { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 40px 20px; color: var(--fg-dim); } .loader .mark { width: 96px; height: 96px; background: url("/assets/loader.svg") no-repeat center / contain; } /* ── Top bar readiness chip ──────────────────────────────────────── */ .chip.ready { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); } .chip.notready { background: color-mix(in srgb, var(--err) 18%, transparent); color: var(--err); border-color: color-mix(in srgb, var(--err) 35%, transparent); } .chip.warming { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, transparent); } /* ── Dashboard stat strip ────────────────────────────────────────── */ .statstrip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; } @media (max-width: 1100px) { .statstrip { grid-template-columns: repeat(2, 1fr); } } .kv { display: grid; grid-template-columns: 160px 1fr; gap: 6px 14px; padding: 4px 0; font-size: 13px; } .kv .k { color: var(--fg-dim); } .kv .v { font-family: var(--mono); color: var(--fg); word-break: break-all; } .kv .v.warn { color: var(--warn); } .kv .v.err { color: var(--err); } .kv .v.ok { color: var(--ok); } /* ── Image rows: amber tint on un-bootable images ────────────────── */ tr.unbootable td { background: color-mix(in srgb, var(--warn) 7%, transparent) !important; } tr.unbootable td:first-child { border-left: 3px solid var(--warn); } .row-warn { color: var(--warn); font-size: 11.5px; margin-top: 2px; } /* ── Table source badge ──────────────────────────────────────────── */ .src-badge { font-family: var(--mono); font-size: 11px; padding: 1px 6px; border-radius: 4px; background: var(--bg-elev); color: var(--fg-dim); } .src-badge.nfs { background: color-mix(in srgb, #7cd3ff 18%, var(--bg-elev)); color: color-mix(in srgb, #7cd3ff 90%, var(--fg)); } /* ── NFS rows ────────────────────────────────────────────────────── */ .nfs-row { display: grid; grid-template-columns: 32px 1fr auto auto auto; align-items: center; gap: 14px; padding: 10px 14px; background: var(--bg-panel-2); border-left: 3px solid var(--accent); border-radius: var(--radius); } .nfs-row.down { border-left-color: var(--err); } .nfs-row .id { font-family: var(--mono); font-size: 12.5px; color: var(--fg); } .nfs-row .meta { color: var(--fg-dim); font-size: 12px; } .nfs-row .err { color: var(--err); font-size: 11.5px; word-break: break-all; } .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } .dot.ok { background: var(--ok); } .dot.err { background: var(--err); } .dot.warn { background: var(--warn); } /* Inline form rows. The default is a 4-column grid sized for the Account card's "Current / New username / New password / Confirm" quartet; the `.cols-3` modifier swaps to a 3-column layout for the SSO header strip (display name / logo URL / metadata source). All `.form-row > label.field` children share the same baseline because their inner inputs share metrics via the global rule above. */ .form-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px 14px; align-items: end; } .form-row.cols-3 { grid-template-columns: repeat(3, 1fr); } .form-row.cols-2 { grid-template-columns: repeat(2, 1fr); } .form-row label.field { margin-bottom: 0; } @media (max-width: 900px) { .form-row, .form-row.cols-3, .form-row.cols-2 { grid-template-columns: 1fr; } } /* ── Queued deployment visual ────────────────────────────────────── */ .queue-track { display: grid; gap: 6px; padding: 10px 0; } .queue-row { display: grid; grid-template-columns: 32px 1fr auto auto; align-items: center; gap: 14px; padding: 8px 14px; background: var(--bg-panel-2); border-radius: var(--radius); border-left: 3px solid var(--accent); } .queue-row.assigned { border-left-color: var(--ok); } .queue-row .pos { font-family: var(--mono); font-size: 15px; color: var(--accent); font-weight: 600; } .queue-row.assigned .pos { color: var(--ok); } .queue-row .mac { font-family: var(--mono); font-size: 13px; } .queue-row .meta { color: var(--fg-dim); font-size: 12px; } .empty { color: var(--fg-dim); padding: 30px; text-align: center; } .msg { color: var(--fg-dim); font-size: 12.5px; margin-top: 8px; } .msg.err { color: var(--err); } .msg.ok { color: var(--ok); } /* ── Terminal pane ──────────────────────────────────────────────── */ .terminal { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--terminal-bg); overflow: hidden; height: calc(100vh - var(--topbar-h) - 90px); min-height: 480px; box-shadow: var(--shadow-card); } /* Terminal pane colours follow the active theme. Hard-coded hexes (#050505, #181818, #cfd6e2 etc.) were leaving the light-mode pane looking dark; we keep palette-aware vars instead so the toggle works. */ .terminal .pane { flex: 1; overflow: auto; padding: 10px 14px; font-family: var(--mono); font-size: 12.5px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; word-break: break-word; } .terminal .pane .lvl-error { color: var(--err); } .terminal .pane .lvl-warn { color: var(--warn); } .terminal .pane .lvl-info { color: var(--fg); } .terminal .pane .lvl-debug { color: var(--fg-dim); } .terminal .pane .lvl-trace { color: var(--fg-dimmer); } .terminal .pane .ts { color: var(--fg-dimmer); } .terminal .pane .tg { color: var(--accent); } .terminal .pane .echo { color: var(--accent); } .terminal .input-row { display: flex; align-items: center; gap: 8px; padding: 8px 14px; background: var(--bg-panel-2); border-top: 1px solid var(--border); } .terminal .input-row .prompt { color: var(--accent); font-family: var(--mono); } .terminal .input-row input { flex: 1; background: transparent; border: 0; color: var(--fg); font: inherit; font-family: var(--mono); font-size: 13px; outline: none; padding: 4px 0; } .terminal .toolbar { display: flex; gap: 8px; align-items: center; padding: 8px 14px; background: var(--bg-panel-2); border-bottom: 1px solid var(--border); font-size: 12px; color: var(--fg-dim); } .terminal .toolbar .right { margin-left: auto; display: flex; gap: 6px; } .terminal .toolbar button { padding: 3px 9px; font-size: 11px; background: transparent; color: var(--fg-dim); border: 1px solid var(--border); font-weight: 500; } .terminal .toolbar button:hover { color: var(--fg); background: var(--bg-elev); } /* ── Auth screen (first-run setup + login) ─────────────────────── Used when /api/me reports setup_required or !authenticated. The regular .shell is hidden; this overlay takes the full viewport so the operator never sees half-loaded dashboard chrome while the auth state is unknown. Same palette as the rest of the UI — borrows the Sonarr/Radarr layout (centered narrow card on the page background). */ .auth-screen { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--bg); padding: 24px; z-index: 100; } .auth-card { width: 100%; max-width: 380px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); padding: 28px 28px 22px; } .auth-card .brand-row { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; } .auth-card .brand-row img { width: 32px; height: 32px; flex: none; } .auth-card .brand-row .name { font-size: 17px; font-weight: 600; letter-spacing: 0.2px; color: var(--fg); } .auth-card h2 { margin: 0 0 6px; font-size: 16px; font-weight: 600; color: var(--fg); } .auth-card .lede { color: var(--fg-dim); font-size: 13px; margin: 0 0 18px; line-height: 1.5; } .auth-card .field { margin-bottom: 12px; } .auth-card input[type="text"], .auth-card input[type="password"] { width: 100%; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: var(--radius); padding: 9px 11px; font: inherit; font-size: 13.5px; } .auth-card input:focus { outline: none; border-color: var(--accent); } .auth-card .submit { width: 100%; padding: 9px 12px; margin-top: 6px; } .auth-card .auth-err { margin-top: 12px; color: var(--err); font-size: 12.5px; } .auth-card .auth-foot { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-soft); color: var(--fg-dimmer); font-size: 11.5px; text-align: center; } .auth-card .sso-btn { width: 100%; margin-top: 10px; background: transparent; color: var(--fg); border: 1px solid var(--border); padding: 9px 12px; } .auth-card .sso-btn:hover { background: var(--bg-panel-2); border-color: var(--accent); color: var(--fg); } .auth-card .sso-btn .meta { color: var(--fg-dim); font-size: 11px; margin-top: 2px; } /* ── Top-right user menu (v0.4.6) ──────────────────────────── The "signed in as X" identity + sign-out moved out of the sidebar footer in v0.4.6 — the sidebar footer is now reserved for the service-state trio (Service status / Advertised URL / Backend version). The button matches the theme toggle's size + chrome so the top-right reads as a tidy two-icon strip. */ .user-menu { position: relative; } .user-btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 32px; background: transparent; color: var(--fg); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; padding: 0; transition: background 0.15s ease, border-color 0.15s ease; } .user-btn:hover { background: var(--bg-panel-2); border-color: var(--accent); } .user-pop { position: absolute; right: 0; top: 38px; min-width: 200px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); padding: 6px; z-index: 60; display: flex; flex-direction: column; gap: 2px; } .user-pop[hidden] { display: none; } .user-pop .user-pop-name { padding: 8px 10px 6px; border-bottom: 1px solid var(--border-soft); margin-bottom: 4px; color: var(--fg); font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .user-pop .user-pop-item { text-align: left; width: 100%; background: transparent; color: var(--fg); border: 0; border-radius: var(--radius); padding: 7px 10px; font: inherit; font-size: 13px; font-weight: 500; cursor: pointer; } .user-pop .user-pop-item:hover { background: var(--bg-panel-2); color: var(--fg); } .user-pop .user-pop-danger { color: var(--err); } .user-pop .user-pop-danger:hover { background: color-mix(in srgb, var(--err) 12%, transparent); color: var(--err); } /* ── About card ─────────────────────────────────────────────────── */ .about-hero { padding: 20px 24px; } .about-hero h2 { font-size: 22px; margin: 0 0 8px; color: var(--fg); } /* Span the full main column rather than capping at 60ch — the page is read at typical desktop widths and the cap was leaving the right two thirds of the panel awkwardly empty. */ .about-hero .lead { color: var(--fg-dim); font-size: 14px; max-width: none; } .about-hero .who { margin-top: 18px; font-size: 13px; } .about-hero .who span { color: var(--fg-dim); } .about-hero .who strong { color: var(--accent); } .about-hero a { color: var(--accent); } /* ── API reference (Settings → bottom) ─────────────────────────── */ .api-ref { display: grid; gap: 18px; padding: 16px; } .api-ref .group h3 { margin: 0 0 8px; font-size: 13px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.8px; } .api-ref .ep { display: grid; grid-template-columns: 64px minmax(200px, 1fr) 2fr; gap: 12px; align-items: baseline; padding: 6px 0; border-top: 1px solid var(--border-soft); font-size: 13px; } .api-ref .ep:first-child { border-top: 0; } .api-ref .ep .method { font-family: var(--mono); font-weight: 600; font-size: 11px; padding: 2px 6px; border-radius: 4px; text-align: center; letter-spacing: 0.6px; } .api-ref .ep .method.get { background: color-mix(in srgb, var(--ok) 22%, transparent); color: var(--ok); } .api-ref .ep .method.post { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); } .api-ref .ep .method.put { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); } .api-ref .ep .method.delete { background: color-mix(in srgb, var(--err) 22%, transparent); color: var(--err); } .api-ref .ep .path { font-family: var(--mono); color: var(--fg); word-break: break-all; } .api-ref .ep .desc { color: var(--fg-dim); } @media (max-width: 900px) { .api-ref .ep { grid-template-columns: 1fr; gap: 4px; } .api-ref .ep .method { justify-self: start; } } /* ── Disk space card ───────────────────────────────────────────── */ .diskbar { height: 10px; border-radius: 5px; background: var(--bg-elev); overflow: hidden; margin-top: 8px; } .diskbar .fill { height: 100%; background: linear-gradient(90deg, var(--accent-dim), var(--accent)); transition: width 0.4s ease; } .diskbar.warn .fill { background: var(--warn); } .diskbar.full .fill { background: var(--err); } .disk-meta { display: flex; gap: 14px; font-size: 12px; color: var(--fg-dim); margin-top: 8px; flex-wrap: wrap; } .disk-meta strong { color: var(--fg); font-weight: 600; font-variant-numeric: tabular-nums; } /* ── Logo upload (Settings) ────────────────────────────────────── */ .logo-preview { display: flex; align-items: center; gap: 14px; padding: 12px; background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: var(--radius); } .logo-preview .swatch { width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); flex: none; } .logo-preview .swatch img { max-width: 48px; max-height: 48px; } .logo-preview .info { flex: 1; min-width: 0; } .logo-preview .info .name { color: var(--fg); font-weight: 600; } .logo-preview .info .meta { color: var(--fg-dim); font-size: 12px; margin-top: 2px; }