/* ISPForge design tokens — SINGLE SOURCE OF TRUTH.
   Served at /ui-assets/tokens.css by every surface via
   core.ui_assets.install_ui_assets(). Do not copy this block into an
   app's own stylesheet: five divergent copies is the exact problem this
   file exists to prevent. */

/* ── scale (theme-independent) ───────────────────────────
   One ramp each for type, radius and spacing. Before this the
   style block carried 11 ad-hoc font sizes (several less than a
   pixel apart — 0.6875 and 0.7 both existed) and 11 radii from
   3px to 14px. Values that don't relate to each other read as
   machine-generated however good each one is on its own.
   Use the tokens; don't add a twelfth value. */
:root {
  /* type — UI ramp */
  --fs-3xs: 0.625rem;    /* 10px — rail labels, version chip, footer */
  --fs-2xs: 0.6875rem;   /* 11px — uppercase section labels, meta */
  --fs-xs:  0.75rem;     /* 12px — hints, badges */
  --fs-sm:  0.8125rem;   /* 13px — UI default: nav, buttons, links */
  --fs-md:  0.875rem;    /* 14px — body copy, table cells */
  --fs-lg:  1rem;        /* 16px — section leads, dialog copy */
  /* type — display ramp */
  --fs-dsp-sm: 1.25rem;  /* 20px — topbar wordmark */
  --fs-dsp-md: 1.5rem;   /* 24px — page titles, stat values */
  --fs-dsp-lg: 1.75rem;  /* 28px — full wordmark, dashboard greeting */
  /* radii — Slate. These went the OTHER way from the note that used to sit
     here, which argued instruments are squarer and took cards to 6px. That
     reasoning is sound in isolation; what it missed is that a card only reads
     as a floating object if its corner is big enough to see against the
     ground, and Slate leans on cards floating. 14px is where a card stops
     looking like a bordered div. Reverting is a three-line edit if it reads
     as consumer-soft in daily use.
     The pill is unchanged, since a pill is a deliberate shape, not a corner. */
  /* Squarer than the reference. The healthcare shot this direction came from
     sits at 20/13 because it is a consumer wellness app; an instrument reads
     squarer, and each pass since has taken another notch out. This is the
     third and last: soft enough to be the same family, hard enough to look
     like a tool. */
  --r-sm:   3px;         /* chips, tags, icon buttons */
  --r-md:   6px;         /* buttons, inputs, rows */
  --r-lg:  10px;         /* cards, modals, palette */
  --r-pill: 999px;
  /* spacing — 4px grid. The target rhythm for new work; existing
     paddings are NOT yet migrated onto it. Moving every padding in
     the app is a visible layout change that wants reviewing on
     screen rather than sweeping blind, so it's a separate pass. */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
  /* Prefixes, ASNs and config diffs are the product's primary content —
     the bare `monospace` keyword renders as Courier on some Linux setups.
     Matches the stack used by marketing + beta-www. */
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  /* Type pairing. Archivo carries titles, stat values and the wordmark;
     Figtree does everything else. Both are self-hosted next to Inter — every
     surface sets `font-src 'self'`, so a Google Fonts link fails SILENTLY and
     falls back to system-ui, which reads as a design that never landed.
     Inter stays in the stack as the fallback so nothing loses its shape if a
     face fails to load. */
  --font-display: 'Archivo', 'Inter', ui-sans-serif, system-ui, sans-serif;
  --font-ui: 'Figtree', 'Inter', ui-sans-serif, system-ui, sans-serif;
  /* The wordmark stays Inter. It is brand, not display type — it should not
     move when the type pairing does. */
  --font-brand: 'Inter', ui-sans-serif, system-ui, sans-serif;
}

/* ── palettes ─────────────────────────────────────────────
   Every colour is defined exactly ONCE here, as --dk-* and --lt-*.
   The blocks below only *assign* one palette to the live token names.
   That assignment list is repeated three times, but the values are not,
   so changing a colour is still a one-line edit.

   Why not a media query holding a second copy of the palette: that's
   how token blocks drift, and drift is what core/ui_assets exists to
   prevent. Why not JavaScript: only the portal had a theme-init script,
   so auth, admin, beta, the peering portal, globaladmin and apex could
   never follow the OS at all — they were stuck on whatever :root said.
   Doing it in CSS means every surface gets it just by loading this file. */
:root {
  /* ── CARD tokens — identical in both themes ──────────────────────────
     This is the structural idea of Slate: content lives on paper, and the
     THEME is the room the paper sits in. So --surface and everything that
     lands on it (text, borders, status) do not vary by theme at all; only
     the ground behind the cards swaps. That is why a card looks the same in
     both mockups and only the page around it changes.

     Measured against the worse of --surface and --surface-alt. */
  --cd-surface: #FBFAF7;
  --cd-surface-alt: #F0EDE6;
  --cd-border: #D8D2C6;      /* 1.47 against --surface */
  --cd-text: #14181D;        /* 15.25 */
  --cd-text-muted: #2C333B;  /* 10.93 */
  --cd-text-subtle: #5A6570; /*  5.09 */
  --cd-text-faint: #636C77;  /*  4.56 — carries every ASN and timestamp */
  /* The mockup's teal, taken exactly. It was the one real colour difference
     from the light study: everything else matched or was within a few units,
     but the accent was Signal Blue here and teal there -- and on the dark
     ground we were already in the teal family, so the app read teal on slate
     and blue on paper.

     Two steps deeper than the study's #17789A, because we have surfaces it
     does not. It has one paper and one ground; we have --surface AND
     --surface-alt, --bg AND --sidebar, and a token has to clear AA on the
     worse of each pair. The study's exact value lands at 4.29 on
     --surface-alt and 4.17 on --sidebar. It also measures 4.40 on the
     study's OWN ground, so it was sub-AA there too and simply had no second
     surface to be caught by. Same hue, enough depth to survive ours. */
  --cd-accent: #156C87;      /*  5.09 on the worse card surface, 5.95 under white */
  --cd-accent-hover: #125F77;
  --cd-accent-faint: rgba(21,108,135,0.08);
  --cd-accent-dim: rgba(21,108,135,0.20);
  /* The accent FILL is not a card token, it is a theme token.
     The sign-in study makes this explicit: its card is theme-invariant paper,
     exactly like ours, but the submit button is cyan with near-black ink on
     dark and teal with white ink on light. So a filled accent button swaps
     while everything else on the paper stays put.

     It cannot just be --accent: that is link text on paper, and light cyan
     there measures 1.4:1. Two different jobs, two tokens. */
  --cd-accent-fill: #156C87;
  --cd-on-accent-fill: #FFFFFF;
  --cd-row-bg: rgba(0,0,0,0.015);
  --cd-row-bg-hover: rgba(0,0,0,0.035);
  --cd-table-header-bg: rgba(0,0,0,0.035);
  --cd-zebra: rgba(0,0,0,0.020);
  --cd-st-ok: #0E7343;      /* 5.66 on card, 4.93 on its own tint */
  --cd-st-warn: #8A5A00;     /* 5.07 */
  --cd-st-crit: #8E0C1A;     /* 8.10 */
  --cd-st-info: #0E5EAA;     /* 5.61 */
  --cd-st-idle: #4A5A78;     /* 5.94 */
  --cd-st-unmanaged: #7C3AED;/* 4.87 */
  /* Status tints are SOLID, not translucent. A badge is a self-contained
     chip: it appears on cards and equally on the page ground, and a 10%
     wash takes the colour of whatever is behind it — so the same badge that
     read fine on paper measured 1.06 on the slate ground, invisible.

     These are the old 10% tints composited over --cd-surface once, here,
     so the chip carries its own surface wherever it lands. Text on each
     clears 4.5, and each stands ~8:1 against the dark ground. */
  --cd-st-ok-bg: #E3EDE5;
  --cd-st-warn-bg: #F0EADE;
  --cd-st-crit-bg: #F0E2E1;
  --cd-st-info-bg: #E3EAEF;
  --cd-st-idle-bg: #E9EAEA;
  --cd-st-unmanaged-bg: #EEE7F6;
  --cd-st-ok-border: rgba(15,122,71,0.32);
  --cd-st-warn-border: rgba(138,90,0,0.32);
  --cd-st-crit-border: rgba(142,12,26,0.32);
  --cd-st-info-border: rgba(14,94,170,0.20);
  /* The remaining two families had tints but no borders, so a callout in
     those colours had to invent one. */
  --cd-st-idle-border: rgba(74,90,120,0.30);
  --cd-st-unmanaged-border: rgba(124,58,237,0.28);
  /* Graphical-object fills: a 13px bar or a chart segment is a mark, not
     text, so WCAG 1.4.11 asks 3:1 rather than 4.5:1 - which buys back the
     vividness of the chart mockups without breaking the measured system.
     Ratios vs paper #FCFCFB / vs the chart track: ok 3.57/3.16,
     warn 3.87/3.42, crit 3.65/3.23, idle 3.50/3.10. Crit and idle are the
     mockups' own hues, which happen to clear the bar; ok and warn are one
     step deeper than the mockups'. FILLS ONLY - any text or pip beside the
     mark stays on --st-*, which is measured for 4.5. Theme-invariant, like
     the paper they are drawn on. */
  /* A soft top-light over every bar fill, so a healthy solid bar reads as
     a drawn object rather than a flat rectangle - the shading register of
     the chart mockups' area fill. Textures stack on top of it. */
  --bar-sheen: linear-gradient(180deg, rgba(255,255,255,.25), rgba(255,255,255,0) 60%);
  --st-ok-fill: #23985F;
  --st-warn-fill: #AD7415;
  --st-crit-fill: #DF5560;
  --st-idle-fill: #7E8894;
  /* meaning-carrying fills: ink = what the device reports, paper = what you
     asked for. Theme-invariant for the same reason the cards are. */
  --cd-fill-observed: #13171C;
  --cd-on-fill-observed: #F1F3F5;
  --cd-on-fill-observed-quiet: #949DA7;
  --cd-fill-intent: #FCFCFB;
  --cd-on-fill-intent: #14181D;
  --cd-on-fill-intent-quiet: #5A626C;
  --cd-sq-1: #79B8D5;
  --cd-sq-2: #57A0C4;
  --cd-sq-3: #3B87AF;
  --cd-sq-4: #256E96;
  --cd-sq-5: #14567A;
  /* Tokens for content INSIDE an observed (ink) card. Measured against
     --cd-fill-observed rather than the paper surfaces. */
  --cd-obs-text: #F1F3F5;         /* 16.18 */
  --cd-obs-text-muted: #C9D0D8;   /* 11.57 */
  --cd-obs-text-subtle: #AAB4BE;  /*  8.55 */
  --cd-obs-text-faint: #949DA7;   /*  6.55 */
  --cd-obs-border: #2C333B;       /*  1.41 against the fill */
  --cd-obs-surface-alt: #1E242B;
  --cd-obs-accent: #7AD5F0;       /* 10.80 */
  --cd-obs-st-ok: #3FCBA5;        /*  8.82 */
  --cd-obs-st-warn: #EDB94A;      /*  9.97 */
  --cd-obs-st-crit: #FF7F6B;      /*  7.28 */
  --cd-obs-st-info: #7FD3F2;      /* 10.71 */
  --cd-obs-st-idle: #9DA6C8;      /*  7.47 */
  --cd-obs-st-unmanaged: #C9A6FA; /*  8.84 */

  /* ── GROUND tokens — the only half that swaps ────────────────────────
     The page, the topbar, the rail and the contextual panel. Text here
     cannot use --text: in the dark theme the ground is slate and the cards
     are paper, so the two need opposite ink. */
  --dkg-bg: #3B434D;
  --dkg-sidebar: #2F3740;
  --dkg-on: #E8ECEF;         /* 8.43 */
  --dkg-on-muted: #C3CBD4;   /* 6.11 */
  --dkg-on-faint: #A8B2BD;   /* 4.66 */
  --dkg-on-accent: #5BC8E8;
  --dkg-accent-fill: #5BC8E8;
  /* The sign-in glow. Two stops, both the same hue, because a gradient needs
     a translucent START (an 8% wash, not a status tint -- an opaque stop makes
     a hard-edged blob) and an END that fades to the SAME hue at zero alpha
     rather than to `transparent`, which is transparent BLACK. */
  --dkg-glow: rgba(91,200,232,0.10);
  --dkg-glow-out: rgba(91,200,232,0);
  --dkg-on-accent-fill: #08171D;  /* 5.19 — the dark study's accent, taken exactly */
  --dkg-border: #525C69;      /* solid, not rgba: HTML email inlines these */
  --dkg-hover: rgba(255,255,255,0.07);
  --dkg-sunk: rgba(0,0,0,0.16);

  --ltg-bg: #F3F2EE;
  --ltg-sidebar: #EDEAE3;
  --ltg-on: #23272D;         /* 12.49 */
  --ltg-on-muted: #4A525C;   /*  6.59 */
  --ltg-on-faint: #61686F;   /*  4.70 */
  --ltg-on-accent: #156C87;  /*  4.96 on the worse ground surface */
  --ltg-glow: rgba(21,108,135,0.07);
  --ltg-glow-out: rgba(21,108,135,0);
  --ltg-border: #D3CDC0;      /* solid, not rgba: HTML email inlines these */
  --ltg-hover: rgba(35,39,45,0.06);
  --ltg-sunk: rgba(0,0,0,0.035);

}

/* Resolution order: an explicit choice wins, then the OS, then dark.
   `:not([data-theme="dark"])` is what makes the explicit choice win —
   when the user has picked dark, this block simply stops matching. */
:root {
  /* cards: paper, identical in both themes */
  --surface: var(--cd-surface);
  --surface-alt: var(--cd-surface-alt);
  --border: var(--cd-border);
  --text: var(--cd-text);
  --text-muted: var(--cd-text-muted);
  --text-subtle: var(--cd-text-subtle);
  --text-faint: var(--cd-text-faint);
  --accent: var(--cd-accent);
  --accent-hover: var(--cd-accent-hover);
  --accent-faint: var(--cd-accent-faint);
  --accent-dim: var(--cd-accent-dim);
  --accent-fill: var(--dkg-accent-fill);
  --on-accent-fill: var(--dkg-on-accent-fill);
  --bg-glow: var(--dkg-glow);
  --bg-glow-out: var(--dkg-glow-out);
  --row-bg: var(--cd-row-bg);
  --row-bg-hover: var(--cd-row-bg-hover);
  --table-header-bg: var(--cd-table-header-bg);
  --zebra: var(--cd-zebra);
  --st-ok: var(--cd-st-ok);
  --st-warn: var(--cd-st-warn);
  --st-crit: var(--cd-st-crit);
  --st-info: var(--cd-st-info);
  --st-idle: var(--cd-st-idle);
  --st-unmanaged: var(--cd-st-unmanaged);
  --st-ok-bg: var(--cd-st-ok-bg);
  --st-warn-bg: var(--cd-st-warn-bg);
  --st-crit-bg: var(--cd-st-crit-bg);
  --st-info-bg: var(--cd-st-info-bg);
  --st-idle-bg: var(--cd-st-idle-bg);
  --st-unmanaged-bg: var(--cd-st-unmanaged-bg);
  --st-ok-border: var(--cd-st-ok-border);
  --st-warn-border: var(--cd-st-warn-border);
  --st-crit-border: var(--cd-st-crit-border);
  --st-info-border: var(--cd-st-info-border);
  --st-idle-border: var(--cd-st-idle-border);
  --st-unmanaged-border: var(--cd-st-unmanaged-border);
  --fill-observed: var(--cd-fill-observed);
  --on-fill-observed: var(--cd-on-fill-observed);
  --on-fill-observed-quiet: var(--cd-on-fill-observed-quiet);
  --fill-intent: var(--cd-fill-intent);
  --on-fill-intent: var(--cd-on-fill-intent);
  --on-fill-intent-quiet: var(--cd-on-fill-intent-quiet);
  --sq-1: var(--cd-sq-1);
  --sq-2: var(--cd-sq-2);
  --sq-3: var(--cd-sq-3);
  --sq-4: var(--cd-sq-4);
  --sq-5: var(--cd-sq-5);
  /* ground: the only half that swaps */
  --bg: var(--dkg-bg);
  --sidebar: var(--dkg-sidebar);
  --on-bg: var(--dkg-on);
  --on-bg-muted: var(--dkg-on-muted);
  --on-bg-faint: var(--dkg-on-faint);
  --on-bg-accent: var(--dkg-on-accent);
  --bg-border: var(--dkg-border);
  --bg-hover: var(--dkg-hover);
  --bg-sunk: var(--dkg-sunk);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
  --accent-fill: var(--cd-accent-fill);
  --on-accent-fill: var(--cd-on-accent-fill);
  --bg-glow: var(--ltg-glow);
  --bg-glow-out: var(--ltg-glow-out);
    /* cards: paper, identical in both themes */
    --surface: var(--cd-surface);
    --surface-alt: var(--cd-surface-alt);
    --border: var(--cd-border);
    --text: var(--cd-text);
    --text-muted: var(--cd-text-muted);
    --text-subtle: var(--cd-text-subtle);
    --text-faint: var(--cd-text-faint);
    --accent: var(--cd-accent);
    --accent-hover: var(--cd-accent-hover);
    --accent-faint: var(--cd-accent-faint);
    --accent-dim: var(--cd-accent-dim);
    --row-bg: var(--cd-row-bg);
    --row-bg-hover: var(--cd-row-bg-hover);
    --table-header-bg: var(--cd-table-header-bg);
    --zebra: var(--cd-zebra);
    --st-ok: var(--cd-st-ok);
    --st-warn: var(--cd-st-warn);
    --st-crit: var(--cd-st-crit);
    --st-info: var(--cd-st-info);
    --st-idle: var(--cd-st-idle);
    --st-unmanaged: var(--cd-st-unmanaged);
    --st-ok-bg: var(--cd-st-ok-bg);
    --st-warn-bg: var(--cd-st-warn-bg);
    --st-crit-bg: var(--cd-st-crit-bg);
    --st-info-bg: var(--cd-st-info-bg);
    --st-idle-bg: var(--cd-st-idle-bg);
    --st-unmanaged-bg: var(--cd-st-unmanaged-bg);
    --st-ok-border: var(--cd-st-ok-border);
    --st-warn-border: var(--cd-st-warn-border);
    --st-crit-border: var(--cd-st-crit-border);
    --st-info-border: var(--cd-st-info-border);
    --fill-observed: var(--cd-fill-observed);
    --on-fill-observed: var(--cd-on-fill-observed);
    --on-fill-observed-quiet: var(--cd-on-fill-observed-quiet);
    --fill-intent: var(--cd-fill-intent);
    --on-fill-intent: var(--cd-on-fill-intent);
    --on-fill-intent-quiet: var(--cd-on-fill-intent-quiet);
    --sq-1: var(--cd-sq-1);
    --sq-2: var(--cd-sq-2);
    --sq-3: var(--cd-sq-3);
    --sq-4: var(--cd-sq-4);
    --sq-5: var(--cd-sq-5);
    /* ground: the only half that swaps */
    --bg: var(--ltg-bg);
    --sidebar: var(--ltg-sidebar);
    --on-bg: var(--ltg-on);
    --on-bg-muted: var(--ltg-on-muted);
    --on-bg-faint: var(--ltg-on-faint);
    --on-bg-accent: var(--ltg-on-accent);
    --bg-border: var(--ltg-border);
    --bg-hover: var(--ltg-hover);
    --bg-sunk: var(--ltg-sunk);
  }
}
[data-theme="light"] {
  --accent-fill: var(--cd-accent-fill);
  --on-accent-fill: var(--cd-on-accent-fill);
  --bg-glow: var(--ltg-glow);
  --bg-glow-out: var(--ltg-glow-out);
  /* cards: paper, identical in both themes */
  --surface: var(--cd-surface);
  --surface-alt: var(--cd-surface-alt);
  --border: var(--cd-border);
  --text: var(--cd-text);
  --text-muted: var(--cd-text-muted);
  --text-subtle: var(--cd-text-subtle);
  --text-faint: var(--cd-text-faint);
  --accent: var(--cd-accent);
  --accent-hover: var(--cd-accent-hover);
  --accent-faint: var(--cd-accent-faint);
  --accent-dim: var(--cd-accent-dim);
  --row-bg: var(--cd-row-bg);
  --row-bg-hover: var(--cd-row-bg-hover);
  --table-header-bg: var(--cd-table-header-bg);
  --zebra: var(--cd-zebra);
  --st-ok: var(--cd-st-ok);
  --st-warn: var(--cd-st-warn);
  --st-crit: var(--cd-st-crit);
  --st-info: var(--cd-st-info);
  --st-idle: var(--cd-st-idle);
  --st-unmanaged: var(--cd-st-unmanaged);
  --st-ok-bg: var(--cd-st-ok-bg);
  --st-warn-bg: var(--cd-st-warn-bg);
  --st-crit-bg: var(--cd-st-crit-bg);
  --st-info-bg: var(--cd-st-info-bg);
  --st-idle-bg: var(--cd-st-idle-bg);
  --st-unmanaged-bg: var(--cd-st-unmanaged-bg);
  --st-ok-border: var(--cd-st-ok-border);
  --st-warn-border: var(--cd-st-warn-border);
  --st-crit-border: var(--cd-st-crit-border);
  --st-info-border: var(--cd-st-info-border);
  --fill-observed: var(--cd-fill-observed);
  --on-fill-observed: var(--cd-on-fill-observed);
  --on-fill-observed-quiet: var(--cd-on-fill-observed-quiet);
  --fill-intent: var(--cd-fill-intent);
  --on-fill-intent: var(--cd-on-fill-intent);
  --on-fill-intent-quiet: var(--cd-on-fill-intent-quiet);
  --sq-1: var(--cd-sq-1);
  --sq-2: var(--cd-sq-2);
  --sq-3: var(--cd-sq-3);
  --sq-4: var(--cd-sq-4);
  --sq-5: var(--cd-sq-5);
  /* ground: the only half that swaps */
  --bg: var(--ltg-bg);
  --sidebar: var(--ltg-sidebar);
  --on-bg: var(--ltg-on);
  --on-bg-muted: var(--ltg-on-muted);
  --on-bg-faint: var(--ltg-on-faint);
  --on-bg-accent: var(--ltg-on-accent);
  --bg-border: var(--ltg-border);
  --bg-hover: var(--ltg-hover);
  --bg-sunk: var(--ltg-sunk);
}
[data-theme="dark"] {
  /* cards: paper, identical in both themes */
  --surface: var(--cd-surface);
  --surface-alt: var(--cd-surface-alt);
  --border: var(--cd-border);
  --text: var(--cd-text);
  --text-muted: var(--cd-text-muted);
  --text-subtle: var(--cd-text-subtle);
  --text-faint: var(--cd-text-faint);
  --accent: var(--cd-accent);
  --accent-hover: var(--cd-accent-hover);
  --accent-faint: var(--cd-accent-faint);
  --accent-dim: var(--cd-accent-dim);
  --row-bg: var(--cd-row-bg);
  --row-bg-hover: var(--cd-row-bg-hover);
  --table-header-bg: var(--cd-table-header-bg);
  --zebra: var(--cd-zebra);
  --st-ok: var(--cd-st-ok);
  --st-warn: var(--cd-st-warn);
  --st-crit: var(--cd-st-crit);
  --st-info: var(--cd-st-info);
  --st-idle: var(--cd-st-idle);
  --st-unmanaged: var(--cd-st-unmanaged);
  --st-ok-bg: var(--cd-st-ok-bg);
  --st-warn-bg: var(--cd-st-warn-bg);
  --st-crit-bg: var(--cd-st-crit-bg);
  --st-info-bg: var(--cd-st-info-bg);
  --st-idle-bg: var(--cd-st-idle-bg);
  --st-unmanaged-bg: var(--cd-st-unmanaged-bg);
  --st-ok-border: var(--cd-st-ok-border);
  --st-warn-border: var(--cd-st-warn-border);
  --st-crit-border: var(--cd-st-crit-border);
  --st-info-border: var(--cd-st-info-border);
  --fill-observed: var(--cd-fill-observed);
  --on-fill-observed: var(--cd-on-fill-observed);
  --on-fill-observed-quiet: var(--cd-on-fill-observed-quiet);
  --fill-intent: var(--cd-fill-intent);
  --on-fill-intent: var(--cd-on-fill-intent);
  --on-fill-intent-quiet: var(--cd-on-fill-intent-quiet);
  --sq-1: var(--cd-sq-1);
  --sq-2: var(--cd-sq-2);
  --sq-3: var(--cd-sq-3);
  --sq-4: var(--cd-sq-4);
  --sq-5: var(--cd-sq-5);
  /* ground: the only half that swaps */
  --bg: var(--dkg-bg);
  --sidebar: var(--dkg-sidebar);
  --on-bg: var(--dkg-on);
  --on-bg-muted: var(--dkg-on-muted);
  --on-bg-faint: var(--dkg-on-faint);
  --on-bg-accent: var(--dkg-on-accent);
  --bg-border: var(--dkg-border);
  --bg-hover: var(--dkg-hover);
  --bg-sunk: var(--dkg-sunk);
  --accent-fill: var(--dkg-accent-fill);
  --on-accent-fill: var(--dkg-on-accent-fill);
  --bg-glow: var(--dkg-glow);
  --bg-glow-out: var(--dkg-glow-out);
}

/* ─────────────────────────────────────────────────────────────────────
   Baseline behaviour every surface gets for free. Lives here rather than
   in each app's stylesheet because an accessibility floor that only some
   pages have is not a floor. Before this: zero :focus-visible and zero
   prefers-reduced-motion across the entire product.
   ───────────────────────────────────────────────────────────────────── */

/* ── page container ──────────────────────────────────────
   Page width used to be set inline, per template, and had drifted to
   NINE different values across the wide pages alone (900/920/960/980/
   1000/1100/1180/1200/1280) with alignment flipping between centred
   and left. Moving between pages, the content edge jumped sideways.

   Two containers, not one — the split was real, not drift: data pages
   want room for dense tables, forms want a readable measure. A 1100px
   form is a bad form.

   The 1280px cap on .page is gone. It was set when content ran flush to
   the window and needed a measure of its own; content now sits inside a
   padded canvas that already provides the gutter, so the cap only made
   pages look inset from a frame that was itself already inset. That is why
   a third of the product read as "narrow body" after the shell changed —
   two nested measures where the design has one.

   .page and .page-wide now differ only in padding. Kept as separate names
   because 33 templates say page-wide deliberately, and collapsing them
   would lose that intent for no gain. */
.page        { max-width: none; padding: var(--sp-8); }
.page-narrow { max-width:  640px; margin-inline: auto; padding: var(--sp-8); }
/* Data tables get the screen. Capping a session list at 1280px on a 1800px
   monitor wastes a third of the viewport — measured against Cloudflare's
   dashboard, which runs its DNS table full-bleed to a 32px gutter. Reading
   content still wants a measure; a table doesn't. */
.page-wide   { max-width: none;   padding: var(--sp-6) var(--sp-8); }
@media (max-width: 760px) {
  .page, .page-narrow { padding: var(--sp-5) var(--sp-4); }
}

/* ── dense tables ─────────────────────────────────────────
   This is a tool people keep open all day. At the old padding a
   viewport held ~8 sessions; an operator with a full IX port needs to
   see the shape of their network, not scroll through it.

   Calibrated against Cloudflare's DNS table rather than guessed: theirs
   runs a 41px row pitch at 16px type. An earlier pass here went to 31px
   at 13px, which was denser than the reference and read as cramped —
   small type doesn't look professional, it looks squeezed. This sits
   between the two: roomier than the over-correction, tighter than the
   original. */
.table-dense .table-cell   { padding: var(--sp-2) var(--sp-4); font-size: var(--fs-md); }
.table-dense .table-header { padding: 6px var(--sp-4); }
.table-dense tbody tr:nth-child(even) > td { background: var(--zebra); }

/* ── callouts ─────────────────────────────────────────────
   An aside that explains or warns, inside page content. Distinct from the
   full-bleed banners at the top of the shell (billing, preview notice) — those
   are app-level and span the viewport; a callout belongs to the paragraph
   it sits next to.

   The severity is carried by the left rule AND named in the text, never by
   colour alone — same constraint as everywhere else in this system, and for
   the same measured reason. Variants map onto the status vocabulary rather
   than inventing a second one. */
.callout       { display:flex; gap:var(--sp-3); padding:var(--sp-3) var(--sp-4);
                 background:var(--surface-alt); border:1px solid var(--border);
                 border-left:3px solid var(--accent); border-radius:var(--r-md);
                 font-size:var(--fs-sm); color:var(--text-muted); }
.callout + .callout { margin-top:var(--sp-3); }
.callout strong,
.callout b     { color:var(--text); font-weight:600; }
.callout p     { margin:0; }
.callout p + p { margin-top:var(--sp-2); }
.callout-icon  { flex:none; margin-top:1px; color:var(--accent); }
.callout-ok    { border-left-color:var(--st-ok); }
.callout-ok   .callout-icon { color:var(--st-ok); }
.callout-warn  { border-left-color:var(--st-warn); }
.callout-warn .callout-icon { color:var(--st-warn); }
.callout-crit  { border-left-color:var(--st-crit); }
.callout-crit .callout-icon { color:var(--st-crit); }

/* Existing templates say .info-banner / .error-banner in 14 places. Rather
   than a third way to draw a note, those names resolve to this component —
   so they inherit the look with no template churn. New code uses .callout. */
.info-banner, .error-banner {
  display:flex; gap:var(--sp-3); padding:var(--sp-3) var(--sp-4);
  background:var(--surface-alt); border:1px solid var(--border);
  border-left:3px solid var(--accent); border-radius:var(--r-md);
  font-size:var(--fs-sm); color:var(--text-muted); margin-bottom:var(--sp-4);
}
.error-banner { border-left-color:var(--st-crit); }

/* ── the approval gate ────────────────────────────────────
   Two pieces that answer the same question at two moments.

   .gate-note sits NEXT TO a submit control and answers it BEFORE the click:
   "what happens when I press this?" A button labelled Deploy that quietly
   files a request instead of deploying is the thing this exists to stop.
   Wording belongs to the template, because the gate differs by surface;
   what is shared is that the answer is visible without pressing anything.

   .inflight answers it AFTER: what has been submitted and is still waiting.

   Both live on the GROUND -- directly inside .page, next to a form. They use
   the --on-bg family for that reason. Inside a card, .card rebinds the ink
   families and these read as paper; that nesting is the documented behaviour
   (ui-guide section 1), so they are safe either way. */
.gate-note     { display:inline-flex; align-items:center; gap:var(--sp-2);
                 font-size:var(--fs-xs); color:var(--on-bg-muted);
                 line-height:1.4; }
.gate-note svg { flex:none; color:var(--st-warn); }

.inflight      { border:1px solid var(--bg-border); border-radius:var(--r-md);
                 padding:var(--sp-3) var(--sp-4); margin:var(--sp-4) 0; }
.inflight-title{ font-size:var(--fs-xs); font-weight:600; letter-spacing:0.06em;
                 text-transform:uppercase; color:var(--on-bg-faint);
                 margin:0 0 var(--sp-2); }
.inflight-row  { display:flex; align-items:center; gap:var(--sp-3);
                 padding:var(--sp-1) 0; font-size:var(--fs-sm); }
.inflight-row .mono { font-family:var(--mono); }
.inflight-row .what { color:var(--on-bg-muted); }
.inflight-row a{ margin-left:auto; color:var(--on-bg-accent);
                 text-decoration:none; }
.inflight-row a:hover { text-decoration:underline; }

/* ── state timeline ───────────────────────────────────────
   A horizontal strip of a session's state over a window. This answers a
   question a table cannot: "is this flapping, or did it drop once?" A row
   reading "Idle" looks identical whether it dropped once or forty times.

   Every state carries a TEXTURE as well as a hue. That's not decoration:
   amber and red cannot clear the colour-vision-deficiency floor on a light
   ground while holding AA contrast (measured — best achievable is dE 6.3
   against a target of 8), so colour alone would fail for a real share of
   operators. The hatch directions are opposed, so warn and crit separate
   even in greyscale or forced-colors. */
.timeline      { display:flex; gap:2px; height:26px; border-radius:var(--r-sm);
                 overflow:hidden; background:var(--surface-alt); }
.tl-seg        { position:relative; min-width:2px; }
.tl-seg-ok     { background:var(--st-ok); }
.tl-seg-warn   { background:var(--st-warn);
                 background-image:repeating-linear-gradient(45deg,
                   rgba(0,0,0,.28) 0 3px, transparent 3px 7px); }
.tl-seg-crit   { background:var(--st-crit);
                 background-image:repeating-linear-gradient(135deg,
                   rgba(0,0,0,.32) 0 3px, transparent 3px 7px); }
.tl-seg-unknown{ background:var(--surface-alt);
                 background-image:repeating-linear-gradient(90deg,
                   var(--border) 0 1px, transparent 1px 6px); }
.tl-axis       { display:flex; justify-content:space-between; margin-top:6px;
                 font-size:var(--fs-3xs); color:var(--text-faint);
                 font-family:var(--mono); }
.tl-legend     { display:flex; flex-wrap:wrap; gap:var(--sp-4); margin-top:var(--sp-3);
                 font-size:var(--fs-2xs); color:var(--text-subtle); }
.tl-key        { display:inline-flex; align-items:center; gap:6px; }
.tl-key i      { width:14px; height:10px; border-radius:2px; flex:none; }

/* ── headroom meter ───────────────────────────────────────
   Received prefixes against the configured max-prefix. A single value
   against a limit is a bar, not a gauge and certainly not a pie — the
   question is "how much room is left", which is a length. */
.meter       { height:6px; border-radius:var(--r-pill); background:var(--surface-alt);
               overflow:hidden; }
.meter > i   { display:block; height:100%; border-radius:var(--r-pill);
               background:var(--st-ok); }
.meter.is-warn > i { background:var(--st-warn); }
.meter.is-crit > i { background:var(--st-crit); }
.meter-row   { display:flex; align-items:baseline; justify-content:space-between;
               gap:var(--sp-3); margin-bottom:5px; }
.meter-val   { font-family:var(--mono); font-size:var(--fs-sm); color:var(--text);
               font-variant-numeric:tabular-nums; }
.meter-cap   { font-size:var(--fs-2xs); color:var(--text-faint); }
/* ── table toolbar ────────────────────────────────────────
   A titled header strip above a table: what the table is, how many
   rows, how to drive it, and a filter. The affordances existed but
   were invisible — nobody discovers click-to-sort or a keyboard
   shortcut that is never mentioned on screen. */
.tbl-panel { border:1px solid var(--border); border-radius:var(--r-lg);
             background:var(--surface); overflow:hidden; }
.tbl-head  { display:flex; align-items:center; justify-content:space-between;
             gap:var(--sp-3); padding:var(--sp-2) var(--sp-4);
             border-bottom:1px solid var(--border); background:var(--table-header-bg); }
.tbl-title { font-size:var(--fs-sm); font-weight:600; color:var(--text); white-space:nowrap; }
.tbl-count { font-family:var(--mono); font-size:var(--fs-2xs); color:var(--text-faint);
             margin-left:var(--sp-2); }
.tbl-tools { display:flex; align-items:center; gap:var(--sp-3); }
.tbl-hint  { font-size:var(--fs-2xs); color:var(--text-faint); white-space:nowrap; }
.tbl-hint kbd { font-family:var(--mono); font-size:var(--fs-3xs); border:1px solid var(--border);
                border-radius:var(--r-sm); padding:1px 4px; color:var(--text-subtle);
                background:var(--surface-alt); margin:0 1px; }
.tbl-filter { display:flex; align-items:center; gap:var(--sp-2); background:var(--surface-alt);
              border:1px solid var(--border); border-radius:var(--r-md); padding:3px var(--sp-3); }
.tbl-filter input { background:none; border:0; outline:0; color:var(--text);
                    font:inherit; font-size:var(--fs-sm); width:170px; }
.tbl-filter input::placeholder { color:var(--text-faint); }
@media (max-width:900px){ .tbl-hint { display:none; } }

/* Keyboard row focus. Ops people live in terminals; j/k and / are the
   idiom they already have in their fingers. */
tr.row-focus > td { background:var(--accent-faint); }
tr.row-focus > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr[hidden] { display:none; }

/* ── sortable columns ─────────────────────────────────────
   Not one of the 31 list pages could be sorted. On the screen an
   operator opens to find what's broken, "show me everything that
   isn't Established" was impossible without reading every row.

   Sorting is client-side because the list endpoints render every row
   (`.all()`), so the full set is already in the DOM — the sort is
   complete and correct by construction. When pagination lands, this
   moves server-side; until then a round trip would buy nothing. */
th[data-sort] { cursor: pointer; user-select: none; white-space: nowrap; }
th[data-sort]:hover { color: var(--text); }
th[data-sort]::after {
  content: "\2195";            /* up-down arrow: sortable but unsorted */
  margin-left: 5px; opacity: .3; font-size: .9em;
}
th[data-sort]:hover::after { opacity: .6; }
th[aria-sort="ascending"]::after  { content: "\2191"; opacity: 1; color: var(--accent); }
th[aria-sort="descending"]::after { content: "\2193"; opacity: 1; color: var(--accent); }

/* ── status treatment ─────────────────────────────────────
   The resting state RECEDES. A coloured pill on every healthy row
   means nothing stands out, which is decorative rather than
   operational — so "Established" is a dot and a muted label, and
   only sessions that need a human carry colour. */
.st-quiet { display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
            color:var(--text-subtle); font-size:var(--fs-sm); }
/* The dot carries the hue, the label stays muted — so a healthy row reads as
   information rather than as a highlight. Override --st-quiet-dot for a
   neutral marker (e.g. "enabled: yes", which must not compete with state). */
.st-quiet::before { content:""; flex:none; width:5px; height:5px;
                    border-radius:var(--r-pill);
                    background:var(--st-quiet-dot, var(--st-ok)); }
.st-loud  { display:inline-flex; align-items:center; border-radius:var(--r-sm);
            padding:1px 7px; font-size:var(--fs-xs); font-weight:600; white-space:nowrap; }
.st-loud-warn { background:var(--st-warn-bg); color:var(--st-warn); }
.st-loud-crit { background:var(--st-crit-bg); color:var(--st-crit); }

/* Severity stripe — the row itself carries the signal, so an exception is
   findable when scanning the left edge rather than the status column. */
tr.sev-warn > td:first-child { box-shadow: inset 3px 0 0 var(--st-warn); }
tr.sev-crit > td:first-child { box-shadow: inset 3px 0 0 var(--st-crit); }

/* ── scrollbars ──────────────────────────────────────────
   These were hidden outright from 2026-04-22 (PR #317 era) to
   stop chrome scrollbars appearing inside tall error dumps on
   the deploys page. That removed the only affordance that a
   container scrolls at all — costly on exactly those tall
   config-diff and error panes. Slim and low-contrast instead
   of absent: the clutter was the width, not the scrollbar. */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: var(--r-pill);
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--text-faint); background-clip: content-box;
}

/* ── focus ───────────────────────────────────────────────
   There was no focus style at all, so keyboard users got the
   browser default or nothing. :focus-visible (not :focus) so
   pointer users don't get a ring on every click. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.input:focus-visible, select.input:focus-visible, textarea:focus-visible {
  outline-offset: 0;
}

/* ── reduced motion ──────────────────────────────────────
   The pulse dot, nav spinner and toast slide all animate
   indefinitely or on every navigation. Honour the OS setting. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── skip link ───────────────────────────────────────── */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 9999;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--accent); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-4); font-size: var(--fs-sm);
  text-decoration: none;
}
.skip-link:focus { left: var(--sp-2); top: var(--sp-2); }

/* ── numerics ────────────────────────────────────────────
   Prefix counts, ASNs, session tallies and timestamps have to
   line up in columns. Inter's proportional figures make them
   jitter as values change; tabular figures don't. */
.table-cell-mono, .param-value-mono, .stat-value, .chain-row-index,
.tb-ver, .mono, code, pre, td, th {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ═══════════════════════════════════════════════════════════════════════
   SLATE COMPONENT LAYER
   Everything below is new vocabulary the repaint could not express with
   colour alone. It is additive: nothing here restyles an existing class,
   so a template opts in by using the new names.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── meaning-carrying card fills ──────────────────────────
   The product's central idea is intent versus observed — it is what drift
   IS — and until now both sides rendered on the same --surface, so the
   distinction lived entirely in headings.

   `--text` cannot help here: one token cannot describe text on three
   different surface luminances, which is why these classes set their own
   `color` and expose a `--on-quiet` for secondary text inside them. Use
   `var(--card-on-quiet)` inside a card rather than --text-faint, or the
   secondary text will be the wrong half of the palette. */
/* Quiet text inside a table or card: the secondary half of a cell, the
   unit after a number, the name after an ASN. globaladmin had its own
   .muted for this, which was never defined anywhere, so those cells
   rendered at full strength and the hierarchy the author intended was
   simply absent. The observed/intent cards override it below. */
.quiet { color: var(--text-muted); }

/* A row for something already accounted for: an exchange already imported,
   a peer already requested. It recedes but stays readable, because the
   operator still needs to read it to know it is done. */
.row-done > td { opacity: 0.55; }
.row-done:hover > td { opacity: 1; }

.card-observed .quiet, .card-intent .quiet { color: var(--card-on-quiet); }
/* Says which half of the truth a card is showing, for the case where the
   fill alone is not enough — greyscale, forced-colors, or a screenshot. */
.src-tag { font-family: var(--mono); font-size: var(--fs-3xs); letter-spacing: .1em;
           text-transform: uppercase; padding: 3px 8px; border-radius: var(--r-pill);
           font-weight: 500; white-space: nowrap; }
.card-observed .src-tag { background: rgba(255,255,255,.13); color: var(--on-fill-observed); }
.card-intent   .src-tag { background: rgba(20,24,29,.07);   color: var(--on-fill-intent-quiet); }

/* ── the card deck ────────────────────────────────────────
   Inventory / in-flight / exceptions, side by side. Replaces three bespoke
   row treatments on the dashboard with one. The right rail is a grid column
   rather than a float so counts line up ACROSS cards, which is the bit that
   makes a deck read as instrumentation instead of three lists. */
.deck { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-4);
        align-items: start; }
@media (max-width: 1080px) { .deck { grid-template-columns: 1fr; } }

.dcard { background: var(--surface); border: 1px solid var(--border);
         border-radius: var(--r-lg); overflow: hidden; }
.dcard-h { display: flex; align-items: center; justify-content: space-between;
           gap: var(--sp-3); padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
           border-bottom: 1px solid var(--border); background: var(--table-header-bg); }
/* The whole title is the drill-in target, not just a chevron: same visual
   weight, far larger hit area. */
.dcard-t { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-sm);
           font-weight: 600; color: var(--text); text-decoration: none;
           border-radius: var(--r-sm); }
.dcard-t:hover { color: var(--accent); }
.dcard-n { font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 500;
           color: var(--text-faint); }
.dcard-act { display: inline-flex; align-items: center; justify-content: center;
             width: 24px; height: 24px; border-radius: var(--r-sm);
             border: 1px solid transparent; background: none; color: var(--text-faint);
             cursor: pointer; text-decoration: none; }
.dcard-act:hover { background: var(--surface-alt); border-color: var(--border);
                   color: var(--text); }
.dcard-f { padding: var(--sp-2) var(--sp-4); border-top: 1px solid var(--border);
           background: var(--table-header-bg); display: flex; align-items: center;
           justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-xs); }
.dcard-f a { color: var(--text-subtle); text-decoration: none; }
.dcard-f a:hover { color: var(--accent); }
.dcard-f .tot { font-weight: 600; color: var(--text-muted); }

.drow { display: grid; grid-template-columns: 5px 1fr auto; align-items: center;
        gap: 0 var(--sp-3); padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-3);
        min-height: 46px; text-decoration: none; color: inherit;
        border-left: 3px solid transparent; }
.drow + .drow { border-top: 1px solid var(--border); }
.drow:hover { background: var(--row-bg-hover); }
.drow-warn { border-left-color: var(--st-warn); }
.drow-crit { border-left-color: var(--st-crit); }
/* These are spans so the row can be an <a>, so they need display:block —
   without it the title, subtitle and rail values all run together on one
   line. Cheap to miss, obvious on screen. */
.drow-main { min-width: 0; display: block; }
.drow-t { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--text);
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.drow-s { display: block; margin-top: 1px; font-size: var(--fs-2xs);
          color: var(--text-faint); white-space: nowrap; overflow: hidden;
          text-overflow: ellipsis; }
.drow-rail { text-align: right; white-space: nowrap; display: block; }
.drow-v { display: block; font-family: var(--mono); font-size: var(--fs-sm);
          color: var(--text-muted); }
.drow-v.ok { color: var(--st-ok); } .drow-v.warn { color: var(--st-warn); }
.drow-v.crit { color: var(--st-crit); }
.drow-age { display: block; margin-top: 1px; font-family: var(--mono); font-size: var(--fs-3xs);
            color: var(--text-faint); }
.drow-empty { display: flex; align-items: center; gap: var(--sp-2);
              padding: var(--sp-6) var(--sp-4); font-size: var(--fs-sm);
              color: var(--text-subtle); }
.plat-chip { display: inline-block; padding: 0 5px; border-radius: var(--r-sm);
             background: var(--surface-alt); border: 1px solid var(--border);
             font-family: var(--mono); font-size: var(--fs-3xs);
             color: var(--text-subtle); vertical-align: 1px; }

/* ── sequential ramp ──────────────────────────────────────
   Magnitude gets ONE hue light to dark, never a rainbow and never the status
   hues (those are reserved and mean state, not amount). Validated: monotonic
   lightness, 0.078 minimum step between adjacent stops, 9.6 degrees of hue
   spread, and the faintest stop clears the contrast floor against BOTH the
   paper and the ink surface — so a heatmap works on either card. */

/* ── texture ──────────────────────────────────────────────
   NOT decoration. Measured, light theme: ok vs warn is dE 6.3 for
   deuteranopes — the floor, not the target — and that pair cannot be fixed
   by re-picking, it is a property of the colour space on a light ground
   while holding AA. So any warn/crit fill big enough to carry a pattern
   carries one, at opposed angles so the two also separate in greyscale.

   Apply with background-color, never the `background` shorthand: the
   shorthand resets background-image and silently removes the texture. */
.tex-warn { background-image: repeating-linear-gradient(45deg,
              rgba(255,255,255,.30) 0 3px, transparent 3px 7px); }
.tex-crit { background-image: repeating-linear-gradient(135deg,
              rgba(255,255,255,.26) 0 3px, transparent 3px 7px); }

/* ── uptime heatmap ───────────────────────────────────────
   Answers what a state column cannot: is this peer unstable, or did it drop
   once? One cell per day per session. */
.hm { display: grid; grid-template-columns: minmax(96px, 152px) 1fr;
      gap: 3px var(--sp-3); align-items: center; }
.hm-lbl { font-size: var(--fs-xs); text-align: right; white-space: nowrap;
          overflow: hidden; text-overflow: ellipsis; color: var(--text-muted); }
.hm-row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 2px; }
.hm-c { aspect-ratio: 1; border-radius: 2px; cursor: pointer;
        background-image: var(--bar-sheen); }
.hm-c:hover { outline: 2px solid var(--text); outline-offset: 1px; }
/* Deliberately OUTSIDE the ramp. A day with no established time at all is a
   different kind of fact from a day that was merely poor; folding it into the
   bottom stop made "hard down for three days" look like "flapping at 50%". */
.hm-zero { background: transparent; box-shadow: inset 0 0 0 1px var(--border); }
.hm-scale { display: inline-flex; align-items: center; gap: 7px;
            font-size: var(--fs-2xs); color: var(--text-faint); }
.hm-scale i { width: 17px; height: 11px; border-radius: 2px; }

/* ── headroom bars ────────────────────────────────────────
   Received prefixes against the configured limit, ordered tightest first.
   .meter above is the single-row version; this is the comparative one. */
.hr { display: grid; grid-template-columns: minmax(96px, 152px) 1fr 84px;
      gap: var(--sp-3) var(--sp-3); align-items: center; font-size: var(--fs-sm); }
.hr-track { position: relative; height: 13px; border-radius: var(--r-sm);
            background: var(--surface-alt); overflow: hidden; }
.hr-fill { height: 100%; border-radius: 0 4px 4px 0;
           background-image: var(--bar-sheen); }
.hr-cap { position: absolute; top: -3px; bottom: -3px; right: 0; width: 2px;
          background: var(--text); opacity: .5; }
.hr-v { text-align: right; font-family: var(--mono); font-size: var(--fs-xs); }

/* ── stacked proportion ───────────────────────────────────
   Parts of one whole. A 2px gap between segments rather than a border, so
   the ground does the separating and no segment inherits a stroke colour. */
.stk { display: flex; gap: 2px; height: 34px; }
.stk > * { border-radius: var(--r-sm); cursor: default;
          background-image: var(--bar-sheen); }
.stk > *:first-child { border-radius: var(--r-md) var(--r-sm) var(--r-sm) var(--r-md); }
.stk > *:last-child  { border-radius: var(--r-sm) var(--r-md) var(--r-md) var(--r-sm); }
.stk-lg { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
          gap: var(--sp-3); margin-top: var(--sp-3); }
.stk-lg .it { display: flex; align-items: flex-start; gap: var(--sp-2);
              font-size: var(--fs-sm); }
.stk-lg .it i { width: 11px; height: 11px; border-radius: 3px; margin-top: 3px;
                flex: none; }
.stk-lg .it b { display: block; font-family: var(--mono); font-weight: 600;
                font-size: var(--fs-md); }
.stk-lg .it s { display: block; text-decoration: none; font-size: var(--fs-2xs);
                color: var(--text-faint); }

/* Texture as well as hue, for the status pairs that sit on the CVD floor:
   warn takes one diagonal, crit the other, so an over-threshold mark and an
   over-critical mark stay distinct even with the colour removed. White
   stripes over the status fill, same treatment in both themes. */
.tex  { background-image: repeating-linear-gradient(45deg,
          rgba(255,255,255,.34) 0 3px, transparent 3px 7px),
          var(--bar-sheen); }
.tex2 { background-image: repeating-linear-gradient(135deg,
          rgba(255,255,255,.30) 0 3px, transparent 3px 7px),
          var(--bar-sheen); }

/* Chart legend row. Swatches repeat the mark's fill AND texture, and every
   band carries a worded label, because hue alone cannot separate the
   green-amber pair for a colour-vision-deficient reader. */
.viz-lg { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-3);
          font-size: var(--fs-2xs); color: var(--text-faint); }
.viz-lg span { display: inline-flex; align-items: center; gap: 6px; }
.viz-lg i { width: 11px; height: 11px; border-radius: 3px; flex: none; }

/* ── config / diff ────────────────────────────────────────
   Rendered artifacts and drift comparisons. Sits on an observed card, since
   it is the machine's output rather than your intent. */
.cfg { font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.75;
       border-radius: var(--r-md); overflow: auto; background: rgba(0,0,0,.26);
       padding: var(--sp-3) 0; }
.card-intent .cfg { background: rgba(20,24,29,.05); }
.card-intent .cfg .add { background: rgba(15,122,71,0.14); }
.card-intent .cfg .del { background: rgba(142,12,26,0.12); }
.cfg .ln { display: grid; grid-template-columns: 26px 1fr; gap: var(--sp-2);
           padding: 0 var(--sp-4) 0 6px; white-space: pre; }
.cfg .g { text-align: center; opacity: .45; user-select: none; }
/* Translucent washes, not the opaque status tints. A code well is INK, and
   --st-ok-bg is a near-white paper tint: on a near-black well it renders as a
   solid light bar at 15:1, with the well's own light ink on top of it. The
   study uses a 14% wash of the status hue for exactly this reason. */
.cfg .add { background: rgba(15,122,71,0.20); }
.cfg .add .g { color: var(--st-ok); opacity: 1; }
.cfg .del { background: rgba(142,12,26,0.24); }
.cfg .del .g { color: var(--st-crit); opacity: 1; }

/* ── hover tooltip ────────────────────────────────────────
   A chart in HTML is interactive by default; every mark above expects one
   of these rather than a title attribute, which cannot be styled and does
   not fire on keyboard focus. */
.viz-tip { position: fixed; z-index: 60; pointer-events: none; display: none;
           background: var(--fill-observed); color: var(--on-fill-observed);
           border-radius: var(--r-md); padding: var(--sp-2) var(--sp-3);
           font-size: var(--fs-xs); line-height: 1.45; max-width: 240px;
           box-shadow: 0 8px 22px -8px rgba(0,0,0,.6); }
.viz-tip b { font-family: var(--mono); font-weight: 600; }
.viz-tip s { display: block; text-decoration: none;
             color: var(--on-fill-observed-quiet); font-size: var(--fs-3xs);
             margin-top: 1px; }

/* ── display type ─────────────────────────────────────────
   Titles and stat values take Archivo, tightened. The display ramp existed
   before this but rendered in the UI face, so a page title was just bigger
   body copy. Applied here rather than per-template so every surface that
   uses the ramp gets the pairing without knowing about it. */
h1, h2, h3, .dash-head h1, .stat-value, .dtile .tv,
.card-header-title, .page-title {
  font-family: var(--font-display);
  letter-spacing: -0.025em;
}

/* ── app banners ──────────────────────────────────────────
   Full-bleed notices between the topbar and the page: preview notice, finish
   setup, approvals waiting, billing. They sit on the GROUND, which in the
   dark theme is slate — so they cannot use the status hues directly, because
   those are tuned for paper and go unreadable on it.

   They get a paper strip instead. That keeps the status vocabulary valid,
   makes them read as a notification rather than a tinted page region, and
   matches the callout grammar already in this file: severity is a left rule
   plus a worded label, never colour alone. */
.app-banner { display: flex; align-items: center; justify-content: space-between;
              gap: var(--sp-3); padding: 9px var(--sp-5);
              background: var(--surface); color: var(--text-muted);
              border: 1px solid var(--border); border-radius: var(--r-lg);
              margin: 0 var(--sp-8) var(--sp-3);
              box-shadow: inset 3px 0 0 var(--accent); font-size: var(--fs-sm); }
.app-banner:first-of-type { margin-top: var(--sp-2); }
.app-banner strong { color: var(--text); font-weight: 600; }
.app-banner a { font-weight: 600; text-decoration: none; white-space: nowrap;
                color: var(--accent); }
.app-banner-info { box-shadow: inset 3px 0 0 var(--st-info); }
.app-banner-info a { color: var(--st-info); }
.app-banner-warn { box-shadow: inset 3px 0 0 var(--st-warn); }
.app-banner-warn a { color: var(--st-warn); }
.app-banner-crit { box-shadow: inset 3px 0 0 var(--st-crit); }
.app-banner-crit a { color: var(--st-crit); }

/* ── display numerals ─────────────────────────────────────
   Stat values are the one place the mockups spend real size: a tile is a
   number with its context demoted beside it, not a label with a number
   under it. The ramp already had --fs-dsp-*; the tiles were using the
   middle step, so they read as slightly-bold body copy. */
.dtile .tv, .stat-value {
  font-family: var(--font-display);
  font-size: clamp(2rem, 2.6vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
}

/* ── auth surface ─────────────────────────────────────────
   Sign in, signup, MFA, invite. One paper card on the ground, with the
   brand INSIDE it rather than floating above — the card is the whole
   object, so the page has one thing on it, not three.

   Federated options stack full width above the divider, because most
   people have one and a password is the fallback rather than the default.
   Side by side they read as equal alternatives to each other rather than
   as the primary path. */
.auth-brand { display: flex; align-items: center; gap: 9px; margin-bottom: 20px; }
.auth-brand .wm { font-family: var(--font-brand); font-size: var(--fs-dsp-sm);
                  font-weight: 800; letter-spacing: -0.06em; color: var(--text); }
.auth-brand .wm i { color: var(--accent); font-style: normal; }
.auth h2 { margin: 0; font-family: var(--font-display); font-size: var(--fs-dsp-md);
           font-weight: 600; letter-spacing: -0.028em; color: var(--text); }
.auth .sub { margin: 5px 0 20px; font-size: var(--fs-md);
             color: var(--text-subtle); line-height: 1.5; }

.fed { display: flex; flex-direction: column; gap: var(--sp-2); }
.fbtn { display: flex; align-items: center; justify-content: center; gap: 9px;
        width: 100%; padding: 10px 14px; border-radius: var(--r-md);
        border: 1px solid var(--border); background: var(--surface);
        color: var(--text); font: inherit; font-size: var(--fs-md);
        font-weight: 500; cursor: pointer; text-decoration: none;
        transition: background 120ms; }
.fbtn:hover { background: var(--surface-alt); }
.fbtn svg { flex: none; }

.or-rule { display: flex; align-items: center; gap: var(--sp-3); margin: 18px 0;
           font-family: var(--mono); font-size: var(--fs-3xs); letter-spacing: .12em;
           text-transform: uppercase; color: var(--text-faint); }
.or-rule::before, .or-rule::after { content: ""; height: 1px; flex: 1;
                                    background: var(--border); }

.auth-field { margin-bottom: 13px; }
.auth-field label { display: block; font-size: var(--fs-xs); font-weight: 600;
                    margin-bottom: 5px; color: var(--text); }
.auth-field .row { display: flex; align-items: baseline; justify-content: space-between; }
.auth-field .row a { font-size: var(--fs-2xs); color: var(--text-subtle);
                     text-decoration: none; }
.auth-field .row a:hover { color: var(--accent); }

.auth-foot { margin-top: 18px; padding-top: 15px; border-top: 1px solid var(--border);
             display: flex; align-items: center; justify-content: space-between;
             gap: var(--sp-3); flex-wrap: wrap; font-size: var(--fs-xs);
             color: var(--text-subtle); }
.auth-foot a { color: var(--text); text-decoration: none; font-weight: 500; }
.auth-foot a:hover { color: var(--accent); }

/* ── drift pair ───────────────────────────────────────────
   Intent beside observed. This is the screen the meaning-carrying fills
   exist for: paper on the left is what you asked for, ink on the right is
   what the device reports, and the distinction is carried by the fill
   before a single word is read.

   Rows that AGREE are kept rather than filtered out. A diff showing only
   the differences hides how much matched, and "one key differs out of
   twenty" reads very differently from "one key differs out of one". */
.dpair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
         margin-top: var(--sp-3); }
@media (max-width: 780px) { .dpair { grid-template-columns: 1fr; } }
.dpair > * { border-radius: var(--r-lg); padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.dpair-h { display: flex; align-items: center; justify-content: space-between;
           gap: var(--sp-3); margin-bottom: var(--sp-2);
           font-size: var(--fs-sm); font-weight: 600; }
.dpair dl { display: grid; grid-template-columns: 14px minmax(0,auto) 1fr;
            gap: 5px var(--sp-2); margin: 0; font-size: var(--fs-xs); }
.dpair dt { font-family: var(--mono); color: var(--text-faint);
            white-space: nowrap; }
.dpair dd { margin: 0; font-family: var(--mono); color: var(--text);
            overflow-wrap: anywhere; }
.dpair .mk { font-family: var(--mono); font-weight: 700; text-align: center; }
.dpair .mk-add { color: var(--st-ok); }
.dpair .mk-del { color: var(--st-crit); }
.dpair .mk-chg { color: var(--st-warn); }
.dpair .row-same dt, .dpair .row-same dd { opacity: .55; }
.dpair .absent { color: var(--text-faint); font-style: italic; }

/* the toggle that opens a pair from the table row */
.dpair-btn { background: none; border: 1px solid var(--border);
             border-radius: var(--r-sm); font: inherit; font-size: var(--fs-2xs);
             color: var(--text-subtle); padding: 2px 8px; cursor: pointer; }
.dpair-btn:hover { background: var(--surface-alt); color: var(--text); }
.dpair-row > td { padding: 0 var(--sp-4) var(--sp-4); }

/* ── code well ────────────────────────────────────────────
   A block of machine output inside a card: rendered config, a diff, a
   docker-compose snippet, a command to paste.

   Templates expressed this as `background: var(--bg)` — which worked while
   --bg was simply "darker than the card". Slate repurposed --bg as the page
   GROUND, so every one of those became a dark panel still carrying card
   ink: 1,391 unreadable lines on the render-preview page alone.

   It takes the observed fill, because that is what it is — the machine's
   output rather than your intent — which also matches the mockups. Tokens
   are scoped inside it for the same reason .card-observed scopes them: a
   badge or a status dot dropped in here must not resolve paper colours. */
.code-well, .cd-wrap {
  background: var(--fill-observed);
  color: var(--on-fill-observed);
  border: 1px solid var(--cd-obs-border);
  border-radius: var(--r-md);
  --text: var(--cd-obs-text);
  --text-muted: var(--cd-obs-text-muted);
  --text-subtle: var(--cd-obs-text-subtle);
  --text-faint: var(--cd-obs-text-faint);
  --border: var(--cd-obs-border);
  --surface: var(--cd-fill-observed);
  --surface-alt: var(--cd-obs-surface-alt);
  --accent: var(--cd-obs-accent);
  --st-ok: var(--cd-obs-st-ok);
  --st-warn: var(--cd-obs-st-warn);
  --st-crit: var(--cd-obs-st-crit);
  --st-info: var(--cd-obs-st-info);
  --st-idle: var(--cd-obs-st-idle);
  --st-unmanaged: var(--cd-obs-st-unmanaged);
  /* The -bg washes too, same values .card-observed uses. Without these a
     diff row's add/remove background resolved the PAPER tints - solid pale
     slabs across the ink well - where the mockups show translucent washes
     that keep the well reading as one dark surface. */
  --st-ok-bg: rgba(63,203,165,0.14);
  --st-warn-bg: rgba(237,185,74,0.14);
  --st-crit-bg: rgba(255,127,107,0.16);
  --st-info-bg: rgba(127,211,242,0.15);
  --st-idle-bg: rgba(157,166,200,0.14);
  --st-unmanaged-bg: rgba(201,166,250,0.13);
  --accent-faint: rgba(122,213,240,0.12);
}

/* A well INSIDE an observed card digs one step deeper than the fill, the
   way the mockups inset the rendered config into the ink card, so the two
   dark surfaces read as card and content rather than one undifferentiated
   slab. */
.card-observed .code-well, .card-observed .cd-wrap {
  background: rgba(0,0,0,.28);
  border-color: transparent;
}

/* ── on the ground ────────────────────────────────────────
   The counterpart to .card-observed and .code-well.

   Shared components — .field-label, tab strips, a paragraph of help text —
   are used BOTH inside cards and directly on the page. They set card ink,
   which is right for the common case and invisible on the slate ground.
   Flipping the component would fix the minority and break the majority.

   So the CONTAINER declares where it is, and everything inside resolves
   accordingly. One class on a toolbar or a page-level block fixes every
   descendant, including components that know nothing about grounds. */
/* ── components ───────────────────────────────────────────────────────
   These were defined in five places: templates/base.html, auth, admin,
   globaladmin and the peering portal, each with its own copy. They drifted,
   because copies do:

     .btn-primary   4 definitions, radius r-sm and r-md, three paddings,
                    and admin's ink was near-black on Signal Blue -- 2.88:1
     .card          5 definitions, radius r-md and r-lg, three shadows
     .input         4 definitions, two backgrounds, two radii
     .badge         3 definitions, r-sm and r-pill, and three different
                    greens: --st-ok-bg here, rgba(74,222,128,.1) in admin,
                    rgba(34,197,94,.12) in the portal
     .table-cell    9px/fs-sm here, 11px/fs-md in admin

   Nobody chose any of that. A customer moving between the portal and a
   peering page sees it as two products, which is the whole reason these
   live in one file now. Genuine local variation stays local; a size
   difference that is deliberate is a modifier, not a redefinition. */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: 0 1px 2px rgba(0,0,0,0.16);
}
[data-theme="light"] .card,
:root:not([data-theme="dark"]) .card { box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.card-header { padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border);
               display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.card-header-title { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
.card-body { padding: var(--sp-5); }
.card-empty { padding: var(--sp-6) var(--sp-5); }
.card-empty p { font-size: var(--fs-sm); color: var(--text-subtle); }

.btn-primary, .btn-secondary, .btn-danger {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: var(--r-md); padding: 7px 14px;
  font-size: var(--fs-sm); font-weight: 500; font-family: inherit;
  text-decoration: none; border: 1px solid transparent;
  cursor: pointer; transition: background 120ms, color 120ms;
}
.btn-primary   { background: var(--accent-fill); color: var(--on-accent-fill); }
.btn-primary:hover   { background: var(--accent-hover); color: #fff; }
.btn-secondary { background: var(--surface-alt); color: var(--text-muted); border-color: var(--border); }
.btn-secondary:hover { background: var(--border); color: var(--text); }
.btn-danger    { background: var(--st-crit-bg); color: var(--st-crit); border-color: var(--st-crit-border); }
.btn-danger:hover    { background: var(--st-crit); color: #fff; border-color: var(--st-crit); }
/* The public entry surfaces -- sign-in, the peering portal -- carry one
   large call to action. That is a deliberate size, so it is a modifier. */
.btn-lg { padding: 11px 22px; font-size: var(--fs-md); justify-content: center; }

.input {
  width: 100%; background: var(--surface-alt);
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 8px 12px; font-size: var(--fs-md); font-family: inherit;
  color: var(--text); outline: none;
  transition: border-color 120ms, box-shadow 120ms;
}
.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
.input::placeholder { color: var(--text-faint); }
select.input { cursor: pointer; }

.table-header {
  font-size: var(--fs-3xs); font-weight: 600; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--text-muted);
  padding: 8px var(--sp-4); text-align: left;
  background: var(--table-header-bg); border-bottom: 1px solid var(--border);
}
.table-cell { padding: 9px var(--sp-4); font-size: var(--fs-sm); color: var(--text-muted); vertical-align: middle; }
.table-cell-primary { color: var(--text); font-weight: 500; }
.table-cell-mono { font-family: var(--mono); }
.table-cell-right { text-align: right; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: var(--r-pill); padding: 2px 9px;
  font-size: var(--fs-xs); font-weight: 500; white-space: nowrap;
}
.badge-green  { background: var(--st-ok-bg);        color: var(--st-ok); }
.badge-yellow { background: var(--st-warn-bg);      color: var(--st-warn); }
.badge-red    { background: var(--st-crit-bg);      color: var(--st-crit); }
.badge-blue   { background: var(--st-info-bg);      color: var(--st-info); }
.badge-gray   { background: var(--surface-alt);     color: var(--text-muted); }
.badge-orange { background: var(--st-idle-bg);      color: var(--st-idle); }
.badge-purple { background: var(--st-unmanaged-bg); color: var(--st-unmanaged); }

/* A card is paper, and paper carries card ink wherever it sits.

   .on-ground rebinds the text tokens for a subtree that sits directly on
   the page ground. Without the inverse, a card nested inside such a subtree
   would inherit ground ink onto paper — light text on a light card, which is
   the exact failure .on-ground exists to prevent, just moved one level down.

   So a card re-establishes the card vocabulary explicitly. The two rules
   compose: ground and paper can nest to any depth and each element takes its
   ink from the surface actually behind it. */
/* A card has padding by default.

   It did not, so all 311 uses hand-specified it and drifted to 24 different
   values: 24px here, 18px 20px there, 14px 18px somewhere else. Nobody
   chose that; each page just picked a number. Side by side it reads as two
   different products, which is what a card default prevents.

   Cards built from .card-header/.card-body delegate padding to those parts,
   so they opt out rather than double up. */
.card { padding: var(--sp-6); }
/* `:has(> table)` used to be here too, so a card could hold a flush table.
   It was too broad: the About page puts a wordmark AND a spec table in one
   card, and zeroing the padding ran both to the card's edges. A card that is
   nothing but a table is what .tbl-panel is for, so this only exempts cards
   built from the header/body parts, which bring their own padding. */
.card:has(> .card-header), .card:has(> .card-body) { padding: 0; }

/* Anything that paints paper restores the card vocabulary. .tbl-panel and
   .dcard were missing, so a table cell or a deck card would have inherited
   ground ink onto paper wherever a ground context wraps them. */
.card, .panel, .stat-card, .tbl-panel, .dcard {
  --text: var(--cd-text);
  --text-muted: var(--cd-text-muted);
  --text-subtle: var(--cd-text-subtle);
  --text-faint: var(--cd-text-faint);
  --border: var(--cd-border);
  --accent: var(--cd-accent);
  --surface: var(--cd-surface);
  --surface-alt: var(--cd-surface-alt);
  color: var(--cd-text);
}

/* ── card fill variants ───────────────────────────────────────────────
   These MUST sit after .card. Both are single-class selectors, so they
   carry identical specificity and the later rule wins — and the markup
   uses `class="card card-observed"` as well as `class="card-observed"`
   alone, so the variant has to be able to beat the base on the same
   element. Moving .card below them silently repaints an observed card
   as paper: near-black ink on a near-black fill, which is invisible
   rather than merely wrong. Guarded by
   test_card_fill_variants_come_after_the_base_card. */
.card-observed {
  background: var(--fill-observed); color: var(--on-fill-observed);
  --card-on-quiet: var(--on-fill-observed-quiet);
  border-color: transparent;
  /* Redefine the card tokens INSIDE this card rather than asking every
     descendant to know it is on ink. Without this, a table, a badge or a
     status dot placed in here renders paper-tuned colours on a near-black
     fill and disappears — and it disappears silently, which is the worst
     way for a colour to be wrong. Every value measured against
     --cd-fill-observed. */
  --text: var(--cd-obs-text);
  --text-muted: var(--cd-obs-text-muted);
  --text-subtle: var(--cd-obs-text-subtle);
  --text-faint: var(--cd-obs-text-faint);
  --border: var(--cd-obs-border);
  --surface: var(--cd-fill-observed);
  --surface-alt: var(--cd-obs-surface-alt);
  --accent: var(--cd-obs-accent);
  --row-bg: rgba(255,255,255,0.02);
  --row-bg-hover: rgba(255,255,255,0.05);
  --table-header-bg: rgba(0,0,0,0.25);
  --zebra: rgba(255,255,255,0.022);
  --st-ok: var(--cd-obs-st-ok);
  --st-warn: var(--cd-obs-st-warn);
  --st-crit: var(--cd-obs-st-crit);
  --st-info: var(--cd-obs-st-info);
  --st-idle: var(--cd-obs-st-idle);
  --st-unmanaged: var(--cd-obs-st-unmanaged);
  --st-ok-bg: rgba(63,203,165,0.14);
  --st-warn-bg: rgba(237,185,74,0.14);
  --st-crit-bg: rgba(255,127,107,0.16);
  --st-info-bg: rgba(127,211,242,0.15);
  --st-idle-bg: rgba(157,166,200,0.14);
  --st-unmanaged-bg: rgba(201,166,250,0.13);
}
.card-intent {
  background: var(--fill-intent); color: var(--on-fill-intent);
  --card-on-quiet: var(--on-fill-intent-quiet);
  border-color: transparent;
}

.on-ground {
  color: var(--on-bg);
  --text: var(--on-bg);
  --text-muted: var(--on-bg-muted);
  --text-subtle: var(--on-bg-muted);
  --text-faint: var(--on-bg-faint);
  --border: var(--bg-border);
  --accent: var(--on-bg-accent);
  --surface-alt: var(--bg-hover);
}
