/* ============================================================================
   Computraq Design Tokens
   Contract: computraq-core/v1.2/specs/design-system.yaml
             computraq-data/v1.0/specs/design-system-data.yaml

   Token names for typography / spacing / radius / elevation / status are FIXED
   by that contract. Do not rename. Additions are allowed; renames are not.

   Every colour pair published here has a measured WCAG 2.x contrast ratio
   recorded in DESIGN_FRAMEWORK.md section 2.6. Nothing below is asserted
   compliant without a number behind it.

   Locales: en, ja (release) + hi, id, pt-BR, ar (architecture).
   Direction: LTR + RTL. Use CSS logical properties only.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   1. FONT FACES
   Self-host all four families. Do not use a third-party font CDN: it adds a
   second connection to the critical path and puts LCP outside our control.
   Subset Latin to the `latin` + `latin-ext` ranges; ship Japanese as a
   unicode-range-scoped face so en users never download it.
   ------------------------------------------------------------------------- */

/* Inter v4.1 — Latin/Greek/Cyrillic UI + financial figures. SIL OFL 1.1.
   Verified: wght 100–900 and opsz 14–32 axes; `tnum` present (61 subs);
   slashed zero via `zero`. Italic is a SEPARATE file in v4.x (the `ital`
   axis was removed in v4.0) — do not expect it on this face.
   Coverage is Latin/Latin-ext/Greek/Cyrillic/Vietnamese ONLY (2,849
   codepoints). No CJK, Arabic or Devanagari — hence the faces below. */
/* Noto Sans JP v2.004 — Japanese. SIL OFL 1.1. Scoped so Latin never
   triggers it. Verified: variable `wght` 100–900. It has NO `tnum` and NO
   `zero` feature — it does not need `tnum` (its digits are already uniform
   at 521/1000), and no slashed-zero glyph exists at all. Neither matters
   for us because ASCII digits resolve to Inter above.
   size-adjust: its Latin is Source Sans 3 scaled ~111.7%, so it renders
   noticeably larger than Inter at the same font-size. 92% brings the two
   into visual agreement and stops a JA fallback from shifting layout. */
/* Noto Sans Arabic — ar (+ ar-XB pseudo-locale). SIL OFL 1.1. */
/* Noto Sans Devanagari — hi. SIL OFL 1.1. */
/* JetBrains Mono — code samples only. SIL OFL 1.1. */
/* ---------------------------------------------------------------------------
   2. PRIMITIVES — mode-independent
   ------------------------------------------------------------------------- */
:root {
  /* ---- 2.1 Font stacks -------------------------------------------------- */
  /* Order matters: Latin face first, then per-script faces, then the platform
     Japanese fonts (macOS / Windows / Android / Linux), then generics.
     A browser walks this list PER CHARACTER, so Japanese text in an English
     page still resolves correctly without any JS or locale class. */
  /* Platform-font names below are exact and order-sensitive:
     - "Hiragino Kaku Gothic ProN" does NOT register on current macOS (26);
       "Hiragino Sans" is the family that does. Both are listed — the first
       for older macOS/iOS, the second for current.
     - Windows spells it "Yu Gothic" (with a space); macOS spells the same
       family "YuGothic" (no space). Both are required.
     - "Meiryo" is a Feature-on-Demand font on Windows 11: present only when
       the Japanese language pack is installed. It is a fallback, not a
       guarantee, which is why it sits after Noto.
     - Android/ChromeOS/Linux register the CJK face as "Noto Sans CJK JP". */
  /* Gen Interface JP — the primary face.
   *
   * It is Inter and Noto Sans JP harmonised into one family: the Japanese is
   * resized, repositioned and respaced to sit correctly beside the Latin. That
   * is precisely the problem this product has, and the reason the earlier
   * Inter + Noto Sans JP stack was a compromise rather than a solution — the
   * two are not metrically harmonised (Plex Sans JP scales Latin at 105%, and
   * Noto's kana fill the em box where Latin caps fill ~0.7), so a mixed
   * en/ja setting needed manual optical correction on every surface. Gen
   * Interface JP removes that class of problem at the font level.
   *
   * SIL Open Font License 1.1, so it is safe for commercial use — unlike the
   * SF Pro Display wordmark outlines, which remain blocked (OPEN-01).
   * npm `gen-interface-jp`; jsDelivr at
   * https://cdn.jsdelivr.net/npm/gen-interface-jp@latest/cdn/
   * Self-host rather than hot-link the CDN: a third-party font request on
   * every page is a privacy and performance liability on a public statistics
   * site, and the CSP for these Workers should not need a font-src exception.
   *
   * Eight weights, Thin 100 to ExtraBold 800. This design uses 400 and 500
   * only — see VOICE.md: headings sit at 400-500, never 700.
   *
   * The fallbacks below still matter: they cover the first paint before the
   * webfont loads, and Arabic and Devanagari, which Gen Interface JP does not
   * include and which the architecture locales require.
   */
  --font-sans:
    "Gen Interface JP",
    "Inter var", "Inter",
    "Noto Sans Arabic var", "Noto Sans Devanagari var",
    /* macOS / iOS */ "Hiragino Sans", "Hiragino Kaku Gothic ProN",
    /* installed or webfont */ "Noto Sans JP",
    /* Android / ChromeOS / Linux */ "Noto Sans CJK JP", "Noto Sans CJK",
    /* Windows */ "Yu Gothic UI", "Yu Gothic", "YuGothic", "Meiryo",
    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
    sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji";

  /* The Display cut carries tighter native letterspacing for large settings.
   * Use it for `display` and `heading-1` only. Because its tracking is built
   * in, the negative --type-*-track values were tuned for Inter and should be
   * re-measured against it rather than applied blindly on top. */
  --font-display:
    "Gen Interface JP Display",
    var(--font-sans);

  --font-mono:
    "JetBrains Mono var", "JetBrains Mono",
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono",
    /* keep CJK legible inside code blocks */
    "Noto Sans JP var", "Hiragino Sans", "Yu Gothic UI", "Meiryo",
    monospace;

  /* Financial amounts deliberately reuse --font-sans. See DESIGN_FRAMEWORK 1.5:
     tabular figures come from the `tnum` feature, NOT from a monospace face. */
  --font-financial: var(--font-sans);

  /* ---- 2.2 Font features ------------------------------------------------ */
  /* All three are Inter/JetBrains Mono feature tags, verified present in the
     shipped binaries. They are inert on Noto Sans JP (which has neither
     `tnum` nor `zero`) — that is harmless, because ASCII digits always
     resolve to Inter. */
  --font-feature-default:   "liga" 1, "calt" 1, "cv05" 1;   /* cv05 = l with tail */
  --font-feature-financial: "tnum" 1, "zero" 1, "calt" 0;   /* tabular + slashed 0 */
  --font-feature-code:      "liga" 0, "calt" 0, "zero" 1;   /* no code ligatures */

  /* ---- 2.3 Type scale (contract roles) ---------------------------------- */
  /* size / line-height / weight / tracking, one triplet per contract role.
     Only `display` and `heading-1` are fluid; everything else is fixed so that
     400% zoom reflow and visual-regression snapshots stay deterministic.     */

  --type-display-size:    clamp(2.25rem, 1.6818rem + 2.2727vw, 3.5rem); /* 36→56px */
  --type-display-line:    1.08;
  --type-display-weight:  500;
  --type-display-track:   -0.022em;

  --type-heading-1-size:  clamp(1.75rem, 1.4773rem + 1.3636vw, 2.5rem);  /* 28→40px */
  --type-heading-1-line:  1.16;
  --type-heading-1-weight: 500;
  --type-heading-1-track: -0.018em;

  --type-heading-2-size:  1.5rem;      /* 24px */
  --type-heading-2-line:  1.28;
  --type-heading-2-weight: 500;
  --type-heading-2-track: -0.014em;

  --type-heading-3-size:  1.1875rem;   /* 19px */
  --type-heading-3-line:  1.36;
  --type-heading-3-weight: 600;
  --type-heading-3-track: -0.008em;

  --type-body-size:       1rem;        /* 16px */
  --type-body-line:       1.6;
  --type-body-weight:     400;
  --type-body-track:      -0.006em;

  --type-body-small-size: 0.875rem;    /* 14px */
  --type-body-small-line: 1.55;
  --type-body-small-weight: 400;
  --type-body-small-track: -0.002em;

  --type-label-size:      0.8125rem;   /* 13px */
  --type-label-line:      1.32;
  --type-label-weight:    500;
  --type-label-track:     0.005em;

  --type-code-size:       0.875rem;    /* 14px */
  --type-code-line:       1.6;
  --type-code-weight:     400;
  --type-code-track:      0em;

  --type-financial-size:  1rem;        /* 16px */
  --type-financial-line:  1.5;
  --type-financial-weight: 500;
  --type-financial-track: 0em;         /* never track financial figures */

  /* ---- 2.4 Spacing (contract steps; 4px base) --------------------------- */
  --space-0:  0;
  --space-1:  0.25rem;   /*  4px */
  --space-2:  0.5rem;    /*  8px */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-6:  1.5rem;    /* 24px */
  --space-8:  2rem;      /* 32px */
  --space-12: 3rem;      /* 48px */
  --space-16: 4rem;      /* 64px */

  /* ---- 2.5 Radius (contract steps) -------------------------------------- */
  /* Derived from the logo: mark-03 uses rx=2.5 on an 8-unit square in a
     32-unit viewBox — a 0.3125 corner ratio — with round line caps/joins. */
  --radius-none:   0;
  --radius-small:  0.25rem;   /*  4px — badges, checkboxes, swatches, tags   */
  --radius-medium: 0.5rem;    /*  8px — buttons, inputs, selects, menu items */
  --radius-large:  0.875rem;  /* 14px — cards, panels, dialogs, table shell  */

  /* ---- 2.6 Stroke weights ----------------------------------------------- */
  --stroke-hairline: 1px;   /* table rules, dividers, default borders */
  --stroke-medium:   1.5px; /* icon strokes at 16–20px (3/32 of the logo grid) */
  --stroke-strong:   2px;   /* icon strokes at 24px+, focus ring, active tab */
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;

  /* ---- 2.7 Motion -------------------------------------------------------- */
  --duration-instant: 0ms;
  --duration-fast:    120ms;  /* hover/focus tint, icon state */
  --duration-base:    180ms;  /* disclosure, tooltip, menu */
  --duration-slow:    240ms;  /* dialog/sheet enter */
  --ease-out:    cubic-bezier(0, 0, 0.2, 1);     /* default; exit-fast */
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);   /* symmetric, for moves */
  --ease-in:     cubic-bezier(0.4, 0, 1, 1);     /* leaving the screen only */

  /* ---- 2.8 Layout -------------------------------------------------------- */
  --container-prose:   68ch;    /* methodology / documentation prose */
  --container-content: 75rem;   /* 1200px — default page shell            */
  --container-wide:    90rem;   /* 1440px — dense tables, trading-desk     */
  --gutter:            var(--space-4);
  --grid-columns:      4;

  --z-base: 0;
  --z-raised: 10;
  --z-sticky: 100;
  --z-nav: 200;
  --z-modal: 1000;
  --z-toast: 1100;
}

@media (min-width: 48rem) {  /* 768px */
  :root { --gutter: var(--space-6); --grid-columns: 8; }
}
@media (min-width: 64rem) {  /* 1024px */
  :root { --gutter: var(--space-8); --grid-columns: 12; }
}

/* ---------------------------------------------------------------------------
   3. COLOUR — primitives (mode-independent)

   THE PALETTE, 2026-08-17. Three ramps, one method.

   Until this revision the system was near-monochrome with a single blue used
   in three places, and the result was a page that was warm grey on warm grey:
   correct, restrained, and unfinished-looking. VOICE.md section 4 has been
   rewritten to match what is below — the rule now is a small designed palette
   used with discipline, not the absence of colour. Restraint is still the
   direction; it is now expressed as "few hues, each with a job" rather than
   "no hues".

   The three ramps and what each is for:

     --gray-*   INK.      Type, rules, the near-black ground. Warm but close
                          to neutral, because text must not carry a cast.
     --clay-*   PAPER.    Every surface. Warm oat through to a warm near-black.
     --teal-*   ACCENT.   Links, focus, the primary action, current-item marks.

   plus --blue-*, which is now only the `information` status.

   ONE DERIVATION FOR ALL OF IT. Every value in this section was produced the
   same way the gray ramp was: fix the hue in OKLCh, fix the chroma, then solve
   the OKLCh lightness numerically until the colour hits the exact WCAG 2.x
   contrast ratio its ROLE requires against the surface it will sit on. Nothing
   here was picked in a colour wheel and nothing was nudged by hand. That is
   also why light and dark are genuine counterparts rather than an inversion:
   each mode's colour is solved against its own canvas, so a token means the
   same thing in both modes even though the hex differs.

   Mid-century modern is the well the hues are drawn from — Eames, Braun, early
   Herman Miller: petrol teal, ochre, brick, aubergine, a muted green, on oat.
   Muted and slightly desaturated by construction (chroma is capped, see 3.2),
   because confidence in this tradition comes from the hue being right, not
   from the hue being loud.

   The neutral ramp below is unchanged by this revision.

   RE-DERIVED WARM (earlier revision). VOICE.md section 4 asks for warmth in
   the neutrals rather
   than pure grey, citing a warm near-black ground. The previous ramp was cool:
   gray-400 through gray-700 measured R-B = -16 to -17, and gray-900 was
   #191d21.

   Method, so this is reproducible and not hand-tuned hex: each step keeps its
   ORIGINAL relative luminance and is re-solved in OKLCh at hue 70 degrees with
   a small chroma that peaks through the mid ramp and tapers at both ends. WCAG
   contrast is a function of relative luminance alone, so holding luminance
   fixed holds every published ratio fixed by construction; the residual drift
   is 8-bit rounding only. All 34 neutral pairs and all 18 chromatic-on-neutral
   pairs were re-measured afterwards. The largest movement in any ratio is
   0.07, no pair fell below its minimum, and no value was adjusted by hand.

   The ramp still pivots: --gray-500 is BOTH the light-mode interactive border
   AND the dark-mode muted text; --gray-600 is BOTH the light-mode muted text
   AND the dark-mode interactive border.
   ------------------------------------------------------------------------- */
:root {
  --gray-0:    #ffffff;
  --gray-50:   #fdfbf8;
  --gray-100:  #faf6f3;
  --gray-150:  #f1ede9;
  --gray-200:  #e5e0db;
  --gray-300:  #d0cbc5;
  --gray-400:  #a6a09b;
  --gray-500:  #8e8883;
  --gray-600:  #706b66;
  --gray-700:  #5f5a56;
  --gray-800:  #45423d;
  --gray-850:  #302d29;
  --gray-900:  #1f1c19;
  --gray-950:  #151210;
  --gray-1000: #0e0b09;

  /* ---- 3.1 CLAY — the warm surface ramp --------------------------------- */
  /* The grounds. The gray ramp above is INK: it sets type and rules, and it
     stays as close to neutral as warmth allows so that text never picks up a
     cast. Clay is the opposite job — it is the paper, and paper is allowed to
     have a colour. Same derivation as the gray ramp (OKLCh, hue held near 73
     degrees, luminance solved to a target WCAG ratio rather than eyeballed),
     with roughly triple the chroma, which is the difference between "warm
     white" and "oat".

     Light steps are solved against #ffffff, dark steps against --gray-1000.
     Every step is quoted with the ratio it was solved to, so the numbers here
     are the derivation, not a description of it. */
  --clay-50:   #fffaf2;  /* oklch(98.7% 0.012 79.8) —  1.04:1 on white  */
  --clay-100:  #fdf3e7;  /* oklch(96.9% 0.019 72.6) —  1.10:1 on white  */
  --clay-150:  #f8eddd;  /* oklch(95.0% 0.024 77.5) —  1.16:1 on white  */
  --clay-200:  #edddcb;  /* oklch(90.6% 0.030 70.7) —  1.33:1 on white  */
  --clay-300:  #d9c8b5;  /* oklch(84.2% 0.032 70.5) —  1.63:1 on white  */
  --clay-800:  #4a4238;  /* oklch(38.4% 0.020 73.3) —  1.99:1 on ink    */
  --clay-850:  #342d25;  /* oklch(30.2% 0.017 70.8) —  1.45:1 on ink    */
  --clay-900:  #221d16;  /* oklch(23.4% 0.015 76.1) —  1.17:1 on ink    */
  --clay-950:  #17130e;  /* oklch(19.0% 0.012 73.2) —  1.06:1 on ink    */

  /* ---- 3.2 TEAL — the accent family ------------------------------------- */
  /* Petrol, at OKLCh hue 195-197. Chosen against the clay ground rather than
     in isolation: teal is the complement of the warm ramp, so it separates
     from every surface in the system at the same time, and it is a colour with
     a real tradition behind it — Eames, Braun, early Herman Miller — rather
     than a product-launch blue. Chroma is capped around 0.085 deliberately.
     The mid-century wells are muted; a fully saturated teal reads as a
     consumer app, which is the one thing a financing tool must not do.

     Seven usable steps, each solved to the ratio its role requires:

       wash     surface tint behind a callout     no minimum
       border   bounds a control                  3:1 (SC 1.4.11)
       text     links, current-item marks         4.5:1 (SC 1.4.3)
       solid    a filled control                  4.5:1 for its own label
       hover    one step deeper in both roles

     --teal-600 has two jobs, exactly as --gray-500/600 do: it is the LIGHT
     accent text (5.57:1 on white) and the DARK interactive border (3.52:1 on
     the dark canvas, 3.00:1 on the lightest dark surface). One colour, two
     modes, both solved. */
  --teal-50:   #e4f9f9;  /* oklch(96.7% 0.022 196.8) —  1.09:1 on white */
  --teal-100:  #d4f3f2;  /* oklch(94.2% 0.032 194.2) —  1.17:1 on white */
  --teal-150:  #a9e2e1;  /* oklch(87.4% 0.058 194.8) — 13.69:1 on ink   */
  --teal-200:  #82cece;  /* oklch(80.2% 0.075 195.8) — 10.90:1 on ink   */
  --teal-300:  #5db6b6;  /* oklch(72.2% 0.085 195.4) —  8.25:1 on ink   */
  --teal-400:  #4c9394;  /* oklch(61.8% 0.071 196.7) —  3.55:1 on white */
  --teal-600:  #227373;  /* oklch(50.8% 0.076 195.0) —  5.57:1 on white */
  --teal-700:  #1e6768;  /* oklch(47.1% 0.070 196.4) —  6.57:1 on white */
  --teal-800:  #0f5152;  /* oklch(39.7% 0.062 196.5) —  9.06:1 on white */
  --teal-900:  #0f3c3d;  /* oklch(32.7% 0.048 197.2) — 12.11:1 on white */
  --teal-975:  #102525;  /* oklch(24.7% 0.027 195.5) —  1.23:1 on ink   */

  /* ---- 3.3 BLUE — the information status only --------------------------- */
  /* Blue is no longer the brand accent; it is the `information` status and
     nothing else. Kept at hue 252 and pulled back to chroma 0.13 so it reads
     as a signal beside the teal rather than competing with it. 57 degrees of
     OKLCh hue separate the two, which is the widest gap in the palette after
     danger/warning. */
  --blue-200:  #95c6fe;  /* oklch(81.3% 0.095 252.3) — 11.01:1 on ink   */
  --blue-300:  #74adec;  /* oklch(73.4% 0.110 251.8) —  8.34:1 on ink   */
  --blue-600:  #2066ac;  /* oklch(50.4% 0.130 252.2) —  5.90:1 on white */
  --blue-700:  #055599;  /* oklch(44.6% 0.130 251.8) —  7.59:1 on white */
}

/* ---------------------------------------------------------------------------
   4. SEMANTIC COLOUR — LIGHT (default)

   Every ratio below is measured, not asserted. Surfaces moved from the gray
   ramp to the clay ramp in this revision, so each text token is quoted against
   the DARKEST surface it can legally sit on (--color-surface-raised) as well
   as against the canvas — a token that only clears 4.5:1 on white is not
   actually safe, and the raised surface is where it would have failed.
   ------------------------------------------------------------------------- */
:root,
[data-theme="light"] {
  color-scheme: light;

  /* The grounds are clay now, not gray. This is the single largest visible
     change: the alternating page bands stop being white-on-white and become
     paper-on-oat. Canvas stays a true white — the contrast headroom the whole
     table below depends on is measured against it, and a page needs one
     surface that is unambiguously the brightest. */
  --color-canvas:          var(--gray-0);     /* #ffffff             */
  --color-surface:         var(--clay-100);   /* 1.10:1 on canvas    */
  --color-surface-raised:  var(--clay-150);   /* 1.16:1 on canvas    */
  --color-surface-sunken:  var(--clay-50);    /* 1.04:1 on canvas    */
  --color-overlay:         rgb(14 11 9 / 0.45);

  /* text on canvas / on surface / on surface-raised */
  --color-text-primary:    var(--gray-900);   /* 16.96 / 15.46 / 14.66 */
  --color-text-secondary:  var(--gray-700);   /*  6.81 /  6.21 /  5.89 */
  --color-text-muted:      var(--gray-600);   /*  5.27 /  4.81 /  4.56 */
  --color-text-on-inverse: var(--gray-0);     /*  9.06:1 on inverse    */
  --color-text-on-accent:  var(--gray-0);     /*  6.57:1 on accent-solid */
  /* Disabled text is exempt from 1.4.3, but is never the only cue for
     disabled state — always pair with aria-disabled + a text reason. */
  --color-text-disabled:   var(--gray-400);   /*  2.59:1 — exempt   */

  /* ---- The accent family. Five steps, one hue, each solved to its role.
     Spend it on links, focus, the current item and the single primary action.
     NEVER to make one option look preferable to another (design-system.yaml:
     "no dark patterns or recommendation cues"). The rule that changed in this
     revision is FREQUENCY, not permission: the accent may now appear as often
     as there are things that genuinely carry those meanings. It still may not
     appear on anything that does not. */
  --color-link:               var(--teal-600); /*  5.57 / 5.08 / 4.82  */
  --color-link-hover:         var(--teal-700); /*  6.57:1 on canvas    */
  --color-accent:             var(--teal-600); /*  5.57:1 on canvas    */
  --color-accent-hover:       var(--teal-700); /*  6.57:1 on canvas    */
  --color-accent-border:      var(--teal-400); /*  3.55 / 3.24 / 3.07  */
  --color-accent-solid:       var(--teal-700); /*  label on it 6.57:1  */
  --color-accent-solid-hover: var(--teal-800); /*  label on it 9.06:1  */
  --color-accent-wash:        var(--teal-50);  /*  1.09:1 on canvas    */

  /* The inverse ground — the filled primary action. It carries the brand hue
     now: a deep petrol rather than a warm near-black. Still ink-dark (9.06:1),
     so it reads as a serious control and not as a coloured button. */
  --color-inverse:         var(--teal-800);   /*  9.06:1 on canvas  */
  --color-inverse-hover:   var(--teal-900);   /* 12.11:1 on canvas  */

  /* Borders. `subtle`/`default` are decorative rules with no WCAG minimum and
     follow the clay ramp so a hairline sits in the same family as the surface
     it divides. `interactive` stays on the INK ramp and is still the ONLY one
     permitted to bound a control (1.4.11) — a control boundary must not
     compete with the accent, which is what marks the control's state. */
  --color-border-subtle:      var(--clay-200); /* 1.33:1 — decorative */
  --color-border-default:     var(--clay-300); /* 1.63:1 — decorative */
  --color-border-interactive: var(--gray-500); /* 3.50 / 3.19 / 3.03 — 1.4.11 */
  --color-border-strong:      var(--gray-700);

  --color-focus-ring: var(--teal-600);         /* 5.57 / 5.08 / 4.82 — 1.4.11 */

  /* Status — contract semantics, re-derived onto the mid-century wells so the
     seven roles stop looking alike: ochre, brick, aubergine, a muted green, a
     signal blue, and warm grey for the two roles that must NOT look like an
     event. Each is solved to ~6:1 on canvas at chroma 0.10-0.15.
     Colour is NEVER the only carrier: every status also ships an icon + a
     translated text label, and now a 3:1 border (see -border below) so the
     chip has an edge when the wash is close to the surface behind it. */
  --color-status-neutral:         var(--gray-700);  /* 6.81:1 */
  --color-status-information:     var(--blue-600);  /* 5.90:1 */
  --color-status-success:         #2d6e3a;          /* 6.17:1 — oklch(48.2% 0.106 148) */
  --color-status-warning:         #7f5413;          /* 6.60:1 — oklch(48.2% 0.095  72) */
  --color-status-danger:          #ac3b34;          /* 6.12:1 — oklch(51.3% 0.149  27) */
  --color-status-blocked:         #794a8d;          /* 6.61:1 — oklch(49.1% 0.115 315) */
  --color-status-pending:         var(--gray-700);  /* 6.81:1 */

  --color-status-neutral-bg:      var(--clay-100);  /* fg 6.21:1 */
  --color-status-information-bg:  #e5f1ff;          /* fg 5.16:1 */
  --color-status-success-bg:      #e1f6e2;          /* fg 5.43:1 */
  --color-status-warning-bg:      #ffedd8;          /* fg 5.77:1 */
  --color-status-danger-bg:       #ffece9;          /* fg 5.37:1 */
  --color-status-blocked-bg:      #f8edff;          /* fg 5.84:1 */
  --color-status-pending-bg:      var(--clay-100);  /* fg 6.21:1 */

  /* Status borders. NEW in this revision, and the reason the washes above are
     allowed to be quiet: a wash at 1.14:1 on canvas is only ~1.04:1 against
     the surface band, so on its own it is not a reliable edge. Each border is
     solved to 3:1 against surface-raised — the worst case — which means the
     chip is bounded by shape as well as by hue, and survives 1.4.1. */
  --color-status-neutral-border:      var(--gray-500); /* 3.50 / 3.03 */
  --color-status-information-border:  #678bb5;         /* 3.54 / 3.06 */
  --color-status-success-border:      #66936c;         /* 3.53 / 3.05 */
  --color-status-warning-border:      #a68256;         /* 3.53 / 3.05 */
  --color-status-danger-border:       #b47972;         /* 3.55 / 3.07 */
  --color-status-blocked-border:      #9c7dab;         /* 3.54 / 3.06 */
  --color-status-pending-border:      var(--gray-500); /* 3.50 / 3.03 */

  /* Data display */
  --color-table-header-bg:  var(--clay-150);
  --color-table-row-hover:  var(--clay-100);
  --color-table-rule:       var(--clay-200);
  --color-code-bg:          var(--clay-100);
  --color-code-border:      var(--clay-200);

  /* Elevation (contract steps). Light mode gets a real, very shallow shadow. */
  --elevation-none:   none;
  --elevation-raised: 0 1px 2px rgb(14 11 9 / 0.06),
                      0 2px 6px rgb(14 11 9 / 0.05);
  --elevation-modal:  0 8px 16px rgb(14 11 9 / 0.10),
                      0 24px 48px rgb(14 11 9 / 0.12);
}

/* ---------------------------------------------------------------------------
   5. SEMANTIC COLOUR — DARK
   Applied both by explicit choice and by system preference.

   Not an inversion. Each value here was solved against the DARK canvas at the
   same hue and to the same role requirement as its light counterpart, which is
   why the hex bear no arithmetic relationship to section 4 while the tokens
   mean exactly the same thing. Ratios are quoted against canvas and against
   --color-surface-raised, the worst case in this mode too.
   ------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --color-canvas:          var(--gray-1000);
    --color-surface:         var(--clay-950);   /* 1.06:1 on canvas */
    --color-surface-raised:  var(--clay-900);   /* 1.17:1 on canvas */
    --color-surface-sunken:  #090806;
    --color-overlay:         rgb(0 0 0 / 0.65);

    /* text on canvas / on surface / on surface-raised */
    --color-text-primary:    var(--gray-100);  /* 18.25 / 17.20 / 15.57 */
    --color-text-secondary:  var(--gray-300);  /* 12.17 / 11.47 / 10.38 */
    --color-text-muted:      var(--gray-500);  /*  5.60 /  5.28 /  4.78 */
    --color-text-on-inverse: var(--gray-1000); /*  8.25:1 on inverse    */
    --color-text-on-accent:  var(--gray-1000); /*  8.25:1 on accent-solid */
    --color-text-disabled:   var(--gray-600);  /*  3.72:1 — exempt      */

    --color-link:               var(--teal-300); /*  8.25 / 7.78 / 7.04 */
    --color-link-hover:         var(--teal-200); /* 10.90:1 on canvas   */
    --color-accent:             var(--teal-300); /*  8.25:1 on canvas   */
    --color-accent-hover:       var(--teal-200); /* 10.90:1 on canvas   */
    --color-accent-border:      var(--teal-600); /*  3.52 / 3.32 / 3.00 */
    --color-accent-solid:       var(--teal-300); /*  label on it 8.25:1 */
    --color-accent-solid-hover: var(--teal-200); /*  label on it 10.90:1 */
    --color-accent-wash:        var(--teal-975); /*  1.23:1 on canvas   */

    --color-inverse:         var(--teal-300);  /*  8.25:1 on canvas */
    --color-inverse-hover:   var(--teal-200);  /* 10.90:1 on canvas */

    --color-border-subtle:      var(--clay-850); /* 1.45:1 — decorative */
    --color-border-default:     var(--clay-800); /* 1.99:1 — decorative */
    --color-border-interactive: var(--gray-600); /* 3.72 / 3.51 — 1.4.11 */
    --color-border-strong:      var(--gray-400);

    --color-focus-ring: var(--teal-300);         /* 8.25 / 7.04 — 1.4.11 */

    --color-status-neutral:      var(--gray-300); /* 12.17:1 */
    --color-status-information:  var(--blue-300); /*  8.34:1 */
    --color-status-success:      #75b97f;         /*  8.41:1 — oklch(72.5% 0.109 148) */
    --color-status-warning:      #dca358;         /*  8.80:1 — oklch(75.4% 0.114  72) */
    --color-status-danger:       #f19085;         /*  8.47:1 — oklch(75.2% 0.120  27) */
    --color-status-blocked:      #c89adc;         /*  8.51:1 — oklch(75.2% 0.105 315) */
    --color-status-pending:      var(--gray-400); /*  7.59:1 */

    --color-status-neutral-bg:     var(--clay-900); /* fg 10.38:1 */
    --color-status-information-bg: #112233;         /* fg  6.87:1 */
    --color-status-success-bg:     #132514;         /* fg  6.91:1 */
    --color-status-warning-bg:     #2e1d09;         /* fg  7.27:1 */
    --color-status-danger-bg:      #351917;         /* fg  6.96:1 */
    --color-status-blocked-bg:     #281c2f;         /* fg  7.02:1 */
    --color-status-pending-bg:     var(--clay-900); /* fg  6.47:1 */

    --color-status-neutral-border:      var(--gray-600); /* 3.72 / 3.19 */
    --color-status-information-border:  #486c93;         /* 3.59 / 3.06 */
    --color-status-success-border:      #48734e;         /* 3.59 / 3.06 */
    --color-status-warning-border:      #856336;         /* 3.58 / 3.06 */
    --color-status-danger-border:       #925b54;         /* 3.59 / 3.06 */
    --color-status-blocked-border:      #7c5f8a;         /* 3.61 / 3.08 */
    --color-status-pending-border:      var(--gray-600); /* 3.72 / 3.19 */

    --color-table-header-bg: var(--clay-900);
    --color-table-row-hover: var(--clay-900);
    --color-table-rule:      var(--clay-850);
    --color-code-bg:         var(--clay-950);
    --color-code-border:     var(--clay-850);

    /* Shadow is nearly invisible on a dark canvas — elevation is carried by
       surface lightness + border, with shadow only to seat modals. */
    --elevation-none:   none;
    --elevation-raised: 0 0 0 1px rgb(255 255 255 / 0.04);
    --elevation-modal:  0 0 0 1px rgb(255 255 255 / 0.06),
                        0 16px 48px rgb(0 0 0 / 0.64);
  }
}

/* Explicit dark choice must beat the light default in both directions.
   Value-for-value identical to the block above; ratios are documented there
   and are not repeated. Keep the two in lockstep. */
[data-theme="dark"] {
  color-scheme: dark;

  --color-canvas:          var(--gray-1000);
  --color-surface:         var(--clay-950);
  --color-surface-raised:  var(--clay-900);
  --color-surface-sunken:  #090806;
  --color-overlay:         rgb(0 0 0 / 0.65);

  --color-text-primary:    var(--gray-100);
  --color-text-secondary:  var(--gray-300);
  --color-text-muted:      var(--gray-500);
  --color-text-on-inverse: var(--gray-1000);
  --color-text-on-accent:  var(--gray-1000);
  --color-text-disabled:   var(--gray-600);

  --color-link:               var(--teal-300);
  --color-link-hover:         var(--teal-200);
  --color-accent:             var(--teal-300);
  --color-accent-hover:       var(--teal-200);
  --color-accent-border:      var(--teal-600);
  --color-accent-solid:       var(--teal-300);
  --color-accent-solid-hover: var(--teal-200);
  --color-accent-wash:        var(--teal-975);

  --color-inverse:         var(--teal-300);
  --color-inverse-hover:   var(--teal-200);

  --color-border-subtle:      var(--clay-850);
  --color-border-default:     var(--clay-800);
  --color-border-interactive: var(--gray-600);
  --color-border-strong:      var(--gray-400);

  --color-focus-ring: var(--teal-300);

  --color-status-neutral:      var(--gray-300);
  --color-status-information:  var(--blue-300);
  --color-status-success:      #75b97f;
  --color-status-warning:      #dca358;
  --color-status-danger:       #f19085;
  --color-status-blocked:      #c89adc;
  --color-status-pending:      var(--gray-400);

  --color-status-neutral-bg:     var(--clay-900);
  --color-status-information-bg: #112233;
  --color-status-success-bg:     #132514;
  --color-status-warning-bg:     #2e1d09;
  --color-status-danger-bg:      #351917;
  --color-status-blocked-bg:     #281c2f;
  --color-status-pending-bg:     var(--clay-900);

  --color-status-neutral-border:      var(--gray-600);
  --color-status-information-border:  #486c93;
  --color-status-success-border:      #48734e;
  --color-status-warning-border:      #856336;
  --color-status-danger-border:       #925b54;
  --color-status-blocked-border:      #7c5f8a;
  --color-status-pending-border:      var(--gray-600);

  --color-table-header-bg: var(--clay-900);
  --color-table-row-hover: var(--clay-900);
  --color-table-rule:      var(--clay-850);
  --color-code-bg:         var(--clay-950);
  --color-code-border:     var(--clay-850);

  --elevation-none:   none;
  --elevation-raised: 0 0 0 1px rgb(255 255 255 / 0.04);
  --elevation-modal:  0 0 0 1px rgb(255 255 255 / 0.06),
                      0 16px 48px rgb(0 0 0 / 0.64);
}

/* ---------------------------------------------------------------------------
   6. BASE ELEMENTS
   ------------------------------------------------------------------------- */
html {
  -webkit-text-size-adjust: 100%;
  /* Never set a px font-size on :root — it breaks user font scaling and the
     400% reflow gate. rem stays anchored to the user's browser setting. */
}

body {
  margin: 0;
  background-color: var(--color-canvas);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  font-weight: var(--type-body-weight);
  letter-spacing: var(--type-body-track);
  font-feature-settings: var(--font-feature-default);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* ---------------------------------------------------------------------------
   7. TYPOGRAPHY ROLES — apply by class or @extend in your component layer
   ------------------------------------------------------------------------- */
.type-display,
.type-heading-1,
.type-heading-2,
.type-heading-3,
.type-body,
.type-body-small,
.type-label,
.type-code,
.type-financial { font-family: var(--font-sans); margin: 0; }

.type-display   { font-size: var(--type-display-size);   line-height: var(--type-display-line);   font-weight: var(--type-display-weight);   letter-spacing: var(--type-display-track); }
.type-heading-1 { font-size: var(--type-heading-1-size); line-height: var(--type-heading-1-line); font-weight: var(--type-heading-1-weight); letter-spacing: var(--type-heading-1-track); }
.type-heading-2 { font-size: var(--type-heading-2-size); line-height: var(--type-heading-2-line); font-weight: var(--type-heading-2-weight); letter-spacing: var(--type-heading-2-track); }
.type-heading-3 { font-size: var(--type-heading-3-size); line-height: var(--type-heading-3-line); font-weight: var(--type-heading-3-weight); letter-spacing: var(--type-heading-3-track); }
.type-body      { font-size: var(--type-body-size);      line-height: var(--type-body-line);      font-weight: var(--type-body-weight);      letter-spacing: var(--type-body-track); }
.type-body-small{ font-size: var(--type-body-small-size);line-height: var(--type-body-small-line);font-weight: var(--type-body-small-weight);letter-spacing: var(--type-body-small-track); }
.type-label     { font-size: var(--type-label-size);     line-height: var(--type-label-line);     font-weight: var(--type-label-weight);     letter-spacing: var(--type-label-track); color: var(--color-text-muted); }

.type-code {
  font-family: var(--font-mono);
  font-size: var(--type-code-size);
  line-height: var(--type-code-line);
  font-weight: var(--type-code-weight);
  letter-spacing: var(--type-code-track);
  font-feature-settings: var(--font-feature-code);
}

/* `financial` is the contract role for every monetary/rate/count figure.
   tabular-nums keeps columns aligned; slashed-zero disambiguates 0 from O. */
.type-financial {
  font-family: var(--font-financial);
  font-size: var(--type-financial-size);
  line-height: var(--type-financial-line);
  font-weight: var(--type-financial-weight);
  letter-spacing: var(--type-financial-track);
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  /* Amounts must never wrap mid-number or be truncated (financial_rules). */
  white-space: nowrap;
  text-overflow: clip;
}

/* ---------------------------------------------------------------------------
   8. LOCALE ADJUSTMENTS
   Negative tracking is a Latin-only device. Applying it to Japanese, Arabic or
   Devanagari damages legibility and, for Arabic, can break joining behaviour.
   ------------------------------------------------------------------------- */
:lang(ja),
:lang(ar),
:lang(hi) {
  letter-spacing: 0 !important;
}

/* Japanese has no interword spaces and a full-height em box: it needs more
   leading than Latin at the same size to stay readable. */
:lang(ja) {
  --type-display-line:    1.25;
  --type-heading-1-line:  1.35;
  --type-heading-2-line:  1.45;
  --type-heading-3-line:  1.55;
  --type-body-line:       1.8;
  --type-body-small-line: 1.75;
  --type-label-line:      1.5;

  /* Headings step 500 -> 600 in Japanese, for two reasons:
     1. Kanji are far denser than Latin glyphs, so at the same numeric weight
        a Japanese heading reads lighter than its English counterpart.
     2. It avoids the weak spot in the fallback chain — Yu Gothic renders
        thin on Windows, and Hiragino is unreliable at weight 500. 600 maps
        onto a real Bold/W6 cut in every fallback family. */
  --type-display-weight:   600;
  --type-heading-1-weight: 600;
  --type-heading-2-weight: 600;
  --type-heading-3-weight: 600;

  line-height: 1.8;
  /* Japanese line breaking: allow breaks anywhere permitted by kinsoku rules. */
  line-break: strict;
  overflow-wrap: anywhere;
  word-break: normal;
}

/* Arabic sits lower and needs more leading for its diacritics/descenders. */
:lang(ar) {
  --type-body-line:       1.85;
  --type-heading-1-line:  1.35;
  line-height: 1.85;
}

/* Devanagari's shirorekha needs headroom. */
:lang(hi) {
  --type-body-line:       1.85;
  line-height: 1.85;
}

/* Digits stay LTR and tabular inside RTL text. */
[dir="rtl"] .type-financial,
[dir="rtl"] .tabular {
  direction: ltr;
  unicode-bidi: embed;
  text-align: end;
}

/* ---------------------------------------------------------------------------
   9. FOCUS — visible in every mode, including forced colors
   ------------------------------------------------------------------------- */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-small);
}
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
}

/* Touch targets: 24x24 CSS px is the WCAG 2.2 AA minimum (2.5.8);
   we ship 44x44 on anything a finger uses. */
.control,
button,
[role="button"],
a.button,
input[type="checkbox"],
input[type="radio"] {
  min-block-size: 44px;
  min-inline-size: 44px;
}
.control--inline { min-block-size: 24px; min-inline-size: 24px; }

/* ---------------------------------------------------------------------------
   10. FORCED COLORS (Windows High Contrast)
   Our palette is dropped entirely; the OS palette wins. What we must do is
   make sure structure that we drew with BACKGROUNDS survives as BORDERS.
   ------------------------------------------------------------------------- */
@media (forced-colors: active) {
  :root {
    --color-canvas:            Canvas;
    --color-surface:           Canvas;
    --color-surface-raised:    Canvas;
    --color-surface-sunken:    Canvas;
    --color-text-primary:      CanvasText;
    --color-text-secondary:    CanvasText;
    --color-text-muted:        GrayText;
    --color-text-disabled:     GrayText;
    --color-link:              LinkText;
    --color-link-hover:        LinkText;
    --color-accent:            Highlight;
    --color-accent-hover:      Highlight;
    --color-accent-border:     CanvasText;
    --color-accent-solid:      Highlight;
    --color-accent-solid-hover:Highlight;
    --color-accent-wash:       Canvas;
    --color-focus-ring:        Highlight;
    --color-border-subtle:     CanvasText;
    --color-border-default:    CanvasText;
    --color-border-interactive:CanvasText;
    --color-border-strong:     CanvasText;
    --color-table-rule:        CanvasText;
    --color-table-header-bg:   Canvas;
    --color-code-bg:           Canvas;
    --elevation-raised:        none;
    --elevation-modal:         none;

    /* The status borders added in section 4 are the reason a chip still reads
       as a chip when the OS palette replaces every hue. They collapse to
       CanvasText here rather than disappearing, which is the same thing the
       .status-chip rule below does — stated on the token as well so a
       component that uses the token directly inherits the behaviour. */
    --color-status-neutral-border:     CanvasText;
    --color-status-information-border: CanvasText;
    --color-status-success-border:     CanvasText;
    --color-status-warning-border:     CanvasText;
    --color-status-danger-border:      CanvasText;
    --color-status-blocked-border:     CanvasText;
    --color-status-pending-border:     CanvasText;
  }

  /* Status colour is gone here — the icon and text label carry the meaning,
     and each status keeps a visible border so the chip is still a chip. */
  .status-chip {
    border: var(--stroke-hairline) solid CanvasText;
    color: CanvasText;
    background: Canvas;
    forced-color-adjust: none;
  }
  .status-chip__icon { forced-color-adjust: auto; }

  /* Zebra striping vanishes in forced colors; restore row separation. */
  .data-table tbody tr { border-block-end: var(--stroke-hairline) solid CanvasText; }

  /* Buttons that were filled become outlined. */
  .button { border: var(--stroke-hairline) solid ButtonText; }
  .button--primary { background: ButtonFace; color: ButtonText; }
}

/* ---------------------------------------------------------------------------
   11. MOTION
   Two independent kill switches, both required:
     - prefers-reduced-motion  → the user's OS setting
     - [data-motion="off"]     → set by the visual-regression harness so that
                                 snapshots are deterministic at 0.005 maxDiff
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 1ms;
    --duration-base: 1ms;
    --duration-slow: 1ms;
  }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

[data-motion="off"] *,
[data-motion="off"] *::before,
[data-motion="off"] *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}

/* The only properties allowed to transition. Animating anything that triggers
   layout (width/height/inset/margin) is what blows the 200ms INP budget. */
.transition {
  transition-property: opacity, transform, color, background-color,
                       border-color, box-shadow, outline-color;
  transition-duration: var(--duration-fast);
  transition-timing-function: var(--ease-out);
}

/* ---------------------------------------------------------------------------
   12. LAYOUT PRIMITIVES — logical properties only, so RTL is free
   ------------------------------------------------------------------------- */
.container {
  inline-size: 100%;
  max-inline-size: var(--container-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--prose { max-inline-size: var(--container-prose); }
.container--wide  { max-inline-size: var(--container-wide); }
.container--full  { max-inline-size: none; }

.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--gutter);
}

/* Stack: the workhorse. Vertical rhythm comes from one owl rule, not margins. */
.stack > * + * { margin-block-start: var(--flow, var(--space-4)); }

.visually-hidden {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}


/* ---------------------------------------------------------------------------
   Base
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  min-block-size: 100vh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3, h4, p, figure, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; }

/* An in-page jump — the error summary's links to each field, a footnote
   reference — must not land the target under the sticky table header or hard
   against the top edge. */
:target { scroll-margin-block-start: var(--space-8); }

/* Links are ink with a hairline rule. Colour is not the only cue that a thing
   is a link, and the accent is not spent here. */
a {
  color: var(--color-text-primary);
  text-decoration: underline;
  text-decoration-thickness: var(--stroke-hairline);
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.22em;
  transition: text-decoration-color var(--duration-base) var(--ease-out);
}
a:hover { text-decoration-color: currentColor; }
a:active { color: var(--color-text-secondary); }

/* A link that is the point of its row or its cell — a borrower name, a document
   — is not "inline in a sentence", so the WCAG 2.2 2.5.8 inline exception does
   not cover it and it must reach the 24px minimum on its own. It becomes a real
   44px target where a finger is doing the work; see the coarse-pointer block. */
.link-target,
.data-table a,
.checklist__name a,
.breadcrumbs a,
.empty-state__actions a {
  display: inline-flex;
  align-items: center;
  min-block-size: 24px;
}

/* A link genuinely set inside a sentence is exempt from 2.5.8 — its height is
   constrained by the line box of the text around it, and making it a block
   would break the paragraph. It still gets a bigger hit area: vertical padding
   on an inline box enlarges the pointer target without changing the line
   height, so the sentence sets exactly as before and the target clears 24px. */
.notice p a,
.status-page__body a,
.empty-state__body a,
.panel__footer a,
.error-summary a { padding-block: 0.4em; }

/* ---------------------------------------------------------------------------
   Skip link
   ------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  z-index: var(--z-toast);
  padding: var(--space-2) var(--space-4);
  background: var(--color-canvas);
  color: var(--color-text-primary);
  border: var(--stroke-strong) solid var(--color-accent);
  border-radius: var(--radius-medium);
  font-size: var(--type-label-size);
  font-weight: 500;
  text-decoration: none;
  transform: translateY(-250%);
}
.skip-link:focus { transform: translateY(0); }

/* ---------------------------------------------------------------------------
   Environment banner. Sandbox must be visually distinct from production, and
   the word does that work; the tint only reinforces it.
   ------------------------------------------------------------------------- */
.env-banner {
  padding-block: var(--space-2);
  background: var(--color-status-warning-bg);
  border-block-end: var(--stroke-hairline) solid var(--color-status-warning);
  font-size: var(--type-label-size);
  line-height: var(--type-label-line);
}
.env-banner__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
}
.env-banner__tag {
  color: var(--color-status-warning);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
}
.env-banner__note { color: var(--color-text-secondary); }

/* ---------------------------------------------------------------------------
   AppShell
   ------------------------------------------------------------------------- */
.app-header {
  position: relative;
  z-index: var(--z-nav);
  background: var(--color-canvas);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.app-header__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-4);
}
.app-brand {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--color-text-primary);
  white-space: nowrap;
}
.app-brand:hover { text-decoration: none; }
.app-brand__mark {
  font-size: var(--type-heading-3-size);
  font-weight: 500;
  letter-spacing: -0.012em;
}
.app-brand__portal {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border-inline-start: var(--stroke-hairline) solid var(--color-border-default);
  padding-inline-start: var(--space-3);
}
.app-header__spacer { flex: 1 1 auto; }
.app-header__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2) var(--space-3);
  min-inline-size: 0;
}

/* The native checkbox is visually clipped rather than display:none, so it
   remains keyboard reachable and exposes checked/unchecked state. Its visible
   labels carry the 44px target and the state-matched aria-expanded value. */
.masthead__toggle[type="checkbox"] {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  min-inline-size: 0;
  min-block-size: 0;
  white-space: nowrap;
  border: 0;
}
.masthead__menu-button {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  border: var(--stroke-hairline) solid var(--color-border-default);
  border-radius: var(--radius-medium);
  color: var(--color-text-primary);
  cursor: pointer;
}
.masthead__menu-button--open { display: none; }
.masthead__toggle:checked ~ .app-header__masthead .masthead__menu-button--closed { display: none; }
.masthead__toggle:checked ~ .app-header__masthead .masthead__menu-button--open { display: flex; }
.masthead__toggle:focus-visible ~ .app-header__masthead .masthead__menu-button {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* Reused from public-web: three drawn rules morph into an x when the checkbox
   is checked, without a glyph request or client script. */
.masthead__menu-glyph,
.masthead__menu-glyph::before,
.masthead__menu-glyph::after {
  display: block;
  inline-size: 18px;
  block-size: var(--stroke-medium);
  background: var(--color-text-primary);
  border-radius: var(--radius-small);
}
.masthead__menu-glyph { position: relative; }
.masthead__menu-glyph::before,
.masthead__menu-glyph::after { content: ""; position: absolute; inset-inline-start: 0; }
.masthead__menu-glyph::before { inset-block-start: -6px; }
.masthead__menu-glyph::after { inset-block-start: 6px; }
.masthead__toggle:checked ~ .app-header__masthead .masthead__menu-glyph { background: transparent; }
.masthead__toggle:checked ~ .app-header__masthead .masthead__menu-glyph::before { inset-block-start: 0; transform: rotate(45deg); }
.masthead__toggle:checked ~ .app-header__masthead .masthead__menu-glyph::after { inset-block-start: 0; transform: rotate(-45deg); }

.identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--type-label-size);
}
.identity__block { text-align: end; line-height: 1.4; }
.identity__name { color: var(--color-text-primary); font-weight: 500; display: block; }
.identity__meta { color: var(--color-text-muted); display: block; }
.identity__role {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.92em;
  color: var(--color-text-muted);
  font-feature-settings: var(--font-feature-code);
}
.identity__signout {
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  background: none;
  border: 0;
  padding: var(--space-2);
  min-block-size: 44px;
  cursor: pointer;
  font-family: inherit;
  text-decoration: underline;
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.22em;
}
.identity__signout:hover { color: var(--color-text-primary); text-decoration-color: currentColor; }
.identity__signout:active { color: var(--color-text-secondary); }

/* Primary navigation. Current items keep the same three cues in both layouts:
   aria-current, weight, and a rule. Below tablet width the checkbox reveals a
   vertical panel; above it the same list is a single scrolling inline row. */
.app-nav {
  display: none;
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  background: var(--color-canvas);
}
.masthead__toggle:checked ~ .app-nav {
  display: block;
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  box-shadow: var(--elevation-raised);
}
.app-nav__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-0);
  list-style: none;
  padding-block: var(--space-2);
}
.app-nav__list > li { min-inline-size: 0; }
.nav-link {
  display: flex;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  font-size: var(--type-label-size);
  font-weight: 400;
  white-space: nowrap;
  color: var(--color-text-secondary);
  text-decoration: none;
  border-block-end: var(--stroke-strong) solid transparent;
  transition: color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
  min-block-size: 44px;
}
.nav-link:hover {
  color: var(--color-text-primary);
  text-decoration: none;
  border-block-end-color: var(--color-border-default);
}
.nav-link:active { color: var(--color-text-secondary); }
/* The focus ring is inset because the nav row is a scroll container: a 2px
   outset ring on the first or last item would be clipped by it. */
.nav-link:focus-visible { outline-offset: calc(var(--focus-ring-offset) * -1); }
.nav-link[aria-current="page"] {
  color: var(--color-text-primary);
  font-weight: 500;
  border-block-end-color: var(--color-text-primary);
}

@media (min-width: 48rem) {
  .masthead__toggle[type="checkbox"] { display: none; }
  .masthead__menu-button,
  .masthead__toggle:checked ~ .app-header__masthead .masthead__menu-button { display: none; }
  .app-nav,
  .masthead__toggle:checked ~ .app-nav {
    position: static;
    display: block;
    box-shadow: none;
  }
  .app-nav__list {
    flex-direction: row;
    flex-wrap: nowrap;
    gap: var(--space-6);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scrollbar-width: thin;
    scroll-padding-inline: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding: 0 var(--gutter);
  }
  .app-nav__list > li { flex: 0 0 auto; }
  .nav-link {
    display: inline-flex;
    padding: var(--space-2) 0;
  }
}

/* ---------------------------------------------------------------------------
   Main and footer

   Density: these are tools someone has open all day, so the page rhythm is a
   step tighter than the marketing site — `--space-8` above the first block
   rather than `--space-12`. The generous measure stays where it earns its
   keep, around the page title, and is taken back everywhere else.
   ------------------------------------------------------------------------- */
/* Paper on oat. The palette revision made the surface ramp warm, and the one
   place that buys the most is here: panels sit at --color-canvas on a
   --color-surface-sunken ground, so every panel edge is legible from the
   surface step alone and none of them needs a shadow to be an object.
   `sunken` rather than `surface` because sunken is, by construction, one step
   below canvas in BOTH modes — light 1.04:1 warmer than white, dark one step
   darker than the near-black — so the panel is the brighter thing either way.
   Using `surface` would invert the relationship in dark mode. */
.app-main {
  flex: 1 1 auto;
  padding-block: var(--space-8) var(--space-16);
  background: var(--color-surface-sunken);
}

.app-footer {
  background: var(--color-surface);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  padding-block: var(--space-12);
  font-size: var(--type-body-small-size);
  color: var(--color-text-muted);
}
.app-footer__cols {
  display: grid;
  gap: var(--space-8) var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}
.app-footer h2 {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-3);
}
.app-footer ul { list-style: none; display: grid; gap: var(--space-2); }
.app-footer a { color: var(--color-text-secondary); }
/* The version line closes the footer. Appearance moved to the masthead, where
   it remains reachable when the mobile navigation is folded. */
.app-footer__base {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4) var(--space-6);
  margin-block-start: var(--space-8);
  padding-block-start: var(--space-4);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
}
.app-footer__version {
  font-variant-numeric: tabular-nums;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
}

/* ---------------------------------------------------------------------------
   Breadcrumbs and PageHeader
   ------------------------------------------------------------------------- */
.breadcrumbs { font-size: var(--type-label-size); margin-block-end: var(--space-4); }
.breadcrumbs ol { display: flex; flex-wrap: wrap; gap: 0 var(--space-2); list-style: none; align-items: center; }
.breadcrumbs li { display: flex; gap: var(--space-2); align-items: center; color: var(--color-text-muted); }
.breadcrumbs li + li::before { content: "/"; color: var(--color-border-default); }
.breadcrumbs a { color: var(--color-text-secondary); }
.breadcrumbs a:hover { color: var(--color-text-primary); }
.breadcrumbs [aria-current="page"] { color: var(--color-text-primary); }

.page-header { margin-block-end: var(--space-8); }
.page-header__eyebrow {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-3);
}
.page-header__title {
  font-size: var(--type-heading-1-size);
  line-height: var(--type-heading-1-line);
  font-weight: var(--type-heading-1-weight);
  letter-spacing: var(--type-heading-1-track);
  max-inline-size: 24ch;
}
.page-header__summary {
  max-inline-size: var(--container-prose);
  color: var(--color-text-secondary);
  margin-block-start: var(--space-4);
}
.page-header__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-8);
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-size: var(--type-body-small-size);
}
.page-header__meta > div { display: grid; gap: var(--space-1); }
.page-header__meta dt {
  font-size: var(--type-label-size);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.page-header__meta dd { color: var(--color-text-primary); }
.page-header__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-6);
}
.page-header--compact { margin-block-end: var(--space-3); }
.page-header--compact .page-header__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-6);
}
.page-header--compact .page-header__eyebrow { margin: 0; }
.page-header--compact .page-header__title {
  max-inline-size: none;
  font-size: var(--type-heading-2-size);
  line-height: var(--type-heading-2-line);
  letter-spacing: var(--type-heading-2-track);
}
.page-header--compact .page-header__meta {
  flex: 1 1 auto;
  justify-content: flex-end;
  gap: var(--space-2) var(--space-6);
  margin: 0;
  padding: 0;
  border: 0;
}
.page-header--compact .page-header__meta > div {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.page-header--compact .page-header__actions { margin-block-start: var(--space-3); }
.page-header__about {
  max-inline-size: var(--container-prose);
  margin-block-end: var(--space-4);
  color: var(--color-text-secondary);
}
.page-header__about > p { padding-inline-end: var(--space-8); }

/* ---------------------------------------------------------------------------
   Sample-data notice. Stated plainly, in the main text, not a footnote.
   ------------------------------------------------------------------------- */
.sample-notice {
  padding-inline-start: var(--space-4);
  border-inline-start: var(--stroke-strong) solid var(--color-accent);
  margin-block-end: var(--space-8);
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
  max-inline-size: var(--container-prose);
}
.sample-notice__label {
  display: block;
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-block-end: var(--space-1);
}
.sample-notice--badge {
  inline-size: fit-content;
  max-inline-size: 100%;
  padding: 0;
  border: 0;
  margin-block-end: var(--space-4);
}
details.sample-notice--badge > summary {
  inline-size: fit-content;
  max-inline-size: 100%;
  padding-block: var(--space-2);
  padding-inline-start: var(--space-3);
  padding-inline-end: calc(var(--space-8) + var(--space-2));
  border: var(--stroke-hairline) solid var(--color-accent-border);
  border-radius: var(--radius-large);
  background: var(--color-canvas);
  color: var(--color-accent);
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: var(--type-label-line);
  text-decoration: none;
  white-space: nowrap;
}
details.sample-notice--badge[open] > summary { margin-block-end: var(--space-2); }
.sample-notice--badge > p {
  max-inline-size: var(--container-prose);
  padding-inline-start: var(--space-3);
  border-inline-start: var(--stroke-strong) solid var(--color-accent);
}

/* ---------------------------------------------------------------------------
   Panels
   ------------------------------------------------------------------------- */
/* A panel is a container, not an object on a surface: it gets an edge and no
   shadow. Shadow is spent on the metric cards instead, which is the one place
   DESIGN_FRAMEWORK 7.4 asks for --elevation-raised, and spending it in exactly
   one place is what lets it mean anything. Six shadowed panels stacked down a
   page is the "card grid used as a default" tell. */
.panel {
  background: var(--color-canvas);
  border: var(--stroke-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-large);
  overflow: hidden;
}
.panel__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4) var(--space-6);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.panel__title {
  font-size: var(--type-heading-3-size);
  line-height: var(--type-heading-3-line);
  font-weight: 500;
  letter-spacing: var(--type-heading-3-track);
}
.panel__subtitle { font-size: var(--type-label-size); color: var(--color-text-muted); }
.panel__body { padding: var(--space-6); }
.panel__body--flush { padding: 0; }
.panel__footer {
  padding: var(--space-4) var(--space-6);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
}

.layout-split {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
@media (min-width: 64rem) {
  .layout-split { grid-template-columns: minmax(0, 5fr) minmax(0, 3fr); }
  .layout-split--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.mt-2 { margin-block-start: var(--space-2); }
.mt-3 { margin-block-start: var(--space-3); }
.mt-4 { margin-block-start: var(--space-4); }
.mt-6 { margin-block-start: var(--space-6); }
.mt-8 { margin-block-start: var(--space-8); }
.weight-regular { font-weight: 400; }

.stack-8 > * + * { margin-block-start: var(--space-8); }
.stack-6 > * + * { margin-block-start: var(--space-6); }
.stack-4 > * + * { margin-block-start: var(--space-4); }
.stack-3 > * + * { margin-block-start: var(--space-3); }
.stack-2 > * + * { margin-block-start: var(--space-2); }

.section-heading {
  font-size: var(--type-heading-2-size);
  line-height: var(--type-heading-2-line);
  font-weight: var(--type-heading-2-weight);
  letter-spacing: var(--type-heading-2-track);
  margin-block-end: var(--space-4);
}
.section-lede {
  max-inline-size: var(--container-prose);
  color: var(--color-text-secondary);
  font-size: var(--type-body-small-size);
  margin-block-end: var(--space-6);
}

/* ---------------------------------------------------------------------------
   MetricCard. Space around the figure is what says the figure matters.
   ------------------------------------------------------------------------- */
.metric-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}
/* One thing is primary.

   Four equal cards in a row is the card grid used as a default: the eye lands
   nowhere and travels nowhere, and it is the single most recognisable tell in
   the critique this pass exists to answer. The lead card takes the whole first
   row and re-composes internally — figure on the left at display size, the
   sentence that qualifies it set beside rather than beneath — so a reader finds
   the number that matters in under two seconds and reads the rest as context to
   it. The counters underneath stay equal because they ARE equal.

   Taking the whole row rather than two tracks of it is deliberate: a two-track
   span depends on how many tracks `auto-fit` happened to produce at that
   width, and at 1200px it produced four, which left the last counter stranded
   alone on a second row. `1 / -1` cannot be wrong at any width. */
@media (min-width: 64rem) {
  /* The second row holds every card except the leader, so the track count is
     the card count minus one. `:has()` keeps this scoped to grids that
     actually have a leader; anything else keeps the intrinsic auto-fit above,
     and a browser without `:has()` falls back to it too. */
  .metric-grid--n2:has(.metric-card--lead) { grid-template-columns: minmax(0, 1fr); }
  .metric-grid--n3:has(.metric-card--lead) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metric-grid--n4:has(.metric-card--lead) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .metric-grid--n5:has(.metric-card--lead) { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  .metric-card--lead {
    grid-column: 1 / -1;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--space-12);
    align-items: baseline;
  }
  .metric-card--lead .metric-card__label { grid-column: 1; grid-row: 1; }
  .metric-card--lead .metric-card__value {
    grid-column: 1;
    grid-row: 2;
    font-size: var(--type-display-size);
    letter-spacing: var(--type-display-track);
  }
  .metric-card--lead .metric-card__context {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: end;
    max-inline-size: 56ch;
  }
}
.metric-card {
  background: var(--color-canvas);
  border: var(--stroke-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-large);
  box-shadow: var(--elevation-raised);
  padding: var(--space-6);
  display: grid;
  align-content: start;
  gap: var(--space-2);
  min-inline-size: 0;
}
/* The lead card is the number a reader should find in under two seconds, so it
   is told apart three ways: a heavier surface, a 2px ink rule down its
   inline-start edge, and a larger figure. The rule is what survives
   forced-colors, where the surface difference is discarded. */
/* --color-surface, not --color-surface-sunken. Sunken is now the ground the
   whole main region sits on, so a sunken card is invisible against it — the
   card was told apart by nothing but its rule. Surface is a step off canvas in
   both modes (warmer in light, lighter in dark), so the lead card reads as a
   different object from both the page and the ordinary cards. */
.metric-card--lead {
  background: var(--color-surface);
  border-inline-start: var(--stroke-strong) solid var(--color-text-primary);
}
.metric-card__label {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.metric-card__value {
  display: block;
  font-size: var(--type-heading-2-size);
  line-height: 1.15;
  font-weight: 500;
  letter-spacing: -0.014em;
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  white-space: nowrap;
}
.metric-card--lead .metric-card__value {
  font-size: var(--type-heading-1-size);
  letter-spacing: var(--type-heading-1-track);
}
.metric-card__context {
  display: block;
  font-size: var(--type-body-small-size);
  line-height: 1.45;
  color: var(--color-text-muted);
  max-inline-size: 34ch;
}

/* ---------------------------------------------------------------------------
   Money and Rate
   ------------------------------------------------------------------------- */
.money {
  font-family: var(--font-financial);
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  white-space: nowrap;
  text-overflow: clip;
}
.money__code {
  font-size: 0.82em;
  font-weight: 400;
  color: var(--color-text-muted);
  letter-spacing: 0.04em;
  margin-inline-end: 0.35em;
}
.money__amount { font-weight: 500; }
/* The derived line is prose about a figure, so it wraps. The figure inside it
   is still a .money span, which does not. */
.money__derived {
  display: block;
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-muted);
  font-weight: 400;
  margin-block-start: var(--space-2);
  white-space: normal;
  max-inline-size: 52ch;
}
.money__derived .money { font-size: 1em; }
.rate {
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  white-space: nowrap;
  font-weight: 500;
}
.rate__bps { color: var(--color-text-muted); font-size: 0.86em; font-weight: 400; margin-inline-start: 0.4em; }

/* ---------------------------------------------------------------------------
   DataTable. The data is the strongest element on the row; the chrome recedes.
   ------------------------------------------------------------------------- */
/* A table sitting directly in a flush panel supplies its own edge gutter so it
   lines up with the panel header. A table inside an already-padded body
   supplies none, because the body has done that work — double-counting it is
   exactly the one-pixel misalignment that reads as untrained. */
.table-region {
  overflow-x: auto;
  --table-gutter: var(--space-6);
  /* The region is the WCAG 1.4.10 escape hatch for a table: the page must never
     scroll sideways, so the table does it instead, inside a labelled region a
     keyboard user can reach and operate with the arrow keys. */
  scroll-padding-inline: var(--table-gutter);
  overscroll-behavior-inline: contain;
}
.panel__body:not(.panel__body--flush) .table-region { --table-gutter: 0px; }
/* Inset, because the enclosing panel clips its overflow and an outset ring on a
   full-bleed table would be cut off exactly where it matters. */
.table-region:focus-visible { outline-offset: calc(var(--focus-ring-offset) * -1); }

.data-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--type-body-small-size);
}
/* The explanatory sentence for a table. Lives outside the scroll region so it
   wraps to the page, and carries the region's edge gutter so it lines up with
   the header row beneath it. */
/* 68ch, not 92ch. `ch` is relative to the element's own font-size, so a 92ch
   measure set at 13px reads as roughly 120 characters of running text — far
   past the 65-75 the contract asks for, and past the point where the eye
   reliably finds the start of the next line. Every small-type paragraph in this
   file caps its own measure for the same reason: inheriting a prose width sized
   for 16px body always overshoots. */
.table-note {
  padding-block: var(--space-2) var(--space-3);
  padding-inline: var(--table-gutter, var(--space-6));
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-muted);
  max-inline-size: 68ch;
}
/* No rule under the note. A border on an element with a capped measure stops
   where the element stops, which drew a hairline that ran two-thirds of the way
   across and then gave up — the single loudest signal of untrained work. The
   header band below it already separates the two. */
.panel__body:not(.panel__body--flush) .table-note { padding-inline: 0; }
@media (max-width: 47.999rem) {
  .panel__body--flush > .table-note,
  .panel > .table-note { padding-inline: var(--space-4); }
}
/* Cell padding is --space-2 block / --space-3 inline, per DESIGN_FRAMEWORK 7.3.
   A row that was 12px/16px reads as a report; 8px/12px reads as an instrument,
   and fits three more rows on a laptop screen without crowding, because the
   hairline rule and the right-aligned figures are already separating them. */
.data-table th, .data-table td {
  padding: var(--space-2) var(--space-3);
  text-align: start;
  vertical-align: baseline;
  border-block-end: var(--stroke-hairline) solid var(--color-table-rule);
}
.data-table th:first-child, .data-table td:first-child { padding-inline-start: var(--table-gutter); }
.data-table th:last-child, .data-table td:last-child { padding-inline-end: var(--table-gutter); }
/* The header band is --color-table-header-bg, per DESIGN_FRAMEWORK 7.3. It has
   to be an opaque fill and not a transparent one, because it is sticky: rows
   scroll underneath it, and a translucent header would print the row on top of
   the column names. Muted text on that band measures 4.56:1. */
.data-table thead th {
  position: sticky;
  inset-block-start: 0;
  background: var(--color-table-header-bg);
  font-size: var(--type-label-size);
  font-weight: 400;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
  padding-block: var(--space-2) var(--space-3);
  z-index: var(--z-raised);
}
.data-table tbody tr:last-child th,
.data-table tbody tr:last-child td { border-block-end: 0; }
.data-table tbody tr { transition: background-color var(--duration-fast) var(--ease-out); }
.data-table tbody tr:hover { background: var(--color-table-row-hover); }
/* The row a keyboard user has walked into. `:focus-within` rather than
   `:focus`, because what they have actually focused is the link inside it, and
   losing the row while reading across eight columns is the whole problem. */
.data-table tbody tr:focus-within { background: var(--color-table-row-hover); }
.data-table th[scope="row"] { font-weight: 500; color: var(--color-text-primary); }
.data-table td { color: var(--color-text-secondary); }

/* A row that is still moving — delivering, scanning, queued, awaiting a second
   operator. Two carriers, neither of them colour: the text steps back to the
   muted ramp, and a dotted rule runs down the inline-start edge. The dots are
   the same convention the value states use for "not settled yet", so a reader
   learns it once. */
.data-table__row--pending th,
.data-table__row--pending td { color: var(--color-text-muted); }
.data-table__row--pending th:first-child,
.data-table__row--pending td:first-child {
  border-inline-start: var(--stroke-strong) dotted var(--color-border-interactive);
  /* Take the rule's width back out of the gutter so the first column still
     lines up with the header above it. max() because the gutter is 0 inside an
     already-padded panel body, and a negative padding is not a thing. */
  padding-inline-start: max(0px, calc(var(--table-gutter) - var(--stroke-strong)));
}
.data-table__row--pending th[scope="row"] { color: var(--color-text-secondary); }
.data-table .num {
  text-align: end;
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  white-space: nowrap;
  color: var(--color-text-primary);
}
/* A date is one atomic value, so it never breaks. Left alone the browser wraps
   it at its own hyphens — "2026-07-" above "17" — which reads as two numbers
   and takes the column's alignment with it. Tabular figures so a column of
   dates aligns digit for digit. */
.data-table time {
  white-space: nowrap;
  font-variant-numeric: tabular-nums slashed-zero;
}
/* Identifiers are mono and, at 320px, longer than the column. `nowrap` here
   would push the whole table wide; `anywhere` lets the identifier break rather
   than the layout. It is never a figure, so breaking it costs nothing — the
   rule that amounts must never wrap does not extend to reference numbers. */
.data-table__id {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.88em;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
  font-feature-settings: var(--font-feature-code);
  margin-block-start: var(--space-1);
}
.data-table__note {
  display: block;
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
  max-inline-size: 34ch;
}
/* The sort control. Every sortable column carries a visible mark at rest, not
   only on hover — an affordance that appears on hover is one a touch user never
   discovers, and one a keyboard user finds only by tabbing blind. The mark is a
   glyph drawn from currentColor rather than an image, so it survives
   forced-colors, and the direction is also stated in words for a screen reader
   via the visually-hidden span in the markup. */
.data-table__sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: none;
  border: 0;
  padding: 0;
  min-block-size: 24px;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: var(--stroke-hairline);
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.25em;
}
.data-table__sort-mark {
  display: inline-block;
  inline-size: 0.8em;
  text-align: center;
  font-variant-numeric: normal;
}
/* Unsorted: both directions offered. Sorted: the one in force. */
.data-table__sort-mark::before { content: "\2195"; }
.data-table__sort[aria-sort="ascending"] .data-table__sort-mark::before { content: "\2191"; }
.data-table__sort[aria-sort="descending"] .data-table__sort-mark::before { content: "\2193"; }
.data-table__sort:hover { color: var(--color-text-primary); text-decoration-color: currentColor; }
.data-table__sort:active { color: var(--color-text-secondary); }
.data-table__sort[aria-sort="ascending"],
.data-table__sort[aria-sort="descending"] { color: var(--color-text-primary); font-weight: 500; }
/* The sorted column reads a shade stronger the whole way down, so the answer to
   "what is this ordered by" is visible from the data as well as the header. */
.data-table thead th[aria-sort="ascending"],
.data-table thead th[aria-sort="descending"] { color: var(--color-text-secondary); }

/* Legend for the value states a table uses. Statistical practice: a table that
   marks cells is a table that explains its marks, on the same screen. */
.value-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  padding-block: var(--space-3);
  padding-inline: var(--table-gutter, var(--space-6));
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
}
.value-legend > div { display: flex; gap: var(--space-2); align-items: baseline; min-inline-size: 0; }
.value-legend dt { flex: 0 0 auto; }
.value-legend dd { max-inline-size: 44ch; }
.panel__body:not(.panel__body--flush) .value-legend { padding-inline: 0; }

/* ---------------------------------------------------------------------------
   Status chip. The word is the semantic; the tint reinforces it and is never
   asked to carry it. Still no glyph — a glyph would add nothing a reader of
   this product does not already read faster in the word.

   Every chip is bounded, using the --color-status-*-border tokens the palette
   revision added. That is not decoration: a status wash sits at roughly 1.1:1
   against the canvas and barely 1.04:1 against the warm clay surface band, so
   on a panel the fill alone is not a reliable edge. Each border token is solved
   to 3:1 against --color-surface-raised, the worst case.

   The border STYLE is then the non-chromatic carrier, and the seven semantics
   divide into four treatments rather than seven, because seven distinguishable
   line styles would be a code nobody learns:

     hairline solid  neutral, information, success  settled; nothing is asked
     1.5px solid     warning, danger                something is asked of you
     dashed          blocked                        waiting on a gate elsewhere
     dotted          pending                        in flight, not an event

   Dotted-for-pending and dashed-for-blocked are the same marks the notices, the
   empty states, the in-flight table rows and the value states use, so a reader
   learns the vocabulary once and it holds across the whole system. Blocked
   earns the broken rule in particular: it is not an error but a fail-closed
   state awaiting a gate, and a broken line reads as "not finished" where a
   solid red one would read as "wrong".
   ------------------------------------------------------------------------- */
.status-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius-small);
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1.5;
  white-space: nowrap;
  border: var(--stroke-hairline) solid transparent;
}
.status-chip__count {
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  font-weight: 500;
}
.status-chip--neutral     { color: var(--color-status-neutral);     background: var(--color-status-neutral-bg);     border-color: var(--color-status-neutral-border); }
.status-chip--information { color: var(--color-status-information); background: var(--color-status-information-bg); border-color: var(--color-status-information-border); }
.status-chip--success     { color: var(--color-status-success);     background: var(--color-status-success-bg);     border-color: var(--color-status-success-border); }
.status-chip--warning     { color: var(--color-status-warning);     background: var(--color-status-warning-bg);     border-color: var(--color-status-warning-border); }
.status-chip--danger      { color: var(--color-status-danger);      background: var(--color-status-danger-bg);      border-color: var(--color-status-danger-border); }
.status-chip--blocked     { color: var(--color-status-blocked);     background: var(--color-status-blocked-bg);     border-color: var(--color-status-blocked-border); border-style: dashed; }
.status-chip--pending     { color: var(--color-status-pending);     background: var(--color-status-pending-bg);     border-color: var(--color-status-pending-border); border-style: dotted; }

/* Warning and danger are the two that ask for an action, so they carry the
   heavier rule. 1.5px rather than 2px: at chip scale 2px starts to read as a
   button and invites a click that is not there. */
.status-chip--warning,
.status-chip--danger { border-width: var(--stroke-medium); }

/* ---------------------------------------------------------------------------
   Value states.

   Four reasons a cell has no figure in it, and they are four different facts.
   Collapsing them into a blank — or into a 0, which is a lie — is the commonest
   way a data screen misleads. Each keeps its word, because an operator wants
   the word rather than a symbol to decode, and each carries a distinct
   underline so the four are told apart with no colour at all.

     nil       no rule      it happened, and the answer is nothing
     pending   dotted       it has not happened yet; an answer is expected
     withheld  dashed       an answer exists and is not shown here
     na        no rule      the question does not apply to this record

   `nil` and `na` share the plain treatment on purpose: neither is waiting on
   anything, and the word is doing the work. The two that a reader might act on
   — wait, or ask for access — are the two that get a rule.
   ------------------------------------------------------------------------- */
.value { color: var(--color-text-muted); }
.value--nil { color: var(--color-text-muted); }
.value--na  { color: var(--color-text-muted); font-style: normal; }
.value--pending {
  color: var(--color-status-pending);
  text-decoration: underline dotted var(--color-border-interactive);
  text-decoration-thickness: var(--stroke-hairline);
  text-underline-offset: 0.25em;
}
.value--withheld {
  color: var(--color-status-blocked);
  text-decoration: underline dashed currentColor;
  text-decoration-thickness: var(--stroke-hairline);
  text-underline-offset: 0.25em;
}
/* A suppressed or not-yet-issued value says why, in the same breath. A cell
   that withholds without a reason is indistinguishable from a defect.

   The reason sits BESIDE the marked word rather than inside it, because
   text-decoration is painted by the element that declares it and a descendant
   cannot remove it: nested, this sentence inherited the dashed underline and
   read as a link four lines long. */
.value-block { display: block; }
.value__reason {
  display: block;
  margin-block-start: var(--space-1);
  font-size: var(--type-label-size);
  line-height: 1.45;
  font-weight: 400;
  color: var(--color-text-muted);
  max-inline-size: 42ch;
}

/* ---------------------------------------------------------------------------
   Buttons.

   Five variants, each drawn at five states. The default is ink; the accent is
   spent on exactly one control per view — the one genuinely waiting on the
   reader — and never to make one option look preferable to another, which the
   contract forbids outright.

   The press state is a real design decision here rather than a flourish. On a
   tool operated all day the acknowledgement that a control took the press is
   what stops the second click, and on a server-rendered form the second click
   is a second POST. It is drawn as a 1px inward nudge plus a step down the
   surface ramp; `translateY` is compositor-only, so it costs nothing against
   the INP budget, and it sits behind both motion kill switches.
   ------------------------------------------------------------------------- */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: 44px;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-medium);
  border: var(--stroke-hairline) solid var(--color-border-interactive);
  background: var(--color-canvas);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--type-label-size);
  font-weight: 500;
  line-height: 1.3;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
.button:hover { background: var(--color-surface); text-decoration: none; }
.button:active { transform: translateY(var(--stroke-hairline)); background: var(--color-surface-raised); }
.button:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring); outline-offset: var(--focus-ring-offset); }

.button--primary {
  background: var(--color-inverse);
  border-color: var(--color-inverse);
  color: var(--color-text-on-inverse);
}
.button--primary:hover { background: var(--color-inverse-hover); border-color: var(--color-inverse-hover); }
.button--primary:active { background: var(--color-inverse-hover); border-color: var(--color-inverse-hover); }

/* The one control per screen that is genuinely waiting on the reader.
   --color-accent-solid, not --color-accent: the accent step is solved for TEXT
   on the canvas, the solid step is solved so a white label on it clears 4.5:1.
   Filling a control with the text step is the commonest way an accent quietly
   fails 1.4.3, and here it would have shipped at 5.57:1 instead of 6.57:1. */
.button--awaiting {
  background: var(--color-accent-solid);
  border-color: var(--color-accent-solid);
  color: var(--color-text-on-accent);
}
.button--awaiting:hover { background: var(--color-accent-solid-hover); border-color: var(--color-accent-solid-hover); }
.button--awaiting:active { background: var(--color-accent-solid-hover); border-color: var(--color-accent-solid-hover); }

/* Destructive. Outlined rather than filled: a filled red button is the most
   mis-clicked control in any admin tool, and the weight it gains is weight it
   should not have. The word carries the consequence, and the server asks again
   where the action cannot be undone. */
.button--danger { color: var(--color-status-danger); border-color: var(--color-status-danger); }
.button--danger:hover { background: var(--color-status-danger-bg); border-color: var(--color-status-danger); }
.button--danger:active { background: var(--color-status-danger-bg); }

.button--quiet {
  border-color: transparent;
  background: none;
  color: var(--color-text-secondary);
  min-block-size: 24px;
  padding-inline: 0;
  text-decoration: underline;
  text-decoration-thickness: var(--stroke-hairline);
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.22em;
}
.button--quiet:hover { background: none; color: var(--color-text-primary); text-decoration-color: currentColor; }
.button--quiet:active { background: none; color: var(--color-text-secondary); }

/* Unavailable. `aria-disabled` rather than `disabled` throughout, so the
   control keeps its place in the tab order and a keyboard user can reach it and
   hear the reason. A control that has vanished, or one that is greyed with no
   explanation, both leave the operator guessing whether the screen is broken.
   The hover, active and pointer affordances are all withdrawn so the control
   does not pretend to respond. */
.button[aria-disabled="true"], .button:disabled {
  color: var(--color-text-disabled);
  border-color: var(--color-border-subtle);
  background: var(--color-surface);
  cursor: not-allowed;
}
.button[aria-disabled="true"]:hover, .button:disabled:hover { background: var(--color-surface); }
.button[aria-disabled="true"]:active, .button:disabled:active { transform: none; background: var(--color-surface); }
.button--quiet[aria-disabled="true"] { background: none; border-color: transparent; text-decoration-color: var(--color-border-subtle); }

/* A disabled control and the reason it is disabled, kept together. */
.button-group { display: inline-flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.button-group__reason {
  font-size: var(--type-label-size);
  line-height: 1.45;
  color: var(--color-text-muted);
  max-inline-size: 42ch;
}

.button-row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; }

/* ---------------------------------------------------------------------------
   Filter toolbar
   ------------------------------------------------------------------------- */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.toolbar__label {
  font-size: var(--type-label-size);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-inline-end: var(--space-2);
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 32px;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-small);
  /* A filter chip is a control, so its boundary is bound by SC 1.4.11 at 3:1.
     It was on --color-border-default, which is a decorative rule at 1.63:1 and
     not permitted to bound a control. --color-border-interactive is the only
     border token that clears the threshold. */
  border: var(--stroke-hairline) solid var(--color-border-interactive);
  background: var(--color-canvas);
  color: var(--color-text-secondary);
  font-size: var(--type-label-size);
  font-weight: 400;
  text-decoration: none;
  transition: border-color var(--duration-base) var(--ease-out);
}
.filter-chip:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); text-decoration: none; background: var(--color-surface); }
.filter-chip:active { background: var(--color-surface-raised); }
/* The chip in force. Three carriers, because a filter that is silently applied
   is how a reader concludes the data is wrong: an inverted fill, a weight
   change, and `aria-current` for anything not looking at the pixels. */
.filter-chip[aria-current="true"] {
  background: var(--color-inverse);
  border-color: var(--color-inverse);
  color: var(--color-text-on-inverse);
  font-weight: 500;
}
.filter-chip[aria-current="true"]:hover { background: var(--color-inverse-hover); border-color: var(--color-inverse-hover); color: var(--color-text-on-inverse); }
/* A filter that would return nothing still shows, and says so. Hiding it makes
   the toolbar re-order itself every time the data moves. */
.filter-chip--empty { color: var(--color-text-muted); border-style: dashed; }
.filter-chip__count { font-variant-numeric: tabular-nums slashed-zero; opacity: 0.75; }

/* ---------------------------------------------------------------------------
   Key/value list
   ------------------------------------------------------------------------- */
.kv { display: grid; gap: var(--space-4); }
.kv > div {
  display: grid;
  grid-template-columns: minmax(9rem, 11rem) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: baseline;
}
.kv dt {
  font-size: var(--type-label-size);
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.kv dd { font-size: var(--type-body-small-size); color: var(--color-text-primary); }
.kv dd .money__derived { margin-block-start: var(--space-1); }

/* ---------------------------------------------------------------------------
   Stepper
   ------------------------------------------------------------------------- */
.stepper { list-style: none; display: grid; }
.stepper__item {
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.stepper__item:last-child { border-block-end: 0; padding-block-end: 0; }
.stepper__item:first-child { padding-block-start: 0; }
.stepper__marker {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--type-label-size);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  border: var(--stroke-hairline) solid var(--color-border-default);
  background: var(--color-canvas);
  color: var(--color-text-muted);
}
.stepper__item--done .stepper__marker {
  background: var(--color-surface-raised);
  border-color: var(--color-border-interactive);
  color: var(--color-text-secondary);
}
.stepper__item--current .stepper__marker {
  background: var(--color-inverse);
  border-color: var(--color-inverse);
  color: var(--color-text-on-inverse);
}
/* Blocked had no treatment at all and rendered exactly like "upcoming", which
   is the opposite of the truth: upcoming means not yet, blocked means not from
   here. A dashed ring, the same mark the blocked chip and the blocked notice
   carry, plus the word already in the markup. */
.stepper__item--blocked .stepper__marker {
  border-style: dashed;
  border-color: var(--color-status-blocked);
  color: var(--color-status-blocked);
}
.stepper__item--blocked .stepper__state { color: var(--color-status-blocked); }
.stepper__item--current .stepper__name { color: var(--color-text-primary); }
.stepper__item--done .stepper__name,
.stepper__item--done .stepper__state { color: var(--color-text-muted); }
.stepper__name { font-weight: 500; font-size: var(--type-body-small-size); }
.stepper__state {
  display: block;
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
}

/* ---------------------------------------------------------------------------
   Timeline and AuditTrail
   ------------------------------------------------------------------------- */
.timeline { list-style: none; display: grid; }
.timeline__item {
  display: grid;
  grid-template-columns: 0.5rem minmax(0, 1fr);
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.timeline__item:last-child { border-block-end: 0; padding-block-end: 0; }
.timeline__item:first-child { padding-block-start: 0; }
.timeline__dot {
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  margin-block-start: 0.42rem;
  background: var(--color-border-interactive);
}
.timeline__dot--success { background: var(--color-status-success); }
.timeline__dot--warning { background: var(--color-status-warning); }
.timeline__dot--danger  { background: var(--color-status-danger); }
/* Pending and blocked are hollow rather than filled: an event that has not
   settled should not look like one that has, and the difference reads without
   colour. */
.timeline__dot--pending {
  background: var(--color-canvas);
  box-shadow: inset 0 0 0 var(--stroke-hairline) var(--color-border-interactive);
}
.timeline__dot--blocked {
  background: var(--color-canvas);
  box-shadow: inset 0 0 0 var(--stroke-medium) var(--color-status-blocked);
}
.timeline__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  justify-content: space-between;
}
.timeline__title { font-weight: 500; font-size: var(--type-body-small-size); }
.timeline__time {
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap;
}
.timeline__body {
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
  margin-block-start: var(--space-2);
  max-inline-size: 68ch;
}
.timeline__actor {
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-2);
  font-family: var(--font-mono);
  font-feature-settings: var(--font-feature-code);
}

/* ---------------------------------------------------------------------------
   DocumentChecklist and ConditionList
   ------------------------------------------------------------------------- */
.checklist { list-style: none; display: grid; }
.checklist__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: start;
  padding-block: var(--space-4);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.checklist__item:last-child { border-block-end: 0; padding-block-end: 0; }
.checklist__item:first-child { padding-block-start: 0; }
.checklist__name { font-weight: 500; font-size: var(--type-body-small-size); }
.checklist__meta {
  display: block;
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-2);
  max-inline-size: 56ch;
}

/* ---------------------------------------------------------------------------
   ReadinessPanel. The bar is a summary of the table beneath it, never the only
   representation of it.
   ------------------------------------------------------------------------- */
.readiness__summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  align-items: baseline;
  margin-block-end: var(--space-4);
}
.readiness__count {
  font-size: var(--type-heading-2-size);
  font-weight: 500;
  letter-spacing: -0.014em;
  font-variant-numeric: tabular-nums slashed-zero;
}
.readiness__caption { font-size: var(--type-body-small-size); color: var(--color-text-muted); }
.readiness__bar {
  display: flex;
  block-size: 0.375rem;
  border-radius: var(--radius-small);
  overflow: hidden;
  background: var(--color-surface-raised);
  margin-block-end: var(--space-6);
}
.readiness__seg--pass    { background: var(--color-status-success); }
.readiness__seg--pending { background: var(--color-status-pending); }
.readiness__seg--fail    { background: var(--color-status-danger); }

/* ---------------------------------------------------------------------------
   InterventionCard
   ------------------------------------------------------------------------- */
.intervention {
  border-inline-start: var(--stroke-strong) solid var(--color-status-warning);
  padding-inline-start: var(--space-4);
  padding-block: var(--space-1);
}
.intervention + .intervention { margin-block-start: var(--space-6); }
.intervention--blocked { border-inline-start-color: var(--color-status-blocked); }
.intervention--danger  { border-inline-start-color: var(--color-status-danger); }
.intervention--pending { border-inline-start-color: var(--color-border-interactive); }
.intervention__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  justify-content: space-between;
}
.intervention__title { font-weight: 500; font-size: var(--type-body-small-size); }
.intervention__body {
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
  margin-block-start: var(--space-2);
  max-inline-size: 68ch;
}
/* A description list rather than a row of nowrap spans. The spans overflowed a
   320px viewport — "Requires CYBERMETRIQ_LEGAL" cannot be made narrower and was
   never allowed to break — and they also asked the reader to infer which word
   was the term and which the value. Now the term is a term. */
.intervention__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  gap: var(--space-2) var(--space-6);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
}
.intervention__meta > div { display: grid; gap: var(--space-1); min-inline-size: 0; }
.intervention__meta dt { letter-spacing: 0.05em; text-transform: uppercase; font-size: 0.92em; }
.intervention__meta dd { color: var(--color-text-secondary); overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
   DualApprovalPanel
   ------------------------------------------------------------------------- */
.approval {
  border: var(--stroke-hairline) solid var(--color-border-subtle);
  border-radius: var(--radius-large);
  overflow: hidden;
  background: var(--color-canvas);
}
.approval + .approval { margin-block-start: var(--space-6); }
.approval__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.approval__title {
  font-size: var(--type-heading-3-size);
  line-height: var(--type-heading-3-line);
  font-weight: 500;
  letter-spacing: var(--type-heading-3-track);
  overflow-wrap: anywhere;
}
/* A permission triple is three long mono tokens with no spaces the browser will
   break at — it overflowed a 320px viewport by 17px. `anywhere` lets the
   identifier wrap instead of the page. */
.approval__permission {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
  overflow-wrap: anywhere;
  font-feature-settings: var(--font-feature-code);
}
.approval__body { padding: var(--space-6); }
.approval__detail { font-size: var(--type-body-small-size); color: var(--color-text-secondary); max-inline-size: 68ch; }
.approval__parties {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-6);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
}
.approval__party-label {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.approval__party-name {
  display: block;
  font-weight: 500;
  margin-block-start: var(--space-2);
  font-size: var(--type-body-small-size);
}
.approval__party--awaiting .approval__party-name { font-weight: 400; color: var(--color-text-muted); }
.approval__party-meta {
  display: block;
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
  font-family: var(--font-mono);
  font-feature-settings: var(--font-feature-code);
}
.approval__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-end;
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-6);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
}
.approval__reason { flex: 1 1 20rem; min-inline-size: 0; }
.approval__blocked {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-6);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-size: var(--type-body-small-size);
  max-inline-size: 68ch;
}
/* The refusal sentence is the most important line on a blocked request, so it
   gets the measure a sentence deserves rather than the panel's full width. */
.approval__blocked > p { max-inline-size: 62ch; line-height: 1.55; }
.approval__blocked-label {
  display: block;
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-status-blocked);
  margin-block-end: var(--space-2);
}
.approval__rule {
  margin-block-start: var(--space-3);
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
  font-feature-settings: var(--font-feature-code);
}
.approval__party-meta { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
   Forms
   ------------------------------------------------------------------------- */
.form-field { display: grid; gap: var(--space-2); }
/* A label is a target — clicking it focuses the control — so it clears 24px in
   its own right rather than relying on the input below it. */
.form-field__label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 24px;
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
}
/* "required", not an asterisk. An asterisk is a symbol that means nothing
   without a key, and a key is one more thing for the reader to go and find. */
.form-field__required {
  font-size: var(--type-label-size);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
  padding-inline: var(--space-2);
  border-radius: var(--radius-small);
  border: var(--stroke-hairline) solid var(--color-border-default);
}
.form-field__hint { font-size: var(--type-label-size); line-height: 1.5; color: var(--color-text-muted); max-inline-size: 56ch; }
/* The error carries the word "Error" as well as the colour and the 2px rule.
   Three carriers, because this is the one message on a form a colour-blind or
   greyscale reader cannot afford to miss (WCAG 1.4.1). */
.form-field__error {
  font-size: var(--type-label-size);
  line-height: 1.5;
  font-weight: 500;
  color: var(--color-status-danger);
  max-inline-size: 56ch;
  border-inline-start: var(--stroke-strong) solid var(--color-status-danger);
  padding-inline-start: var(--space-3);
}
.form-field__error-label {
  display: inline-block;
  margin-inline-end: var(--space-2);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 0.92em;
}
.form-field__error-label::after { content: ":"; }
/* The field a submission was refused on. Its label steps from secondary ink to
   primary, so the eye lands on the broken field when it scans a form of eight
   — the message itself is carried by the word "Error" and the 2px rule, not by
   this. The modifier shipped with no rule at all until the class-coverage gate
   caught it, which is the one thing that gate is for. */
.form-field--invalid > .form-field__label { color: var(--color-text-primary); }
.form-field--invalid > .form-field__hint { color: var(--color-text-secondary); }

.input, .select, .textarea {
  inline-size: 100%;
  min-block-size: 44px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-medium);
  border: var(--stroke-hairline) solid var(--color-border-interactive);
  background: var(--color-canvas);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--type-body-small-size);
  transition: border-color var(--duration-fast) var(--ease-out);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--color-border-strong); }
/* The border thickens on focus as well as the ring appearing. The ring alone is
   two pixels of colour on a field the eye may already have lost at 400% zoom. */
.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
  border-color: var(--color-focus-ring);
  border-width: var(--stroke-medium);
}
.input:disabled, .select:disabled, .textarea:disabled {
  color: var(--color-text-disabled);
  background: var(--color-surface);
  border-color: var(--color-border-subtle);
  border-style: dashed;
  cursor: not-allowed;
}
.input:read-only:not(:disabled) { background: var(--color-surface); }
.input::placeholder, .textarea::placeholder { color: var(--color-text-muted); opacity: 1; }
.select { appearance: auto; }
.textarea { min-block-size: 4.5rem; resize: vertical; line-height: 1.5; }
/* A text field wider than about 45 characters invites input it cannot hold. */
.input--short { max-inline-size: 30rem; }

/* margin-block-start is deliberately left alone so the enclosing stack still
   owns the vertical rhythm; resetting it here would silently win on
   specificity and collapse the gap above the group. */
.fieldset { border: 0; padding: 0; margin-inline: 0; margin-block-end: 0; min-inline-size: 0; }
.fieldset > .form-field__label { display: flex; }
.fieldset__controls { margin-block-start: var(--space-3); }
/* An invalid group takes the same 2px rule an invalid field does, so a
   fieldset's error is as findable as an input's. */
.fieldset--invalid > .fieldset__controls {
  border-inline-start: var(--stroke-strong) solid var(--color-status-danger);
  padding-inline-start: var(--space-3);
}

/* WCAG 2.2 2.5.8 measures the target, and for a checkbox that is the box
   itself as much as the label beside it — so the box is 24x24, the minimum,
   rather than the 17px the browser draws. The row remains 44px and clickable
   across its width, which is what a finger actually uses. */
.checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: 44px;
  cursor: pointer;
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
}
.checkbox-row:hover { color: var(--color-text-primary); }
.checkbox-row input[type="checkbox"],
.checkbox-row input[type="radio"] {
  inline-size: var(--space-6);
  block-size: var(--space-6);
  min-inline-size: var(--space-6);
  min-block-size: var(--space-6);
  margin: 0;
  accent-color: var(--color-accent);
  flex: 0 0 auto;
}
.checkbox-row input[type="checkbox"]:disabled + *,
.checkbox-row input[type="radio"]:disabled + * { color: var(--color-text-disabled); }
.checkbox-row:has(input:disabled) { cursor: not-allowed; color: var(--color-text-disabled); }
.checkbox-list { display: grid; }

.input[aria-invalid="true"], .textarea[aria-invalid="true"], .select[aria-invalid="true"] {
  border-color: var(--color-status-danger);
  border-width: var(--stroke-medium);
}

/* The summary a failed submission lands on. It carries `autofocus` in the
   markup, which is the only way to move focus here with no JavaScript at all —
   ordinary portal screens run under `script-src 'none'`. */
.error-summary {
  border: var(--stroke-hairline) solid var(--color-status-danger);
  border-inline-start-width: var(--stroke-strong);
  border-radius: var(--radius-medium);
  padding: var(--space-4) var(--space-6);
  margin-block-end: var(--space-8);
  max-inline-size: var(--container-prose);
}
.error-summary:focus-visible { outline-offset: var(--focus-ring-offset); }
.error-summary__title {
  font-size: var(--type-heading-3-size);
  font-weight: 500;
  color: var(--color-status-danger);
}
.error-summary__count {
  margin-block-start: var(--space-2);
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
  max-inline-size: 60ch;
}
.error-summary ul { list-style: disc; padding-inline-start: var(--space-6); margin-block-start: var(--space-3); }
.error-summary li { font-size: var(--type-body-small-size); }
.error-summary li + li { margin-block-start: var(--space-1); }
.error-summary a { color: var(--color-status-danger); }

/* ---------------------------------------------------------------------------
   Notices
   ------------------------------------------------------------------------- */
.notice {
  padding-inline-start: var(--space-4);
  border-inline-start: var(--stroke-strong) solid var(--color-border-interactive);
  font-size: var(--type-body-small-size);
  color: var(--color-text-secondary);
  max-inline-size: var(--container-prose);
}
.notice__title {
  display: block;
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  margin-block-end: var(--space-1);
}
/* All seven contract semantics, not the five a screen happens to use today.
   A component that covers only the cases in front of it is how the sixth case
   ships unstyled. The rule style repeats the chip's code — solid for settled,
   dashed for blocked, dotted for pending — so the two components teach the
   reader the same vocabulary. */
.notice--neutral { border-inline-start-color: var(--color-border-interactive); }
.notice--neutral .notice__title { color: var(--color-text-secondary); }
.notice--information { border-inline-start-color: var(--color-status-information); }
.notice--information .notice__title { color: var(--color-status-information); }
.notice--success { border-inline-start-color: var(--color-status-success); }
.notice--success .notice__title { color: var(--color-status-success); }
.notice--warning { border-inline-start-color: var(--color-status-warning); }
.notice--warning .notice__title { color: var(--color-status-warning); }
.notice--danger { border-inline-start-color: var(--color-status-danger); }
.notice--danger .notice__title { color: var(--color-status-danger); }
.notice--blocked { border-inline-start-style: dashed; border-inline-start-color: var(--color-status-blocked); }
.notice--blocked .notice__title { color: var(--color-status-blocked); }
.notice--pending { border-inline-start-style: dotted; border-inline-start-color: var(--color-status-pending); }
.notice--pending .notice__title { color: var(--color-status-pending); }
.notice__actions { margin-block-start: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* ---------------------------------------------------------------------------
   Empty states. Left-aligned and composed. An operational tool is mostly empty
   states, so they are designed screens rather than fallbacks.
   ------------------------------------------------------------------------- */
.empty-state {
  padding: var(--space-8) var(--space-6);
  max-inline-size: 62ch;
  border-inline-start: var(--stroke-strong) solid var(--color-border-subtle);
}
.empty-state__label {
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-3);
}
.empty-state__title {
  font-size: var(--type-heading-2-size);
  line-height: var(--type-heading-2-line);
  font-weight: var(--type-heading-2-weight);
  letter-spacing: var(--type-heading-2-track);
  color: var(--color-text-primary);
}
.empty-state__body {
  margin-block-start: var(--space-4);
  color: var(--color-text-secondary);
  font-size: var(--type-body-small-size);
  line-height: 1.6;
}
.empty-state__meta {
  margin-block-start: var(--space-3);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  max-inline-size: 62ch;
}
.empty-state__actions { margin-block-start: var(--space-6); display: flex; gap: var(--space-3); flex-wrap: wrap; }

/* Four empty states, four rules. "No data" and "withheld for policy" and
   "not arrived yet" and "could not be produced" are four different facts, and
   a reader acts on each differently: read on, ask for access, wait, or report
   it. The line style says which before the words are read, and says it with no
   colour at all — solid ink, dotted, dashed, and a 2px danger rule. */
.empty-state--pending { border-inline-start-style: dotted; border-inline-start-color: var(--color-border-interactive); }
.empty-state--withheld { border-inline-start-style: dashed; border-inline-start-color: var(--color-status-blocked); }
.empty-state--withheld .empty-state__label { color: var(--color-status-blocked); }
.empty-state--error { border-inline-start-color: var(--color-status-danger); }
.empty-state--error .empty-state__label { color: var(--color-status-danger); }

/* ---------------------------------------------------------------------------
   Code samples
   ------------------------------------------------------------------------- */
.code-block {
  border: var(--stroke-hairline) solid var(--color-code-border);
  border-radius: var(--radius-large);
  overflow: hidden;
  background: var(--color-code-bg);
}
.code-block__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-block-end: var(--stroke-hairline) solid var(--color-code-border);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
}
.code-block__lang {
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
  font-feature-settings: var(--font-feature-code);
}
.code-block pre {
  margin: 0;
  padding: var(--space-4);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--type-code-size);
  line-height: var(--type-code-line);
  font-feature-settings: var(--font-feature-code);
  color: var(--color-text-primary);
}
.code-inline {
  font-family: var(--font-mono);
  font-size: 0.9em;
  padding: 0.08em 0.32em;
  border-radius: var(--radius-small);
  background: var(--color-code-bg);
  border: var(--stroke-hairline) solid var(--color-code-border);
  font-feature-settings: var(--font-feature-code);
  white-space: nowrap;
}
.method {
  display: inline-block;
  min-inline-size: 4rem;
  font-family: var(--font-mono);
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
  font-feature-settings: var(--font-feature-code);
}
.endpoint-path {
  font-family: var(--font-mono);
  font-size: var(--type-body-small-size);
  font-feature-settings: var(--font-feature-code);
  color: var(--color-text-primary);
}

/* ---------------------------------------------------------------------------
   Bar comparison with its table equivalent
   ------------------------------------------------------------------------- */
/* One custom property is the whole series knob, the mechanism CHART_SYSTEM
   takes from btxprice: the fill, and anything else that ever joins it, reads
   `--chart-line`, so recolouring a series is one declaration on the figure.
   The default is ink rather than the accent — a single-series bar comparison is
   not a place to spend the accent, and every bar here is directly labelled with
   its own figure, so colour is carrying nothing on its own (WCAG 1.4.1). */
.metric-chart { --chart-line: var(--color-text-secondary); min-inline-size: 0; }
.chart-bars { display: grid; gap: var(--space-3); }
.chart-bars__row {
  display: grid;
  grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: center;
  min-inline-size: 0;
}
.chart-bars__label { font-size: var(--type-label-size); color: var(--color-text-secondary); min-inline-size: 0; }
.chart-bars__track {
  block-size: 0.375rem;
  background: var(--color-surface-raised);
  border-radius: var(--radius-small);
  overflow: hidden;
  min-inline-size: 0;
}
.chart-bars__fill { display: block; block-size: 100%; background: var(--chart-line, var(--color-text-secondary)); }
.chart-bars__value {
  font-family: var(--font-financial);
  font-size: var(--type-label-size);
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: var(--font-feature-financial);
  color: var(--color-text-primary);
  white-space: nowrap;
}

/* Every disclosure uses the same circled plus. The horizontal stroke and
   circle live in ::after; the vertical stroke is ::before so opening can scale
   that one stroke to zero without rotating the mark into an x. */
details > summary {
  position: relative;
  list-style: none;
  padding-inline-end: calc(var(--space-8) + var(--space-2));
}
details > summary::-webkit-details-marker { display: none; }
details > summary::marker { content: ""; }
details > summary::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-1);
  inset-block-start: calc(50% - var(--space-3));
  inline-size: var(--space-6);
  block-size: var(--space-6);
  border: var(--stroke-hairline) solid color-mix(in srgb, currentColor 48%, transparent);
  border-radius: 50%;
  background: linear-gradient(currentColor, currentColor) center / 10px var(--stroke-hairline) no-repeat;
}
details > summary::before {
  content: "";
  position: absolute;
  z-index: 1;
  inset-inline-end: calc(var(--space-4) - (var(--stroke-hairline) / 2));
  inset-block-start: calc(50% - 5px);
  inline-size: var(--stroke-hairline);
  block-size: 10px;
  background: currentColor;
  transform: scaleY(1);
  transform-origin: center;
  transition: transform var(--duration-fast) var(--ease-out);
}
details[open] > summary::before { transform: scaleY(0); }

/* Disclosure. `details`/`summary` is the primary progressive-disclosure tool
   in this system, and the only one available: ordinary portal screens run under
   `script-src 'none'`, so anything needing a click handler does not exist. The
   nonce-only OTP enhancement is isolated from these disclosures. */
details.disclosure > summary {
  cursor: pointer;
  min-block-size: 44px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-label-size);
  color: var(--color-text-secondary);
  text-decoration: underline;
  text-decoration-thickness: var(--stroke-hairline);
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.22em;
}
details.disclosure > summary:hover { color: var(--color-text-primary); text-decoration-color: currentColor; }
details.disclosure > summary:active { color: var(--color-text-secondary); }
details.disclosure > summary:focus-visible { outline-offset: var(--focus-ring-offset); }
details.disclosure[open] > summary { margin-block-end: var(--space-3); color: var(--color-text-primary); }

/* ---------------------------------------------------------------------------
   Loading.

   These portals render synchronously — every screen is complete when it
   arrives — so nothing here waits. The state exists for a surface that
   streams, and it is deliberately static: DESIGN_FRAMEWORK 4.3 rules out
   shimmer loops both because an infinite animation is a vestibular and
   attention cost, and because it makes the 0.005 snapshot diff
   non-deterministic. A skeleton at --color-surface says the same thing.
   ------------------------------------------------------------------------- */
.loading { display: grid; gap: var(--space-3); padding-block: var(--space-4); }
.loading__label { font-size: var(--type-label-size); color: var(--color-text-muted); }
.skeleton {
  display: block;
  block-size: var(--space-3);
  border-radius: var(--radius-small);
  background: var(--color-surface-raised);
  max-inline-size: 100%;
}

/* ---------------------------------------------------------------------------
   Sign-in
   ------------------------------------------------------------------------- */
.signin { max-inline-size: 44rem; padding-block: var(--space-16); }
.auth-flow { position: relative; }
.auth-mode-toggle:not(:checked) ~ .auth-signin-panel { display: none; }
.auth-mode-toggle:checked ~ .auth-signup-panel { display: none; }
.auth-mode-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 24px;
  color: var(--color-text-primary);
  text-decoration: underline;
  text-decoration-thickness: var(--stroke-hairline);
  text-decoration-color: var(--color-border-default);
  text-underline-offset: 0.22em;
  cursor: pointer;
}
.auth-mode-link:hover { text-decoration-color: currentColor; }
.auth-mode-toggle:focus-visible ~ .auth-signup-panel .auth-mode-link,
.auth-mode-toggle:focus-visible ~ .auth-signin-panel .auth-mode-link {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset);
}
.auth-form { display: grid; gap: var(--space-6); max-inline-size: 32rem; }
.auth-form > .button { justify-self: start; }
.auth-mode-prompt,
.auth-portal-summary,
.auth-delivery {
  color: var(--color-text-secondary);
  font-size: var(--type-body-small-size);
  line-height: 1.6;
  max-inline-size: 62ch;
}
.auth-portal-summary { margin-block-start: var(--space-4); }
.auth-notice { margin-block-end: var(--space-6); }
.auth-flow > .error-summary,
.auth-code > .error-summary { margin-block-start: var(--space-6); }
.auth-code__input {
  max-inline-size: 14rem;
  font-family: var(--font-mono);
  font-size: var(--type-heading-2-size);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.42em;
  text-align: center;
}
.auth-delivery { display: grid; gap: var(--space-3); }
.auth-personas > summary { font-weight: 500; }
.auth-personas > p { margin-block: var(--space-3); }
.persona-list { list-style: none; display: grid; gap: 0; }
.persona {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-6);
  align-items: center;
  padding-block: var(--space-4);
  border-block-end: var(--stroke-hairline) solid var(--color-border-subtle);
}
.persona:last-child { border-block-end: 0; }
.persona__name { font-weight: 500; font-size: var(--type-body-small-size); }
.persona__roles {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--type-label-size);
  color: var(--color-text-muted);
  margin-block-start: var(--space-1);
  font-feature-settings: var(--font-feature-code);
}
.persona__note {
  display: block;
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-secondary);
  margin-block-start: var(--space-2);
  max-inline-size: 60ch;
}
.persona__roles { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
   Status pages
   ------------------------------------------------------------------------- */
.status-page { max-inline-size: var(--container-prose); padding-block: var(--space-16); }
.status-page__code {
  font-family: var(--font-mono);
  font-size: var(--type-label-size);
  letter-spacing: 0.1em;
  color: var(--color-text-muted);
  font-feature-settings: var(--font-feature-code);
}
.status-page__title {
  font-size: var(--type-heading-1-size);
  line-height: var(--type-heading-1-line);
  font-weight: var(--type-heading-1-weight);
  letter-spacing: var(--type-heading-1-track);
  margin-block-start: var(--space-3);
}
.status-page__body { margin-block-start: var(--space-6); color: var(--color-text-secondary); }
.status-page__body p { max-inline-size: 68ch; }
.status-page__body p + p { margin-block-start: var(--space-4); }
/* Its own `ch` cap. Inheriting the page's 68ch — set against 16px body — puts
   this 13px mono line at about 110 characters, well past the measure. */
.status-page__detail {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: var(--stroke-hairline) solid var(--color-border-subtle);
  font-family: var(--font-mono);
  font-size: var(--type-label-size);
  line-height: 1.5;
  color: var(--color-text-muted);
  max-inline-size: 68ch;
  overflow-wrap: anywhere;
  font-feature-settings: var(--font-feature-code);
}

/* ---------------------------------------------------------------------------
   Reflow: 400% zoom and narrow viewports
   ------------------------------------------------------------------------- */
@media (max-width: 47.999rem) {
  .kv > div { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .approval__head, .panel__header { flex-direction: column; align-items: flex-start; }
  .identity__block { text-align: start; }
  .checklist__item, .persona { grid-template-columns: minmax(0, 1fr); }
  .chart-bars__row { grid-template-columns: minmax(0, 1fr) auto; }
  .chart-bars__track { grid-column: 1 / -1; }
  .table-region { --table-gutter: var(--space-4); }
  .value-legend { padding-inline: var(--space-4); }
  /* The identity block is the only thing in the header bar that can be made
     smaller without losing information: the org and the roles move under the
     name instead of sitting beside the brand, and the whole bar stays two
     lines rather than three. */
  .app-header__bar { gap: var(--space-3); }
  .app-header__spacer { display: none; }
  .app-header__controls { inline-size: 100%; justify-content: space-between; }
  .identity { flex: 1 1 12rem; justify-content: space-between; min-inline-size: 0; }
  .app-brand { flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
  .app-brand__portal { border-inline-start: 0; padding-inline-start: 0; }
  .approval__actions { flex-direction: column; align-items: stretch; }
  .empty-state { padding-inline: var(--space-4); }
}

@media (prefers-reduced-motion: reduce) {
  details > summary::before { transition: none; }
}

/* ---------------------------------------------------------------------------
   Touch.

   24x24 is the WCAG 2.2 AA floor (SC 2.5.8) and is what every control here
   meets at every size. 44x44 is this system's own commitment, and it is applied
   where a finger is actually doing the work rather than everywhere: a
   coarse-pointer query is the honest test for that, and it keeps a
   trading-desk display dense while a phone stays operable. The size difference
   is real — a mis-tap on a filter chip re-queries the server.
   ------------------------------------------------------------------------- */
@media (pointer: coarse) {
  .filter-chip,
  .data-table__sort,
  .button--quiet,
  .link-target,
  .data-table a,
  .checklist__name a,
  .breadcrumbs a,
  .nav-link { min-block-size: 44px; }
  .data-table th, .data-table td { padding-block: var(--space-3); }
  .checkbox-row input[type="checkbox"],
  .checkbox-row input[type="radio"] {
    inline-size: var(--space-8);
    block-size: var(--space-8);
    min-inline-size: var(--space-8);
    min-block-size: var(--space-8);
  }
}

/* ---------------------------------------------------------------------------
   Forced colors (Windows High Contrast).

   tokens.css maps the semantic tokens onto the system keywords. What it cannot
   know is which structures in THIS layer were drawn with a background, and a
   background is exactly what forced-colors discards. Everything below either
   drew a shape with a fill — a progress segment, a timeline dot, a step marker,
   a bar — or told two states apart with one, and each needs a border or a
   pattern to survive.

   The rule of thumb: if removing every background from a component would leave
   a reader unable to answer the question the component exists to answer, it
   belongs in this block.
   ------------------------------------------------------------------------- */
@media (forced-colors: active) {
  /* Panels, cards and code blocks were separated from the page by a surface
     step. That step is gone, so the edge has to carry it. */
  .panel,
  .metric-card,
  .approval,
  .code-block,
  .error-summary { border: var(--stroke-hairline) solid CanvasText; }
  .metric-card--lead { border-inline-start-width: var(--stroke-strong); }

  /* The readiness bar is three filled segments and nothing else. Filled becomes
     invisible, so each segment gains an outline and a distinct fill pattern
     drawn from the system palette; the counts above it state the same fact in
     words, which is why the bar is a summary and never the only representation. */
  .readiness__bar { border: var(--stroke-hairline) solid CanvasText; }
  .readiness__seg--pass    { background: Highlight; }
  .readiness__seg--pending { background: Canvas; border-inline-end: var(--stroke-hairline) solid CanvasText; }
  .readiness__seg--fail    { background: GrayText; }

  /* Bars, dots and markers: outline them so a shape remains where a fill was. */
  .chart-bars__track { border: var(--stroke-hairline) solid CanvasText; }
  .chart-bars__fill { background: Highlight; }
  .timeline__dot { background: CanvasText; }
  .timeline__dot--pending,
  .timeline__dot--blocked { background: Canvas; box-shadow: inset 0 0 0 var(--stroke-medium) CanvasText; }
  .stepper__marker { border: var(--stroke-hairline) solid CanvasText; forced-color-adjust: none; background: Canvas; color: CanvasText; }
  .stepper__item--current .stepper__marker { background: Highlight; color: HighlightText; }
  .stepper__item--blocked .stepper__marker { border-style: dashed; }

  /* The current nav item and the applied filter were both inverted fills. */
  .nav-link[aria-current="page"] { border-block-end-color: Highlight; }
  .filter-chip { border: var(--stroke-hairline) solid ButtonText; }
  .filter-chip[aria-current="true"] { border-width: var(--stroke-strong); background: Highlight; color: HighlightText; forced-color-adjust: none; }

  /* An unavailable control must still read as unavailable when every colour is
     the system's. GrayText is the one the OS reserves for exactly this. */
  .button[aria-disabled="true"], .button:disabled { color: GrayText; border-color: GrayText; }
  .input:disabled, .select:disabled, .textarea:disabled { color: GrayText; border-color: GrayText; }

  /* tokens.css collapses every chip to a solid CanvasText border here, which is
     right for the colour and wrong for the line: a dash and a dot are shapes,
     they cost nothing in the system palette, and they are the only thing left
     telling "waiting on a gate" apart from "in flight" once the hue is gone. */
  .status-chip--blocked { border-style: dashed; }
  .status-chip--pending { border-style: dotted; }
  .status-chip--warning,
  .status-chip--danger { border-width: var(--stroke-medium); }

  /* The value states were told apart by an underline style, which survives, and
     a colour, which does not. The underline is doing the work already; make
     sure it is not dropped along with the hue. */
  .value--pending { text-decoration: underline dotted CanvasText; }
  .value--withheld { text-decoration: underline dashed CanvasText; }
  .skeleton { border: var(--stroke-hairline) solid GrayText; }
  .empty-state { border-inline-start-color: CanvasText; }
  details > summary::after { border-color: CanvasText; }
  details > summary::before { background: CanvasText; }
  .theme-toggle__button,
  .masthead__menu-button { border-color: ButtonText; }
  .data-table__row--pending th:first-child,
  .data-table__row--pending td:first-child { border-inline-start-color: CanvasText; }
}

/* --- The appearance control ---------------------------------------------- */
/* The current icon is an inline SVG; the form carries the next preference.
   A 44px target is inexpensive in the masthead and satisfies coarse pointers
   without a second sizing mode. */
.theme-toggle { display: flex; margin: 0; }
.theme-toggle__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  padding: var(--space-2);
  border: var(--stroke-hairline) solid var(--color-border-default);
  border-radius: var(--radius-medium);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
}
.theme-toggle__button:hover { color: var(--color-text-primary); background: var(--color-surface-raised); }
.theme-toggle__button:active { color: var(--color-text-secondary); background: var(--color-surface); }
.theme-toggle__icon { display: block; flex: 0 0 auto; }
