/* Ziltch SHARED CHROME — the ONE source of the design tokens + the header/brand chrome, loaded by BOTH installable theme
   features (the Ziltch.Theme SITE Layout and the Ziltch.Theme.Admin admin Layout). Erin-directed 2026-07-17:
   "share (NOT DUPLICATE!!!) header/footer/style between site and admin." These rules used to live in ziltch.css (site
   only); they were relocated here verbatim so the admin surface renders the SAME ZiltchChromeView header from the SAME
   CSS — one file, two themes, nothing to keep in sync. ziltch.css now loads this first and owns only the site-LAYOUT
   rules (body flex frame, front-door, content) that must NOT reach the admin layout. */

/* 🏛️ DARK-FIRST, FOLLOWING THE SYSTEM (Erin GOVERNING 2026-08-07: "dark first" + "light/dark = follow system").
   BOTH HALVES ARE LOAD-BEARING AND THEY ARE NOT IN TENSION. Dark-first means dark is the BASE declaration and the
   platform's identity -- it is what a browser with no preference gets, and what every value here is designed
   against. Follow-system means a visitor whose OS asks for light GETS light. `prefers-color-scheme: light` is
   therefore an OVERRIDE ON TOP of a dark base, not a peer of it.

   ⚠️ AN EARLIER VERSION OF THIS BLOCK READ "dark unconditionally; the OS is not consulted" AND THAT WAS WRONG --
   I took "dark first" to mean "dark only". Corrected the same session. Recorded rather than silently rewritten
   because the two readings produce IDENTICAL CSS for every dark-OS visitor, so nothing would have surfaced the
   error until a light-OS visitor arrived and got a site that ignored their setting.

   🔑 EVERY COLOUR IS DEFINED EXACTLY ONCE, IN THE `--p-{dark,light}-*` PALETTES BELOW, AND NOTHING READS THEM
   DIRECTLY. The four condition blocks that follow only SELECT a palette by aliasing. That is what makes this
   "cannot drift" rather than "currently consistent": a colour and its `-rgb` triplet are declared side by side in
   one place, so the pairing that broke this file before (a hex moving while its triplet stayed) is now
   structurally impossible -- there is no second copy to forget. A mistake in an alias points at a DEFINED token
   and shows up as a missing value; a mistake in a duplicated hex is invisible.

   🎨 BRAND IS TWO VALUES AND THAT IS NOT A COMPROMISE -- it is what this cone's two stylesheets were each half
   expressing. A SURFACE brand must be deep enough to carry white ink on a button; a TEXT brand must be light
   enough to read as a link against the surface. On dark those are different colours (#5b3e78 / #b79bd6); on light
   one value does both (#472c54). Wiring links and buttons to a single token is why they kept disagreeing about
   what "the brand purple" is.

   🧭 AND IT ENDS THE TWO-PALETTE CASCADE CONFLICT AT THE ROOT. `ziltch-terminal.css` (Ziltch.Gateway) declares the
   DARK terminal palette at `:root` document-wide and loads AFTER this file, so it won every name it declared and
   lost every one it did not -- composing a half-dark, half-light third palette (near-white body text on white).
   Dark-first makes the two AGREE by default. The `--ztc-*` bridge stays anyway: agreement is not a guarantee, and
   on a light-OS visitor they legitimately differ, which is exactly when the private mirror earns its keep. */
:root {
    color-scheme: dark light;

    /* ── THE DARK PALETTE — the base identity. Declared once; pairs adjacent. ─────────────────────────────────── */
    --p-dark-brand: #5b3e78;            --p-dark-brand-rgb: 91, 62, 120;
    --p-dark-brand-deep: #472c54;
    --p-dark-brand-bright: #7a57a0;
    --p-dark-brand-text: #b79bd6;       --p-dark-brand-text-rgb: 183, 155, 214;
    --p-dark-brand-link-hover: #d3c0e8;
    --p-dark-brand-tint: #1a2036;
    --p-dark-gold: #e9c46a;             --p-dark-gold-rgb: 233, 196, 106;
    --p-dark-ink: #dbe4f5;              --p-dark-ink-rgb: 219, 228, 245;
    --p-dark-ink-dim: #7e8aa3;
    --p-dark-line: #24314a;
    --p-dark-surface: #0b1020;          --p-dark-surface-rgb: 11, 16, 32;
    --p-dark-surface-2: #121a2e;

    /* ── THE LIGHT PALETTE — the system-preference override. ──────────────────────────────────────────────────── */
    --p-light-brand: #472c54;           --p-light-brand-rgb: 71, 44, 84;
    --p-light-brand-deep: #472c54;
    --p-light-brand-bright: #6b4a8f;
    --p-light-brand-text: #472c54;      --p-light-brand-text-rgb: 71, 44, 84;
    --p-light-brand-link-hover: #6b4a8f;
    --p-light-brand-tint: #f3eef7;
    --p-light-gold: #c79a3a;            --p-light-gold-rgb: 199, 154, 58;
    --p-light-ink: #20232e;             --p-light-ink-rgb: 32, 35, 46;
    --p-light-ink-dim: #5b6070;
    --p-light-line: #e6e6ec;
    --p-light-surface: #ffffff;         --p-light-surface-rgb: 255, 255, 255;
    --p-light-surface-2: #f6f5f9;

    /* scheme-independent */
    --zt-brand-ink: #ffffff;      /* white sits on the brand SURFACE in both schemes -- both are deep purples */
    --zt-radius: 8px;
    --zt-font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

    /* ── ACTIVATION 1 of 4: DARK, the base. ───────────────────────────────────────────────────────────────────── */
    --zt-brand: var(--p-dark-brand);
    --zt-brand-rgb: var(--p-dark-brand-rgb);
    --zt-brand-deep: var(--p-dark-brand-deep);
    --zt-brand-bright: var(--p-dark-brand-bright);
    --zt-brand-text: var(--p-dark-brand-text);
    --zt-brand-tint: var(--p-dark-brand-tint);
    --zt-gold: var(--p-dark-gold);
    --zt-gold-rgb: var(--p-dark-gold-rgb);
    --zt-ink: var(--p-dark-ink);
    --zt-ink-rgb: var(--p-dark-ink-rgb);
    --zt-ink-dim: var(--p-dark-ink-dim);
    --zt-line: var(--p-dark-line);
    --zt-surface: var(--p-dark-surface);
    --zt-surface-rgb: var(--p-dark-surface-rgb);
    --zt-surface-2: var(--p-dark-surface-2);
    --ziltch-accent: var(--p-dark-brand-text);

    --ztc-brand: var(--p-dark-brand);
    --ztc-brand-rgb: var(--p-dark-brand-rgb);
    --ztc-brand-bright: var(--p-dark-brand-bright);
    --ztc-link: var(--p-dark-brand-text);
    --ztc-link-rgb: var(--p-dark-brand-text-rgb);
    --ztc-link-hover: var(--p-dark-brand-link-hover);
    --ztc-ink: var(--p-dark-ink);
    --ztc-ink-rgb: var(--p-dark-ink-rgb);
    --ztc-ink-dim: var(--p-dark-ink-dim);
    --ztc-surface: var(--p-dark-surface);
    --ztc-surface-rgb: var(--p-dark-surface-rgb);
    --ztc-surface-2: var(--p-dark-surface-2);
    --ztc-line: var(--p-dark-line);

    /* The emergent-surface palette — UniverseSurfaceIsland + RenderFloor read these. Aliased to the chrome tokens,
       so the surface IS the chrome's palette rather than a second one that has to be kept in step. */
    --zt-bg: var(--zt-surface);
    --zt-fg: var(--zt-ink);
    --zt-muted: var(--zt-ink-dim);
    --zt-accent: var(--zt-gold);
    --zt-accent-2: var(--zt-brand-text);
    --zt-grid: var(--zt-line);
    --zt-panel: var(--zt-surface-2);
}

/* ── ACTIVATION 2 of 4: LIGHT when the SYSTEM asks — unless the page explicitly asked for dark. ───────────────────
   The `:not([data-bs-theme="dark"])` is what lets an explicit dark toggle survive a light OS. It also equalises
   specificity with the explicit blocks below (`:not()` takes its argument's specificity), so those must come AFTER
   this in source order to win — they do. */
@media (prefers-color-scheme: light) {
    :root:not([data-bs-theme="dark"]) {
        color-scheme: light;
        --zt-brand: var(--p-light-brand);
        --zt-brand-rgb: var(--p-light-brand-rgb);
        --zt-brand-deep: var(--p-light-brand-deep);
        --zt-brand-bright: var(--p-light-brand-bright);
        --zt-brand-text: var(--p-light-brand-text);
        --zt-brand-tint: var(--p-light-brand-tint);
        --zt-gold: var(--p-light-gold);
        --zt-gold-rgb: var(--p-light-gold-rgb);
        --zt-ink: var(--p-light-ink);
        --zt-ink-rgb: var(--p-light-ink-rgb);
        --zt-ink-dim: var(--p-light-ink-dim);
        --zt-line: var(--p-light-line);
        --zt-surface: var(--p-light-surface);
        --zt-surface-rgb: var(--p-light-surface-rgb);
        --zt-surface-2: var(--p-light-surface-2);
        --ziltch-accent: var(--p-light-brand-text);

        --ztc-brand: var(--p-light-brand);
        --ztc-brand-rgb: var(--p-light-brand-rgb);
        --ztc-brand-bright: var(--p-light-brand-bright);
        --ztc-link: var(--p-light-brand-text);
        --ztc-link-rgb: var(--p-light-brand-text-rgb);
        --ztc-link-hover: var(--p-light-brand-link-hover);
        --ztc-ink: var(--p-light-ink);
        --ztc-ink-rgb: var(--p-light-ink-rgb);
        --ztc-ink-dim: var(--p-light-ink-dim);
        --ztc-surface: var(--p-light-surface);
        --ztc-surface-rgb: var(--p-light-surface-rgb);
        --ztc-surface-2: var(--p-light-surface-2);
        --ztc-line: var(--p-light-line);
    }
}

/* ── ACTIVATION 3 and 4 of 4: the EXPLICIT toggles (TheAdmin ships one). Last in source order deliberately. ────── */
:root[data-bs-theme="light"] {
    color-scheme: light;
    --zt-brand: var(--p-light-brand);
    --zt-brand-rgb: var(--p-light-brand-rgb);
    --zt-brand-deep: var(--p-light-brand-deep);
    --zt-brand-bright: var(--p-light-brand-bright);
    --zt-brand-text: var(--p-light-brand-text);
    --zt-brand-tint: var(--p-light-brand-tint);
    --zt-gold: var(--p-light-gold);
    --zt-gold-rgb: var(--p-light-gold-rgb);
    --zt-ink: var(--p-light-ink);
    --zt-ink-rgb: var(--p-light-ink-rgb);
    --zt-ink-dim: var(--p-light-ink-dim);
    --zt-line: var(--p-light-line);
    --zt-surface: var(--p-light-surface);
    --zt-surface-rgb: var(--p-light-surface-rgb);
    --zt-surface-2: var(--p-light-surface-2);
    --ziltch-accent: var(--p-light-brand-text);

    --ztc-brand: var(--p-light-brand);
    --ztc-brand-rgb: var(--p-light-brand-rgb);
    --ztc-brand-bright: var(--p-light-brand-bright);
    --ztc-link: var(--p-light-brand-text);
    --ztc-link-rgb: var(--p-light-brand-text-rgb);
    --ztc-link-hover: var(--p-light-brand-link-hover);
    --ztc-ink: var(--p-light-ink);
    --ztc-ink-rgb: var(--p-light-ink-rgb);
    --ztc-ink-dim: var(--p-light-ink-dim);
    --ztc-surface: var(--p-light-surface);
    --ztc-surface-rgb: var(--p-light-surface-rgb);
    --ztc-surface-2: var(--p-light-surface-2);
    --ztc-line: var(--p-light-line);
}

:root[data-bs-theme="dark"] {
    color-scheme: dark;
    --zt-brand: var(--p-dark-brand);
    --zt-brand-rgb: var(--p-dark-brand-rgb);
    --zt-brand-deep: var(--p-dark-brand-deep);
    --zt-brand-bright: var(--p-dark-brand-bright);
    --zt-brand-text: var(--p-dark-brand-text);
    --zt-brand-tint: var(--p-dark-brand-tint);
    --zt-gold: var(--p-dark-gold);
    --zt-gold-rgb: var(--p-dark-gold-rgb);
    --zt-ink: var(--p-dark-ink);
    --zt-ink-rgb: var(--p-dark-ink-rgb);
    --zt-ink-dim: var(--p-dark-ink-dim);
    --zt-line: var(--p-dark-line);
    --zt-surface: var(--p-dark-surface);
    --zt-surface-rgb: var(--p-dark-surface-rgb);
    --zt-surface-2: var(--p-dark-surface-2);
    --ziltch-accent: var(--p-dark-brand-text);

    --ztc-brand: var(--p-dark-brand);
    --ztc-brand-rgb: var(--p-dark-brand-rgb);
    --ztc-brand-bright: var(--p-dark-brand-bright);
    --ztc-link: var(--p-dark-brand-text);
    --ztc-link-rgb: var(--p-dark-brand-text-rgb);
    --ztc-link-hover: var(--p-dark-brand-link-hover);
    --ztc-ink: var(--p-dark-ink);
    --ztc-ink-rgb: var(--p-dark-ink-rgb);
    --ztc-ink-dim: var(--p-dark-ink-dim);
    --ztc-surface: var(--p-dark-surface);
    --ztc-surface-rgb: var(--p-dark-surface-rgb);
    --ztc-surface-2: var(--p-dark-surface-2);
    --ztc-line: var(--p-dark-line);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
   THE FOUNDATION — scale tokens, and the BOOTSTRAP BRIDGE that makes all of this reach code we do not own.

   Erin 2026-08-07: "I want the ziltch site and admin to ultimately use the same style/layout/ux elements... heavily
   customize in a way that cannot drift" + "Blazor first" + "pretty/elegant" + "we need a foundation on which we can
   grow that."

   🔑 WHY THE BRIDGE IS THE WHOLE POINT AND THE ONE THING THAT COULD NOT BE DONE WITH COMPONENTS: a Blazor component
   can only style a surface WE author. The admin's forms, tables, editors, modals and pagers are OrchardCore's markup
   authored against Bootstrap 5 — we are never going to rewrite them, and we do not have to. TheAdmin IS Bootstrap 5,
   Bootstrap 5 reads CSS custom properties, so re-pointing --bs-* at the --zt-* palette restyles OC's ENTIRE admin
   from this file, with zero markup touched. That is what turns "two surfaces that look different" into one body.

   ⚠️ AND IT IS WHY TOKENS COME BEFORE COMPONENTS. Components reach only what we write; tokens reach everything —
   OC's admin, our components, Bootstrap's defaults, and anything added later. Elegance lives almost entirely in
   palette / type scale / spacing / radius rather than in markup, so this is also the cheapest surface to make good.

   ⚠️ THE -rgb TRIPLETS ARE NOT OPTIONAL. Bootstrap composes alpha colours as rgba(var(--bs-primary-rgb), .5); if the
   hex moves and the triplet does not, every translucent surface silently keeps the OLD hue and nothing errors. They
   are declared beside their hex for exactly that reason — the pair must move together or not at all.

   ⚠️ THIS PARAGRAPH USED TO SAY "DARK MODE IS TWO MECHANISMS AND BOTH ARE HANDLED" — OS preference and
   TheAdmin's data-bs-theme toggle. AFTER THE DARK-FIRST FLIP THERE IS ONE MECHANISM AND IT RUNS THE OTHER WAY:
   dark is unconditional at :root, and the only switch is an explicit opt-in to LIGHT. The OS is not consulted.
   Kept as a correction rather than deleted because the old text prescribed wiring the bridge to a media query,
   which would now be a second declaration of the default — the exact drift surface this file keeps removing.
   ═════════════════════════════════════════════════════════════════════════════════════════════════════════════════ */
:root {
    /* TYPE SCALE — a ratio, not a pile of one-off sizes. 1.200 (minor third): dense enough for an operator surface
       without the jumps reading as decorative. Every size below derives from --zt-fs-base so re-scaling the product
       is ONE number. */
    /* ⚠️ THE SIZES BELOW NOW ACTUALLY DERIVE FROM --zt-fs-base. Until 2026-08-29 they were hardcoded rem literals
       (0.694rem, 0.833rem, ...) while this comment claimed a derivation, so the "re-scaling is ONE number" knob was
       decorative: --zt-fs-base was referenced by nothing but --bs-body-font-size. calc() makes the claim true. The
       computed values are byte-identical at base 1rem, so this changed no pixel — it reconnected the knob.

       ⚠️ AND THE KNOB IS STILL OVERRIDDEN DOWNSTREAM, which is a RULING someone owes, not a bug to patch here:
       ziltch-terminal.css redefines --zt-fs-xs/sm/md/lg as PX at plain :root and Layout.cshtml loads it LAST
       (126 chrome, 127 ziltch, 128 terminal), so at equal specificity the px scale wins DOCUMENT-WIDE — front door
       and admin chrome included, not just the gateway app. Both files are documented as "THE design system"; link
       order picks the winner. Consequences: (1) this knob moves nothing for xs-lg; (2) terminal.css:216's
       `font-size:17.5px` on html.zt-app-html — written to answer Erin's "everything is tiny" — cannot scale its
       own px tokens, defeating that fix; (3) the scale is mixed-unit at the seam, lg=19px then xl=1.44rem. DO NOT
       "fix" this by deleting one side: which file owns the type scale is a design ruling (@echo governs UI shape).
       ─────────────────────────────────────────────────────────────────────────────────────────────────────────── */
    --zt-fs-base: 1rem;
    --zt-fs-xs: calc(var(--zt-fs-base) * 0.694);
    --zt-fs-sm: calc(var(--zt-fs-base) * 0.833);
    --zt-fs-md: calc(var(--zt-fs-base) * 1);
    --zt-fs-lg: calc(var(--zt-fs-base) * 1.2);
    --zt-fs-xl: calc(var(--zt-fs-base) * 1.44);
    --zt-fs-2xl: calc(var(--zt-fs-base) * 1.728);
    --zt-fs-3xl: calc(var(--zt-fs-base) * 2.074);
    --zt-lh-tight: 1.25;
    --zt-lh-base: 1.55;

    /* SPACING RHYTHM — a 4px base step. Named by step rather than by use, so a component cannot invent a
       "just slightly different" gap without it being visible as a raw value in review. */
    --zt-space-1: 0.25rem;
    --zt-space-2: 0.5rem;
    --zt-space-3: 0.75rem;
    --zt-space-4: 1rem;
    --zt-space-5: 1.5rem;
    --zt-space-6: 2rem;
    --zt-space-8: 3rem;

    /* RADIUS + ELEVATION — --zt-radius already existed and stays the base; the rest derive from it. */
    --zt-radius-sm: calc(var(--zt-radius) / 2);
    --zt-radius-lg: calc(var(--zt-radius) * 1.5);
    --zt-radius-pill: 999px;
    --zt-shadow-1: 0 1px 2px rgb(0 0 0 / 6%), 0 1px 3px rgb(0 0 0 / 8%);
    --zt-shadow-2: 0 4px 12px rgb(0 0 0 / 10%);
    --zt-shadow-3: 0 12px 32px rgb(0 0 0 / 14%);

    /* rgb triplets for the DARK (default) palette — see the -rgb warning above. The light opt-in re-points them. */
    --zt-brand-rgb: 91, 62, 120;
    --zt-gold-rgb: 233, 196, 106;
    --zt-ink-rgb: 219, 228, 245;
    --zt-surface-rgb: 11, 16, 32;
}

:root[data-bs-theme="light"] {
    --zt-brand-rgb: 71, 44, 84;
    --zt-gold-rgb: 199, 154, 58;
    --zt-ink-rgb: 32, 35, 46;
    --zt-surface-rgb: 255, 255, 255;
}

/* ── THE BRIDGE: Bootstrap (and therefore ALL of OrchardCore's admin) reads Ziltch's palette ─────────────────────── */
/* 🔴 `:root:root` IS DELIBERATE AND IS THE WHOLE REASON THIS WORKS. Measured on the live body: the first version used
   a plain `:root` and `--bs-primary` computed to #0d6efd — BOOTSTRAP'S DEFAULT BLUE. The bridge was inert.

   Bootstrap's stylesheet is loaded AFTER this file and declares its palette on `:root` too. Equal specificity means
   SOURCE ORDER decides, and Bootstrap is later, so every --bs-* here was being overwritten. Repeating the selector
   (`:root:root`) doubles specificity, which order cannot beat.

   ⚠️ AND THE FAILURE WAS SILENT AND FLATTERING: the SCALE tokens (--zt-fs-*, --zt-space-*, --zt-shadow-*) worked
   fine, because Bootstrap does not define those names — so a spot-check of "did my tokens land" returned YES while
   the half that reaches OrchardCore's admin was doing nothing. Check the BRIDGED name (--bs-primary), never a token
   only we declare. */
/* 🔴 THE BRIDGE READS `--ztc-*`, NOT `--zt-*`, AND THAT IS THE WHOLE POINT — MEASURED LIVE ON /Login 2026-08-07.
   `ziltch-terminal.css` (Ziltch.Gateway) declares the DARK terminal palette at plain `:root` and is loaded
   DOCUMENT-WIDE on every page including this light OC chrome. It loads AFTER this file, so at equal specificity it
   WINS for the names it declares and LOSES for the ones it does not:

       --zt-brand   #5b3e78 (terminal)   vs  --zt-brand-rgb   71,44,84  = #472c54 (here)     DISAGREE
       --zt-gold    #e9c46a (terminal)   vs  --zt-gold-rgb   199,154,58 = #c79a3a (here)     DISAGREE
       --zt-ink     #e8ecf8 (terminal)   vs  --zt-ink-rgb     32,35,46  = #20232e (here)     DISAGREE
       --zt-surface #ffffff (here, terminal declares none)                                   agree

   ⇒ the bridge was composing a HALF-DARK, HALF-LIGHT palette: `--bs-body-color: #e8ecf8` (near-white) against
   `--bs-body-bg: #ffffff` (white). Any Bootstrap surface that paints its own body background — a card, modal,
   dropdown, offcanvas, `.bg-body` — drew NEAR-WHITE TEXT ON WHITE. And every alpha composition
   (`rgba(var(--bs-primary-rgb), …)`: focus rings, `.text-bg-primary`, link hovers) painted a DIFFERENT purple
   than the solid beside it.

   ⚠️ NEITHER SHEET IS WRONG ON ITS OWN AND THAT IS WHY THIS SURVIVED: each declares a coherent palette, and the
   incoherence exists only in the CASCADE between two assemblies. Reading either file end to end shows nothing.
   Only the computed value on a live page does — which is the same trap this file already documents one block down
   for `--bs-primary` (`check the BRIDGED name, never a token only we declare`), one level up: check the COMPUTED
   PAIR, never a declaration.

   `--ztc-*` is declared HERE AND NOWHERE ELSE (grep it), so a colour and its triplet cannot desynchronize no
   matter what any other assembly puts on `:root`. The `--zt-*` tokens keep their existing meaning for the
   terminal's own scoped surfaces; this only stops them silently deciding the OC chrome's palette. */
:root:root {
    /* NO --ztc-* VALUES HERE. They are SELECTED by the four activation blocks at the top of this file; this block
       only MAPS them onto Bootstrap's names. Having both was a live bug for one commit: this selector is
       `:root:root` (0,2,0) and the follow-the-system light block is `:root:not([data-bs-theme="dark"])` (also
       0,2,0, since :not() takes its argument's specificity) -- equal, so SOURCE ORDER decided, and this block is
       later. The light activation set `color-scheme: light` and then had its palette overwritten from here.
       ⚠️ THE TELL WAS AN INTERNAL CONTRADICTION, NOT A WRONG COLOUR: computed `color-scheme: light` against a
       #0b1020 surface. Both halves came from blocks that were individually correct, and only reading them
       TOGETHER showed one had been half-overridden -- the same cascade-between-two-declarations shape this file
       already documents for the two stylesheets, arriving inside a single file. */

    --bs-primary: var(--ztc-brand);
    --bs-primary-rgb: var(--ztc-brand-rgb);
    /* LINK reads the TEXT brand, not the surface brand — see the two-values note at the top of this file. Wiring
       both to one token is what made links illegible on dark or buttons washed out on light, depending on which
       value won. */
    --bs-link-color: var(--ztc-link);
    --bs-link-color-rgb: var(--ztc-link-rgb);
    --bs-link-hover-color: var(--ztc-link-hover);
    --bs-body-bg: var(--ztc-surface);
    --bs-body-bg-rgb: var(--ztc-surface-rgb);
    --bs-body-color: var(--ztc-ink);
    --bs-body-color-rgb: var(--ztc-ink-rgb);
    --bs-secondary-color: var(--ztc-ink-dim);
    --bs-secondary-bg: var(--ztc-surface-2);
    --bs-tertiary-bg: var(--ztc-surface-2);
    --bs-border-color: var(--ztc-line);
    --bs-border-radius: var(--zt-radius);
    --bs-border-radius-sm: var(--zt-radius-sm);
    --bs-border-radius-lg: var(--zt-radius-lg);
    --bs-border-radius-pill: var(--zt-radius-pill);
    --bs-font-sans-serif: var(--zt-font-ui);
    --bs-body-font-size: var(--zt-fs-base);
    --bs-body-line-height: var(--zt-lh-base);
    --bs-box-shadow: var(--zt-shadow-2);
    --bs-box-shadow-sm: var(--zt-shadow-1);
    --bs-box-shadow-lg: var(--zt-shadow-3);
}

/* The OS-dark branch above already flipped --zt-*; this re-points the rgb triplets that cannot follow a var()
   indirection, so Bootstrap's alpha surfaces track the dark palette instead of silently keeping the light hue. */
/* NO LIGHT BRIDGE OVERRIDE EITHER, FOR THE SAME REASON: the activation blocks already select the light palette
   into --ztc-*, so a second copy here could only drift from them. */

/* NO EXPLICIT-DARK BLOCK, DELIBERATELY. Dark is the ROOT default now, so `data-bs-theme="dark"` needs no
   declaration of its own -- it already gets exactly the root palette. The block that used to sit here re-declared
   the whole dark set, and after the dark-first flip it still carried the PRE-FLIP values (`--zt-brand: #b79bd6`
   as a SURFACE colour), so TheAdmin's dark toggle would have silently reintroduced the one-token-two-jobs bug
   this flip exists to end. A second declaration of a default is drift surface with no upside. */

/* ── THE BUTTON BRIDGE — the half `--bs-primary` CANNOT reach ────────────────────────────────────────────────────── */
/* 🔴 RE-POINTING `--bs-primary` DOES NOT RESTYLE A SINGLE BUTTON, AND THE PALETTE BRIDGE ABOVE LOOKS LIKE IT SHOULD.
   Bootstrap 5.3 compiles each button variant to its own LOCAL custom properties with LITERAL colours baked in —
   `.btn-primary { --bs-btn-bg: #0d6efd; --bs-btn-hover-bg: #0b5ed7; … }` — and `.btn` then reads only those locals.
   `--bs-primary` is never consulted at render time, so a correct, live, verified palette bridge leaves every
   primary button Bootstrap blue.

   ⚠️ THE SAME SILENT-AND-FLATTERING FAILURE AS THE `:root:root` ONE ABOVE, one layer in: checking `--bs-primary`
   computes to the brand returns YES, and the button beside it is still blue. The bridged NAME was right and the
   name that DRAWS was a different one. Check the rendered control, not the variable it ought to be reading.

   DOUBLED CLASS (`.btn-primary.btn-primary`) for the same reason `:root:root` is doubled — Bootstrap declares these
   at single-class specificity and loads later, so source order wins unless specificity beats it. */
.btn-primary.btn-primary {
    --bs-btn-color: #ffffff;
    --bs-btn-bg: var(--ztc-brand);
    --bs-btn-border-color: var(--ztc-brand);
    --bs-btn-hover-color: #ffffff;
    --bs-btn-hover-bg: var(--ztc-brand-bright);
    --bs-btn-hover-border-color: var(--ztc-brand-bright);
    --bs-btn-active-color: #ffffff;
    --bs-btn-active-bg: var(--ztc-brand-bright);
    --bs-btn-active-border-color: var(--ztc-brand-bright);
    --bs-btn-disabled-color: #ffffff;
    --bs-btn-disabled-bg: var(--ztc-brand);
    --bs-btn-disabled-border-color: var(--ztc-brand);
    --bs-btn-focus-shadow-rgb: var(--ztc-brand-rgb);
}

.btn-outline-primary.btn-outline-primary {
    --bs-btn-color: var(--ztc-brand);
    --bs-btn-border-color: var(--ztc-brand);
    --bs-btn-hover-color: #ffffff;
    --bs-btn-hover-bg: var(--ztc-brand);
    --bs-btn-hover-border-color: var(--ztc-brand);
    --bs-btn-active-color: #ffffff;
    --bs-btn-active-bg: var(--ztc-brand);
    --bs-btn-active-border-color: var(--ztc-brand);
    --bs-btn-disabled-color: var(--ztc-brand);
    --bs-btn-disabled-border-color: var(--ztc-brand);
    --bs-btn-focus-shadow-rgb: var(--ztc-brand-rgb);
}

/* NO PER-SCHEME BUTTON INK OVERRIDE, AND THAT IS A RESULT OF THE TWO-VALUE BRAND RATHER THAN AN OMISSION.
   `--ztc-brand` is a SURFACE colour in both schemes -- #5b3e78 dark, #472c54 light -- and both are deep enough to
   carry white. The overrides that used to sit here set the ink to `--ztc-surface` (i.e. DARK ink) because the old
   dark brand was the LIFTED #b79bd6, which white could not sit on. Splitting surface-brand from text-brand
   removed the need for the override entirely; leaving it would now paint dark ink on a deep purple button. */

/* ── header (the shared ZiltchChromeView brand bar) ──────────────────────────────────────────────────────────────── */
/* THE HEADER'S HEIGHT IS A TOKEN, NOT AN EMERGENT NUMBER — and it is declared here because a SECOND surface has to
   agree with it. The admin Layout offsets TheAdmin's `position:fixed` sidebar below this bar, so the offset and the
   bar's real height must match; on 2026-07-29 they did not. The offset read `var(--zt-header-h, 3.5rem)` = 56px
   against a bar that measured 59px, so the sidebar sat 3px UNDER the header. The fallback was a guess and the token
   was never defined, which means the guess was always what ran.
   Defining it here and driving `min-height` FROM it makes the two agree BY CONSTRUCTION: whoever restyles the bar
   changes one number and the sidebar follows, instead of the sidebar silently overlapping. In rem, so both move
   together if the root font-size changes. */
:root { --zt-header-h: 3.7rem; }

/* ✒️ THE TYPE SCALE — SIX STEPS, AND THE PRODUCT CURRENTLY HAS 48.
   Measured 2026-09-12 across the served tree: 48 distinct font sizes in 934 literal declarations, thirteen of them
   between 0.6rem and 0.875rem. 0.8 against 0.8125 is a 0.2 PIXEL difference and 0.8 against 0.82 is 0.32px — nobody
   can see either, so those values encode no design intent. They are different authors typing different numbers into
   the same visual slot, which is what a design system looks like when it is a folder rather than a decision.

   THE FLOOR IS 11px AND IT IS A CRAFT BAR, NOT A CONFORMANCE ONE — say so rather than borrowing authority: WCAG sets
   NO minimum font size (1.4.4 asks only that text scale to 200%, which 8px text does). The reason it is worth a
   floor anyway is the inverse of conformance — no checker will ever flag a product that renders its own
   explanations at a size most people cannot read, and 241 declarations in this tree sit at or below 10px.

   Steps are spaced so ADJACENT ONES ARE TELLABLE APART (>=2px at the small end, where confusion actually happens).
   Sizes are on a 16px root; each is a clean pixel value so nothing lands on a half-pixel and blurs. */
:root {
    --zt-text-xs:   0.6875rem;  /* 11px — the FLOOR. Badges, micro-labels, timestamps. Nothing smaller ships. */
    --zt-text-sm:   0.8125rem;  /* 13px — secondary text: meta lines, captions, dense table cells. */
    --zt-text-base: 0.9375rem;  /* 15px — body. The default reading size for prose and controls. */
    --zt-text-lg:   1.125rem;   /* 18px — subheads, card titles, the emphasis step above body. */
    --zt-text-xl:   1.5rem;     /* 24px — section headings. */
    --zt-text-2xl:  2rem;       /* 32px — page titles. */
}
.ziltch-header { border-bottom: 1px solid var(--zt-grid); min-height: var(--zt-header-h);
    background: color-mix(in srgb, var(--zt-bg) 78%, transparent);
    backdrop-filter: blur(14px) saturate(1.3); -webkit-backdrop-filter: blur(14px) saturate(1.3);
    position: sticky; top: 0; z-index: 100; box-shadow: 0 1px 0 color-mix(in srgb, var(--zt-accent) 12%, transparent); }
.ziltch-header .navbar { padding-top: .55rem; padding-bottom: .55rem; }
.ziltch-header .navbar-brand { font-weight: 800; letter-spacing: .3px; color: var(--zt-fg);
    display: inline-flex; align-items: center; gap: .5rem; transition: color .14s; }
/* Bootstrap's default .navbar-brand:hover sets --bs-navbar-brand-hover-color (a dark stroke) — which reads BLACK on the
   dark chrome (Erin 2026-07-17). Override with an on-brand amber rollover, matching the app-nav hover below. */
.ziltch-header .navbar-brand:hover,
.ziltch-header .navbar-brand:focus { color: var(--zt-accent); }
.ziltch-logo { height: 26px; width: 26px; border-radius: 7px; vertical-align: middle;
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--zt-accent) 30%, transparent), 0 2px 10px color-mix(in srgb, var(--zt-accent) 22%, transparent); }

/* ── the account cluster (user label · admin/site switch · sign-out · signed-out CTAs) ────────────────────────────────
   TOKEN-DRIVEN ON PURPOSE. These controls used Bootstrap's `text-light` / `btn-outline-light` / `btn-light`, which are
   FIXED near-white (#f8f9fa) and silently assume the bar behind them is DARK. The site's front door IS dark, so that
   held there by accident — and the moment the SAME component rendered on the admin surface, where these tokens resolve
   LIGHT by default (--zt-surface: #fff), the entire cluster became white-on-white and VANISHED. Measured live before
   the fix: headerBg `srgb 1 1 1 / .78`, text `rgb(248,249,250)`.
   Reading --zt-fg / --zt-accent instead means the prefers-color-scheme block already flips them, so ONE definition is
   legible on BOTH surfaces in BOTH schemes rather than one surface being correct by luck. */
.ziltch-header .zt-chrome-user { color: var(--zt-muted); }
/* 👆 44px MINIMUM TOUCH TARGET (WCAG 2.5.5 / Apple HIG), MEASURED NOT ASSUMED. At 390x780 on the live
   anonymous front door, 2026-09-12, EVERY interactive element in this chrome was under it:
       Ziltch brand lockup   92 x 41    3px short on height
       Toggle navigation     48 x 40    4px short on height
       site nav "Home"       43 x 21    23px short (fixed separately in the theme's own layout)
   Three of three. Nothing here is dramatically wrong — 40 and 41 against 44 — which is exactly why it
   survived: a 4px miss looks fine and fails the same standard a 24px miss does. min-height rather than
   padding so the fix cannot be undone by a font-size change, and inline-flex + center so the existing
   label stays optically centred as the box grows. Width is handled by the rule below. */
.ziltch-header .zt-chrome-btn,
.ziltch-header .navbar-toggler,
.ziltch-header .navbar-brand { min-height: 44px; display: inline-flex; align-items: center; }
/* min-WIDTH as well, and the first pass forgot it: with min-height alone the theme toggle went 30x31 to
   30x44 — tall enough and still 14px too NARROW, because it is a single-glyph button and its width comes
   from one character. Caught by re-measuring after the fix rather than by assuming height was the only
   axis that was short. The brand needs no min-width (92px). */
.ziltch-header .zt-chrome-btn, .ziltch-header .navbar-toggler {
    min-width: 44px; justify-content: center; }
.ziltch-header .zt-chrome-btn { color: var(--zt-fg); border: 1px solid var(--zt-grid);
    background: transparent; font-weight: 600; letter-spacing: .01em;
    transition: color .14s, border-color .14s, background .14s; }
/* THE HOVER TINTS AND BORDERS IN ACCENT; THE INK STAYS READABLE. `color: var(--zt-accent)` on the 10%
   accent tint reads 2.38:1 IN LIGHT (#c79a3a on #f9f5eb) against the 4.5 of WCAG 1.4.3 — dark is fine at
   9.50:1, so this is the MIRROR of tonight's other contrast defects, which all failed in dark and passed in
   light. Either arm alone would have missed one of them. --zt-ink clears both (dark 12.42:1, light 14.38:1)
   and the affordance is untouched: the tinted ground and the accent border still change on hover, which is
   where an accent belongs.
   ⚠️ COMPUTED, NOT PROBED, AND THE ASSUMPTION IS STATED: this background is 10% accent over a header that is
   itself `color-mix(var(--zt-bg) 78%, transparent)` with a backdrop-filter, so its true ground is whatever
   is behind the sticky header. The number above assumes the ordinary case — the header over the plain page
   ground, which is every page but the front door. On the front door the header sits over the fixed hero, so
   the real ratio there is a RANGE over scroll position, not a value, and only pixel sampling settles it.
   The static probe correctly REFUSES to score this rule; it is here because the light arm fails even in the
   friendliest case, and a defect that fails its own best case needs no worst case. */
.ziltch-header .zt-chrome-btn:hover,
.ziltch-header .zt-chrome-btn:focus { color: var(--zt-ink);
    border-color: color-mix(in srgb, var(--zt-accent) 60%, transparent);
    background: color-mix(in srgb, var(--zt-accent) 10%, transparent); }
/* SaaS-app markets nav — the live trading/AI surfaces, inline in the header chrome (same set as the app shell). */
.ziltch-app-nav { flex-direction: row; gap: .15rem; margin-left: 1.25rem; }
.ziltch-app-nav .nav-link { font-size: .84rem; font-weight: 500; color: var(--zt-muted);
    letter-spacing: .01em; padding: .3rem .7rem; border-radius: var(--zt-radius);
    transition: background .14s, color .14s; }
.ziltch-app-nav .nav-link:hover { color: var(--zt-fg); background: color-mix(in srgb, var(--zt-accent) 12%, transparent); }
.ziltch-app-nav .nav-link.active { color: var(--zt-accent); }

/* Mobile collapse (below .navbar-expand-md) — the toggler folds the nav; on-brand focus ring, dark-mode aware icon. */
.ziltch-header .navbar-toggler { border-color: var(--zt-line); padding: .25rem .5rem; }
.ziltch-header .navbar-toggler:focus { box-shadow: 0 0 0 .2rem var(--zt-brand-tint); }
@media (prefers-color-scheme: dark) {
    /* Bootstrap's default toggler icon is a dark stroke — invert it so it reads on the dark chrome. */
    .ziltch-header .navbar-toggler-icon {
        background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(219,228,245,0.85)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
    }
}
@media (max-width: 767.98px) {
    .ziltch-header .navbar-collapse { padding-top: .5rem; }
    .ziltch-app-nav { flex-direction: column; gap: .1rem; margin-left: 0; margin-top: .35rem; }
    .ziltch-gateway-badge { margin-left: 0 !important; margin-top: .5rem; align-self: flex-start; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   STICKY FOOTER — the footer sits at the BOTTOM OF THE VIEWPORT when the page
   is short, instead of riding up under the header.

   Erin 2026-08-07: "I see header and footer squished together. should be footer
   cannot scroll upward past bottom of page."

   WHY IT LOOKED LIKE A SQUEEZE: nothing in this file set html/body/main/footer
   layout at all, so a short page (the site home is ~5.7 KB of markup) let the
   footer follow the content directly — header, one screen of nothing, footer,
   all stacked in the top few hundred pixels. It is not a spacing bug; there was
   no layout claiming the viewport height.

   THE MECHANISM: body becomes a flex COLUMN of at least one viewport tall, and
   the content region takes the slack (`flex: 1 0 auto`). `margin-top: auto` on
   the footer is deliberately kept as well — it is what pins the footer down if
   a page ever renders a content region this file does not know about, so the
   rule degrades to "footer at the bottom" rather than "footer under the header".

   ⚠️ THIS FILE IS THE SHARED CHROME AND THE RULE IS DELIBERATELY WRITTEN TO BE
   SHARED. Both theme Layouts load it, and the site and admin content regions
   have DIFFERENT class names, so all three are listed rather than styling one
   and letting the other drift — that split is exactly what this file exists to
   prevent. `min-height: 100dvh` handles mobile browser chrome; the `100vh`
   declaration before it is the fallback for engines without dvh. */
html { min-height: 100%; }

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
}

body > main,
.ziltch-site-main,
.ziltch-admin-shell {
    flex: 1 0 auto;
}

/* THE FOOTER'S HEIGHT IS A TOKEN FOR THE SAME REASON THE HEADER'S IS: A SECOND SURFACE HAS TO AGREE WITH IT.
   Erin 2026-08-07: "the unauth bg image is cut off at the bottom of the page by the footer at full screen... any way
   to align the bottom with the top of the footer bar?" The front-door backdrop is `position: fixed; inset: 0`, so it
   spans the WHOLE viewport and the footer bar sits on top of its bottom edge — the image does not end at the footer,
   it continues behind it. Ending it at the footer means insetting its bottom by exactly the footer's height, and
   that number has to come from somewhere.

   🔑 3.5rem is a MEASUREMENT ROUNDED UP, NOT A GUESS — the footer rendered 55px (3.44rem) live. Rounding UP is what
   makes the token AUTHORITATIVE rather than descriptive: min-height now governs, so the bar is exactly --zt-footer-h
   and the backdrop's inset cannot disagree with it. Had I rounded down, content would set the height and the token
   would silently be wrong by the difference.

   ⚠️ THE HEADER LEARNED THIS THE EXPENSIVE WAY AND THE LESSON IS ONE BLOCK UP: its offset read
   `var(--zt-header-h, 3.5rem)` against a bar that measured 59px, because the token was never defined and the
   FALLBACK was always what ran. A fallback for a token nobody declares is just a guess with extra syntax.
   Residual, stated: if footer content ever exceeds the token (a wrap on a very narrow viewport), the bar grows and
   the backdrop stops short by the difference. min-height cannot prevent that; only a fixed height could, and that
   would clip the content instead. Growing is the safer failure. */
:root { --zt-footer-h: 3.5rem; }

.ziltch-footer {
    flex-shrink: 0;
    margin-top: auto;
    min-height: var(--zt-footer-h);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════
   ZILTCH ADMIN SHELL — the operator surface, styled on the SAME tokens as the site.

   Erin 2026-08-07: "admin left nav is not styled correctly — I assume these are the missing admin theme styles —
   ziltch styling needs to be completed."

   🔑 THE CAUSE WAS NOT MISSING STYLESHEETS, IT WAS MISMATCHED SELECTORS. TheAdmin's CSS loads (verified live) but
   targets ITS OWN structure — `#ta-left-sidebar`, `.ta-navbar-top`, `.ta-content`. The combined theme renders
   `.ziltch-admin-shell` / `.ziltch-admin-nav` / `.ziltch-admin-main`, so none of TheAdmin's rules could ever apply.
   Borrowing a stylesheet gets you its VARIABLES; it does not get you its LAYOUT unless you also adopt its class
   names — and adopting them would make the admin permanently TheAdmin-shaped, which is the opposite of one body.

   ⇒ so the admin gets ZILTCH styling, built entirely from the token layer: same palette, same type scale, same
   spacing rhythm, same radius as the site. That is what "style them together as one body" means concretely — not
   two stylesheets that agree, ONE set of tokens that both surfaces spend.
   ═════════════════════════════════════════════════════════════════════════════════════════════════════════════════ */
/* 🔴 NO PAGE MARGIN AROUND THE NAV (Erin 2026-08-07: "should not have page margins around it"). The shell used to
   pad all FOUR sides, which inset the rail from the viewport edge and left a strip of page showing down its left —
   so the sidebar read as a floating card parked near the edge rather than as part of the window's frame. Padding is
   now the CONTENT's, not the SHELL's: the rail runs flush to the edge and `.ziltch-admin-main` carries its own
   breathing room. `gap` goes too, for the same reason — the separation between rail and content is the rail's
   BORDER, not empty page. */
.ziltch-admin-shell {
    display: grid;
    grid-template-columns: minmax(13rem, 16rem) 1fr;
    align-items: stretch;   /* was `start` — with no padding the rail should reach the full height, not float */
    gap: 0;
    padding: 0;
    max-width: 100%;
}

/* A FLUSH RAIL, NOT A CARD. The radius, drop shadow and all-round border were what MADE it look like a card sitting
   on a page — removing the shell's padding alone would have left a rounded card jammed into the corner, which is
   worse than the inset version. A rail keeps ONE border (the edge it shares with the content) and drops the rest. */
.ziltch-admin-nav {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    gap: var(--zt-space-1);
    padding: var(--zt-space-3) var(--zt-space-2);
    background: var(--zt-surface-2);
    border: 0;
    border-right: 1px solid var(--zt-line);
    border-radius: 0;
    box-shadow: none;
    /* Full viewport height minus the chrome bar above it, so the rail's surface reaches the bottom of the window
       instead of ending where its items do. --zt-header-h is the shared token the header drives its own height
       from, so these cannot disagree. */
    min-height: calc(100dvh - var(--zt-header-h));
    max-height: calc(100dvh - var(--zt-header-h));
    overflow-y: auto;
}

/* OC's Menu shape renders <ul class="menu list-group"> — a Bootstrap LIST-GROUP, which is a bordered card stack and
   reads as a form control rather than navigation. Neutralised here rather than by fighting Bootstrap per-item: the
   list-group look is what made the site nav render as "a list at the top that doesn't make sense". */
.ziltch-admin-nav ul,
.ziltch-admin-nav .menu {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border: 0;
    background: none;
}

.ziltch-admin-nav li,
.ziltch-admin-nav .list-group-item {
    border: 0;
    background: none;
    padding: 0;
}

.ziltch-admin-nav a {
    display: block;
    padding: var(--zt-space-2) var(--zt-space-3);
    border-radius: var(--zt-radius-sm);
    color: var(--zt-ink);
    text-decoration: none;
    font-size: var(--zt-fs-sm);
    line-height: var(--zt-lh-tight);
    /* Long operator labels must not wrap into two lines and break the rhythm. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background-color .12s ease, color .12s ease;
}

/* THE HOVER TINTS THE GROUND AND LEAVES THE INK READABLE. --zt-brand is a FILL colour; used as INK on the
   brand TINT it reads 1.84:1 in dark against the 4.5 WCAG 1.4.3 requires (#5b3e78 on #1a2036). Light was
   fine at 10.47:1, which is why it survived — the same single-arm blindness that hid the focus ring.
   --zt-brand-bright does NOT rescue it (dark 2.84:1, still failing); only an ink does: --zt-ink reads
   12.60:1 dark and 13.71:1 light. The accent is carried by the TINTED GROUND, which is where an accent
   belongs; the text's job is to be read. Measured 2026-09-12 by the static state-contrast probe, which
   exists because no DOM probe can hover. */
.ziltch-admin-nav a:hover,
.ziltch-admin-nav a:focus-visible {
    background: var(--zt-brand-tint);
    color: var(--zt-ink);
}

.ziltch-admin-nav a.active,
.ziltch-admin-nav a[aria-current="page"] {
    background: var(--zt-brand);
    color: var(--zt-brand-ink);
    font-weight: 600;
}

/* One indent at any depth (Erin 2026-11-02: "OC indented item styles are confusing"): a top group is a heading,
   its members sit one step in, and a deeper group is a caption over its members, never a further indent. */
.ziltch-admin-nav ul ul {
    margin-left: calc(var(--zt-space-3) + .55em);
    padding-left: var(--zt-space-2);
    border-left: 1px solid var(--zt-line);
}
.ziltch-admin-nav ul ul ul { margin-left: 0; padding-left: 0; border-left: 0; }
/* OC's rare fourth level (Settings > Search > SEO) keeps a rule so a sibling after it does not read as its member. */
.ziltch-admin-nav ul ul ul ul { padding-left: var(--zt-space-2); border-left: 1px solid var(--zt-line); }

/* Every top row keeps one icon column, so a heading with no icon lines up with one that has it. */
.ziltch-admin-nav .menu-admin > li > a {
    display: grid;
    grid-template-columns: 1.1em minmax(0, 1fr) auto;
    column-gap: var(--zt-space-2);
    align-items: center;
}
.ziltch-admin-nav .menu-admin > li > a > .icon { grid-column: 1; display: inline-flex; justify-content: center; opacity: .75; }
.ziltch-admin-nav .menu-admin > li > a > .title { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ziltch-admin-nav .menu-admin > li:has(> ul) > a { font-weight: 600; color: var(--zt-ink); }
.ziltch-admin-nav ul ul .icon { display: none; }

.ziltch-admin-nav ul ul a {
    font-size: var(--zt-fs-xs);
    color: var(--zt-ink-dim);
}
.ziltch-admin-nav ul ul a:hover,
.ziltch-admin-nav ul ul a:focus-visible,
.ziltch-admin-nav ul ul a.active,
.ziltch-admin-nav ul ul a[aria-current="page"] {
    color: var(--zt-ink);
}

/* A caption group ends on a rule, so the sibling after it does not read as its member (Placements under Content Definition). */
.ziltch-admin-nav ul ul li:has(> ul) {
    border-bottom: 1px solid var(--zt-line);
    margin-bottom: var(--zt-space-2);
    padding-bottom: var(--zt-space-1);
}

/* A group inside a group is a caption: always open, not a link, members at the same indent as its siblings. */
.ziltch-admin-nav ul ul li:has(> ul) > a,
.ziltch-admin-nav ul ul li:has(> ul) > a:hover {
    pointer-events: none;
    background: none;
    margin-top: var(--zt-space-2);
    padding-bottom: var(--zt-space-1);
    font-size: var(--zt-fs-xs);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--zt-ink-dim);
}

/* Top groups fold (ziltch-admin-nav.js). Folding waits for the script, so without it every member stays visible. */
.ziltch-admin-nav.zt-nav-ready .zt-group:not(.zt-open) > ul { display: none; }
.ziltch-admin-nav .zt-group > a { cursor: pointer; }
.ziltch-admin-nav .zt-group > a::after {
    grid-column: 3;
    content: "";
    width: .45em;
    height: .45em;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    opacity: .6;
    transition: transform .12s ease;
}
.ziltch-admin-nav .zt-group.zt-open > a::after { transform: rotate(45deg); }
.ziltch-admin-nav ul ul li > a::after,
.ziltch-admin-nav ul ul .zt-group > a::after { content: none; }

.ziltch-admin-main {
    min-width: 0;   /* grid children default to min-content; without this a wide table blows the column out */
    /* The shell no longer pads, so the CONTENT owns its margins — this is the other half of "no page margins
       around the nav": the rail goes flush and the reading column keeps its gutter. */
    padding: var(--zt-space-5);
    padding-bottom: var(--zt-space-8);
}

.ziltch-admin-messages:empty { display: none; }

/* The rail folds (ziltch-admin-nav.js): a column on a wide screen, a drawer over the page on a phone. */
.zt-admin-tools { display: flex; gap: var(--zt-space-1); margin: calc(-1 * var(--zt-space-3)) 0 var(--zt-space-2); }
.zt-admin-tool {
    display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem;
    border: 1px solid var(--zt-line); border-radius: 6px; background: var(--zt-surface-2); color: var(--zt-fg);
    font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.zt-admin-tool:hover, .zt-admin-tool:focus-visible { color: var(--zt-accent); border-color: var(--zt-accent); }
.zt-admin-tool[aria-pressed="true"] { color: var(--zt-accent); }
.zt-rail-backdrop { display: none; }
.ziltch-admin-shell.zt-rail-closed { grid-template-columns: 1fr; }
.ziltch-admin-shell.zt-rail-closed > .ziltch-admin-nav { display: none; }

@media (max-width: 60rem) {
    .ziltch-admin-shell { grid-template-columns: 1fr; }
    .ziltch-admin-nav {
        position: fixed; top: var(--zt-header-h); left: 0; bottom: 0; z-index: 1031;
        width: min(85vw, 20rem); min-height: 0; max-height: none;
        box-shadow: 0 0 24px rgb(0 0 0 / .45);
    }
    .ziltch-admin-shell:not(.zt-rail-closed) > .zt-rail-backdrop {
        display: block; position: fixed; inset: var(--zt-header-h) 0 0 0; z-index: 1030; background: rgb(0 0 0 / .4);
    }
    .ziltch-admin-main { padding: var(--zt-space-3); }
    .zt-admin-tools { margin-top: 0; }
}

/* Full screen: every chrome bar goes, the page keeps the window. */
html.zt-full .ziltch-header,
html.zt-full .ziltch-footer,
html.zt-full .ziltch-admin-nav,
html.zt-full .zt-rail-toggle,
html.zt-full .zt-rail-backdrop,
html.zt-full .zt-zed-dock,
html.zt-full .zt-page-head { display: none !important; }
html.zt-full body.zt-zed-docked { padding-right: 0; }
html.zt-full .ziltch-admin-shell { grid-template-columns: 1fr; }
html.zt-full { --zt-header-h: 0rem; }
html.zt-full .ziltch-admin-main { padding: var(--zt-space-2); }
html.zt-full .zt-admin-tools { position: fixed; top: var(--zt-space-2); right: var(--zt-space-2); z-index: 95; margin: 0; }

/*  ⚠️ NO FALLBACKS IN THE RULES BELOW, AND THAT IS THE POINT OF THE OMISSION.
    My own DesignTokenFallbackGateTests went RED on these: `var(--zt-fg, #20232e)` pushed --zt-fg from
    3 distinct fallback spellings to 4, over its frozen budget. #20232e appeared in exactly one file —
    this one — so the new spelling was mine, added tonight, by the author of the ratchet.
    ⚖️ THE FIX IS NOT TO RAISE THE BUDGET, which is the one move that gate exists to prevent. Nor is it to
    borrow an existing spelling: the other three (#d7e2ef, #dbe4f5, #e6ebf5) are all DARK-scheme inks and
    this rule runs in a light context, so reusing one would trade a ratchet breach for a wrong colour.
    🔑 THE FALLBACK WAS DEAD CODE. A `var(--x, y)` fallback only applies when --x is UNDEFINED, and every
    token here is defined at :root in this very stylesheet — so the fallback could never render, and its
    only possible effect was to be a WRONG second opinion about the design system that a reader might
    believe. `var(--zt-surface, #fff)` was already that: at :root, --zt-surface resolves to --p-dark-surface
    (#0b1020), so the fallback asserted white for a token whose base value is midnight.
    Bare tokens: correct, shorter, and the ratchet returns to 3.                                          */
/* ─── THE SIGN-IN PAGE IS A DESIGNED PAGE ─────────────────────────────────────────────────────────────
   🩸 MEASURED 2026-09-12 on the live /Login at 1440x900. The form sat 151px from the top with 497px of
   dead white space beneath it — a 1:3.3 imbalance that reads as an unfinished page, on the one surface
   where the product asks for a password. Three defects, all measured, none of them in our markup (the
   view is precompiled inside OrchardCore.Users) and all three reachable from this stylesheet:

     1. 497px dead space below a top-anchored form.
     2. The password REVEAL control outweighed the primary action: `btn-secondary` solid grey
        rgb(108,117,125) at 46px, against the `btn-primary` brand fill at 70px — a utility control
        nearly as heavy as the CTA, and sitting ABOVE it. Hierarchy inverted.
     3. The focus ring was Bootstrap's default BLUE (border rgb(134,183,254), glow rgba(13,110,253,.25))
        — a competitor's accent on our credential field, in a product whose brand is #472c54.

   ⚖️ THE CARD WRAPS THE HEADING AND THE FORM, AND THE FIRST VERSION DID NOT. Carding `.auth-form` alone
   moved the form to optical centre and left the "Log in" h1 orphaned at the top of the page — two parts
   of one thing in two places, a defect my own fix created and a screenshot caught. `:has(> .auth-form)`
   scopes this to a grid cell that actually contains an auth form, so no other column is touched.

   📐 max-width is min(26rem, 100vw - 2rem), NOT 26rem. At 390px the flat 26rem (416px) made the card
   WIDER than the viewport: it rendered 390px with ZERO gutters, border running off both screen edges.
   Caught by measuring gutters at phone width rather than trusting margin-inline:auto, which has nothing
   to distribute once the element exceeds its container.

   Verified in pixels at 1440x900 and 390x780 by injecting this exact rule on the live page:
     1440   card 416px · 179 above / 333 below (was 151 / 497) · heading inside the card · overflow 0
      390   card 358px · gutters 16px / 16px · overflow 0                                              */
.ziltch-site-main :is([class*="col-"]):has(> .auth-form) {
    margin-block: clamp(1.5rem, 10vh, 6rem) 3rem;
    max-width: min(26rem, calc(100vw - 2rem));
    margin-inline: auto;
    float: none;
    padding: 1.75rem 1.5rem;
    border: 1px solid var(--zt-line);
    border-radius: 12px;
    background: var(--zt-surface);
    box-shadow: 0 1px 2px rgba(16, 20, 40, .04), 0 8px 24px rgba(16, 20, 40, .06);
}
.ziltch-site-main :is([class*="col-"]):has(> .auth-form) h1 { font-size: 1.3rem; margin-bottom: .35rem; }
/* Ghost, not a solid fill — the reveal is a utility and must not read as loud as "Log in". */
.auth-form #togglePassword { background: transparent; color: var(--zt-muted);
    border: 1px solid var(--zt-line); border-left: 0; }
/* 🔴 THE HOVER GROUND WAS A LIGHT-ARM LITERAL PAINTED IN BOTH ARMS. `rgba(71, 44, 84, .06)` is
   --p-light-brand at 6%, hardcoded, so the DARK card got the light brand too. Measured 2026-09-12,
   rest-vs-hover ground contrast (the number that says "can you see the state change at all"):
       light  #ffffff -> #f4f2f5   1.11:1   a real, if quiet, affordance
       dark   #0b1020 -> #0f1223   1.02:1   *** DEAD. No perceptible hover in the dark arm.
   Asymmetric by construction, which is exactly why one-arm testing never saw it: the arm that works
   is the one a default test browser asks for. --zt-brand-tint balances them (light 1.14, dark 1.18)
   and keeps ink at 13.7 / 12.6 against the 4.5 WCAG 1.4.3 wants. NOT --zt-line, which scores higher
   (1.24 / 1.45) and is a BORDER token — right number, wrong job, the mistake
   FocusIndicatorsUseThemeTokensGateTests exists to name.
   ⚖️ HONEST SEVERITY: the ink shift (--zt-muted -> --zt-fg) carries most of this control's hover
   affordance and was already theme-correct, so the dark arm was weak rather than unusable. */
.auth-form #togglePassword:hover { background: var(--zt-brand-tint); color: var(--zt-fg); }
/* 🔴 BRAND FOCUS, NOT BOOTSTRAP BLUE — AND NOT THE BRAND COLOUR I FIRST REACHED FOR.
   8fa988b2dc hardcoded #472c54 here. Measured 2026-09-12 in BOTH arms, which is the check that commit
   never ran (I verified the light card only, because that is what the test browser asked for):
       ring #472c54   on the LIGHT card #ffffff   11.96:1   PASS
       ring #472c54   on the DARK  card #0b1020    1.58:1   *** FAIL   (WCAG 1.4.11 needs 3.0)
   A focus indicator invisible in one of the product's two themes is the same defect I found in this
   theme's own history four hours earlier — correct on one ground, unreadable on another — and I
   committed it myself the same night.
   ⚖️ AND "USE THE TOKEN" WAS NOT ENOUGH, WHICH IS THE PART WORTH KEEPING. --zt-brand DOES flip per theme
   (#472c54 light / #5b3e78 dark) and STILL FAILS at 2.17:1, because a brand FILL colour and a focus
   INDICATOR have different jobs: the fill only has to carry its own ink, the indicator has to be seen
   against the surface it sits on. --zt-brand-bright is the pair that satisfies both:
       light #6b4a8f on #ffffff  7.00:1      dark #7a57a0 on #0b1020  3.33:1
   3px ring so the focused field is unmistakable at a glance; the halo is derived from the same token by
   color-mix rather than a second hardcoded rgba that could drift from it. */
.auth-form .form-control:focus {
    border-color: var(--zt-brand-bright);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--zt-brand-bright) 22%, transparent);
}

/* ─── THE 404 IS A PAGE, NOT A SENTENCE ───────────────────────────────────────────────────────────────
   🩸 MEASURED 2026-09-12 at 1440x900 on the live site. /this-page-does-not-exist rendered ONE 40px
   sentence, flush left at x=72, with 763px of empty white beneath it and NOT ONE LINK inside <main>. The
   only route onward was the site nav — which until an hour earlier was itself illegible on the front door.
   A visitor who mistypes a URL or follows a stale link got a server sentence and a wall of nothing.

   The recovery LINK is added by the layout (it can see Context.Response.StatusCode; this stylesheet
   cannot, and CSS cannot create an anchor). These rules do the rest: centre the block, bring OC's oversized
   heading down to a page heading, and give the way out a real 44px target.

   ⚠️ THE NAV EXCEPTION IS NOT COSMETIC. `.ziltch-site-nav` is a CHILD of <main>, so centring the error
   content centred the NAVIGATION too — "Home" floated above the heading reading as a breadcrumb instead of
   as nav. A defect this very fix created, caught by looking at the screenshot rather than the numbers,
   which all said "centred: true". The nav is returned to the page's normal left edge; only the error
   content is centred.

   Verified in pixels at 1440x900: heading 40px -> 32px and centred · CTA 190x44 (meets the 44px target)
   · links in <main> 1 -> 2 · nav back at x=72 · overflow 0                                               */
.ziltch-site-main:has(.zt-deadend) {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    padding-block: clamp(2rem, 12vh, 7rem) 4rem;
}
.ziltch-site-main:has(.zt-deadend) h1 { font-size: clamp(1.5rem, 4vw, 2rem); margin: 0 0 .6rem; }
.ziltch-site-main:has(.zt-deadend) > .ziltch-site-nav { align-self: stretch; text-align: left; }
.zt-deadend { max-width: min(30rem, calc(100vw - 2rem)); }
.zt-deadend__say { color: var(--zt-muted); font-size: .95rem; margin: 0 0 1.4rem; }
.zt-deadend__go {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 0 1.1rem; border-radius: 8px; text-decoration: none;
    /* 🔴 --zt-brand-bright, NOT --zt-brand, AND THE DIFFERENCE IS A FAILURE IN DARK. f1bdd0a6ad used
       --zt-brand. Measured 2026-09-12 in both arms:
           dark   fill #5b3e78 vs page #070b1a   2.24:1  *** FAIL   (WCAG 1.4.11 needs 3.0)
           light  fill #472c54 vs page #ffffff  11.96:1      PASS
       The button's INK was fine either way (8.73:1 on the dark fill) — what failed is the button as an
       OBJECT: its fill was barely distinguishable from the page behind it, so the control's boundary
       disappeared even though its label was legible. A filled button has TWO contrast obligations and I
       had only checked the one that shows up in a text-contrast tool.
       --zt-brand-bright satisfies both in both arms: dark 3.45 fill / 5.68 ink, light 7.00 / 7.00. */
    background: var(--zt-brand-bright); color: var(--zt-brand-ink); font-weight: 600;
}
/* Hover derived from the token rather than a hardcoded #5a3a6a that only ever suited the light arm.
   THE HOVER DEEPENS TOWARD --zt-brand; IT MUST NOT LIGHTEN TOWARD WHITE. Measured 2026-09-12, and the
   lightening version was mine: mixing 82% toward white gave #9275b1 in dark, and white ink on it reads
   3.89:1 against the 4.5 WCAG 1.4.3 requires of body text -- the RESTING state passes at 5.68:1, so the
   button was compliant until you pointed at it. Light scraped through at 4.52:1, a margin of 0.02.
   Lightening a filled button while its ink stays white LOSES contrast by construction; deepening toward
   the darker brand token GAINS it, in both arms, monotonically: dark 6.16:1, light 7.67:1, and the fill
   still clears 1.4.11 against the page (dark 3.18:1, light 7.67:1).
   ⚠️ AND THE REASON IT SURVIVED AN AUDIT: every contrast probe in this repo measures the RESTING state.
   A static probe cannot hover, so :hover/:active fills are unmeasured everywhere -- this one was found by
   computing the mix by hand, not by any instrument we own. */
.zt-deadend__go:hover { background: color-mix(in srgb, var(--zt-brand-bright) 82%, var(--zt-brand));
    color: var(--zt-brand-ink); }

/* Rich text (markdown, math, code) drawn by Ziltch.Gateway.Render.RichTextView: Zed's replies and the rich-text lens. */
.zt-rich > :first-child { margin-top: 0; }
.zt-rich > :last-child { margin-bottom: 0; }
.zt-rich p, .zt-rich ul, .zt-rich ol, .zt-rich pre, .zt-rich blockquote, .zt-rich .zt-math { margin: 0 0 .55rem; }
.zt-rich ul, .zt-rich ol { padding-left: 1.3rem; }
.zt-rich h3, .zt-rich h4, .zt-rich h5, .zt-rich h6 { font-size: 1rem; margin: .3rem 0 .4rem; }
.zt-rich code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: .875em; background: var(--zt-panel);
    padding: .05rem .3rem; border-radius: var(--zt-radius-sm); }
.zt-rich pre { background: var(--zt-panel); border: 1px solid var(--zt-grid); border-radius: var(--zt-radius);
    padding: .5rem .7rem; overflow-x: auto; }
.zt-rich pre code { background: none; padding: 0; white-space: pre; }
.zt-rich blockquote { border-left: 2px solid var(--zt-grid); padding-left: .6rem; color: var(--zt-muted); }
.zt-rich a { color: var(--zt-accent); }
.zt-rich--lens { color: var(--zt-fg); overflow-wrap: anywhere; font-size: .86rem; }

/* Site navigation: the chip survives the front door's artwork only; the hit area is 44px everywhere (WCAG 2.5.5). */
/* row is stated: OC renders the menu as Bootstrap's .list-group, which is a column. */
.ziltch-site-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: row; gap: 1rem; flex-wrap: wrap; }
.ziltch-site-nav li { margin: 0; }
/* The anchor for the 44px hit area below; scoped to the hero it made BODY the anchor and ate every click. */
.ziltch-site-nav a { text-decoration: none; position: relative; }
body:has(.zt-frontdoor-bg) .ziltch-site-nav a {
    display: inline-block;
    padding: .15rem .5rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--zt-surface) 78%, transparent);
    position: relative;
}
.ziltch-site-nav a::after {
    content: "";
    position: absolute;
    inset: -11.5px -0.5px;
}
.ziltch-site-nav a:hover { text-decoration: underline; }
.ziltch-site-nav a:focus-visible { outline: 2px solid var(--zt-brand-bright); outline-offset: 2px; }

/* Zed dock: reserves its width on the body so no page renders under it; a slide-over when narrow. */
:root { --zt-dock-w: clamp(20rem, 26vw, 30rem); }
.zt-zed-dock { position: fixed; top: 0; right: 0; bottom: 0; width: var(--zt-dock-w); z-index: 1040;
    display: flex; flex-direction: column; min-width: 0;
    background: var(--zt-panel, #0f1626); border-left: 1px solid var(--zt-grid, #24314a);
    transition: transform .18s ease; }
.zt-zed-dock__body { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.zt-zed-dock__toggle { position: absolute; left: -1.6rem; top: 50%; transform: translateY(-50%);
    width: 1.6rem; height: 3rem; padding: 0; line-height: 3rem; cursor: pointer;
    background: var(--zt-panel, #0f1626); color: var(--zt-muted, #7e8aa3);
    border: 1px solid var(--zt-grid, #24314a); border-right: 0; border-radius: .4rem 0 0 .4rem; }
body.zt-zed-docked { padding-right: var(--zt-dock-w); }
body.zt-zed-docked.zt-zed-closed { padding-right: 0; }
body.zt-zed-closed .zt-zed-dock { transform: translateX(100%); }
body.zt-zed-closed .zt-zed-dock__toggle { transform: translateY(-50%) rotate(180deg); }
body.zt-full-bleed .ziltch-main { max-width: none; width: 100%; padding-left: 0; padding-right: 0; }
@media (max-width: 52rem) {
    :root { --zt-dock-w: min(92vw, 26rem); }
    body.zt-zed-docked { padding-right: 0; }
}
