/* ==== vendored: colors.css (75f78b674dd7) ==== */
/* ============================================================
   Australis Skyfall — Color Tokens v2
   A ground-up web palette derived from the Australis Dark
   PHILOSOPHY (not its ANSI values). Built in OKLCH so the laws
   are explicit and every family stays perceptually consistent:

   - All hues are cooler than neutral; nothing warmer than amber.
   - One lightness law across every chromatic family:
       deep   L 0.48  — light-theme fills & colored text
       base   L 0.66  — dark-theme fills (the working color)
       bright L 0.80  — dark-theme colored text & highlights
   - Chroma is tuned per hue so no family shouts over another.
   - Neutrals (Sea ramp) drift in hue from ice-blue (265°) toward
     ocean green (195°) as they brighten — the aurora signature.
   - Medium / medium-high contrast for eye comfort.

   Families: Aurora (blue, cyan, green — use generously, in that
   order) and Dawn (amber, red, violet — semantic accents only:
   warning, danger, AI).
   ============================================================ */

:root {
  /* ---- Sea — neutral ramp (hue 265° → 195° as L rises) ---- */
  --sea-10: oklch(0.23 0.02 265);
  --sea-15: oklch(0.27 0.024 268); /* dark canvas — the Australis anchor */
  --sea-20: oklch(0.31 0.022 262);
  --sea-25: oklch(0.35 0.02 258);
  --sea-30: oklch(0.4 0.018 252);
  --sea-40: oklch(0.48 0.016 245);
  --sea-50: oklch(0.56 0.015 235);
  --sea-60: oklch(0.64 0.014 228);
  --sea-70: oklch(0.72 0.014 220);
  --sea-75: oklch(0.78 0.015 212);
  --sea-80: oklch(0.83 0.017 205);
  --sea-90: oklch(0.9 0.02 198);
  --sea-94: oklch(0.94 0.014 195);
  --sea-96: oklch(0.96 0.01 195);
  --sea-98: oklch(0.98 0.006 195);

  /* ---- Ice — main colors (aliases into the ramp) ---- */
  --ice-black: var(--sea-15);
  --ice-white: var(--sea-75);
  --ice-bright-white: var(--sea-90);

  /* ---- Aurora — primary families (blue > cyan > green) ---- */
  --blue-deep: oklch(0.48 0.12 262);
  --blue-base: oklch(0.66 0.12 262);
  --blue-bright: oklch(0.8 0.09 262);
  --cyan-deep: oklch(0.48 0.08 200);
  --cyan-base: oklch(0.66 0.1 200);
  --cyan-bright: oklch(0.8 0.1 200);
  --green-deep: oklch(0.48 0.11 158);
  --green-base: oklch(0.66 0.13 158);
  --green-bright: oklch(0.8 0.13 158);

  /* ---- Dawn — accent families (semantic use only) ---- */
  --amber-deep: oklch(0.52 0.1 75);
  --amber-base: oklch(0.7 0.12 78);
  --amber-bright: oklch(0.82 0.13 82);
  --red-deep: oklch(0.5 0.15 25);
  --red-base: oklch(0.62 0.16 25);
  --red-bright: oklch(0.78 0.11 28);
  --violet-deep: oklch(0.5 0.15 292);
  --violet-base: oklch(0.66 0.14 292);
  --violet-bright: oklch(0.8 0.1 292);
}

/* ============================================================
   Semantic aliases — DARK (default, first-class)
   ============================================================ */
:root {
  color-scheme: dark;

  /* Surfaces */
  --surface-sunken: var(--sea-10);
  --surface-base: var(--sea-15);
  --surface-raised: var(--sea-20);
  --surface-overlay: oklch(0.34 0.022 260);
  --surface-card: var(--sea-20);
  --surface-input: var(--sea-10);
  --surface-scrim: oklch(0.17 0.02 265 / 0.72);

  /* Text */
  --text-heading: var(--sea-90);
  --text-body: var(--sea-75);
  --text-muted: var(--sea-60);
  --text-faint: var(--sea-50);
  --text-inverse: var(--sea-15);
  --text-link: var(--blue-bright);
  --text-link-hover: var(--sea-90);

  /* Borders */
  --border-subtle: var(--sea-25);
  --border-default: var(--sea-30);
  --border-strong: var(--sea-40);
  --border-focus: var(--blue-base);

  /* Accent (primary = aurora blue) */
  --accent: var(--blue-base);
  --accent-hover: oklch(0.71 0.11 262);
  --accent-active: oklch(0.6 0.13 262);
  --accent-bright: var(--blue-bright);
  --accent-text: var(--blue-bright);
  --accent-contrast: var(--sea-10);
  --accent-soft: color-mix(in oklab, var(--blue-base) 16%, transparent);
  --accent-soft-hover: color-mix(in oklab, var(--blue-base) 26%, transparent);

  /* Secondary accent (aurora cyan) */
  --secondary: var(--cyan-base);
  --secondary-bright: var(--cyan-bright);
  --secondary-text: var(--cyan-bright);
  --secondary-soft: color-mix(in oklab, var(--cyan-base) 14%, transparent);

  /* Semantic status */
  --success: var(--green-base);
  --success-text: var(--green-bright);
  --success-soft: color-mix(in oklab, var(--green-base) 14%, transparent);
  --warning: var(--amber-base);
  --warning-text: var(--amber-bright);
  --warning-soft: color-mix(in oklab, var(--amber-base) 13%, transparent);
  --danger: var(--red-base);
  --danger-hover: oklch(0.67 0.15 25);
  --danger-text: var(--red-bright);
  --danger-contrast: var(--sea-10);
  --danger-soft: color-mix(in oklab, var(--red-base) 13%, transparent);
  --info: var(--blue-base);
  --info-text: var(--blue-bright);
  --info-soft: color-mix(in oklab, var(--blue-base) 14%, transparent);
  --ai: var(--violet-base);
  --ai-text: var(--violet-bright);
  --ai-soft: color-mix(in oklab, var(--violet-base) 13%, transparent);

  /* Selection */
  --selection-bg: var(--blue-base);
  --selection-fg: var(--sea-10);
}

/* ============================================================
   Semantic aliases — LIGHT ("Skyfall Day")
   Same laws, inverted: surfaces at L 0.94–0.98, text at L 0.27–0.45,
   chromatic fills & colored text drop to the deep (L 0.48) step.
   ============================================================ */
[data-theme="light"],
html.light {
  color-scheme: light;

  /* Surfaces */
  --surface-sunken: var(--sea-94);
  --surface-base: var(--sea-96);
  --surface-raised: var(--sea-98);
  --surface-overlay: #ffffff;
  --surface-card: var(--sea-98);
  --surface-input: #ffffff;
  --surface-scrim: oklch(0.27 0.024 268 / 0.4);

  /* Text */
  --text-heading: var(--sea-15);
  --text-body: oklch(0.38 0.02 255);
  --text-muted: var(--sea-50);
  --text-faint: var(--sea-60);
  --text-inverse: var(--sea-90);
  --text-link: var(--blue-deep);
  --text-link-hover: var(--sea-15);

  /* Borders */
  --border-subtle: oklch(0.89 0.014 210);
  --border-default: oklch(0.84 0.016 215);
  --border-strong: var(--sea-75);
  --border-focus: var(--blue-deep);

  /* Accent */
  --accent: var(--blue-deep);
  --accent-hover: oklch(0.53 0.12 262);
  --accent-active: oklch(0.44 0.12 262);
  --accent-bright: var(--blue-base);
  --accent-text: var(--blue-deep);
  --accent-contrast: #ffffff;
  --accent-soft: color-mix(in oklab, var(--blue-deep) 10%, transparent);
  --accent-soft-hover: color-mix(in oklab, var(--blue-deep) 18%, transparent);

  /* Secondary accent */
  --secondary: var(--cyan-deep);
  --secondary-bright: var(--cyan-base);
  --secondary-text: var(--cyan-deep);
  --secondary-soft: color-mix(in oklab, var(--cyan-deep) 9%, transparent);

  /* Semantic status */
  --success: var(--green-deep);
  --success-text: var(--green-deep);
  --success-soft: color-mix(in oklab, var(--green-deep) 10%, transparent);
  --warning: var(--amber-deep);
  --warning-text: var(--amber-deep);
  --warning-soft: color-mix(in oklab, var(--amber-base) 16%, transparent);
  --danger: var(--red-deep);
  --danger-hover: oklch(0.55 0.16 25);
  --danger-text: var(--red-deep);
  --danger-contrast: #ffffff;
  --danger-soft: color-mix(in oklab, var(--red-deep) 8%, transparent);
  --info: var(--blue-deep);
  --info-text: var(--blue-deep);
  --info-soft: color-mix(in oklab, var(--blue-deep) 9%, transparent);
  --ai: var(--violet-deep);
  --ai-text: var(--violet-deep);
  --ai-soft: color-mix(in oklab, var(--violet-deep) 9%, transparent);

  /* Selection */
  --selection-bg: var(--blue-base);
  --selection-fg: #ffffff;
}

@media (prefers-color-scheme: light) {
  html:not([data-theme="dark"]):not([data-theme="light"]) {
  color-scheme: light;

  /* Surfaces */
  --surface-sunken: var(--sea-94);
  --surface-base: var(--sea-96);
  --surface-raised: var(--sea-98);
  --surface-overlay: #ffffff;
  --surface-card: var(--sea-98);
  --surface-input: #ffffff;
  --surface-scrim: oklch(0.27 0.024 268 / 0.4);

  /* Text */
  --text-heading: var(--sea-15);
  --text-body: oklch(0.38 0.02 255);
  --text-muted: var(--sea-50);
  --text-faint: var(--sea-60);
  --text-inverse: var(--sea-90);
  --text-link: var(--blue-deep);
  --text-link-hover: var(--sea-15);

  /* Borders */
  --border-subtle: oklch(0.89 0.014 210);
  --border-default: oklch(0.84 0.016 215);
  --border-strong: var(--sea-75);
  --border-focus: var(--blue-deep);

  /* Accent */
  --accent: var(--blue-deep);
  --accent-hover: oklch(0.53 0.12 262);
  --accent-active: oklch(0.44 0.12 262);
  --accent-bright: var(--blue-base);
  --accent-text: var(--blue-deep);
  --accent-contrast: #ffffff;
  --accent-soft: color-mix(in oklab, var(--blue-deep) 10%, transparent);
  --accent-soft-hover: color-mix(in oklab, var(--blue-deep) 18%, transparent);

  /* Secondary accent */
  --secondary: var(--cyan-deep);
  --secondary-bright: var(--cyan-base);
  --secondary-text: var(--cyan-deep);
  --secondary-soft: color-mix(in oklab, var(--cyan-deep) 9%, transparent);

  /* Semantic status */
  --success: var(--green-deep);
  --success-text: var(--green-deep);
  --success-soft: color-mix(in oklab, var(--green-deep) 10%, transparent);
  --warning: var(--amber-deep);
  --warning-text: var(--amber-deep);
  --warning-soft: color-mix(in oklab, var(--amber-base) 16%, transparent);
  --danger: var(--red-deep);
  --danger-hover: oklch(0.55 0.16 25);
  --danger-text: var(--red-deep);
  --danger-contrast: #ffffff;
  --danger-soft: color-mix(in oklab, var(--red-deep) 8%, transparent);
  --info: var(--blue-deep);
  --info-text: var(--blue-deep);
  --info-soft: color-mix(in oklab, var(--blue-deep) 9%, transparent);
  --ai: var(--violet-deep);
  --ai-text: var(--violet-deep);
  --ai-soft: color-mix(in oklab, var(--violet-deep) 9%, transparent);

  /* Selection */
  --selection-bg: var(--blue-base);
  --selection-fg: #ffffff;
  }
}


/* ==== vendored: layout.css (a3826169b7a6) ==== */
/* Australis Skyfall — spacing, radii, shadows, motion (v3 "calm depth") */

:root {
  /* Spacing (4px base) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --space-32: 128px;

  /* Semantic spacing — airy content, compact chrome */
  --pad-chrome: var(--space-2);   /* app chrome: rails, toolbars, list rows */
  --pad-content: var(--space-8);  /* work surfaces: page bodies, card interiors breathe */
  --gap-pane: var(--space-4);     /* gutter between panes/cards on the canvas */
  --measure: 68ch;                /* long-form reading width */

  /* Radii — moderate, calm */
  --radius-xs: 6px;
  --radius-sm: 8px;
  --radius-md: 10px;  /* buttons, inputs, menu items */
  --radius-lg: 14px;  /* cards, panes, popovers */
  --radius-xl: 16px;  /* dialogs, drawers, command palette */
  --radius-full: 999px;

  /* Depth — crisp 1px hairline + layered soft shadow underneath.
     Every elevated surface pairs border: 1px solid var(--border-subtle)
     with one of these. Shadows are cool-tinted and two-layer:
     a tight contact shadow + a wide ambient falloff. */
  --shadow-xs: 0 1px 2px oklch(0.13 0.02 265 / 0.22);
  --shadow-sm: 0 1px 2px oklch(0.13 0.02 265 / 0.2), 0 2px 8px -2px oklch(0.13 0.02 265 / 0.24);
  --shadow-md: 0 2px 4px oklch(0.13 0.02 265 / 0.22), 0 10px 28px -6px oklch(0.13 0.02 265 / 0.36);
  --shadow-lg: 0 4px 8px oklch(0.13 0.02 265 / 0.26), 0 28px 64px -12px oklch(0.13 0.02 265 / 0.5);
  --shadow-drawer: 0 8px 16px oklch(0.13 0.02 265 / 0.28), 0 32px 80px -8px oklch(0.13 0.02 265 / 0.55);

  /* Glows — hero moments ONLY: empty states, featured cards, command palette */
  --glow-accent: 0 0 72px -16px color-mix(in oklab, var(--blue-base) 42%, transparent);
  --glow-secondary: 0 0 72px -16px color-mix(in oklab, var(--cyan-base) 38%, transparent);
  --glow-ai: 0 0 72px -16px color-mix(in oklab, var(--violet-base) 40%, transparent);

  /* Active rail — left accent bar on active nav / selected items */
  --rail-active: inset 2px 0 0 0 var(--accent);

  /* Focus ring */
  --focus-ring: 0 0 0 2px var(--surface-base), 0 0 0 4px var(--border-focus);

  /* Motion — calm, no bounce */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* @kind other */
  --duration-fast: 120ms; /* @kind other */
  --duration-base: 180ms; /* @kind other */
  --duration-slow: 300ms; /* @kind other */
  --duration-drawer: 260ms; /* @kind other */
}

[data-theme="light"],
html.light {
  --shadow-xs: 0 1px 2px oklch(0.3 0.02 255 / 0.07);
  --shadow-sm: 0 1px 2px oklch(0.3 0.02 255 / 0.06), 0 2px 8px -2px oklch(0.3 0.02 255 / 0.08);
  --shadow-md: 0 2px 4px oklch(0.3 0.02 255 / 0.06), 0 10px 28px -6px oklch(0.3 0.02 255 / 0.12);
  --shadow-lg: 0 4px 8px oklch(0.3 0.02 255 / 0.07), 0 28px 64px -12px oklch(0.3 0.02 255 / 0.18);
  --shadow-drawer: 0 8px 16px oklch(0.3 0.02 255 / 0.08), 0 32px 80px -8px oklch(0.3 0.02 255 / 0.22);
  --glow-accent: 0 0 72px -16px color-mix(in oklab, var(--blue-deep) 26%, transparent);
  --glow-secondary: 0 0 72px -16px color-mix(in oklab, var(--cyan-deep) 24%, transparent);
  --glow-ai: 0 0 72px -16px color-mix(in oklab, var(--violet-deep) 25%, transparent);
}

@media (prefers-color-scheme: light) {
  html:not([data-theme="dark"]):not([data-theme="light"]) {
  --shadow-xs: 0 1px 2px oklch(0.3 0.02 255 / 0.07);
  --shadow-sm: 0 1px 2px oklch(0.3 0.02 255 / 0.06), 0 2px 8px -2px oklch(0.3 0.02 255 / 0.08);
  --shadow-md: 0 2px 4px oklch(0.3 0.02 255 / 0.06), 0 10px 28px -6px oklch(0.3 0.02 255 / 0.12);
  --shadow-lg: 0 4px 8px oklch(0.3 0.02 255 / 0.07), 0 28px 64px -12px oklch(0.3 0.02 255 / 0.18);
  --shadow-drawer: 0 8px 16px oklch(0.3 0.02 255 / 0.08), 0 32px 80px -8px oklch(0.3 0.02 255 / 0.22);
  --glow-accent: 0 0 72px -16px color-mix(in oklab, var(--blue-deep) 26%, transparent);
  --glow-secondary: 0 0 72px -16px color-mix(in oklab, var(--cyan-deep) 24%, transparent);
  --glow-ai: 0 0 72px -16px color-mix(in oklab, var(--violet-deep) 25%, transparent);
  }
}


/* ==== vendored: typography.css (2ec1a667ced0) ==== */
/* Australis Skyfall — Typography tokens */

:root {
  /* Families */
  --font-display: "Bespoke Sans", "Avenir Next", "Segoe UI", sans-serif;
  --font-body: "Supreme", "Helvetica Neue", "Segoe UI", sans-serif;
  --font-mono: "VictorMono Nerd Font", "SF Mono", "Cascadia Code", monospace;

  /* Scale (web-app oriented; 14px UI base) */
  --text-2xs: 11px;
  --text-xs: 12px;
  --text-sm: 13px;
  --text-base: 14px;
  --text-md: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 36px;
  --text-4xl: 48px;
  --text-5xl: 64px;

  /* Weights */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;
  --weight-extrabold: 800;

  /* Line heights */
  --leading-tight: 1.15;
  --leading-snug: 1.35;
  --leading-normal: 1.55;
  --leading-relaxed: 1.7;

  /* Tracking */
  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-wide: 0.06em;
  --tracking-caps: 0.1em;
}


/* Homepage — Australis Skyfall, dual theme.
 *
 * SOURCE OF TRUTH: this file plus the three VENDORED token files beside it.
 * Do not hand-edit `conf/custom.css`; it is generated. Run `theme/build.py`
 * after changing anything here, then
 * `scripts/deploy-stack.sh esh-docker-vm homepage --conf`.
 *
 * ── WHAT IS VENDORED, AND WHY IT IS UNTOUCHABLE ──────────────────────────
 *   colors.css      Skyfall v2 palette — Sea/Ice/Aurora/Dawn in OKLCH, plus
 *                   the semantic layer for BOTH themes.
 *   layout.css      v3 "calm depth" — spacing, radii, the shadow system,
 *                   glows, the active rail, motion.
 *   typography.css  families, scale, weights, leading, tracking.
 *
 * These three are copied VERBATIM from the Skyfall bundle and must stay that
 * way. `build.py` hashes them and fails the build if a byte moves. Everything
 * this dashboard needs on top of them lives in THIS file, expressed through
 * the semantic layer — never against a raw family token, and never a literal
 * colour. That rule is not fussiness: the theme this one replaces built its
 * own parallel palette "derived from the philosophy", which is how a design
 * system quietly forks, and it had to be torn out twice.
 *
 * ── DUAL THEME, AND THE ONE PIECE OF PLUMBING IT NEEDS ───────────────────
 * Skyfall keys light mode off `[data-theme="light"]`. Homepage keys it off a
 * `light` / `dark` CLASS on <html>, and does not touch data attributes. Rather
 * than edit the vendored file (fork) or duplicate its light block by hand
 * (drift), `build.py` rewrites the selector at build time so both spellings
 * are honoured. See BRIDGE_SELECTOR there.
 *
 * Dark is `:root` and remains first-class. Light is Skyfall Day. Both are
 * reachable from Homepage's own theme toggle, which appears because
 * settings.yaml no longer pins `theme:`.
 *
 * ── TYPE: TWO DOCUMENTED SUBSTITUTIONS ───────────────────────────────────
 * Skyfall names Bespoke Sans (display) / Supreme (body, UI) / Victor Mono
 * Nerd Font (data, code). Only Supreme was ever vendored into this repo, and
 * Skyfall's own notes call Victor Mono "user-supplied", so substituting is
 * expected rather than a deviation:
 *   Supreme        400/500/700 — CANONICAL, the body and UI face.
 *   Space Grotesk  variable    — stands in for Bespoke Sans (display).
 *   JetBrains Mono variable    — stands in for Victor Mono (data, metadata).
 * Both stand-ins are neutral grotesques in the right register. Swap them the
 * moment the real faces arrive: it is a two-line change in build.py's FONTS
 * and the --font-display / --font-mono overrides below.
 *
 * ── WHERE THIS DEPARTS FROM THE PREVIOUS THEME, DELIBERATELY ─────────────
 * 1. Depth is now the RECIPE, not a choice: every elevated surface carries a
 *    1px hairline AND a two-layer shadow, never one without the other.
 * 2. Radii move to Skyfall's scale — cards at --radius-lg (14px), controls at
 *    --radius-md (10px).
 * 3. The full-width aurora ribbon under the tab bar is GONE. Skyfall sanctions
 *    exactly two accent expressions — the active rail and hero-only glows —
 *    and a decorative gradient across the chrome is neither. The colour it was
 *    carrying now lands where canon puts it: a 2px accent bar plus an
 *    --accent-soft fill on the active tab. If you want the ribbon back, that
 *    is a deliberate documented deviation, not an oversight.
 * 4. Widget stat values move from the display face to mono. Skyfall is
 *    explicit: numbers and telemetry are always --font-mono.
 */

@font-face {
  font-family: "Space Grotesk";
  src: url(data:font/woff2;base64,[encoded data, 29720 characters]) format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url(data:font/woff2;base64,[encoded data, 41912 characters]) format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Supreme";
  src: url(data:font/woff2;base64,[encoded data, 24936 characters]) format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Supreme";
  src: url(data:font/woff2;base64,[encoded data, 25104 characters]) format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Supreme";
  src: url(data:font/woff2;base64,[encoded data, 25048 characters]) format('woff2');
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
}

/* ---- Family substitutions ------------------------------------------------
   Overriding typography.css's declarations rather than editing it, so the
   vendored file stays byte-identical and the swap is visible in one place. */
:root {
  --font-display: "Space Grotesk", "Bespoke Sans", ui-sans-serif, sans-serif;
  --font-body: "Supreme", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "VictorMono Nerd Font", ui-monospace, monospace;

  /* Width the status cluster occupies in a card's top-right. Load-bearing in
     two places that must agree: the inset reserved on the title line, and the
     negative margin the description uses to opt back out of it. Sized for the
     widest real cluster — a latency badge plus a status word. */
  --status-gutter: 96px;
}

/* ---- Icon colour ---------------------------------------------------------
   Homepage draws every mdi and simple-icons glyph as a MASK over a gradient
   fill, so this ramp IS the icon colour — `color:` does nothing to them.

   ⚠ These must be space-separated sRGB CHANNELS, because Homepage consumes
   them as `rgb(var(--color-logo-start))`. An oklch() token cannot be fed
   through that, so the four values below are the exact sRGB conversions of
   real Skyfall tokens rather than colours anyone picked by eye:
       dark  --sea-80    oklch(0.83 0.017 205) -> 187 203 204
             --blue-base oklch(0.66 0.12  262) -> 105 145 220
       light --sea-40    oklch(0.48 0.016 245) ->  86  95 102
             --blue-deep oklch(0.48 0.12  262) ->  54  91 161
   Recompute with theme/oklch_to_srgb.py if a token ever changes.
   Light uses the deep (L 0.48) step for both stops, per Skyfall's lightness
   law: deep is the light-theme value for fills and coloured marks.

   ⚠ NOT `:root`. Homepage sets these same two variables on `.theme-slate`,
   and that class sits on <html> — the very element `:root` matches. Same
   element, so the cascade decides, and `.theme-slate` (0,1,0) outranks
   `:root` (0,0,1): a `:root` override here is silently ignored. `html[class]`
   is (0,1,1) and wins without hard-coding which `theme-*` class is active.
   Specificity alone is not enough either — a custom property resolves from
   the NEAREST ancestor that sets it, so this has to land on <html>. */
html[class] {
  --color-logo-start: 187 203 204;
  --color-logo-stop: 105 145 220;
}

html[data-theme="light"],
html.light {
  --color-logo-start: 86 95 102;
  --color-logo-stop: 54 91 161;
}

/* ---- Canvas --------------------------------------------------------------
   The app canvas is --surface-sunken and work floats on it as cards. Flat
   semantic fill only: Skyfall forbids full-bleed imagery and decorative
   gradients behind text.

   `#background` is load-bearing, not defensive tidiness. Homepage
   server-renders the wallpaper as an INLINE style on `<div id="background">`
   and Next.js caches that rendered page, so removing the `background:` block
   from settings.yaml is not enough on its own — a restart does not clear it,
   only a full recreate does. Enforcing it here is deterministic and immediate,
   and keeps the canvas correct if the setting is ever re-added by accident. */
html,
body,
#page_wrapper,
#background,
body > div {
  background-color: var(--surface-sunken) !important;
  background-image: none !important;
}

body {
  font-family: var(--font-body) !important;
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
}

/* ---- Group heading — the Skyfall eyebrow ---------------------------------
   Mono, uppercase, --text-2xs, --tracking-caps, in the secondary accent, with
   a hairline running to the right edge so groups read as register bands
   rather than titles floating over a grid. ALL-CAPS is sanctioned here
   specifically: Skyfall allows it for tiny tracked eyebrow labels and table
   headers, and nowhere else.

   Bookmark groups get the identical treatment. They are a separate component
   with a parallel class family, and none of these selectors matched it before
   — so UltraSeedbox rendered with Homepage's stock 20px sans heading while
   every service group above it wore the eyebrow. One un-themed heading in a
   column of themed ones is more conspicuous than the whole page being
   un-themed. */
.services-group > button.group,
.bookmark-group > button.group {
  gap: 0;
  margin-bottom: var(--space-1);
  margin-top: var(--space-6);
}

/* …except the first group on a tab, which should sit up against the tab bar
   rather than floating below it. */
.services-group:first-of-type > button.group {
  margin-top: var(--space-2);
}

.service-group-icon,
.bookmark-group-icon {
  display: none !important;
}

h2.service-group-name,
h2.bookmark-group-name {
  font-family: var(--font-mono) !important;
  font-size: var(--text-2xs) !important;
  font-weight: var(--weight-medium) !important;
  line-height: 1 !important;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--secondary-text) !important;
  white-space: nowrap;
  display: flex;
}

.services-group > button.group::after,
.bookmark-group > button.group::after {
  content: "";
  flex: 1;
  height: 1px;
  margin-left: var(--space-3);
  background: var(--border-subtle);
}

/* The chevron sits after the rule, muted. */
.services-group > button.group svg,
.bookmark-group > button.group svg {
  color: var(--text-faint);
  margin-left: var(--space-2);
  width: 14px;
  height: 14px;
}

/* ---- Service cards — the calm-depth recipe -------------------------------
   HAIRLINE + TWO-LAYER SHADOW, NEVER ONE WITHOUT THE OTHER. That pairing is
   Skyfall's defining move; a card with a border and no shadow, or a shadow
   and no border, is off-system. Height stays content-driven — equal-height
   stretching turns short cards into hollow boxes. */
ul.services-list {
  align-items: start !important;
  row-gap: var(--space-2);
}

li.service > .service-card {
  height: auto !important;
  background: var(--surface-card) !important;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg) !important;
  box-shadow: var(--shadow-sm) !important;
  margin-bottom: 0 !important;
  transition: background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

/* Hover: surface lifts a step and the shadow deepens sm -> md, per canon.
   No scale, no translate — Skyfall has no bounce anywhere. */
li.service > .service-card:hover {
  background: var(--surface-raised) !important;
  border-color: var(--border-default);
  box-shadow: var(--shadow-md) !important;
}

/* Service name — display face, tight. Description — body face, muted.
 *
 * ⚠ `.service-name` IS NOT THE TITLE. Homepage puts the title in as a bare
 * text node and then nests `<p class="service-description">` as a sibling
 * inside the SAME div, so every rule here lands on a box containing both. The
 * title has no element of its own and cannot be selected — which is why it
 * cannot be given its own truncation, and why the description needs an
 * explicit escape hatch from anything the title needs. */
.service-name {
  font-family: var(--font-display) !important;
  font-size: var(--text-base) !important;
  font-weight: var(--weight-medium) !important;
  /* Pinned so the status cluster can be centred on this exact line rather
     than floating against the card's top edge. Changing one without the
     other breaks the alignment. */
  line-height: 17px !important;
  letter-spacing: var(--tracking-tight);
  color: var(--text-heading) !important;

  /* THE GUTTER MUST BE HELD BY WRAPPING, NOT BY CLIPPING.
     This rule used to read `padding-right: 78px; overflow: hidden;
     text-overflow: ellipsis; white-space: nowrap` and it did not work: CSS
     clips overflow at the PADDING box, not the content box, so the reserved
     78px was spill room the title printed straight through. Six cards on the
     AI tab rendered their name underneath their own status pill. The ellipsis
     never appeared either: it is painted by the block whose own line
     overflows, and that block here is the anonymous box around the text node,
     which does not carry the `overflow`. Wrapping has neither problem — line
     breaking respects the content box, so the gutter genuinely holds. */
  padding-right: var(--status-gutter) !important;
  white-space: normal;
  overflow: visible;
}

p.service-description {
  font-family: var(--font-body) !important;
  font-size: var(--text-xs) !important;
  font-weight: var(--weight-regular) !important;
  line-height: 17px !important;
  color: var(--text-muted) !important;
  margin-top: 3px;
  white-space: normal;

  /* Opt back out of the title's gutter. The status cluster is absolutely
     positioned over the FIRST line only (top: 13px, height: 17px, so it
     occupies y 13-30); the description never starts above y 33. Nothing can
     collide, so it takes the full card width back and the gutter costs no
     description space at all. */
  margin-right: calc(var(--space-2) - var(--status-gutter));

  /* Floor of two lines, ceiling of three. The floor keeps the common
     one-line/two-line mix bottom-aligned, which is most of what made rows
     look ragged. The ceiling is the other half: a handful of four- and
     five-line descriptions were dragging their entire row 30-50px taller than
     every card beside them, and at four cards across that gap is the most
     visible defect on the page. Not equal-heights — that inflates short cards
     into hollow boxes to match a widget card twice their height. */
  min-height: 34px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- Status — semantic dots, not chips -----------------------------------
   The stock filled emerald pills read louder than the service names they
   annotate. A semantic dot plus a mono micro-label at faint contrast: present
   when you look for it, silent when you are not. The dot colours come from
   --success/--danger/--warning-text, so they take the deep step automatically
   in Skyfall Day rather than staying dark-theme bright on a white card.

   The cluster is CENTRED ON THE SERVICE NAME'S LINE, not parked in the card's
   top-right corner. Homepage positions it `absolute top-0 right-0`, which
   floats it ~7px above the title's optical centre and reads as a misalignment
   on every card. The offsets below reconstruct the title line's geometry:
   .service-name sits 5px below the card's top edge and carries 8px of its own
   top padding, so its 17px line box starts at 13px and centres at 21.5px.
   These three numbers move together — if the name's font-size or line-height
   changes, recompute all of them. */
.service-tags {
  top: 13px !important;
  height: 17px;
  align-items: center !important;
  gap: var(--space-3) !important;
  margin-top: 0 !important;
  /* 14px right inset, against the 8px of text padding on the left — the
     optical match, since the pill has no glyph hard against its edge. */
  margin-right: 14px !important;
}

/* Every child of the cluster shares one baseline: status pills, latency
   badges and container-stat buttons alike. */
.service-tag {
  display: flex !important;
  align-items: center !important;
  height: 17px;
  line-height: 17px !important;
}

.service-tag,
.docker-status,
.site-monitor-status,
.service-tag > div:not(.sr-only) {
  background: transparent !important;
  background-color: transparent !important;
  padding: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

.docker-status > div,
.service-tag .text-\[8px\] {
  display: flex !important;
  align-items: center !important;
  font-family: var(--font-mono) !important;
  font-size: 9px !important;
  font-weight: var(--weight-medium) !important;
  line-height: 1 !important;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted) !important;
}

/* Flex-centred rather than nudged with vertical-align: the dot is an optical
   round on a cap-height uppercase run, so it needs true centring, not a magic
   pixel offset that only looks right at one font size. */
.docker-status > div::before {
  content: "\25CF"; /* ● */
  display: inline-block;
  margin-right: 6px;
  font-size: 7px;
  line-height: 1;
  color: var(--text-faint);
}

/* ⚠ Homepage emits `docker-status-<state>`, not `status-<state>`. Selectors
   matching the latter silently hit nothing — which is how an earlier pass
   shipped "themed" status pills that were still stock green. */
.docker-status-healthy > div::before,
.docker-status-running > div::before {
  color: var(--success-text);
}

.docker-status-unhealthy > div::before,
.docker-status-exited > div::before,
.docker-status-dead > div::before {
  color: var(--danger-text);
}

.docker-status-starting > div::before,
.docker-status-paused > div::before {
  color: var(--warning-text);
}

/* Ping / latency — mono metadata, tabular, no chip. Deliberately the info
   family and not a status colour: "how fast" must not read as "is it alive". */
.site-monitor-status > div {
  font-family: var(--font-mono) !important;
  font-size: 9px !important;
  font-weight: var(--weight-regular) !important;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase !important;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint) !important;
}

/* ---- Widget stat strip ---------------------------------------------------
   A footer strip inside the card: hairline above, cells divided by hairlines,
   value then a mono eyebrow label. The card stays one object instead of three
   nested boxes.

   Values are MONO, not the display face. Skyfall is explicit that numbers and
   telemetry always take --font-mono; hierarchy against the label comes from
   weight and size, which is the same tool the type system uses everywhere
   else. Padding is tight because a stat strip adds its full height to its own
   card and to nothing else in the row — at four cards across, one AdGuard
   card opens a void beside three ordinary ones. It cannot be made free, but
   it can be made small. */
.service-container {
  margin-top: var(--space-2);
  padding-top: 2px;
  border-top: 1px solid var(--border-subtle);
  gap: 0 !important;
}

.service-container > div {
  background: transparent !important;
  border-radius: 0 !important;
  margin: 0 !important;
  padding: 6px 4px 5px !important;
  border-left: 1px solid var(--border-subtle);
  min-width: 0; /* let a long value shrink the cell instead of overflowing */
}

.service-container > div:first-child {
  border-left: 0;
}

/* value — nowrap because a long one (AdGuard's "32.767 ms") otherwise wraps
   mid-cell and drags the whole card taller than its row-mates. */
.service-container > div > div:not(.uppercase) {
  font-family: var(--font-mono) !important;
  font-size: var(--text-sm) !important;
  font-weight: var(--weight-bold) !important;
  letter-spacing: var(--tracking-tight);
  line-height: 1.2 !important;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--text-heading) !important;
}

/* label */
.service-container > div > div.uppercase {
  font-family: var(--font-mono) !important;
  font-size: 9px !important;
  font-weight: var(--weight-regular) !important;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted) !important;
  margin-top: 2px;
}

/* Trailing widget rows — Jellyfin's "No Active Streams" strip and its empty
   second row, which Homepage emits AFTER .service-container as its own block
   of Tailwind-tinted pills. Untouched they broke out of the card's language:
   two light grey bars with their own radius hanging under an otherwise
   hairline-ruled footer. Not hidden — "no active streams" is the answer to
   the question the card exists to answer. */
li.service .service-container ~ div > div {
  background: transparent !important;
  border-radius: 0 !important;
  border-top: 1px solid var(--border-subtle);
  height: auto !important;
  min-height: 16px;
  margin-top: 0 !important;
}

li.service .service-container ~ div span {
  position: static !important;
  display: block;
  padding: 3px 4px 2px !important;
  margin: 0 !important;
  font-family: var(--font-mono) !important;
  font-size: 9px !important;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted) !important;
}

/* ---- Bookmarks -----------------------------------------------------------
   Same depth recipe as a service card. The name takes the display face and
   the description drops to mono metadata, so a bookmark row reads as a
   quieter sibling of a service card rather than a different widget that
   wandered in. */
li.bookmark > a {
  background: var(--surface-card) !important;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg) !important;
  box-shadow: var(--shadow-sm) !important;
  margin-bottom: var(--space-2) !important;
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

li.bookmark > a:hover {
  background: var(--surface-raised) !important;
  border-color: var(--border-default);
  box-shadow: var(--shadow-md) !important;
}

.bookmark-icon {
  background: transparent !important;
  border-radius: 0 !important;
  border-right: 1px solid var(--border-subtle);
}

.bookmark-name {
  font-family: var(--font-display) !important;
  font-size: var(--text-sm) !important;
  font-weight: var(--weight-medium) !important;
  color: var(--text-heading) !important;
}

.bookmark-description {
  font-family: var(--font-mono) !important;
  font-size: 10px !important;
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted) !important;
}

/* ---- Tab bar — the active rail -------------------------------------------
   Skyfall sanctions exactly two accent expressions: the active rail (a 2px
   accent bar on the active item, paired with an --accent-soft fill) and
   hero-only glows. This is the rail, turned through 90° for a horizontal bar.

   The previous theme ran a full-width blue -> cyan -> green aurora gradient
   under this bar. It was the nicest thing on the page and it is gone on
   purpose: a decorative gradient across the chrome is neither of the two
   sanctioned expressions, and Skyfall permits the aurora motif only on
   hero/empty-state areas, never behind body text. A plain hairline separates
   chrome from content instead, and the colour lands on the active tab. */
ul.sm\:flex {
  position: relative;
  background: transparent !important;
  background-color: transparent !important;
  border-radius: 0 !important;
  gap: 0;
  overflow: visible;
}

ul.sm\:flex::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--border-subtle);
}

ul.sm\:flex > li {
  height: 40px !important;
  border-radius: 0 !important;
  background: transparent !important;
  overflow: visible;
}

ul.sm\:flex > li > button {
  margin: 0 !important;
  height: 100%;
  border-radius: 0 !important;
  background: transparent !important;
  background-color: transparent !important;
  box-shadow: none !important;
  border-top: 2px solid transparent;
  font-family: var(--font-mono) !important;
  font-size: var(--text-2xs) !important;
  font-weight: var(--weight-medium) !important;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted) !important;
  transition: color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

ul.sm\:flex > li > button:hover {
  color: var(--text-body) !important;
  background: var(--accent-soft) !important;
}

/* The active rail: 2px accent bar + --accent-soft fill. `dark:bg-white/10` is
   the class Homepage puts on the selected tab; it is the only handle it
   gives. */
ul.sm\:flex > li > button.dark\:bg-white\/10 {
  border-top-color: var(--accent) !important;
  color: var(--text-heading) !important;
  background: var(--accent-soft) !important;
}

/* ---- Header chrome: resource widget + search -----------------------------
   Same depth recipe as everything else, so the header belongs to the page
   instead of hovering above it. --pad-chrome, not --pad-content: this is app
   chrome, and Skyfall's negative-space rule is compact chrome / airy content. */
div.flex.flex-col.justify-center.mt-2,
div.flex.flex-row.self-center.flex-wrap.justify-between {
  background: var(--surface-card) !important;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg) !important;
  box-shadow: var(--shadow-sm) !important;
}

/* The resource widget is INSIDE that strip, so it must not repeat the recipe
   — nested hairline-and-shadow reads as a box in a box. */
.information-widget-resource {
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  background: transparent !important;
}

/* The CPU / memory / disk glyphs. Homepage colours them from its own Tailwind
   theme, which lands almost invisible on Skyfall Day's near-white chrome. */
.information-widget-resource svg {
  color: var(--text-muted) !important;
  opacity: 1 !important;
}

.information-widget-resource .text-xs,
.information-widget-resource span {
  font-family: var(--font-mono) !important;
  font-size: var(--text-2xs) !important;
  letter-spacing: var(--tracking-wide);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted) !important;
}

.resource-usage,
[class*="resource-usage"] {
  background: var(--surface-input) !important;
  height: 3px !important;
  border-radius: var(--radius-full) !important;
}

.resource-usage > div,
[class*="resource-usage"] > div {
  background: var(--secondary) !important;
  border-radius: var(--radius-full) !important;
}

/* Search input */
input[type="text"],
input[type="search"] {
  font-family: var(--font-body) !important;
  font-size: var(--text-base) !important;
  color: var(--text-body) !important;
  background: transparent !important;
}

input::placeholder {
  color: var(--text-faint) !important;
  font-family: var(--font-mono) !important;
  font-size: var(--text-xs) !important;
  letter-spacing: var(--tracking-wide);
}

/* ---- Theme toggle --------------------------------------------------------
   Our own switch, injected by conf/custom.js. Homepage's built-in one is
   unavailable: rendering it needs `theme:` unpinned in settings.yaml, and an
   unpinned `theme:` makes the page's data loader throw — see the note in
   custom.js for the measurement. Sized and shaped like an IconButton from the
   Skyfall component set: --radius-md, chrome padding, hairline, and the same
   accent-soft hover every interactive surface uses. */
#skyfall-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin-left: auto;
  align-self: center;
  flex: 0 0 auto;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

#skyfall-theme-toggle:hover {
  color: var(--text-heading);
  background: var(--accent-soft);
  border-color: var(--border-default);
}

#skyfall-theme-toggle:active {
  background: var(--accent-soft-hover);
}

/* ---- Focus — Skyfall's ring ---------------------------------------------- */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: none !important;
  box-shadow: var(--focus-ring) !important;
  border-radius: var(--radius-md);
}

/* ---- Version / footer ---------------------------------------------------- */
#version,
#version * {
  font-family: var(--font-mono) !important;
  font-size: var(--text-2xs) !important;
  color: var(--text-faint) !important;
}

/* ---- Reduced motion — canon requires honouring it ------------------------ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}
