/* ════════════════════════════════════════════════════════════════════
   FROST — the single design-token spine + core kit.
   Two modes: Arclight (dark, default) · Bloom (light).
   Scoped to html so these tokens WIN over every legacy :root
   block (higher specificity) without !important. This one file re-themes
   the whole portal; page-level styles are migrated onto it in later phases.
   Rulebook: frost portal design rules (2026-07-15).
   ════════════════════════════════════════════════════════════════════ */

/* ---- font roles (apply the type system everywhere) ---- */
html{
  --display:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif;
  /* The WORDMARK stack, and it is deliberately not --display.
     Until 2026-08-19 the House of Shishir wordmark asked for
     `Fraunces, 'Cormorant Garamond', 'EB Garamond', Georgia, serif` — and Frost
     has no @font-face, no font files and no font CDN, so NONE of the first
     three has ever existed on any machine that opened Frost. Measured: all
     three return the identical advance as the generic `serif`, i.e. the mark
     rendered in the browser's Times. That is the typographic twin of the
     user-agent-blue bug: an unloaded font is a typeface decision made by the
     browser.
     This stack contains ONLY fonts that genuinely ship with macOS and Windows,
     ordered so the two the founder actually uses land within 1 unit of each
     other: Iowan Old Style 257.87u, Georgia 258.83u for "HOUSE OF SHISHIR" at
     27px. Do not add a font here that is not installed by the OS. */
  --display-mark:"Iowan Old Style",Georgia,Palatino,"Palatino Linotype","Times New Roman",serif;
  --ui:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,system-ui,sans-serif;
  --dc-ui:var(--ui); --rm-ui:var(--ui);
  --mono:ui-monospace,"SF Mono","JetBrains Mono","Roboto Mono",Menlo,monospace;
  /* V14 (21 Oct 2026): ONE font for every figure. The UI sans with tabular
     numerals — columns of rupees line up digit under digit. Use with
     font-variant-numeric:tabular-nums (the .num / .v14-num classes do both). */
  --num:var(--ui);
  --radius-1:8px; --radius-2:12px;
  /* V14 surface — "Modern & minimal" pilot, Family Office expenses page only
     (scoped by .ui-v14, 2026-10-21). Soft grey canvas, white rounded cards,
     barely-there shadow, one warm hero gradient. Gradients are dark enough at
     every stop to carry --c-invert-fg ink at 4.5:1. */
  --radius-3:16px; --radius-pill:999px;
  --v14-canvas:var(--bg); --v14-surface:var(--card); --v14-soft:var(--parchment-2);
  --v14-line:var(--border); --v14-ink:var(--ink); --v14-muted:var(--muted); --v14-on-ink:var(--parchment);
  --v14-glass:rgba(244,239,230,.14); --v14-glass-bd:rgba(244,239,230,.28);
  --v14-on-grad:#F4EFE6; --v14-on-grad-muted:rgba(244,239,230,.82);
  --v14-track:rgba(244,239,230,.24);
  --shadow-soft:0 1px 2px rgba(14,17,22,.04),0 4px 16px rgba(14,17,22,.05);
  --shadow-lift:0 2px 6px rgba(14,17,22,.06),0 14px 32px rgba(14,17,22,.09);
  /* TACTILE LAYER (22 Oct 2026, founder: "clean, professional, minimal, 3D,
     button clicking and hovering UI like V14"). Elevation + button depth, ONE
     place; parchment_theme.css rebinds the colours. Never write a shadow raw. */
  --elev-1:0 1px 1px rgba(14,17,22,.05),0 2px 4px rgba(14,17,22,.05),0 8px 20px -8px rgba(14,17,22,.10);
  --elev-2:0 2px 3px rgba(14,17,22,.06),0 6px 12px rgba(14,17,22,.07),0 18px 36px -12px rgba(14,17,22,.16);
  --btn-depth:inset 0 1px 0 rgba(255,255,255,.55),0 1px 2px rgba(14,17,22,.12),0 3px 0 -1px rgba(14,17,22,.10);
  --btn-raise:inset 0 1px 0 rgba(255,255,255,.65),0 2px 4px rgba(14,17,22,.14),0 8px 16px -6px rgba(14,17,22,.20);
  --btn-press:inset 0 2px 4px rgba(14,17,22,.22),0 0 0 rgba(14,17,22,0);
  --focus-ring:0 0 0 2px var(--v14-surface),0 0 0 4px var(--c-gold-solid);
  --lift-y:-2px; --press-y:1px;
  --grad-gold:linear-gradient(135deg,var(--c-gold-solid) 0%,#3F3216 55%,var(--ink) 100%);

  /* ---- SPACING SCALE (founder HARD rule, 2026-08-13) ----------------------
     "Keep a standardised spacing between all elements fixed across Frost."
     Before this there was no scale: department pages used 6, 7, 9, 10, 11, 12,
     14, 16, 18, 22, 24 and 26px gaps chosen a page at a time, which is why the
     Operations overview read as if the blocks had been dropped rather than
     laid out.

     Seven steps. Nothing between them. If a gap needs a value that is not on
     this list, the layout is wrong, not the list.

       --sp-1  4px   inside a control (icon→label)
       --sp-2  8px   inside a block (label→value, chip→chip)
       --sp-3  12px  between rows in a list
       --sp-4  16px  between blocks inside a section
       --sp-5  24px  between sections on a page
       --sp-6  32px  between major regions
       --sp-7  48px  page top/bottom breathing room

     Use the three SEMANTIC aliases in page CSS, not the raw steps — they say
     what the gap is for, so a later change stays coherent. */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px;
  --sp-5:24px; --sp-6:32px; --sp-7:48px;
  --sp-tight:var(--sp-2);      /* within a block */
  --sp-block:var(--sp-4);      /* block → block */
  --sp-section:var(--sp-5);    /* section → section */
  --pad-card:var(--sp-3) var(--sp-4);  /* every card / panel, no exceptions.
                                  12px 16px since 2026-08-18 (founder): it was
                                  14px 16px, and 14 is not on the scale above —
                                  Frost's own rule 1 broken by Frost's own
                                  token. Snapped DOWN to keep pages short. */
  --pad-chip-y:var(--sp-1);            /* the two halves of --pad-chip, named so a
                                  chip can compensate its own trailing tracking
                                  (`calc(var(--pad-chip-x) - var(--track))`)
                                  without hard-coding 8px — 2026-08-19. */
  --pad-chip-x:var(--sp-2);
  --pad-chip:var(--pad-chip-y) var(--pad-chip-x);  /* 4px 8px — every chip / pill / badge.
                                  Added 2026-08-18. Before this, chips invented
                                  their own vertical padding 111 times across
                                  the console (1px, 2px and 3px), all of it
                                  below --sp-1 and none of it on any scale.
                                  A chip is a control, so it gets ONE token —
                                  same reasoning as --control-h. */
  --pad-page:var(--sp-5);      /* page gutter */
  --control-h:38px;            /* every button, select and input target (see the
                                  coarse-pointer override below — 38px is a
                                  mouse target, 44px is a thumb target) */

  /* ---- FIXED-OBJECT CONSTANTS (2026-08-18) -------------------------------
     These are NOT spacing choices and the --sp-* scale does not govern them:
     each one measures a real object that something else must clear. Snapping
     58px to 48px does not make a page tidier, it slides content under the
     mobile tab bar. Named here so the audit can tell a measurement apart from
     a margin, and so a change to the bar changes every clearance with it. */
  --table-max-h:75vh;          /* tall tables scroll inside this so their header can stick */
  --tabbar-h:58px;             /* mobile bottom tab bar (mobile_app.css) */
  --tabbar-clear:calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  --staffbar-clear:80px;       /* staff detail sticky action bar */
  /* ── THE PAGE HEADLINE ───────────────────────────────────────────────────
   2026-09-11, founder: *"all of the main headings and announcement pages for
   every page in family office and overall in Frost has to be reworked, this
   design and section UI and interface, interactiveness needs to be redone."*

   `.headline` is the heading block on 208 templates — every page that does not
   use the department cockpit kit. It had NO base styling anywhere: the only
   rules touching it were a non-Frost brand override and one mobile font-size.
   So on 208 pages the eyebrow, the title and the subtitle rendered as three
   identical paragraphs of body text, and the eyebrow's `← Finance` link came
   out as default underlined link text. There was nothing to redesign, because
   nothing had been designed.

   This is the same defect as the cockpit kit shipping markup without its
   stylesheet, and it takes the same shape of fix: style the contract here, at
   zero specificity, so all 208 pages inherit it and none has to be edited.
   Deliberately matches `_page_hero.html`, which is the same design for the
   pages that DO use the kit — one heading in Frost, two entry points.
   ──────────────────────────────────────────────────────────────────────── */
.headline { margin:0 0 var(--sp-section); padding-bottom:var(--sp-block);
            border-bottom:1px solid var(--border); }
.headline__eyebrow { margin:0 0 var(--sp-1); font-size:var(--fs-micro);
                     --track:var(--track-3); letter-spacing:var(--track);
                     text-transform:uppercase; font-weight:650;
                     color:var(--c-gold-fg); }
/* The eyebrow is usually a back-link. It is navigation, so it reads as a
   control — not as underlined prose the size of the body copy. */
.headline__eyebrow a { color:inherit; text-decoration:none; }
.headline__eyebrow a:hover { text-decoration:underline; }
.headline__text { font-size:var(--fs-display); font-weight:700; line-height:1.1;
                  min-width:0; overflow-wrap:anywhere; }
.headline__sub { display:block; margin-top:var(--sp-2); max-width:68ch;
                 font-size:var(--fs-base); color:var(--fg-muted); }
/* Actions, where a page has them, are controls with their own box and gap —
   the defect that put `Home planner →Personal books →` on one unbroken line. */
.headline__actions { display:flex; gap:var(--sp-2); flex-wrap:wrap;
                     margin-top:var(--sp-3); }
.headline__actions a { display:inline-flex; align-items:center;
                       min-height:var(--control-h); padding:0 var(--sp-3);
                       border:1px solid var(--c-gold-bd); border-radius:999px;
                       background:var(--c-gold-bg); color:var(--c-gold-fg);
                       font-size:var(--fs-small); font-weight:650;
                       text-decoration:none; white-space:nowrap; }
.headline__actions a:hover { background:var(--c-gold-solid); color:var(--c-invert-fg); }
@media (max-width:52rem){ .headline__text { font-size:var(--fs-xl); } }

/* THE BOTTOM-RIGHT LADDER. Three fixed things want that corner — the
     scroll-to-top button, the realtime toast stack and its mute pill — and
     each arrived separately, picked its own `bottom`, and won or lost on
     z-index alone. On 2026-09-11 the toast wrap (z-index 9999) sat exactly
     over the scroll button (z-index 60) at desktop width, and the mobile tab
     bar (z-index 80) sat over it at phone width: a control that renders, is
     the right colour, and cannot be reached. A corner with no owner gets
     three owners. `--fab-h` is the button; anything stacking ABOVE it starts
     at `--fab-clear`. Both clear a real object, so the spacing scale does not
     govern them (CLAUDE.md, 2026-08-18). */
  --fab-h:42px;                /* #scrollTopBtn, 46px under pointer:coarse */
  --fab-clear:calc(var(--sp-4) + var(--fab-h) + var(--sp-3));
  --topbar-indent:42px;        /* clears the absolutely-placed topbar glyph */

  /* ---- TYPE RAMP (founder-approved 2026-08-18) ---------------------------
     Frost had no type tokens at all. Sizes were chosen a page at a time, which
     produced SIXTEEN of them in the founder cockpit alone — 13px and 13.5px
     side by side, which is not a hierarchy, it is the absence of one.

     Eight steps. Nothing between them. Same law as the spacing scale: if a
     label needs a size that is not on this list, the hierarchy is wrong, not
     the list.

       --fs-micro   10px  uppercase letterspaced labels
       --fs-tiny    11px  chips, meta, timestamps
       --fs-small   12px  secondary text
       --fs-body    13px  TABLE BODY — the cockpit's workhorse
       --fs-base    14px  default UI text
       --fs-lg      16px  section heads
       --fs-xl      20px  page titles
       --fs-display 28px  hero numbers

     Collapsed into it: 9, 9.5, 10.5 → 10 · 11.5 → 11 · 12.5 → 12 · 13.5 → 13
     14.5, 15 → 14 · 17, 18 → 16 · 22, 24 → 20 · 30, 32 → 28 */
  --fs-micro:10px; --fs-tiny:11px; --fs-small:12px; --fs-body:13px;
  --fs-base:14px;  --fs-lg:16px;   --fs-xl:20px;    --fs-display:28px;

  /* ---- TRACKING SCALE + the trailing-space law (2026-08-19, founder) ------
     Founder, on the House of Shishir wordmark: *"remove the space after Shishir
     then fix that space everywhere across Frost."*

     THE DEFECT, and it is in the CSS spec rather than in any one page:
     `letter-spacing` inserts its tracking AFTER the last character too. A
     letterspaced string therefore always carries one tracking-width of DEAD
     SPACE on its right edge — invisible, but it is part of the box. So:

       · centred text sits half a tracking LEFT of where it looks like it
         should (`OPERATIONS` in a centred tab reads off-centre),
       · text bracketed between two rules has a bigger gap on the right than
         on the left (exactly what the founder photographed),
       · and any box sized from a text measurement is one tracking too wide.

     THE FIX, declared once, here. Write your tracking as `--track` and take
     `letter-spacing` from it; the net below reclaims the trailing space for
     you. You do not have to know this rule exists to be protected by it:

         .thing { --track: var(--track-4); letter-spacing: var(--track); }

     Enforced by `ops/hig_audit.py::check_letterspacing_trailing` at pre-commit
     and in the nightly `sentinel.checks.design_rules`. */
  --track-1:.02em;   /* display type, optical nudge only */
  --track-2:.04em;   /* chips, pills, tags */
  --track-3:.06em;   /* small-caps meta, table eyebrows */
  --track-4:.10em;   /* the wordmark, buttons, tab labels */
  --track-5:.14em;   /* footer column heads, section eyebrows */
  --track-6:.20em;   /* the widest tracking Frost uses */
  /* deal-flow chip palette (mode-independent constants — moved here from the
     stray cockpit.css :root so frost.css is the sole token source) */
  --chip-operating:#1e88e5; --chip-hyd:#43a047; --chip-fundraise:#8e24aa;
  --chip-personal:#fb8c00; --chip-unknown:#757575; --chip-stage:#455a64;
  --bd-yellow:#fff8c4; --bd-yellow-fg:#6e5d00;
}

/* ════════════════════════════════════════════════════════════════════
   THE TRAILING-SPACE NET (2026-08-19). See the tracking scale above.

   `--track` is registered as a NON-INHERITED property with an initial value of
   zero. That is what makes the net below safe to point at every text element in
   Frost: on an element that never opted in, `calc(-1 * var(--track))` resolves
   to `0em`, which is already every one of these elements' used value. Nothing
   moves. On an element that DID opt in, the trailing tracking is reclaimed from
   the layout and the ink lands where the eye expects it.

   Zero specificity (`:where`), so any real rule still wins — the net is a
   floor, exactly like the `a` colour rule and the `-btn` anchor rule.

   `blockquote` and `figure` are deliberately absent: they are the only two
   elements whose user-agent margin-inline-end is NOT zero (40px), so they are
   the only two the net could not be inert on.
   ════════════════════════════════════════════════════════════════════ */
@property --track {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 0em;
}
:where(h1,h2,h3,h4,h5,h6,p,a,span,div,li,dt,dd,td,th,label,button,small,
       strong,em,b,i,legend,summary,figcaption,caption,abbr,time,output) {
  margin-inline-end: calc(-1 * var(--track));
}

/* ════ ARCLIGHT — dark (default) ════ */
html.theme-dark, :root{
  /* grounds & surfaces */
  --bg:#0C0F17; --bg2:#0E121C; --bg-elevated:#141926; --bg-elev:#141926; --bg-soft:#10141F;
  --surface:#141926; --surface-muted:#1C2233; --dc-bg:#0C0F17;
  --parchment:#141926; --parchment-2:#1C2233; --brand-bg-elev:#141926;
  /* panels / cards */
  --card:#141926; --cardin:#1C2233; --dc-panel:#141926; --panel:#141926; --panel-2:#1C2233; --panel-3:#232B42;
  --dc-panel-2:#1C2233; --rm-panel:#141926; --rm-panel-2:#1C2233; --rm-ink-soft:#C0CADE;
  /* text */
  --fg:#EBEFF8; --ink:#EBEFF8; --dc-ink:#EBEFF8; --rm-ink:#EBEFF8;
  --fg-muted:#C0CADE; --ink-soft:#C0CADE; --soft:#C0CADE; --dc-ink-soft:#C0CADE;
  --ink-dim:#828FA8; --muted:#828FA8; --mut:#828FA8; --dc-muted:#828FA8; --rm-muted:#828FA8; --rm-dim:#828FA8; --faint:#5A6478;
  /* lines */
  --border:rgba(130,160,240,.13); --border-strong:rgba(130,160,240,.24); --brand-border:rgba(130,160,240,.24);
  --line:rgba(130,160,240,.10); --rule:rgba(130,160,240,.10); --rule-soft:rgba(130,160,240,.06);
  --bd:rgba(130,160,240,.13); --dc-line:rgba(130,160,240,.10); --rm-line:rgba(130,160,240,.10);
  /* accent + gold (gold = brand & money only) */
  --accent:#5B8DEF; --accent-h:#82A6F6; --accent-p:#3E6BC4; --brand-accent:#5B8DEF;
  --gold:#D4AF57; --gold-2:#8C7436; --bronze:#D4AF57; --dc-gold:#D4AF57; --rm-gold:#D4AF57;
  --cyan:#48D6E0; --violet:#A98BF5;
  /* semantic */
  --pos:#46E09A; --success:#46E09A; --dc-green:#46E09A; --rm-green:#46E09A;
  --warn:#E7AC52; --dc-amber:#E7AC52; --rm-amber:#E7AC52;
  --danger:#FF6E6E; --neg:#FF6E6E; --crit:#FF6E6E; --dc-red:#FF6E6E; --rm-red:#FF6E6E;
  --pos-b:rgba(70,224,154,.14); --warn-b:rgba(231,172,82,.15); --crit-b:rgba(255,110,110,.14); --acc-b:rgba(91,141,239,.16);
  /* effects */
  --focus:rgba(91,141,239,.55); --hud-glow:0 0 24px rgba(91,141,239,.28);
  --shadow:0 1px 0 rgba(255,255,255,.04),0 14px 38px -18px rgba(0,0,0,.7);
  --shadow-2:0 1px 0 rgba(255,255,255,.04),0 20px 50px -22px rgba(0,0,0,.78);

  /* ---- SEMANTIC COLOUR ROLES — Arclight (2026-08-18) ---------------------
     The console page sheets carried ~250 raw hex values between them, and
     almost none of them matched a token. They were not a palette; they were
     250 separate decisions, each one written the day someone needed a tinted
     block and reached for the colour picker.

     They collapse into SEVEN families × THREE roles. A block that needs to
     read as gold takes all three from the same family and stops choosing:

       --c-<family>-bg   the tinted ground behind the block
       --c-<family>-bd   its border
       --c-<family>-fg   text and icons on that ground

     This is why they live in the theme block rather than next to the scale:
     a raw #26200E is a dark-mode decision hardcoded into a page, which is the
     whole reason Bloom looked wrong — every one of those tinted blocks stayed
     dark when the founder switched to light. Defined per theme, the SAME rule
     in the SAME page renders correctly in both. */
  --c-gold-bg:#26200E;    --c-gold-bd:#7A5F1B;    --c-gold-fg:#E7C98A;
  --c-accent-bg:#17203A;  --c-accent-bd:#1F3A8A;  --c-accent-fg:#6FA0E0;
  --c-pos-bg:#12301F;     --c-pos-bd:#1F6432;     --c-pos-fg:#6DD28B;
  --c-danger-bg:#2A1416;  --c-danger-bd:#8A1F1F;  --c-danger-fg:#E3A99F;
  --c-violet-bg:#1E1830;  --c-violet-bd:#3A2D5A;  --c-violet-fg:#9A8FB0;
  --c-cyan-bg:#0E2A2E;    --c-cyan-bd:#146A7A;    --c-cyan-fg:#7FD3DC;
  --c-neutral-bg:#1C2233; --c-neutral-bd:#3B4250; --c-neutral-fg:#9E9E9E;

  /* ---- INVERTED SURFACES ------------------------------------------------
     A fourth role, and the one that stops this whole migration from breaking
     things. Not every dark fill in the console is a mistake left over from the
     dark era: a few components deliberately paint a dark ground and write
     light ink on it — the nav dropdowns, the done/undo buttons, the trajectory
     tip, a filled reject button.

     Repainting those grounds cream while their ink stayed light would have
     produced cream-on-cream: invisible, and invisible does not look broken in
     a screenshot, it looks empty. The pair has to move together, so it is one
     concept with two tokens.

       --c-invert-bg / --c-invert-fg   a deliberately dark surface
       --c-<family>-solid              a SATURATED fill that carries invert-fg
                                       (a filled danger button, a violet chip)

     If you paint --c-invert-bg, the ink on it is --c-invert-fg. Never mix one
     with a family tint. */
  --c-invert-bg:#0E1116;  --c-invert-fg:#F4EFE6;  --c-invert-bd:#2A3038;
  --c-gold-bright:#D4AF57;   --c-accent-bright:#5B8DEF; --c-pos-bright:#46E09A;
  --c-danger-bright:#FF6E6E; --c-violet-bright:#A98BF5; --c-cyan-bright:#48D6E0;
  --c-neutral-bright:#828FA8;   /* gold ink ON an inverted surface. The
                                parchment gold (#7A5F1B) is 1.6:1 there —
                                gold has to get BRIGHTER on a dark ground,
                                not darker, or the accent disappears. */
  --c-gold-solid:#8C7436;   --c-accent-solid:#3E6BC4; --c-pos-solid:#1E5233;
  --c-danger-solid:#8C2C2C; --c-violet-solid:#5F44A8; --c-cyan-solid:#146A7A;
  --c-neutral-solid:#0E1116;
  color-scheme:dark;
}

/* ════ BLOOM — light ════ */
html.theme-light{
  --bg:#EEF3F5; --bg2:#F1F5F8; --bg-elevated:#FFFFFF; --bg-elev:#FFFFFF; --bg-soft:#F3F7F9;
  --surface:#FFFFFF; --surface-muted:#F3F7F9; --dc-bg:#EEF3F5;
  --parchment:#FFFFFF; --parchment-2:#F3F7F9; --brand-bg-elev:#FFFFFF;
  --card:#FFFFFF; --cardin:#F3F7F9; --dc-panel:#FFFFFF; --panel:#FFFFFF; --panel-2:#F3F7F9; --panel-3:#E6EEF3;
  --dc-panel-2:#F3F7F9; --rm-panel:#FFFFFF; --rm-panel-2:#F3F7F9; --rm-ink-soft:#3A505C;
  --fg:#132029; --ink:#132029; --dc-ink:#132029; --rm-ink:#132029;
  --fg-muted:#3A505C; --ink-soft:#3A505C; --soft:#3A505C; --dc-ink-soft:#3A505C;
  /* #68798A measured 4.48:1 on white — AA wants 4.5, so every small label in
     the department kit (.dc-kpi__lbl, .dc-targets__lbl, .dc-rollup__status)
     failed by two hundredths. #617283 is the nearest shade on the same hue
     that clears 4.5 on white AND on both tinted grounds in use:
     4.95 on #FFFFFF, 4.54 on #FDF3F1, 4.59 on #F3F7F9. */
  --ink-dim:#617283; --muted:#617283; --mut:#617283; --dc-muted:#617283; --rm-muted:#617283; --rm-dim:#617283; --faint:#95A6B2;
  --border:rgba(20,50,70,.13); --border-strong:rgba(20,50,70,.22); --brand-border:rgba(20,50,70,.22);
  --line:rgba(20,50,70,.11); --rule:rgba(20,50,70,.11); --rule-soft:rgba(20,50,70,.06);
  --bd:rgba(20,50,70,.13); --dc-line:rgba(20,50,70,.11); --rm-line:rgba(20,50,70,.11);
  --accent:#1E86A6; --accent-h:#146A88; --accent-p:#0F5A76; --brand-accent:#1E86A6;
  /* LINK INK IS NOT THE ACCENT FILL. --accent is a fill, a border and a glow,
     where 4.5:1 does not apply; as the colour of WORDS it measured 3.84:1 on
     #FDF3F1 and 3.88 on #F3F7F9, so every inherited link — .dd-row__fix, the
     act that ends a disruption row — was unreadable. Splitting the two keeps
     the brand fill untouched and gives prose a shade that clears AA: 6.09 on
     white, 5.59 and 5.65 on the tints. Each theme rebinds these to its own. */
  --link-ink:#146A88; --link-ink-h:#0F5A76;
  --gold:#9A7A28; --gold-2:#C6A24C; --bronze:#9A7A28; --dc-gold:#9A7A28; --rm-gold:#9A7A28;
  --cyan:#1596A6; --violet:#7A50C0;
  --pos:#12915A; --success:#12915A; --dc-green:#12915A; --rm-green:#12915A;
  --warn:#B0741A; --dc-amber:#B0741A; --rm-amber:#B0741A;
  --danger:#C8302A; --neg:#C8302A; --crit:#C8302A; --dc-red:#C8302A; --rm-red:#C8302A;
  --pos-b:rgba(18,145,90,.12); --warn-b:rgba(176,116,26,.13); --crit-b:rgba(200,48,42,.11); --acc-b:rgba(30,134,166,.12);
  --focus:rgba(30,134,166,.5); --hud-glow:0 0 22px rgba(30,134,166,.18);
  --shadow:0 1px 2px rgba(20,40,55,.05),0 14px 34px -18px rgba(20,50,70,.24);
  --shadow-2:0 1px 2px rgba(20,40,55,.06),0 20px 46px -22px rgba(20,50,70,.28);

  /* ---- SEMANTIC COLOUR ROLES — Bloom (2026-08-18) ------------------------
     Same seven families, same three roles, inverted: the ground goes pale, the
     text goes deep. Every value here was already somewhere in the console —
     these are the light-mode tints the page sheets were reaching for and could
     not name. See the Arclight block above for why this is the fix. */
  --c-gold-bg:#FBF6E2;    --c-gold-bd:#DED3B8;    --c-gold-fg:#8A6D12;
  --c-accent-bg:#EEF5FF;  --c-accent-bd:#C6D8EE;  --c-accent-fg:#1F4E8A;
  --c-pos-bg:#E3F1E8;     --c-pos-bd:#BFE0BF;     --c-pos-fg:#1E5233;
  --c-danger-bg:#FDF3F1;  --c-danger-bd:#E7B9B1;  --c-danger-fg:#B3261E;
  --c-violet-bg:#F6F1FF;  --c-violet-bd:#DDD2F0;  --c-violet-fg:#5F44A8;
  --c-cyan-bg:#EEF3F5;    --c-cyan-bd:#B8D4DA;    --c-cyan-fg:#146A7A;
  --c-neutral-bg:#F5F5F5; --c-neutral-bd:#DDDDDD; --c-neutral-fg:#6B6B6B;
  --c-invert-bg:#132029;  --c-invert-fg:#FFFFFF;  --c-invert-bd:#3B4250;
  --c-gold-bright:#E5C97A;   --c-accent-bright:#4C84C9; --c-pos-bright:#2F8A47;
  --c-danger-bright:#C8302A; --c-violet-bright:#7A50C0; --c-cyan-bright:#1596A6;
  --c-neutral-bright:#68798A;
  --c-gold-solid:#9A7A28;   --c-accent-solid:#1F4E8A; --c-pos-solid:#12915A;
  --c-danger-solid:#C8302A; --c-violet-solid:#7A50C0; --c-cyan-solid:#146A7A;
  --c-neutral-solid:#132029;
  color-scheme:light;
}

/* ════ THE PAGE SHELL — founder, 2026-08-18 ════════════════════════════════
   "I want everything designed and spaced like [Influencer & PR] for every page
   — that follows my keep-it-clean rule."

   Three pages, three different amounts of breathing room, and the cause was not
   that anyone chose badly. It was that `main.cockpit` had NO desktop padding at
   all, so a page was inset only if it happened to define its own wrapper — and
   exactly two of 442 templates did. Everything else rendered flush to the
   browser edge.

   That is the spacing rule one level up from the --sp-* scale. The scale says
   which VALUES are legal; it never said who owns the page gutter, so nobody
   did. This block answers it once:

     · --pad-page gutter on every side, on every page, inherited not remembered
     · 1600px max-width so a 27" display does not stretch a table to 2000px
     · centred, so the gutter is equal on both sides

   A page must not re-declare these. Two did (`.ip-page`, `.oi-wrap`) and have
   been reduced to what they actually are — a stacking context — because a page
   that pads itself inside a padded shell is double-padded, which is the same
   inconsistency in the opposite direction. */
html main.cockpit{
  max-width:1600px;
  margin-inline:auto;
  padding:var(--pad-page);
  min-width:0;
}
/* Deliberately NOT display:flex. The page shell owns the GUTTER; each page's
   own wrapper owns its internal rhythm. Changing the display mode of every
   page's parent at once is a blast radius nobody can screenshot. */

/* ---- base ---- */
html body{
  background:var(--bg); color:var(--fg); font-family:var(--ui);
  -webkit-font-smoothing:antialiased;
  background-image:radial-gradient(1100px 520px at 84% -10%, color-mix(in oklab,var(--accent) 8%,transparent), transparent 60%);
  background-attachment:fixed;
}
html h1,html h2,html h3.display,
html .display,html .brand-lockup,html .wordmark{font-family:var(--display)}
/* `overflow-wrap:normal` is load-bearing, not tidying: rule 5 below sets
   `break-word` on every cell for the longest thing in a box (a URL, a SKU),
   and that rule is WRONG for the most precise thing in it. A number broken
   between digits leaves a first line that is itself a plausible number, so
   nothing looks broken — it just reads wrong by an order of magnitude. */
html .num,html [class*="__val"],html .amt,html .money{font-variant-numeric:tabular-nums;font-family:var(--mono);overflow-wrap:normal;word-break:normal}

/* FOOTER-FLUSH: footer touches the viewport bottom on a short page (founder
   2026-10-28). Column body, main grows, footer takes the slack. */
html body{min-height:100dvh;display:flex;flex-direction:column;margin:0}
html body>main{flex:1 0 auto;min-width:0;width:100%;box-sizing:border-box}
html body>.footer{margin-top:auto;flex:none}
@media print{html body{min-height:0;display:block}}

/* ---- footer menu (migrated from onehouse.css) ---- */
html .footer{width:100%;margin:var(--sp-7) 0 0;padding:16px 22px 18px;border-top:1px solid var(--border);color:var(--muted);font-size:var(--fs-body);display:flex;flex-direction:column;gap:14px;background:var(--bg-elevated);box-sizing:border-box;box-shadow:0 -1px 0 color-mix(in oklab,var(--gold) 24%,transparent)}
html .footer__row{display:flex;justify-content:center;align-items:center;gap:14px;flex-wrap:wrap}
html .footer__menu{display:grid;grid-template-columns:repeat(4,minmax(140px,220px));gap:24px;justify-content:center;margin:0 auto;width:100%;max-width:1280px}
html .footer__col{display:flex;flex-direction:column;gap:6px}
html .footer__col-head{font-size:var(--fs-micro);font-weight:700;text-transform:uppercase;letter-spacing:.14em;color:var(--gold);margin-bottom:4px}
html .footer__col a{color:var(--muted);text-decoration:none;font-size:var(--fs-body);padding:1px 0}
html .footer__col a:hover{color:var(--fg);text-decoration:underline}
html .footer__row--meta{border-top:1px solid var(--border);padding-top:10px;margin-top:4px;font-size:var(--fs-tiny);color:var(--muted)}
@media(max-width:900px){html .footer__menu{grid-template-columns:repeat(2,minmax(140px,1fr))}}

/* ---- mode toggle button (top bar) ---- */
html .frost-mode{cursor:pointer;width:34px;height:32px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--muted);display:inline-grid;place-items:center;font-size:var(--fs-base);line-height:1;transition:.15s}
html .frost-mode:hover{color:var(--fg);border-color:var(--border-strong)}
html .frost-mode:focus-visible{outline:2px solid var(--focus);outline-offset:2px}

/* ---- signature infographic base classes (kit) ---- */
html .fx-glass{background:linear-gradient(160deg,color-mix(in oklab,var(--card) 92%,transparent),var(--card));border:1px solid var(--border-strong);border-radius:14px;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),var(--shadow)}
html .fx-glow{text-shadow:var(--hud-glow)}
html .fx-gauge-track{stroke:var(--panel-3);fill:none}
html .fx-gauge-val{stroke:var(--accent);fill:none;stroke-linecap:round;filter:drop-shadow(0 0 6px color-mix(in oklab,var(--accent) 60%,transparent))}
html .fx-live{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-tiny);color:var(--muted)}
html .fx-live .p{width:7px;height:7px;border-radius:50%;background:var(--pos);animation:fx-pulse 2.4s infinite}
@keyframes fx-pulse{0%{box-shadow:0 0 0 0 color-mix(in oklab,var(--pos) 60%,transparent)}70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
html .fx-grid-bg{background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);background-size:34px 34px}
@media(prefers-reduced-motion:reduce){html *{animation:none!important;transition:none!important}}

/* radial gauge — wrapper + centred readout (SVG uses .fx-gauge-track/-val) */
html .fx-gauge{position:relative;display:inline-grid;place-items:center;line-height:1}
html .fx-gauge svg{display:block;transform:rotate(-90deg)}
html .fx-gauge__c{position:absolute;inset:0;display:grid;place-content:center;text-align:center}
html .fx-gauge__num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:700;color:var(--fg);letter-spacing:-.01em}
html .fx-gauge__lbl{font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:.1em;color:var(--muted);margin-top:2px}
html .fx-gauge-val--gold{stroke:var(--gold);filter:drop-shadow(0 0 6px color-mix(in oklab,var(--gold) 55%,transparent))}
html .fx-gauge-val--pos{stroke:var(--pos);filter:drop-shadow(0 0 6px color-mix(in oklab,var(--pos) 55%,transparent))}
html .fx-gauge-val--warn{stroke:var(--warn);filter:drop-shadow(0 0 6px color-mix(in oklab,var(--warn) 55%,transparent))}
html .fx-gauge-val--crit{stroke:var(--crit);filter:drop-shadow(0 0 6px color-mix(in oklab,var(--crit) 55%,transparent))}
@media(prefers-reduced-motion:no-preference){
  html .fx-gauge-val{transition:stroke-dashoffset 1.1s cubic-bezier(.22,1,.36,1)}
}

/* ════════════════════════════════════════════════════════════════════
   COMPONENT KIT — reusable, token-only, ADD-ONLY class names.
   Everything resolves through the Arclight/Bloom token spine above, so
   both modes are covered automatically and no legacy page can break.
   ════════════════════════════════════════════════════════════════════ */

/* ---- buttons ---- */
html .fbtn{
  --_bg:var(--card); --_fg:var(--fg); --_bd:var(--border-strong);
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  padding:9px 16px;border-radius:var(--radius-1);border:1px solid var(--_bd);
  background:var(--_bg);color:var(--_fg);font:600 13px/1 var(--ui);
  cursor:pointer;text-decoration:none;white-space:nowrap;
  transition:transform .12s,box-shadow .15s,border-color .15s,background .15s,filter .15s;
}
html .fbtn:hover{transform:translateY(-1px);border-color:var(--accent);box-shadow:var(--shadow)}
html .fbtn:active{transform:translateY(0)}
/* Not `opacity:.5` — see rule 2e below. Fading a control halves its
   contrast, so a 5:1 button became a 2.5:1 button the moment it went off.
   The disabled net paints this from the neutral tint family; all this rule
   still owns is that a dead button does not respond to a click. */
html .fbtn:disabled,html .fbtn[aria-disabled="true"]{pointer-events:none}
html .fbtn--primary{--_bd:transparent;color:#fff;
  background:linear-gradient(135deg,var(--accent-h),var(--accent) 55%,var(--accent-p));
  box-shadow:0 6px 18px -8px color-mix(in oklab,var(--accent) 70%,transparent)}
html .fbtn--primary:hover{filter:brightness(1.06);border-color:transparent}
/* A GOLD BUTTON PAINTS THE BRIGHT GOLD, NOT THE INK GOLD. `--gold` is the
   token for gold TEXT on cream and is deliberately dark (#7A5F1B in the
   parchment theme); used as a FILL it left the button's own dark label at
   3.72:1 — measured on /finance/remittance, 2026-09-18. `--c-gold-bright` is
   the fill rung of the same family and parchment_theme.css already states the
   pairing: "As a FILL for a primary action: dark ink on it is 8.1:1, which is
   why the gold button keeps its gold instead of fading to a cream tint." The
   label takes --ink rather than a near-black literal, so the pair moves
   together if the palette ever does. */
html .fbtn--gold{--_bd:transparent;color:var(--ink);
  background:linear-gradient(135deg,color-mix(in oklab,var(--c-gold-bright) 88%,var(--parchment)),var(--c-gold-bright) 60%,var(--gold-2));
  box-shadow:0 6px 18px -8px color-mix(in oklab,var(--c-gold-bright) 66%,transparent)}
html .fbtn--gold:hover{filter:brightness(1.05);border-color:transparent}
html .fbtn--ghost{--_bg:transparent;--_bd:var(--border);color:var(--fg-muted)}
html .fbtn--ghost:hover{color:var(--fg);background:color-mix(in oklab,var(--accent) 8%,transparent)}
html .fbtn--danger{--_bd:transparent;color:#fff;
  background:linear-gradient(135deg,color-mix(in oklab,var(--danger) 88%,#fff),var(--danger))}
html .fbtn--danger:hover{filter:brightness(1.05);border-color:transparent}
html .fbtn--sm{padding:6px 11px;font-size:var(--fs-small);border-radius:7px;gap:5px}

/* ---- card ---- */
html .fcard{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-2);
  padding:16px 18px;box-shadow:var(--shadow)}
html .fcard--pad-lg{padding:22px 24px}
html a.fcard,html .fcard--link{text-decoration:none;color:inherit;display:block;transition:border-color .12s,transform .12s}
html a.fcard:hover,html .fcard--link:hover{border-color:var(--accent);transform:translateY(-1px)}

/* ---- KPI (label + mono value) ---- */
html .fkpi{display:flex;flex-direction:column;gap:5px;min-width:0}
html .fkpi__lbl{font:600 10px/1.1 var(--ui);text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
html .fkpi__val{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:700;
  font-size:clamp(18px,1.9vw,26px);line-height:1.04;color:var(--fg);letter-spacing:-.01em;overflow-wrap:anywhere}
html .fkpi__val small{font-size:var(--fs-body);font-weight:600;color:var(--muted)}
html .fkpi__sub{font:400 11px/1.25 var(--ui);color:var(--muted)}
html .fkpi--pos .fkpi__val{color:var(--pos)}
html .fkpi--warn .fkpi__val{color:var(--warn)}
html .fkpi--crit .fkpi__val{color:var(--crit)}
html .fkpi--gold .fkpi__val{color:var(--gold)}

/* ---- pill / status chip ---- */
html .fpill{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;
  font:600 11px/1.5 var(--ui);border:1px solid var(--border);background:var(--card);color:var(--fg-muted)}
html .fpill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
html .fpill--pos{color:var(--pos);background:var(--pos-b);border-color:transparent}
html .fpill--warn{color:var(--warn);background:var(--warn-b);border-color:transparent}
html .fpill--crit{color:var(--crit);background:var(--crit-b);border-color:transparent}
html .fpill--acc{color:var(--accent);background:var(--acc-b);border-color:transparent}
html .fpill--plain::before{display:none}

/* ---- inputs ---- */
html .finput{width:100%;padding:9px 12px;border-radius:var(--radius-1);border:1px solid var(--border-strong);
  background:var(--cardin);color:var(--fg);font:400 13px/1.4 var(--ui);transition:border-color .15s,box-shadow .15s}
html .finput::placeholder{color:var(--faint)}
html .finput:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in oklab,var(--accent) 22%,transparent)}
html select.finput{appearance:none;cursor:pointer}
html textarea.finput{resize:vertical;line-height:1.5}

/* ---- table ---- */
html .ftable{width:100%;border-collapse:collapse;font:400 13px/1.5 var(--ui)}
html .ftable th{text-align:left;font:600 10px/1.1 var(--ui);text-transform:uppercase;letter-spacing:.07em;
  color:var(--muted);padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border-strong);white-space:nowrap}
html .ftable td{padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--line);color:var(--fg)}
html .ftable tbody tr:hover{background:color-mix(in oklab,var(--accent) 6%,transparent)}
html .ftable .num,html .ftable td.num{font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right}

/* ---- tabs ---- */
html .ftabs{display:flex;gap:2px;border-bottom:1px solid var(--border);flex-wrap:wrap}
html .ftab{padding:9px 14px;font:600 13px/1 var(--ui);color:var(--muted);text-decoration:none;
  border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .12s,border-color .12s;cursor:pointer}
html .ftab:hover{color:var(--fg)}
html .ftab--active,html .ftab[aria-selected="true"]{color:var(--fg);border-bottom-color:var(--accent)}

/* ---- tap targets on touch devices (Apple HIG 44pt) ------------------------
   Found 2026-08-17 by ops/hig_audit.py, which runs the HIG against Frost's own
   standing rules. Frost had NO rule on hit size, so every control inherited
   --control-h:38px — fine under a mouse, 6pt short under a thumb, and the
   founder uses Frost on his phone.

   Density is deliberate on desktop (frost/CLAUDE.md rule 2), so this does not
   move the desktop number. It raises the target only where a target is a
   finger: `pointer:coarse` is true on phones and tablets, false on every
   trackpad and mouse. Anything with its own hardcoded height gets a min-height
   floor rather than a rewrite, so no layout is redesigned by this block. */
@media (pointer:coarse){
  :root{ --control-h:44px; }
  html button, html [role="button"]:not(tr):not(td):not(th), html input:not([type="checkbox"]):not([type="radio"]),
  html select, html textarea, html .btn, html .chip, html .frost-mode{
    min-height:44px;
  }
  /* A tap target has TWO dimensions. This block set height alone for years, so
     every icon-width control passed a height check and failed a real thumb:
     all 46 failures measured on 2026-09-16 were width, not height. Inputs and
     selects are excluded — they are already full-width or content-sized, and a
     44px floor on a narrow numeric field looks broken. */
  html button, html [role="button"]:not(tr):not(td):not(th), html .btn, html .chip{
    min-width:44px;
  }
  /* Checkboxes and radios stay their drawn size; the LABEL carries the target. */
  html label:has(> input[type="checkbox"]), html label:has(> input[type="radio"]){
    min-height:44px; display:inline-flex; align-items:center;
  }

  /* An ANCHOR doing a button's job needs a button's target.
     The rule above matched `button`, `.btn` and `.chip` and stopped there —
     the same blind spot the button-parity rule exists for, one property along:
     Frost's convention is one `*-btn` class worn by a real <button> AND by an
     <a>, and on a phone the anchor half was 24px while the button half was 44.
     Measured 2026-09-16 on the new GV pages: 46 targets under standard with
     ZERO horizontal overflow — the layouts were right, the touch targets were
     not, and nothing in Frost was running the check that says so. */
  html a[class*="-btn"], html a[class*="-tab"], html a[class*="-chip"],
  html a[class*="-pill"], html button[class*="-btn"]{
    min-height:44px; min-width:44px; display:inline-flex; align-items:center;
    justify-content:center;
  }

  /* Dense by design, and still reachable. A sort header or a cell control in a
     table row is not a primary action, so it takes the 36px table-row standard
     rather than 44 — forcing 44 on every cell is how a dense table becomes
     unreadable, and how an audit gets ignored. */
  html a[class*="-sort"], html a[class*="-cell"], html button[class*="-cell"],
  html td a, html th a{
    min-height:36px; min-width:36px; display:inline-flex; align-items:center;
  }
  html th a{ justify-content:inherit; }
}

/* ---- focus visibility everywhere (a11y) ---- */
html a:focus-visible,html button:focus-visible,html input:focus-visible,
html select:focus-visible,html textarea:focus-visible,html [tabindex]:focus-visible{
  outline:2px solid var(--focus);outline-offset:2px;border-radius:4px;
}

/* ---- Wave-0 cross-cutting utilities (2026-07-18 audit) ---- */
/* Wrap any wide table so it scrolls inside its own box instead of forcing the
   page body to scroll sideways on mobile. Usage: <div class="table-scroll">…</div> */
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
/* Sticky table headers, Frost-wide (C4, founder 2026-10-13, Teams intern chat).
   Every table's header row stays in view while its body scrolls; a page that
   needs nothing special inherits it. The wrapper classes below are the scroll
   boxes tables already live in; capping their height gives the header
   something to stick to (a sticky header inside an unbounded overflow box
   never sticks, because the box never scrolls vertically). */
html thead th{position:sticky;top:0;z-index:2;background:var(--card)}
.table-scroll,.pb-tblwrap,.ftable-wrap,.od-tblwrap{max-height:var(--table-max-h);overflow-y:auto}
.table-scroll > table{min-width:max-content}

/* Semantic status-pill tints — restored after the dark-theme find/replace
   flattened them to one neutral (audit theme T5). Keyed by data-state so a pill
   reads live/ok/warn/bad/dark at a glance, in both Arclight and Bloom. */
.status-pill{display:inline-block;padding:2px 9px;border-radius:999px;
  font:700 10.5px/1.5 var(--ui,-apple-system,sans-serif);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap}
.status-pill[data-state="live"],.status-pill[data-state="ok"],.status-pill[data-state="good"],
.status-pill[data-state="paid"],.status-pill[data-state="done"],.status-pill[data-state="resolved"]{
  color:var(--ok,#1f9d5c);background:color-mix(in srgb,var(--ok,#1f9d5c) 15%,transparent)}
.status-pill[data-state="warn"],.status-pill[data-state="stale"],.status-pill[data-state="pending"],
.status-pill[data-state="due"],.status-pill[data-state="open"]{
  color:var(--warn,#b8860b);background:color-mix(in srgb,var(--warn,#b8860b) 16%,transparent)}
.status-pill[data-state="bad"],.status-pill[data-state="dark"],.status-pill[data-state="overdue"],
.status-pill[data-state="escalated"],.status-pill[data-state="failed"],.status-pill[data-state="unpaid"]{
  color:var(--bad,#c23b3f);background:color-mix(in srgb,var(--bad,#c23b3f) 15%,transparent)}

/* ════════════════════════════════════════════════════════════════════
   CONTAINMENT (founder HARD rule, 2026-08-15) — nothing renders wider
   than the box it lives in.
   "boxes going out of the section, redesign properly and ensure this is
    always consistent in design in Frost."

   THE DEFECT, measured on /sales/couture at 1280px: in the 300px left
   rail the "＋ Add a new client" form painted its inputs, its textarea
   and its Add-client button 36px past the card's right edge. Two
   independent causes, both estate-wide, neither of them couture's:

   1. NO BORDER-BOX ANYWHERE. Not one stylesheet base.html loads declared
      box-sizing (onehouse.css does — base.html does not load it), so every
      box was content-box. The rail's search input at width:100% resolved
      to 266 + 24 padding + 2 border = 292px inside a 266px card: 9px out.
      EVERY `width:100%` control in Frost was overflowing by exactly its
      own padding + border, on every page.
   2. INTRINSIC INPUT WIDTH MEETING min-width:auto. An <input> with no
      width is sized by its `size` attribute (default 20 chars = 129px of
      text + 18px chrome = 147px here), and as a grid/flex item
      min-width:auto refuses to shrink below that. The two-up field row
      therefore contributed 147+147+8 = 302px of min-content into a 266px
      card, the form's auto track blew out to 302px, and every sibling in
      the form stretched to 302 with it.

   WHAT THIS BLOCK GUARANTEES, for every page, without the page knowing:
   padding and border are counted inside a declared width; a form control
   is never wider than its container; a label-wrapped field takes the
   field's width instead of asserting a chars-wide one; grid and flex
   children may shrink instead of pushing out; and a URL / email / handle
   wraps instead of forcing a horizontal scrollbar.

   Every rule below either shrinks an overflow or is a no-op. None of them
   can make a box wider, and none touch colour, type or spacing. Clipping
   (overflow:hidden) is deliberately NOT used — it hides the symptom and
   eats the content with it.
   ════════════════════════════════════════════════════════════════════ */

/* 1 — border-box everywhere. Declared DIRECTLY on every element, not via
       the `* {box-sizing:inherit}` idiom: inheritance travels the flattened
       tree, and the couture form sits inside a <details>, whose children are
       slotted through a UA shadow root that no author selector can reach —
       so `inherit` resolved against the slot's initial content-box and the
       whole form stayed content-box while its parent was border-box.
       Measured, not assumed. Scoped under `html` so it outranks any legacy
       `*{}` reset without !important. */
html,html *,html *::before,html *::after{box-sizing:border-box}

/* 2 — a form control is never wider than its container. max-width can only
       ever shrink a box, so this cannot disturb a control that already
       fits. Covers the intrinsic-width case (`size` attr) and the
       inherited-width case alike. */
html input,html select,html textarea,html button{max-width:100%}

/* 3 — a CLOSED <details> shows its summary and nothing else. 2026-08-20.
       The browser hides a closed disclosure's content with `display:none` in
       its own user-agent sheet, and **any author `display:` on a descendant
       beats it**, because an author rule always outranks the UA sheet. It does
       not matter how specific the author rule is; one declaration anywhere
       resurrects the whole panel.

       On the founder's cockpit, `.fc-learn__list { display: flex }` did exactly
       that: the collapsed "show more" list — eleven learning rows, 1,156px of
       them — was fully laid out and painted the entire time, straight down over
       the "I'm busy" delegation switch below it. The Skip button of one row sat
       exactly on top of the OFF switch, so a click aimed at delegation hit Skip
       instead. It went unseen for two months only because an ancestor happened
       to be an overflow:auto box that clipped the spill; the moment that
       workaround was removed the rows appeared on top of the next block.

       There are 382 `display:` declarations across the console stylesheets and
       any one of them can do this to any <details> in Frost, so the guard
       belongs here, once, where every page inherits it — not in the sheet that
       happened to get caught. `!important` is right precisely because this must
       beat an author declaration; the semantics are not a judgement call, a
       closed disclosure showing its contents is never intended. Where content
       must stay laid out while closed, open the element. */
html details:not([open]) > :not(summary){display:none!important}

/* ── SCROLL TO TOP ─────────────────────────────────────────────────────────
   Founder, 2026-08-18: *"the icon we added for all our website to scroll all
   the way to the top is not there in Frost?"* It IS there — base.html has
   rendered the button since 2026-06-18, with its show-past-240px script. What
   it never had was a single line of CSS anywhere in the console, so it laid out
   as a default `<button>` in the footer flow: present, unstyled, and invisible
   as a control. A feature with no appearance is a feature nobody has.

   mobile_app.css already offsets it above the tab bar, which is the clue that
   it was meant to be fixed-position all along. */
html #scrollTopBtn{
  position:fixed; right:var(--sp-4); bottom:var(--sp-4); z-index:60;
  width:42px; height:42px; border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center;
  /* A DELIBERATELY DARK SURFACE, not a tinted panel. 2026-08-18: the first
     version painted --panel on --panel — a cream disc with a hairline, on
     cream. Every computed-style check passed (fixed, 42x42, opacity 1,
     visible) and the founder still could not find it, twice, because
     "rendered" and "visible" are different questions and only a screenshot
     answers the second. --c-invert-bg implies --c-invert-fg; the arrow is gold
     because gold is what Frost uses to say "this is the control". */
  background:var(--c-invert-bg); color:var(--c-gold-bright);
  border:1px solid var(--c-invert-bd); box-shadow:var(--shadow-2);
  cursor:pointer; padding:0;
  transition:transform .12s ease-out, background-color .12s, color .12s;
}
html #scrollTopBtn:hover{ background:var(--c-gold-solid); color:var(--c-invert-fg);
                          transform:translateY(-2px); }
/* Touch: 44pt is the Apple minimum and this is a one-handed control. */
/* Touch enlarges the button, so the whole ladder above it moves with it —
   ONE formula, not a second hard-coded number that drifts from the first. */
@media (pointer:coarse){ html{ --fab-h:46px; }
                         html #scrollTopBtn{ width:46px; height:46px; } }
html #scrollTopBtn[hidden]{ display:none; }
@media (prefers-reduced-motion: reduce){
  html #scrollTopBtn{ transition:none; }
  html #scrollTopBtn:hover{ transform:none; }
}

/* ── GLOBAL "REPORT A FLAW" ────────────────────────────────────────────────
   Weekend plan item 3 (2026-10-09). Opposite corner from #scrollTopBtn (left,
   not right) so the two never collide. Classes, not inline styles — an
   inline `style=""` outranks every selector in this sheet including the
   `html #id` form scrollTopBtn needed to clear the mobile tab bar, so the
   same mobile override below would have had no way to win the cascade. */
html .frost-flaw-fab{
  position:fixed; left:var(--sp-4); bottom:var(--sp-4); z-index:60;
  display:inline-flex; align-items:center; gap:var(--sp-2);
  padding:0 var(--sp-3); height:var(--control-h); border-radius:999px;
  background:var(--c-invert-bg); color:var(--c-gold-bright);
  border:1px solid var(--c-invert-bd); box-shadow:var(--shadow-2);
  font:600 var(--fs-small)/1 'Inter',system-ui,sans-serif; cursor:pointer;
  transition:transform .12s ease-out;
}
html .frost-flaw-fab:hover{ transform:translateY(-2px); }
html .frost-flaw-panel{
  position:fixed; left:var(--sp-4);
  bottom:calc(var(--sp-4) + var(--control-h) + var(--sp-2) + var(--fab-h) + var(--sp-2)); z-index:61;
  width:min(360px, calc(100vw - 2*var(--sp-4))); box-sizing:border-box;
  background:var(--bg-elevated); border:1px solid var(--border);
  border-radius:var(--radius-2); box-shadow:var(--shadow-2); padding:var(--sp-3);
}
html .frost-flaw-panel[hidden]{ display:none; }
html .frost-flaw-panel__title{ font-weight:600; margin-bottom:var(--sp-2); }
html .frost-flaw-panel__text{
  width:100%; max-width:100%; box-sizing:border-box; resize:vertical;
  border:1px solid var(--border); border-radius:var(--radius-1);
  padding:var(--sp-2); font:inherit; min-width:0;
}
html .frost-flaw-panel__file{ width:100%; max-width:100%; margin-top:var(--sp-2); }
html .frost-flaw-panel__row{
  display:flex; gap:var(--sp-2); margin-top:var(--sp-2);
  align-items:center; flex-wrap:wrap;
}
html .frost-flaw-panel__status{ font-size:var(--fs-tiny); min-width:0; }
html .frost-flaw-send, html .frost-flaw-cancel{
  height:var(--control-h); padding:0 var(--sp-3); border-radius:var(--radius-1);
  font:600 var(--fs-small)/1 inherit; cursor:pointer; box-sizing:border-box;
}
html .frost-flaw-send{ background:var(--c-pos-solid); color:var(--c-invert-fg); border:none; }
html .frost-flaw-cancel{ background:transparent; color:inherit; border:1px solid var(--border); }
@media (prefers-reduced-motion: reduce){ html .frost-flaw-fab:hover{ transform:none; } }

/* 2a — EVERY LINK IS A BRAND COLOUR. ───────────────────────────────────────
       Founder, 2026-08-18: *"the blue colour is not matching the Frost design
       rules — fix everywhere and do not ever use any colour thats not in our
       brand guidelines."*

       He is right and the cause is an omission, not a choice: `frost.css`
       styled `.footer__col a`, `a.fcard` and `a[class*="-btn"]`, and never
       declared a base colour for a plain `<a>`. So every unstyled link fell
       through to the USER AGENT default — #0000EE blue, #551A8B purple once
       visited. Those two are the only colours on any Frost page that nobody
       chose, and the bounded-palette rule could not catch them because they
       appear in no stylesheet.

       So: the base link colour is a token, everywhere, in both themes. Anything
       that deliberately wants another colour still sets its own — `:where()`
       keeps this at zero specificity. */
/* :where() so this contributes ZERO specificity. The first version of this
   rule was `html a:visited{...}`, which at (0,0,1,2) OUTRANKS a component class
   like `.od-tab{color:var(--muted)}` at (0,0,1,0) — so a tab you had clicked
   turned gold and a tab you had not stayed grey, and the nav and the tab strip
   both rendered in two colours depending on where the founder had been. He
   reported it twice in one screenshot pair, correctly, as colour inconsistency.

   A base rule must be the FLOOR, never the ceiling: it kills the browser's blue
   and purple and loses to every class that states its own colour. */
html :where(a){color:var(--link-ink)}
html :where(a:hover){color:var(--link-ink-h)}
html :where(a:visited){color:var(--link-ink)}
html :where(a:visited:hover){color:var(--link-ink-h)}

/* 2b — A BUTTON IS A BUTTON WHATEVER TAG IT IS WEARING. ─────────────────────
       Founder, 2026-08-18, on two screenshots of the same defect: *"button
       spacing not done properly, against our design rules"* and *"some text is
       centred properly and others isnt, fix all permanently."*

       Both were one root cause. Frost's convention is a `*-btn` class shared by
       a real `<button>` and an `<a>` that does the same job — "Resolve all 26"
       beside "Plan a shoot →". Every one of those classes was written for the
       button and inherited by the anchor, and an anchor is not a button:

         · a `<button>` centres its label; an `<a>` does not, so the two sat
           side by side with their text on different axes (screenshot 2);
         · an `<a>` is `display:inline`, so where the class forgot to say
           otherwise it could not sit on a row, wrapped, and stacked with the
           button above it at a different width (screenshot 1);
         · an `<a>` is underlined and link-coloured, so half a toolbar rendered
           as chrome and half as prose.

       Fixing the two pages that were on screen would have left the same bug in
       the other 185 anchors across 15 button classes. This is the permanent
       fix: any anchor whose class carries the house `-btn` suffix gets the
       button's box, whichever sheet defined the class and whether or not that
       sheet remembered. `:where()` contributes ZERO specificity, so a class
       that deliberately overrides any of this still wins without !important. */
html a[class*="-btn"]:where(:not(.od-tab)){
  display:inline-flex;align-items:center;justify-content:center;
  text-decoration:none;text-align:center;
}
/* A BUTTON'S LABEL IS A LABEL, NOT PROSE — 2026-10-08.
   Rule 5 gives every cell `overflow-wrap:break-word` for the longest thing in
   a box (a URL, a SKU). Inside a table that is also what decides the column's
   intrinsic width, so a six-column row at phone width squeezed the Manage cell
   to 51px and broke "Salary & details" between letters over five lines — while
   the scroller sat there unused, because a cell that can break never demands
   more room. Giving the label `nowrap` sets a real floor under the column, the
   table then exceeds the viewport, and `.fo2-scroll` finally engages. The
   `-tab`/`-chip`/`-pill` families are labels for the same reason.
   `:where()` keeps specificity at zero, so a class that wants to wrap still
   wins without !important. */
html :where(a,button,summary)[class*="-btn"],
html :where(a,button,summary)[class*="-tab"],
html :where(a,button,summary)[class*="-chip"],
html :where(a,button,summary)[class*="-pill"]{white-space:nowrap}
/* The colour is separate: a button class that sets its own colour must keep it,
   so this only removes the UA's link colour, and only where the class has not
   spoken.

   2026-10-03 — IT DID NOT DO THAT, and the comment above is why it took so
   long to see. `:where()` zeroes only what is INSIDE it; the
   `a[class*="-btn"]` outside it still counts, so the old selector
   `html a[class*="-btn"]:where(:not([class*="--go"]))` was (0,1,2) and
   outranked EVERY component class at (0,1,0). Far from losing to a class that
   had spoken, it overrode all of them and forced the page's own ink onto any
   anchor-button.

   Measured on /fleet: `.fl-btn--act` declares cream on the accent blue
   (`--c-invert-fg` on `--c-accent-solid`) and rendered #0E1116 on #23496E —
   **2.03:1** — because the variant's colour was discarded and the button
   inherited the body ink, while its `background` (which this rule does not
   touch) still applied. Dark ink on a saturated blue, exactly the pairing the
   token spine forbids.

   The `:not([class*="--go"])` carve-out is the tell: someone hit this once,
   on one naming convention, and exempted that one suffix instead of fixing the
   specificity. `--act`, `--primary` and every other variant kept losing.

   The whole compound now sits inside `:where()`, so it is (0,0,1): still later
   than — and therefore still beating — `html :where(a){color:var(--link-ink)}`
   at the same specificity, so the UA blue stays dead, and now genuinely
   beaten by any class that states its own colour. The carve-out is deleted
   because it has nothing left to protect. */
html :where(a[class*="-btn"]){color:inherit}
html a[class*="-btn"]:hover{text-decoration:none}

/* 2b — A BUTTON LABEL NEVER BREAKS BETWEEN LETTERS. 2026-09-22: `.ipd-btn`
   on /gv/ip-rights rendered "Paperwork" down FIVE lines inside a 44px box at
   phone width. No stylesheet declares that and no audit could see it —
   contrast is fine, nothing overflows, every value is a token. The cause is
   the containment rule itself: `overflow-wrap:anywhere` (rule 4, so URLs and
   Instagram handles wrap) lowers the element's MIN-CONTENT width to one
   character, so a squeezed flex child is free to shrink to nothing and the
   only place left to break is mid-word. A rigid sibling (`flex-shrink:0`)
   supplies the squeeze.

   So every house button gets a floor and truncates instead of stacking. `ch`
   is relative to the element's own font, so this holds at any text size, and
   `max-width:100%` (already on most of these classes) keeps the floor from
   overflowing a genuinely narrow parent. Same reasoning as the button-parity
   rule: declare it once, centrally, so a class written next year inherits it
   without its author knowing this rule exists. */
html a[class*="-btn"], html button[class*="-btn"]{
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  min-width:min(100%, 8ch); flex-shrink:1;
}

/* 2c — two controls side by side always have a gap between them. The
       collections table put `Products ▾` and `Cockpit →` in one cell with no
       container, so they touched. A gap that has to be remembered per cell is a
       gap that will be forgotten; this is the floor. */
html :where(td,th) > a[class*="-btn"] + a[class*="-btn"],
html :where(td,th) > a[class*="-btn"] + button[class*="-btn"],
html :where(td,th) > button[class*="-btn"] + a[class*="-btn"],
html :where(td,th) > button[class*="-btn"] + button[class*="-btn"]{
  margin-left:var(--sp-1);
}

/* 2d — `.btn` IS A HOUSE CLASS AND THE HOUSE PAINTS IT. ─────────────────────
       Measured 2026-09-21 on the live box: every `<button class="btn">` in
       Frost renders `13.3333px Arial`, a 2px `rgba(118,118,118,.3)` border,
       `border-radius:0` and `padding:1px 6px`. That is not a Frost button —
       it is the Chromium user-agent button, because NO LOADED STYLESHEET
       DEFINES `.btn` AT ALL. 197 elements across 137 templates.

       How it went missing: `onehouse.css` carried the only definition
       (`background:var(--accent); color:var(--parchment)` — the pairing
       `theme_contrast.css` still vouches for in its own comment, "cream on
       bronze, 5.6:1"). Commit a5888be8 (18 Aug, "design: the rules stopped
       being advice") deleted that sheet and the class was never re-homed. The
       sheets that survived only ever styled its MODIFIERS — `.btn--ghost`,
       `.btn--quiet`, the brand override — so a grep for `.btn` kept finding
       hits and the base was gone.

       This is the same defect class as the browser blue two rules above: a
       colour, a font and a box that NOBODY CHOSE. The bounded-palette audit is
       blind to it by construction, because there is nothing in any sheet to
       inspect. `:where()` keeps it at zero specificity, so every modifier and
       every page that already states its own look still wins. */
html :where(.btn){
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-1);
  padding:var(--sp-2) var(--sp-3);
  border:1px solid transparent;border-radius:var(--radius-2);
  background:var(--accent);color:var(--parchment);
  font-family:var(--ui);font-size:var(--fs-small);font-weight:600;line-height:1.3;
  cursor:pointer;text-decoration:none;white-space:nowrap;box-sizing:border-box;
}
html :where(.btn:hover){filter:brightness(1.05)}
html :where(.btn--quiet,.btn--ghost){background:transparent;border-color:var(--accent)}
html :where(.btn--sm){padding:var(--sp-1) var(--sp-2);font-size:var(--fs-tiny)}

/* 2e — A DISABLED CONTROL IS A STATE THE DESIGN SYSTEM PAINTS. ─────────────
       The two WCAG AA failures `contrast_audit` had been reporting for a
       fortnight — "Send" on /chat at 1.98:1 and "Generate IRN" on
       /gst-invoices at 1.97:1 — were BOTH this, and neither colour appears in
       any stylesheet. Chromium paints a disabled button
       `color:rgba(16,16,16,.3)` on `background:rgba(239,239,239,.3)`; that
       composites to grey-on-cream at 1.97:1 and there is no sheet to grep.

       The house answer is the neutral tint family, which is a real, declared,
       measured pairing in every theme — parchment 5.07:1, Arclight dark
       5.91:1, Bloom light 4.89:1 — so "off" reads as off without the label
       going to fog.

       NOT `opacity`. Fading the whole control halves its contrast by
       definition, which is how a compliant button becomes a 2:1 button the
       moment it is disabled; `.fbtn`, `.od-btn`, `.pc-judge-btn` and
       `.tp__tick` all did exactly that and are repointed here. Opacity is also
       invisible to any check that reads declarations, for the same reason the
       UA default is.

       `:where()` = zero specificity: a class that genuinely wants its own
       disabled look still wins without `!important`. */
html :where(button,[role="button"],.btn,.fbtn,[class*="-btn"],[class*="_btn"],
            input[type="submit"],input[type="button"],input[type="reset"],select,
            textarea,input):where(:disabled,[disabled],[aria-disabled="true"]){
  background:var(--c-neutral-bg);
  color:var(--c-neutral-fg);
  border-color:var(--c-neutral-bd);
  cursor:not-allowed;
  filter:none;
  -webkit-text-fill-color:var(--c-neutral-fg);
}
/* A disabled control's own children inherit the same ink — the /chat defect
   was a `<span>` INSIDE the button, so painting only the button would have
   left the label exactly where it was. */
html :where(button,[role="button"],.btn,.fbtn,[class*="-btn"],[class*="_btn"]):where(:disabled,[disabled],[aria-disabled="true"]) *{
  color:inherit;-webkit-text-fill-color:inherit;
}

/* 3 — a label-wrapped field fills its field. Frost's form convention is
       <label>Caption<input></label>, and that input is a FIELD, not an
       inline chip: it should take the width of the field it labels, not
       the width of 20 characters of whatever font it inherited. Anything
       the author sized on purpose (size=, cols=) and anything that is not
       a text field (checkbox, radio, file, range, colour, the buttons)
       keeps its own width. Standalone inputs — toolbar filters, in-table
       cells, search boxes not wrapped in a label — are untouched.

       Every exclusion sits inside :where(), which contributes ZERO
       specificity — otherwise a chain of eight :not([type=…]) would outrank
       any page's own `.thing input{width:…}` and this safety net would start
       dictating widths it knows nothing about. Measured: without :where() it
       overrode `.sp-in{min-width:90px}` on /sales and squeezed the planner's
       inputs to 51px. A page always wins. */
html label > input:where(:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=color]):not([type=submit]):not([type=button]):not([type=image]):not([size])),
html label > textarea:where(:not([cols])){width:100%}

/* 4 — grid and flex children may shrink. `min-width:auto` is the reason a
       track refuses to go below its content's intrinsic width — cause (2)
       above. On a normal-flow block min-width:auto already computes to 0,
       so this is a no-op everywhere EXCEPT where the element actually is a
       grid or flex item, which is exactly where the defect lives. */
html label,html form,html fieldset{min-width:0}
html input,html select,html textarea{min-width:0}
/* 4b — …but a control that may shrink must not shrink into uselessness.
        Measured on /cockpit at 390px: with a plain min-width:0 the target
        picker collapsed from 255px to 63px — contained, and unusable.
        `min(100%,10ch)` is a floor of ten characters that can itself never
        exceed the container, so it protects legibility where there is room
        and yields completely where there is not. Never overflows. Controls
        the author sized on purpose (size=) and non-text controls
        (checkbox, radio, colour, range, file) keep their own minimum. */
html input:where(:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=color]):not([type=range]):not([type=file]):not([type=submit]):not([type=button]):not([size])),
html select,html textarea{min-width:min(100%,10ch)}

/* 4c — a TEXT element carrying DATA is a flex child too. Rule 4 above only
        ever covered form controls, and rule 5 below only ever covered cells,
        links and code — so a `<span>` holding a document title fell through
        both. Measured on /real-estate 2026-09-18: `.hp-docs__label{flex:none}`
        inside a flex row reached **2475px** in a 1440px viewport, hidden by an
        ancestor's overflow:hidden so the page never scrolled and every
        scrollWidth-based check passed it.

        THE TRAP, and the reason this needs its own rule rather than an extra
        selector on rule 4: `min-width:0` ALONE DOES NOTHING HERE. `flex:none`
        is `flex-shrink:0`, and an item that may not shrink will not shrink no
        matter how low its min-width goes. The shrink factor is the fix; the
        min-width only lets the shrink reach below the content's intrinsic
        width once it is allowed at all.

        Shrinking costs nothing when there is room — flex-shrink only engages
        on an overflowing line — so this is inert on every row that already
        fits, and it is exactly the rows that do not fit where it acts.

        Scoped to the house's own naming for data-bearing text. Three sibling
        templates had copy-pasted the identical `…-docs__label{flex:none}` line
        (real-estate, sidhesh, fundraising) and only one was on the Sentinel
        surface list; a net here is what makes the other two — and the one
        written next year — correct without their authors knowing this rule
        exists. Same idiom as the `a[class*="-btn"]` button-parity net. */
html [class*="__label"],html [class*="__title"],
html [class*="__name"],html [class*="__path"]{min-width:0;flex-shrink:1}

/* 4d — A GRID TRACK MAY SHRINK TOO, and `1fr` on its own will not let it.
        Measured 2026-09-18 on /fundraising and /sidhesh at 390px: 78 and 57
        elements overflowing, every one of them clipped by an ancestor so the
        page looked fine and was not — the same invisible class as the
        /real-estate find that this rule block was opened for.

        THE CAUSE, and it is a spelling, not a layout mistake. `1fr` is
        shorthand for `minmax(auto,1fr)`, and that `auto` minimum resolves to
        the ITEM'S MIN-CONTENT SIZE. So a single-column responsive grid —
        `@media (max-width:900px){ .fr-grid{grid-template-columns:1fr} }`, the
        most ordinary line in this codebase — hands the track a floor of
        whatever its widest child happens to need. On /fundraising the floor
        came out at 859px inside a 264px box; the grid item, its heading, its
        list and every row in it were painted 532px past the card's right edge.
        The media query had done its job and the track ignored it.

        WHY THIS IS THE CHILD'S min-width AND NOT THE TRACK'S SPELLING. The
        track's `auto` minimum is the item's min-content contribution, which is
        clamped by the item's own `min-width`. Give the item `min-width:0` and
        the floor goes to zero, `1fr` resolves to its share of the container,
        and the content wraps. Proven by injection before it was written: the
        selector below took /fundraising 78 → 0 and /sidhesh 57 → 0 with
        nothing else changed. There are 299 bare-`fr` track declarations across
        the console stylesheets, so the alternative — rewriting every one to
        `minmax(0,1fr)` — is 299 edits that a page written next month can
        silently reopen. One net here cannot be forgotten.

        `min-width:auto` computes to 0 on anything that is NOT a flex or grid
        item, so on the overwhelming majority of elements this declaration is
        literally inert; it acts only where the defect can exist. Everything is
        wrapped in `:where()`, which contributes ZERO specificity, so it sits
        below every author rule in the cascade — `.table-scroll > table{
        min-width:max-content}` above still wins, and so does any page that has
        set a deliberate floor. Form controls are NOT in this list: they are
        governed by rules 4 and 4b, which give them a legibility floor of
        `min(100%,10ch)` instead of zero. Nor is `<table>`: a table's
        convention in Frost is a scroller, not a squeeze. */
html :where(div,section,article,aside,nav,header,footer,main,
            figure,figcaption,ul,ol,li,dl,dt,dd,p,
            h1,h2,h3,h4,h5,h6,details,summary){min-width:0}

/* 5 — long unbroken tokens wrap instead of pushing the page sideways. This
       system is full of them: URLs, emails, Instagram handles, SKUs.
       `break-word` on cells (no effect on intrinsic column sizing — it only
       breaks a token that would otherwise overflow); `anywhere` reserved
       for the elements whose content IS a single long token. */
html td,html th{overflow-wrap:break-word}
html a[href^="http"],html a[href^="mailto:"],html a[href^="tel:"],html code{overflow-wrap:anywhere}
/* 5b — the same distinction for the data-bearing text of rule 4c: a label, a
        title or a name is prose that may contain one long token, so it breaks
        only where it would otherwise overflow; a `__path` IS a single long
        token (an iCloud/OneDrive path, a URL) and breaks anywhere. */
html [class*="__label"],html [class*="__title"],
html [class*="__name"]{overflow-wrap:break-word}
html [class*="__path"]{overflow-wrap:anywhere}

/* ── BACK TO THEIA ──────────────────────────────────────────────────────────
   Founder, 2026-09-21: *"once you click on any external report or link there
   is no option to go back to the theia portal."* An iOS Home Screen app has no
   browser chrome at all — no back gesture to the previous site, no address
   bar — so a link out of `/theia/live` strands him on whatever page arrives.
   This is the door home, rendered by `base.html` on any page opened with
   `?from=theia`.

   Sticky rather than fixed: fixed would sit on top of the page's own first
   element on a short screen, and a bar that covers the thing you came to read
   is its own defect. Sticky keeps it reachable without ever overlapping.

   Note it also styles `.impersonation-banner`, which has existed in
   `base.html` since the view-as feature shipped and was declared in NO
   stylesheet — a raw unstyled div, the same class of defect as `.btn` being
   undefined for a month. Found while looking for a pattern to copy. */
.theia-return,
.impersonation-banner {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--sp-2); padding: var(--sp-2) var(--pad-page);
  background: var(--c-gold-bg);
  border-bottom: 1px solid var(--c-gold-bd);
  color: var(--c-gold-fg);
  font: 600 var(--fs-tiny)/1.4 var(--ui);
  box-sizing: border-box; max-width: 100%;
}
.theia-return__link,
.impersonation-banner .imp-return {
  font-weight: 700; color: var(--c-gold-fg);
  text-decoration: underline; white-space: nowrap;
}
.theia-return__what,
.impersonation-banner span {
  color: var(--c-gold-fg); font-weight: 400;
  min-width: 0; overflow-wrap: break-word;
}

/* Idle sign-out warning (staff launch 2026-10-12). Raised by
   static/js/idle_timeout.js at minute 28 of no activity; the session ends at
   30 on the server whatever the page does. Fixed to the bottom edge so it
   never covers the page's own heading, and it carries ONE action. */
.idle-warn {
  position: fixed; left: var(--sp-3); right: var(--sp-3); bottom: var(--sp-3);
  z-index: 90; margin: 0 auto; max-width: 32rem;
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
  padding: var(--pad-card);
  background: var(--c-gold-bg); color: var(--c-gold-fg);
  border: 1px solid var(--c-gold-bd); border-radius: var(--radius-1);
  box-shadow: var(--shadow-2);
  font: 600 var(--fs-small)/1.4 var(--ui);
  box-sizing: border-box;
}
.idle-warn[hidden] { display: none; }
.idle-warn__text { flex: 1 1 12rem; min-width: 0; overflow-wrap: break-word; }
.idle-warn__btn {
  min-height: var(--control-h); padding: 0 var(--sp-4);
  background: var(--c-gold-solid); color: var(--c-invert-fg);
  border: 0; border-radius: var(--radius-1);
  font: 700 var(--fs-small)/1 var(--ui); cursor: pointer;
}

/* ── KPI-CENTRE: BOX CONTENT IS CENTRED (2026-10-21, founder) ─────────────────
   *"ensure that all information in any headings or such boxes are centre
   aligned and perfectly placed in the centre of these boxes aligned perfectly
   in the middle in height and width and never flowing out of the box for any
   reason whatsoever."*  One net for every KPI / stat box class in Frost, so a
   class written next year inherits it. `html:root` out-ranks a page sheet's
   `.ui-v14 .x` without !important. Equal heights come from the grid's stretch;
   a long figure shrinks with its box (cqi) instead of being clipped.
   `ops/hig_audit.py::check_kpi_centring` fails if this net goes missing
   or any sheet re-aligns a KPI/stat box to the left. */
html:root :is([class$="-kpi"], [class*="-kpi "], .fkpi, .fc-stat){
  display:flex; flex-direction:column; align-items:stretch; justify-content:center;
  text-align:center; box-sizing:border-box; min-width:0; max-width:100%;
  container-type:inline-size;
}
html:root :is([class$="-kpi"], [class*="-kpi "], .fkpi, .fc-stat) > *{
  min-width:0; max-width:100%; text-align:center; text-wrap:balance;
}
html:root :is(.v14-kpis, .dc-kpis) { align-items:stretch; grid-auto-rows:1fr; }
html:root .v14-kpi__v{ font-size:clamp(var(--fs-base), 8cqi, var(--fs-xl)); }


/* ════════════════════════════════════════════════════════════════════════════
   V14 KIT — the shared Frost look (21 Oct 2026, founder: "Proceed with V14 …
   all tables and boxes designed how I asked … maximum motion … 3D").
   Cream ground, white rounded cards, ONE gold→ink hero per page, pills for
   every action, 40px table rows, tabular figures. Tokens only.
   Markup comes from the macros in templates/_dept_cockpit_kit.html — see
   docs/V14_DESIGN_KIT.md. Unscoped so every page can use it; specificity is
   one class (or `html .x`) so the Family Office `.ui-v14 .x` page still wins.
   ════════════════════════════════════════════════════════════════════════════ */
html .v14-num, html .v14-dt .num { font-family:var(--num); font-variant-numeric:tabular-nums; }

/* ---- hero: gold → ink, centred title, 3-6 KPI boxes ---------------------- */
html .v14-mast { background:var(--grad-gold); color:var(--v14-on-grad);
  border-radius:var(--radius-3); box-shadow:var(--shadow-lift);
  padding:var(--sp-5) var(--sp-5) var(--sp-4); margin:0 0 var(--sp-section);
  display:flex; flex-direction:column; align-items:center; gap:var(--sp-3);
  text-align:center; min-width:0; box-sizing:border-box; }
html .v14-mast__eyebrow { font-size:var(--fs-tiny); font-weight:700; letter-spacing:.08em;
  text-transform:uppercase; color:var(--v14-on-grad-muted); margin:0; }
html .v14-mast__title { font-family:var(--display); font-size:var(--fs-display); font-weight:600;
  line-height:1.15; margin:0; color:var(--v14-on-grad); text-wrap:balance; max-width:100%; }
html .v14-mast__sub { font-size:var(--fs-small); color:var(--v14-on-grad-muted); margin:0;
  max-width:64ch; text-wrap:balance; }
html .v14-mast__acts { display:flex; flex-wrap:wrap; justify-content:center; gap:var(--sp-2); }
html .v14-mast__acts .v14-pill { background:var(--v14-glass); border-color:var(--v14-glass-bd); color:var(--v14-on-grad); }
html .v14-mast__acts .v14-pill--go { background:var(--c-gold-bright); border-color:var(--c-gold-bright); color:var(--ink); }
html .v14-mast__kpis { display:grid; gap:var(--sp-3); width:100%; margin-top:var(--sp-2);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  align-items:stretch; grid-auto-rows:1fr; }
html .v14-hero-kpi { background:var(--v14-glass); border:1px solid var(--v14-glass-bd);
  border-radius:var(--radius-2); padding:var(--pad-card); gap:var(--sp-1);
  backdrop-filter:blur(6px); }
html .v14-hero-kpi__k { font-size:var(--fs-tiny); font-weight:600; color:var(--v14-on-grad-muted); }
html .v14-hero-kpi__v { font-family:var(--num); font-variant-numeric:tabular-nums; font-weight:700;
  font-size:clamp(var(--fs-base), 9cqi, var(--fs-xl)); line-height:1.1; color:var(--v14-on-grad);
  white-space:nowrap; }
html .v14-hero-kpi__s { font-size:var(--fs-tiny); color:var(--v14-on-grad-muted); }
/* The hero ground is already gold, so gold ink on it reads 3.5:1. The gold
   card keeps the hero's own ink and carries its accent on the border instead. */
html .v14-hero-kpi--gold { border-color:var(--c-gold-bright); }
html .v14-hero-kpi--gold .v14-hero-kpi__v { color:var(--v14-on-grad); }

/* ---- pills: every action, button or link ---------------------------------- */
html .v14-pill { display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-1);
  min-height:var(--control-h); padding:0 var(--sp-4); border-radius:var(--radius-pill);
  border:1px solid var(--v14-line); background:var(--v14-surface); color:var(--v14-ink);
  font:600 var(--fs-small)/1 var(--ui); text-decoration:none; cursor:pointer; white-space:nowrap;
  box-sizing:border-box; max-width:100%; transition:transform .15s ease, box-shadow .15s ease; }
html .v14-pill:hover { transform:translateY(-1px); box-shadow:var(--shadow-soft); color:var(--v14-ink); }
html .v14-pill:visited { color:var(--v14-ink); }
html .v14-pill--go, html .v14-pill--go:visited { background:var(--ink); border-color:var(--ink); color:var(--c-invert-fg); }
html .v14-pill--go:hover { color:var(--c-invert-fg); }
html .v14-pill--sm { min-height:calc(var(--sp-6) - var(--sp-1)); padding:0 var(--sp-3); font-size:var(--fs-tiny); }
html .v14-pill:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }

/* ---- chips (status, tone) ------------------------------------------------ */
html .v14-chip { display:inline-flex; align-items:center; gap:var(--sp-1); padding:var(--pad-chip);
  border-radius:var(--radius-pill); font-size:var(--fs-tiny); font-weight:600; line-height:1.3;
  border:1px solid var(--c-neutral-bd); background:var(--c-neutral-bg); color:var(--c-neutral-fg); white-space:nowrap; }
html .v14-chip--gold   { border-color:var(--c-gold-bd);   background:var(--c-gold-bg);   color:var(--c-gold-fg); }
html .v14-chip--pos    { border-color:var(--c-pos-bd);    background:var(--c-pos-bg);    color:var(--c-pos-fg); }
html .v14-chip--accent { border-color:var(--c-accent-bd); background:var(--c-accent-bg); color:var(--c-accent-fg); }
html .v14-chip--violet { border-color:var(--c-violet-bd); background:var(--c-violet-bg); color:var(--c-violet-fg); }
html .v14-chip--cyan   { border-color:var(--c-cyan-bd);   background:var(--c-cyan-bg);   color:var(--c-cyan-fg); }
html .v14-chip--ink    { border-color:var(--c-invert-bd); background:var(--c-invert-bg); color:var(--c-invert-fg); }
/* No red in V14: "late" and "danger" read as gold (founder, 21 Oct). */
html .v14-chip--late, html .v14-chip--danger { border-color:var(--c-gold-bd); background:var(--c-gold-bg); color:var(--c-gold-fg); }

/* ---- section card --------------------------------------------------------- */
html .v14-sec { background:var(--v14-surface); border:1px solid var(--v14-line);
  border-radius:var(--radius-3); box-shadow:var(--shadow-soft); padding:var(--pad-card);
  margin:0 0 var(--sp-section); min-width:0; box-sizing:border-box; }
html .v14-sec__head { display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between;
  gap:var(--sp-2); margin:0 0 var(--sp-3); }
html .v14-sec__head h2 { font-family:var(--display); font-size:var(--fs-lg); margin:0; }
html .v14-sec__meta { font-size:var(--fs-tiny); color:var(--v14-muted); }

/* ---- filter bar (GET form; filters live in the URL) ---------------------- */
html .v14-fbar { display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--sp-2); margin:0 0 var(--sp-3); min-width:0; }
html .v14-fld { display:flex; flex-direction:column; gap:var(--sp-1); min-width:0; flex:0 1 12rem;
  font-size:var(--fs-tiny); font-weight:600; color:var(--v14-muted); }
html .v14-fld--grow { flex:1 1 16rem; }
html .v14-fld :is(input, select) { min-height:var(--control-h); border:1px solid var(--v14-line);
  border-radius:var(--radius-pill); background:var(--v14-surface); color:var(--v14-ink);
  padding:0 var(--sp-3); font:500 var(--fs-small)/1.2 var(--ui); max-width:100%; box-sizing:border-box; }

/* ---- data table: 40px rows, sticky header, header sort, tabular figures -- */
html .v14-tblwrap { overflow:auto; max-block-size:min(75vh, calc(var(--sp-7) * 16));
  border:1px solid var(--v14-line); border-radius:var(--radius-2); background:var(--v14-surface);
  min-width:0; -webkit-overflow-scrolling:touch; }
html .v14-dt { width:100%; border-collapse:separate; border-spacing:0; font-size:var(--fs-small); }
html .v14-dt th { position:sticky; top:0; z-index:1; background:var(--v14-soft); color:var(--v14-muted);
  font-size:var(--fs-tiny); font-weight:700; text-align:start; white-space:nowrap;
  padding:0 var(--sp-3); block-size:calc(var(--sp-6) + var(--sp-2)); border-bottom:1px solid var(--v14-line); }
/* 40px rows — the V14 standard; ops/hig_audit.py::check_table_row_height. */
html .v14-dt td { block-size:calc(var(--sp-6) + var(--sp-2)); padding:0 var(--sp-3);
  border-bottom:1px solid var(--v14-line); vertical-align:middle; color:var(--v14-ink); }
html .v14-dt :is(th, td).num { text-align:end; }
html .v14-dt tbody tr { transition:background-color .15s ease; }
html .v14-dt tbody tr:hover td { background:var(--v14-soft); }
html .v14-dt tbody tr:last-child td { border-bottom:0; }
html .v14-dt tfoot td { position:sticky; bottom:0; background:var(--v14-soft); font-weight:700;
  border-top:1px solid var(--v14-line); border-bottom:0; }
html .v14-dt tr.v14-row--late td:first-child { box-shadow:inset 3px 0 0 var(--c-gold-solid); }
html .v14-dt tr.v14-row--muted td { color:var(--v14-muted); }
html .v14-dt td.v14-dt__act { text-align:end; white-space:nowrap; }
/* CELL-CENTRE NET (founder 2026-10-26: "centre align and spacing rules not being followed").
   Three things the founder kept seeing on the filing grid, closed for every table at once:
   1. a header or cell sits at the TOP of its box (a legacy `.fo2-tbl` said vertical-align:top);
   2. a cell holding only a control, chip or mark glyph hangs at the left while its siblings centre;
   3. an empty-looking banner whose only content is the "?" help icon.
   A column of marks / acts / status carries `.v14-dt__c` on its th AND its td — static/js/table_centre.js
   tags it (a bare "—" filler and a header cannot be seen by CSS). ops/hig_audit.py::check_cell_centre + the rendered audit
   (ops/viewport_overflow_probe.py --design, sentinel.checks.rendered_design_audit). */
html table :is(th, td) { vertical-align:middle; }
html table.v14-dt__mid > :is(thead, tbody, tfoot) > tr > :is(th, td) { vertical-align:middle; }
/* CELL-MARGIN NET (2026-10-26): a margin on the first or last child of a cell
   shifts the content off the cell's centre; the cell's own padding is the only spacing. */
html table.v14-dt__mid > :is(thead, tbody, tfoot) > tr > :is(th, td) > :first-child { margin-block-start:0; }
html table.v14-dt__mid > :is(thead, tbody, tfoot) > tr > :is(th, td) > :last-child { margin-block-end:0; }
html table.v14-dt__mid > :is(thead, tbody, tfoot) > tr > :is(th, td) > input[type="checkbox"]:only-child { display:block; margin-block:0; margin-inline:auto; }
html table :is(th, td).v14-dt__c.v14-dt__c,
html :is(.v14-dt, .fo2-tbl) td:has(> :is([class*="-btn"], [class*="-pill"], [class*="-chip"], [class*="-badge"], [class*="pb-cell"], [class*="-tag"], button):only-child) { text-align:center; }
html :is(.v14-dt, .fo2-tbl) td:has(> details:only-child > summary:first-child) { text-align:center; }
html summary:is([class*="-btn"], [class*="-pill"]) { white-space:normal; overflow-wrap:anywhere; max-inline-size:100%; box-sizing:border-box; }
html table td.v14-dt__c :is(form, .ba-act) { justify-content:center; align-items:center; }
html .fo2-sec__head > .fo2-sec__meta { flex:1 1 100%; text-align:start; white-space:normal; overflow-wrap:anywhere; min-inline-size:0; }
html :is(.fo2-gap:has(> .v14-why:only-child), .v14-why-only:not(td, th)) { display:flex; justify-content:flex-end; align-items:center;
  border:0; background:none; padding:0; margin:0 0 var(--sp-tight); }
/* CONTAINMENT NET for chips, pills, status and buttons: wider than the parent / text spilling its own box
   is the rendered audit's overflow-container + overflow-text. One rule for every `*-chip` etc. written next year. */
html :is([class*="-chip"], [class*="-pill"], [class*="-status"], [class*="-badge"], [class*="-tag"], [class*="-btn"], .btn) {
  max-inline-size:100%; box-sizing:border-box; overflow-wrap:anywhere; }
html :is([class*="-chip"], [class*="-pill"], [class*="-status"], [class*="-badge"], [class*="-tag"]) { white-space:normal; }
/* In a table cell a chip never breaks mid-word (the "anywhere" above shrinks the column to one letter wide and stretches the row). */
html td :is([class*="-chip"], [class*="-pill"], [class*="-status"], [class*="-badge"], [class*="-tag"]) { overflow-wrap:normal; word-break:normal; white-space:nowrap; }
html a.v14-sorth, html a.v14-sorth:visited { color:inherit; text-decoration:none; display:inline-flex;
  align-items:center; gap:var(--sp-1); }
html a.v14-sorth::after { content:"\2195"; opacity:.4; }
html th[aria-sort="ascending"] a.v14-sorth::after { content:"\2191"; opacity:1; color:var(--c-gold-fg); }
html th[aria-sort="descending"] a.v14-sorth::after { content:"\2193"; opacity:1; color:var(--c-gold-fg); }
html th[aria-sort] a.v14-sorth { color:var(--v14-ink); }
html .v14-dt__empty td { text-align:center; color:var(--v14-muted); block-size:calc(var(--sp-7) * 2); }
/* ROW RHYTHM (founder 2026-10-26: "awkward box spacings, some big some small"). Every
   data-table row is ONE line: cells never wrap, a long value is cut with an ellipsis
   (the cell carries a title), and anything bigger lives behind "View more" (a closed
   <details class="v14-more"> in the cell, or a tr.v14-dt__more detail row opened by
   .v14-more-btn). Applies to every .v14-dt and every legacy table that wears it, so a
   table written next year inherits it. ops/hig_audit.py::check_table_row_rhythm. */
html :is(.v14-dt, .v14-dt__one) tbody > tr:not(.v14-dt__more) > td { white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  max-inline-size:calc(var(--sp-7) * 6); vertical-align:middle; }
html :is(.v14-dt, .v14-dt__one) tbody > tr:not(.v14-dt__more) > td:is(.v14-dt__act, .ba-mono, .num, .ba-num) { max-inline-size:none; overflow:visible; }
html :is(.v14-dt, .v14-dt__one) tbody > tr:not(.v14-dt__more) > td .ba-act { flex-wrap:nowrap; align-items:center; }
html :is(.v14-dt, .v14-dt__one) tbody > tr:not(.v14-dt__more) > td :is(input, select, textarea) { max-inline-size:100%; block-size:var(--control-h); resize:none; }
html :is(.v14-dt, .v14-dt__one) tbody > tr:not(.v14-dt__more):hover { background:transparent; }
/* One-line rows: a cell's stacked lines (name over role, label over note) sit side by side, so a row is one line high. */
html .v14-dt__one tbody > tr:not(.v14-dt__more) > td > :is(div, p, small, span, strong, em):not([class*="-btn"]):not(details) { display:inline; }
html .v14-dt__one tbody > tr:not(.v14-dt__more) > td > br { display:none; }
html .v14-dt tr.v14-dt__more[hidden] { display:none; }
html .v14-dt tr.v14-dt__more > td { block-size:auto; white-space:normal; padding:var(--sp-4) var(--sp-5);
  background:var(--v14-soft); border-bottom:1px solid var(--v14-line); }
html .v14-more__grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(calc(var(--sp-7) * 4), 1fr));
  gap:var(--sp-4); margin:0; }
html .v14-more__grid > div { min-width:0; }
html .v14-more__grid dt { font-size:var(--fs-micro); font-weight:700; text-transform:uppercase; color:var(--v14-muted);
  margin:0 0 var(--sp-1); }
html .v14-more__grid dd { margin:0; font-size:var(--fs-small); color:var(--v14-ink); overflow-wrap:anywhere; }
/* inline disclosure inside a cell: closed = its summary and nothing else (one line) */
html details.v14-more { display:inline-block; vertical-align:middle; }
/* A title that sits beside its View more is capped so the summary always has room: a long link
   was as wide as the clipped cell and the summary landed on top of it (/ipo, /gv/agreements). */
html td:has(> details.v14-more) > :is(a, strong, b, span, div):not(details) { display:inline-block; max-inline-size:calc(var(--sp-7) * 4);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle; }
html td:has(> details.v14-more[open]) > :is(a, strong, b, span, div):not(details) { display:block; max-inline-size:100%; white-space:normal; }
html details.v14-more > summary { cursor:pointer; color:var(--c-gold-fg); font-size:var(--fs-tiny); font-weight:600;
  list-style:none; white-space:nowrap; }
html details.v14-more > summary::-webkit-details-marker { display:none; }
html details.v14-more[open] { display:block; white-space:normal; }
html details.v14-more > .v14-more__body { display:grid; gap:var(--sp-1); padding:var(--sp-2) 0; }

/* ---- pager ---------------------------------------------------------------- */
html .v14-pager { display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
  gap:var(--sp-1); margin:var(--sp-3) 0 0; }
html .v14-pg, html .v14-pg:visited { min-width:var(--control-h); min-height:var(--control-h);
  display:inline-flex; align-items:center; justify-content:center; padding:0 var(--sp-2);
  border-radius:var(--radius-pill); border:1px solid var(--v14-line); color:var(--v14-ink);
  text-decoration:none; font:600 var(--fs-small)/1 var(--num); font-variant-numeric:tabular-nums; box-sizing:border-box; }
html .v14-pg.is-on { background:var(--ink); border-color:var(--ink); color:var(--c-invert-fg); }
html .v14-pg.is-gap { border-color:transparent; }
html .v14-pager__n { font-size:var(--fs-tiny); color:var(--v14-muted); margin-inline-start:var(--sp-2); }

/* ---- 3D bar chart: --all on desktop, --data (months with data) on phone - */
html .v14-c3d { position:relative; min-width:0; }
html .v14-c3d svg { display:block; width:100%; height:auto; overflow:visible; }
html .v14-c3d__data { display:none; }
html .v14-c3d__note { font-size:var(--fs-tiny); color:var(--v14-muted); margin:var(--sp-2) 0 0; text-align:center; }
@media (max-width:40rem) {
  html .v14-c3d__all { display:none; }
  html .v14-c3d__data { display:block; }
}
html .v14-c3d .v14-stop-a { stop-color:var(--c-gold-bright); }
html .v14-c3d .v14-stop-b { stop-color:var(--c-gold-solid); }
html .v14-c3d .v14-stop-ink-a { stop-color:var(--c-gold-solid); }
html .v14-c3d .v14-stop-ink-b { stop-color:var(--ink); }
html .v14-c3d__axis { stroke:var(--v14-line); }
html .v14-c3d__target { stroke:var(--ink); stroke-dasharray:4 4; }
html .v14-c3d__lbl { fill:var(--v14-muted); font-size:var(--fs-tiny); }
html .v14-c3d__val { fill:var(--v14-ink); font:700 var(--fs-tiny) var(--num); font-variant-numeric:tabular-nums; }
html .v14-c3d__val--in { fill:var(--c-invert-fg); }
html .v14-c3d .v14-tchip { fill:var(--ink); }
html .v14-c3d .v14-tchip__t { fill:var(--c-invert-fg); font-size:var(--fs-micro); font-weight:700; }
html .v14-c3d .v14-bar3d { cursor:pointer; outline:none; transform-box:fill-box; transform-origin:50% 100%;
  animation:v14-rise-bar .5s cubic-bezier(.22,1,.36,1) both; animation-delay:calc(var(--i, 0) * 40ms);
  transition:transform .2s ease; }
html .v14-c3d .v14-bar3d__face { stroke:none; }
html .v14-c3d .v14-bar3d__top { fill:var(--c-gold-bright); opacity:.75; }
html .v14-c3d .v14-bar3d__side { fill:var(--c-gold-solid); opacity:.55; }
html .v14-c3d .v14-bar3d.is-over .v14-bar3d__side { fill:var(--ink); opacity:.7; }
html .v14-c3d .v14-bar3d.is-nil .v14-bar3d__face { fill:var(--v14-line); }
html .v14-c3d .v14-bar3d:is(:hover, :focus-visible) { transform:translateY(calc(var(--sp-1) * -1)); }
html .v14-c3d .v14-bar3d:focus-visible .v14-bar3d__face { stroke:var(--ink); stroke-width:1.5; }
html .v14-tip { position:fixed; z-index:50; pointer-events:none; background:var(--ink); color:var(--c-invert-fg);
  font-size:var(--fs-tiny); font-weight:600; padding:var(--sp-1) var(--sp-2); border-radius:var(--radius-1);
  box-shadow:var(--shadow-lift); max-inline-size:min(18rem, 80vw); transform:translate(-50%, -100%); }
html .v14-tip[hidden] { display:none; }

/* ---- card grid, empty state, notes --------------------------------------- */
html .v14-cards { display:grid; gap:var(--sp-3); grid-template-columns:repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  margin:0 0 var(--sp-section); min-width:0; }
html .v14-card { background:var(--v14-surface); border:1px solid var(--v14-line); border-radius:var(--radius-3);
  box-shadow:var(--shadow-soft); padding:var(--pad-card); display:flex; flex-direction:column; gap:var(--sp-2);
  min-width:0; color:var(--v14-ink); text-decoration:none; box-sizing:border-box;
  transition:transform .2s ease, box-shadow .2s ease; }
html a.v14-card:visited { color:var(--v14-ink); }
html .v14-card__t { font-family:var(--display); font-size:var(--fs-base); font-weight:600; margin:0; }
html .v14-card__v { font:700 var(--fs-lg)/1.1 var(--num); font-variant-numeric:tabular-nums; }
html .v14-card__s { font-size:var(--fs-tiny); color:var(--v14-muted); margin:0; }
html .v14-card__foot { margin-top:auto; display:flex; flex-wrap:wrap; gap:var(--sp-2); }
html .v14-empty { text-align:center; padding:var(--sp-6) var(--sp-4); border:1px dashed var(--v14-line);
  border-radius:var(--radius-3); background:var(--v14-surface); display:flex; flex-direction:column;
  align-items:center; gap:var(--sp-2); }
html .v14-empty__t { font-family:var(--display); font-size:var(--fs-base); font-weight:600; margin:0; }
html .v14-empty__b { font-size:var(--fs-small); color:var(--v14-muted); margin:0; max-width:52ch; }
html .v14-notes { display:grid; gap:var(--sp-3); grid-template-columns:repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); margin:0 0 var(--sp-block); }
html .v14-note { background:var(--v14-soft); border-radius:var(--radius-2); padding:var(--pad-card); min-width:0; }
html .v14-note__t { font-size:var(--fs-small); font-weight:700; margin:0 0 var(--sp-1); }
html .v14-note ul { margin:0; padding-inline-start:var(--sp-4); display:grid; gap:var(--sp-1);
  font-size:var(--fs-small); color:var(--v14-ink); }

/* ---- page hero (dc.head / _page_hero) — centred, pills ------------------- */
html :is(.ph__title, .ph h1) { text-align:center; }
html :is(.ph__sub, .ph__eyebrow) { text-align:center; }
html .ph__acts, html .ph__links { justify-content:center; }
html main.cockpit :is(.ph__act, .fbtn) { border-radius:var(--radius-pill); }

/* ---- department KPI strip (dc.kpi_strip) — V14 white cards --------------- */
html:root .dc-kpi { background:var(--v14-surface); border:1px solid var(--v14-line);
  border-radius:var(--radius-3); box-shadow:var(--shadow-soft); gap:var(--sp-1);
  transition:transform .2s ease, box-shadow .2s ease; }
html:root .dc-kpi__lbl { font-size:var(--fs-tiny); }
html:root .dc-kpi__num { font-family:var(--num); font-variant-numeric:tabular-nums;
  font-size:clamp(var(--fs-small), 8cqi, var(--fs-xl)); }
html:root .dc-kpi__sub { font-size:var(--fs-tiny); }
html:root .dc-kpi--red, html:root .dc-kpi--danger { box-shadow:inset 0 3px 0 var(--c-gold-solid), var(--shadow-soft); }
html:root :is(.dc-kpi--red, .dc-kpi--danger) .dc-kpi__num { color:var(--c-gold-fg); }

/* ---- attention section (dc.attention) — V14 card, pills, no red ---------- */
html .attn { background:var(--v14-surface); border:1px solid var(--v14-line); border-radius:var(--radius-3);
  box-shadow:var(--shadow-soft); padding:var(--pad-card); margin:0 0 var(--sp-section); }
html .attn .dd-row { border-radius:var(--radius-2); transition:transform .2s ease, box-shadow .2s ease; }
html .attn .dd-row:hover { box-shadow:var(--shadow-soft); }
html .attn :is(.dd-row--red, .dd-row--danger, .dd-row--late) { box-shadow:inset 3px 0 0 var(--c-gold-solid); }
html .attn :is(.dd-btn, .dd-row__fix) { border-radius:var(--radius-pill); min-height:calc(var(--sp-6) - var(--sp-1)); }

/* ---- motion: transform/opacity only, all off under reduced motion -------- */
@keyframes v14-rise-bar { from { transform:scaleY(0); opacity:0; } to { transform:none; opacity:1; } }
@keyframes v14-in { from { opacity:0; transform:translateY(var(--sp-2)); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion:no-preference) {
  html .v14-rise { animation:v14-in .45s cubic-bezier(.22,1,.36,1) both; animation-delay:calc(var(--i, 0) * 50ms); }
  html :is(.v14-tilt, .v14-card, .dc-kpi):hover { transform:translateY(-2px) perspective(40rem) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); box-shadow:var(--shadow-lift); }
  html .v14-dt.is-sorting tbody { opacity:.4; transition:opacity .15s ease; }
  @view-transition { navigation:auto; }
}
@media (prefers-reduced-motion:reduce) {
  html :is(.v14-rise, .v14-bar3d) { animation:none !important; }
  html :is(.v14-tilt, .v14-card, .dc-kpi, .v14-pill):hover { transform:none !important; }
}

/* ── V14 topbar + nav (21 Oct 2026) ────────────────────────────────────
   White masthead (parchment_theme binds --bg-elevated to white), and the
   primary nav as pills: the active section is a filled ink pill, hover is a
   soft cream pill. Colour and size only — no item, link or route changes. */
html .mainnav { background:var(--v14-surface); border-bottom:1px solid var(--v14-line); }
html .mainnav__list { gap:var(--sp-1); padding-block:var(--sp-2); }
html .mainnav__top { border-radius:var(--radius-pill); border-bottom:0;
  padding:var(--sp-2) var(--sp-4); font-size:var(--fs-small); }
html .mainnav__top:hover { background:var(--v14-soft); color:var(--v14-ink); }
html .mainnav__top.is-active { background:var(--v14-ink); color:var(--v14-on-ink); }
html.theme-light .mainnav__top.is-active { color:var(--v14-on-ink); }
html .topbar { box-shadow:0 1px 0 var(--v14-line); }


/* ── TACTILE LAYER (22 Oct 2026) ───────────────────────────────────────────
   Cards sit on a layered shadow and rise on hover; every button wears depth,
   raises on hover, sinks on :active and shows a gold focus ring. ONE block:
   a `*-btn` class written next year inherits it. Colours are never touched
   here, only shadow / transform / focus, so no ink-on-ground pair can move.
   transform + box-shadow only; flat under prefers-reduced-motion. */
html :is(.v14-sec, .attn, .v14-card, .dc-kpi, .v14-hero-kpi, .v14-tblwrap) { box-shadow:var(--elev-1); }
html :is(.v14-sec, .attn):hover { box-shadow:var(--elev-2); }
html :is(.v14-card, .dc-kpi):hover { box-shadow:var(--elev-2); }
html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn):not(:disabled):not([aria-disabled="true"]) {
  box-shadow:var(--btn-depth); transition:transform .15s ease, box-shadow .15s ease; }
html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn):not(:disabled):not([aria-disabled="true"]):hover {
  transform:translateY(var(--lift-y)); box-shadow:var(--btn-raise); }
html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn):not(:disabled):not([aria-disabled="true"]):active {
  transform:translateY(var(--press-y)); box-shadow:var(--btn-press); transition-duration:.05s; }
html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn, .v14-sorth, .v14-why):focus-visible {
  outline:none; box-shadow:var(--focus-ring); }
@media (prefers-reduced-motion:reduce) {
  html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn):hover,
  html :is([class*="-btn"], a[class*="-btn"], .v14-pill, .fbtn):active { transform:none !important; }
}

/* ── "?" tooltip: the home of every sentence that used to be a paragraph ─── */
html .v14-why { position:relative; display:inline-block; vertical-align:baseline; }
html .v14-why__q { display:inline-flex; align-items:center; justify-content:center;
  inline-size:var(--sp-5); block-size:var(--sp-5); border-radius:var(--radius-pill);
  border:1px solid var(--v14-line); background:var(--v14-surface); color:var(--v14-muted);
  font:700 var(--fs-tiny)/1 var(--ui); cursor:help; box-shadow:var(--btn-depth); }
html .v14-why:hover .v14-why__q, html .v14-why:focus-within .v14-why__q { color:var(--v14-ink); box-shadow:var(--btn-raise); }
html .v14-why__tip { display:none; position:absolute; z-index:40; inset-inline-start:0; inset-block-start:calc(100% + var(--sp-1));
  inline-size:max-content; max-inline-size:min(24rem, 80vw); box-sizing:border-box;
  padding:var(--pad-card); border-radius:var(--radius-2); border:1px solid var(--v14-line);
  background:var(--v14-surface); color:var(--v14-ink); box-shadow:var(--elev-2);
  font:400 var(--fs-small)/1.45 var(--ui); text-align:start; white-space:normal; overflow-wrap:anywhere; }
html .v14-why:hover .v14-why__tip, html .v14-why:focus-within .v14-why__tip { display:block; }
@media (max-width:40rem) {
  html .v14-why { position:static; }
  html .v14-why__tip { position:fixed; inset-inline:var(--sp-4); inset-block-start:auto; inset-block-end:var(--sp-4);
    inline-size:auto; max-inline-size:none; }
}

/* ════ SELECT MENUS ARE READABLE — founder, 2026-10-26 ═══════════════════════
   "All directors" opened to a dark grey menu with only the ticked row legible.
   Cause: `.ba-filters select{background:transparent}` with no `color`. A select
   does not inherit ink (UA default black), and macOS Chrome paints the popup
   from the select's own background; transparent falls through to the SYSTEM
   menu, which is dark in OS dark mode — black on dark grey. Options therefore
   always carry a token fill AND its pair ink, so no page can leave the menu to
   the OS. `:where` keeps the closed control overridable by a page; the option
   rule is not, because a menu is never the place for a page to be creative. */
html :where(select){ color:var(--fg); background-color:var(--bg-elevated); }
html select option, html select optgroup{ background-color:var(--bg-elevated); color:var(--fg); }
html select option:disabled{ color:var(--ink-dim); }

/* VIEW MORE FOR CUT TEXT: net for static/js/table_centre.js. A cut cell anchors to the START with the
   ellipsis at the END (a centred overflow clips both ends); the generated Detail cell is never cut. */
html :is(table, .v14-dt) td.v14-vm-cut { text-align:start; justify-content:flex-start; text-overflow:ellipsis; }
html table tbody > tr > td.v14-vm-c, html table thead > tr > th.v14-vm-c { max-inline-size:none; overflow:visible; text-align:center; white-space:nowrap; inline-size:1%; }
html table td.v14-vm-c .v14-vm-btn { background:none; border:0; box-shadow:none; color:var(--c-gold-fg); font-size:var(--fs-tiny);
  font-weight:600; padding:0 var(--sp-2); cursor:pointer; white-space:nowrap;
  /* Row-neutral: a link-style control at text height, so injecting it never stretches a compact row. */
  display:inline; min-height:0; min-width:0; line-height:inherit; text-decoration:underline; }
html table tr.v14-dt__more[hidden] { display:none; }
html table tr.v14-dt__more > td { white-space:normal; overflow:visible; max-inline-size:none; text-align:start;
  padding:var(--sp-4) var(--sp-5); background:var(--v14-soft); border-bottom:1px solid var(--v14-line); }
