:root{
  /* Base palette: brand blue */
  --blue-50:#EEF3FE;--blue-100:#DCE6FD;--blue-200:#BACDFB;--blue-300:#8FACF7;
  --blue-400:#5E85F0;--blue-500:#2D62E8;--blue-600:#1D4CCB;--blue-700:#193DA1;
  --blue-800:#1A357F;--blue-900:#182C61;--blue-950:#101C3D;

  /* Base palette: cool neutrals */
  --neutral-0:#FFFFFF;--neutral-25:#FAFBFC;--neutral-50:#F4F6F8;--neutral-100:#EAEEF2;
  --neutral-200:#DCE2E9;--neutral-300:#C3CBD5;--neutral-400:#98A3B0;--neutral-500:#6E7A89;
  --neutral-550:#5C6775;--neutral-600:#515C6B;--neutral-700:#3A4451;--neutral-800:#262E39;--neutral-900:#161C24;

  /* Base palette: teal — secondary, and the AI-agent surface family */
  --teal-50:#E8F7F5;--teal-100:#CCEEEA;--teal-200:#99DCD5;--teal-300:#5CC3B9;
  --teal-400:#26A79B;--teal-500:#0F8C82;--teal-600:#0B6F68;--teal-700:#0A5751;

  /* Base palette: semantics */
  --green-50:#E8F6EF;--green-100:#CCEBDD;--green-500:#0E8A5F;--green-600:#0A6E4C;--green-700:#08573C;
  --amber-50:#FDF3E3;--amber-100:#FAE6C2;--amber-500:#B4730E;--amber-600:#945D07;--amber-700:#74490A;
  --red-50:#FDECEB;--red-100:#FAD6D3;--red-500:#C7362F;--red-600:#A52822;--red-700:#82201B;

  /* Semantic aliases: text */
  --text-heading:var(--neutral-900);
  /* Lockup wordmark on LIGHT grounds only. Navy, not --text-heading: the wordmark is a
     brand mark, so it sits on the blue axis rather than the neutral ramp. On DARK grounds
     the whole lockup is white — see --text-on-nav. Prose mentions of "Ryber" inside
     running copy stay --text-heading; they are words, not marks. */
  --brand-wordmark:var(--blue-950);
  --text-body:var(--neutral-700);
  /* UI-07 / WP-U4.4, corrected: the first fix set --neutral-550 to #667381 and cited
     4.86:1 — true on WHITE only. Ryber has four light surfaces, and muted metadata sits
     on all of them, so a white-only measurement is the wrong bar:
         #667381 → card 4.85 PASS · inset 4.68 PASS · page 4.47 FAIL · sunken 4.24 FAIL
     Darkened again to #5C6775, which clears AA for normal text on EVERY light surface:
         #5C6775 → card 5.75 · inset 5.54 · page 5.31 · sunken 5.03
     Quote a contrast figure with its background or it means nothing. --neutral-500 stays
     in the ramp for borders, icons and text on the navy nav, measured separately.
     Never use --text-subtle (#98A3B0, 2.6:1 on white) for text a user must read. */
  --text-muted:var(--neutral-550);
  --text-subtle:var(--neutral-400);
  --text-inverse:var(--neutral-0);
  /* Foregrounds for --surface-nav (#131A2E). Blue-axis, never neutral: a low-saturation
     grey on a blue ground reads as the surface's own shadow rather than as palette.
     Measured on the CURRENT chrome navy — label 9.62:1 · icon 5.8:1 · muted 4.97:1.
     Re-measure these whenever --surface-nav changes; a ratio without its background is
     worthless, and this block twice outlived the surface it was measured against.
     The muted step MUST sit below the icons or the hierarchy is nominal only: #8296BC
     matched the icons exactly, and the obvious darker fix #6B7FA6 fails AA. */
  --text-on-nav:var(--neutral-0);
  --text-on-nav-label:#B4C2DC;
  --text-on-nav-muted:#748AB4;
  --icon-on-nav:#7E96C4;
  --icon-on-nav-active:var(--blue-300);
  --text-link:var(--blue-600);
  --text-link-hover:var(--blue-700);
  --text-accent:var(--blue-600);
  --text-success:var(--green-600);
  --text-warning:var(--amber-600);
  --text-danger:var(--red-600);
  --text-disabled:var(--neutral-400);

  /* Semantic aliases: surfaces */
  --surface-page:var(--neutral-50);
  --surface-card:var(--neutral-0);
  --surface-sunken:var(--neutral-100);
  --surface-inset:var(--neutral-25);
  /* Chrome navy, deliberately NOT --blue-950. The nav is the largest coloured field in
     the product; at --blue-950's 58% saturation it shared hue AND note with --blue-600,
     spending the accent's "this does something" exclusivity. Measured: chrome h224
     s41.5 l12.7 vs login navy --blue-950 s58.4 l15.1 — the chrome is DARKER and
     less saturated, which is what keeps brand blue the only saturated blue on screen. Foregrounds measured
     on it: see the --*-on-nav block above (single source of truth). */
  --surface-nav:#131A2E;
  /* The sign-in ground. The nav itself is a FLAT --surface-nav fill — deliberately not a
     gradient — so this token is the login's alone: a marketing surface on the more saturated
     --blue-950 axis (readme, Mark ink). The cost is that the sign-in transition's wipe
     uncovers a flat rail one shade cooler than the ground it replaces. */
  --surface-login-gradient:radial-gradient(110% 80% at 6% 0%,#182C61 0%,var(--blue-950) 46%,var(--surface-deck-deep) 100%);
  --border-on-nav:rgba(255,255,255,.22);
  --surface-nav-hover:#1A2338;
  --surface-nav-active:#212C45;
  --surface-hover:var(--neutral-50);
  --surface-pressed:var(--neutral-100);
  --surface-selected:var(--blue-50);
  --surface-disabled:var(--neutral-100);
  --surface-overlay:rgba(22,28,36,.48);
  --surface-agent:var(--teal-50);
  /* Parity only - the landing page has no Tooltip component, but
   * check-design-token-parity.mjs compares this file value-for-value against
   * frontend/src/styles/ryber-tokens/colors.css, which does. See that file's own
   * comment for the reasoning behind these three values. */
  --surface-tooltip:var(--neutral-900);
  --text-on-tooltip:var(--text-inverse);
  --border-tooltip:rgba(255,255,255,.14);

  /* Semantic aliases: borders */
  --border-subtle:var(--neutral-100);
  --border-default:var(--neutral-200);
  --border-strong:var(--neutral-300);
  /* Control boundaries carry WCAG 1.4.11 (3:1) — a field border IS the control.
     Decorative borders below stay lighter; they divide content that is legible anyway. */
  --border-field:#828E9C;
  /* The on-navy counterpart of --border-field, and required for the same reason: a field
     border IS the control, so it carries 1.4.11's 3:1. rgba(255,255,255,.4) composites to
     rgb(112,119,139) over --blue-950 = 3.73:1, and higher over the darker nav navies.
     --border-on-nav (.22) is a HAIRLINE value — it composites to 2.02:1 and must never be
     used as a control boundary, which is what it was doing on the login's fields. */
  --border-field-on-nav:rgba(255,255,255,.4);
  /* The on-navy focus ring. --focus-ring is built on --blue-500 at .28 alpha, which composites
     to 1.33:1 over --blue-950 — focus is invisible there, failing 2.4.7 outright and 2.4.11's
     3:1. This composites to rgb(99,122,182) = 3.97:1, keeping the blue focus identity with
     headroom. Every dark control path and every navy surface must use this, not --focus-ring. */
  --focus-ring-on-nav:0 0 0 3px rgba(143,172,247,.65);
  --border-field-hover:var(--neutral-550);
  --border-focus:var(--blue-500);
  /* The boundary of a selected control (Chip, segment, selected list row). An alias because
     Chip previously held --blue-200 raw, which the dark theme could not reach — governance
     rule 2 above. */
  --border-selected:var(--blue-200);
  --border-danger:var(--red-500);
  --border-grid:var(--neutral-100);

  /* Semantic aliases: interactive */
  --action-primary:var(--blue-600);
  --action-primary-hover:var(--blue-700);
  --action-primary-active:var(--blue-800);
  --action-primary-subtle:var(--blue-50);
  /* The tonal button's hover and press. Aliases, not the raw 100/200 steps Button used to
     read — governance rule 2: a raw step keeps its near-white light value on a dark ground. */
  --action-primary-subtle-hover:var(--blue-100);
  --action-primary-subtle-active:var(--blue-200);
  --action-secondary:var(--neutral-0);
  --action-danger:var(--red-500);
  --action-danger-hover:var(--red-600);
  --focus-ring:0 0 0 3px rgba(45,98,232,.28);
  --focus-ring-danger:0 0 0 3px rgba(199,54,47,.26);

  /* Semantic aliases: status — mirrors the universal status table, keyed by code.
     Each family carries a BORDER as well as fg/bg: the 50-step pastels are 1.05–1.10:1
     against --surface-card, so an unbordered tag has no silhouette of its own and
     dissolves into a white card on a bright display. The border is the family's own fg
     at 30% alpha rather than a ramp step — the 100 steps exist for only some families
     (there is no --neutral-150, no --teal-25), and an alpha of fg re-tunes itself if a
     ramp moves. 30% lands ~1.9:1 on the tag's own bg: below the 3:1 graphic bar on
     purpose, because the border defines an EDGE, it does not carry the meaning — the
     dot, the label and fg do that, so 1.4.11 is not the applicable test here. */
  --status-draft-fg:var(--neutral-600);--status-draft-bg:var(--neutral-100);--status-draft-border:rgba(81,92,107,.30);
  --status-pending-fg:var(--amber-600);--status-pending-bg:var(--amber-50);--status-pending-border:rgba(148,93,7,.30);
  --status-posted-fg:var(--green-600);--status-posted-bg:var(--green-50);--status-posted-border:rgba(10,110,76,.30);
  --status-rejected-fg:var(--red-600);--status-rejected-bg:var(--red-50);--status-rejected-border:rgba(165,40,34,.30);
  --status-void-fg:var(--neutral-550);--status-void-bg:var(--neutral-50);--status-void-border:rgba(92,103,117,.30);
  --status-agent-fg:var(--teal-600);--status-agent-bg:var(--teal-50);--status-agent-border:rgba(11,111,104,.30);
  /* On-navy status tints. Authored per ground rather than derived from --status-*-fg: that
     member is a FOREGROUND, so theme-dark re-points it from a dark step (#A52822) to a light
     one (#D19A97), and any "lift toward white" formula built on it inverts — the dark theme's
     error tint desaturated to grey-lilac. These are theme-independent BY DESIGN: the login and
     deck grounds are --blue-950 in both themes, so their tints must not move either. Same
     pattern as --border-field-on-nav and --focus-ring-on-nav. */
  --status-rejected-on-nav-fg:#D19A97;--status-rejected-on-nav-bg:rgba(199,54,47,.3);--status-rejected-on-nav-border:rgba(209,154,151,.62);
  --status-pending-on-nav-fg:#CDA05A;--status-pending-on-nav-bg:rgba(180,115,14,.24);--status-pending-on-nav-border:rgba(232,183,106,.5);
  --status-info-fg:var(--blue-700);--status-info-bg:var(--blue-50);--status-info-border:rgba(25,61,161,.30);

  /* Presentation grounds — the deck family, all on the blue axis. The dark ground is
     --blue-950, the marketing navy the login panel already uses, NOT the chrome
     --surface-nav: a deck is a marketing surface, and the two navies differ on purpose
     (58% vs 42% saturation — see --surface-nav). Light slides sit on --blue-50, not
     --neutral-0, so a data slide reads as the same family rather than a cold white
     break. The teal ramp supplies the agent accent on both grounds. */
  --surface-deck:var(--blue-950);
  --surface-deck-deep:#0A1229;
  --surface-deck-raised:#1A2C55;
  --surface-deck-light:var(--blue-50);
  /* Foregrounds on --surface-deck (#101C3D): --text-inverse 16.8:1 · --text-on-nav-label
     9.4:1 · --text-on-nav-muted 4.83:1 · --teal-300 8.0:1 · --blue-300 7.5:1. The nav
     foregrounds were measured on #131A2E and still clear AA here (#101C3D is marginally
     lighter), so reuse them instead of adding a parallel set. Those figures hold on the
     FLAT ground only. Title/statement/close slides wash the ground with a radial whose
     light stop is --blue-900 (#182C61), where --text-on-nav-muted is 3.84:1 and FAILS
     normal text — anything sitting near a gradient's light end takes the label step
     (7.44:1 on #182C61). A ratio measured on the flat ground says nothing about the wash.
     On --surface-deck-raised (#1A2C55): inverse 13.5:1 · label 7.6:1 · --teal-300 6.5:1.
     --text-on-nav-muted is 3.9:1 there — FAILS normal text; use the label step on cards.
     On --surface-deck-light (#EEF3FE): --text-heading 14.5:1 · --text-muted 5.1:1 ·
     --teal-600 5.4:1 · --teal-500 3.7:1 as a graphic rule (1.4.11's 3:1 bar). */
  --border-on-deck:rgba(255,255,255,.14);
  --border-on-deck-strong:rgba(255,255,255,.34);
  --surface-agent-on-deck:rgba(92,195,185,.12);
  --border-agent-on-deck:rgba(92,195,185,.40);
  --status-pending-on-deck-fg:#F0C88A;
  --status-pending-on-deck-bg:rgba(180,115,14,.24);
  /* Oversized separator numeral only — decorative, 1.7:1 on --surface-deck-deep and
     deliberately below the text bar. Never use it for text a reader must read. */
  --figure-ghost-on-deck:#23386B;

  /* Financial semantics — Ryber-specific */
  --figure-debit:var(--neutral-900);
  --figure-credit:var(--blue-700);
  --figure-negative:var(--red-600);
  --figure-positive:var(--green-600);
  /* TEXT aliases must use the 600 steps. The 500 steps are fills, borders and graphics:
     as text on white --green-500 is 4.36:1 and --neutral-400 is 2.6:1, both below AA.
     Ryber renders figures at 21px non-bold, which is NOT large text (that needs >=24px,
     or >=18.66px bold), so the normal-text 4.5:1 bar applies to every one of these. */
  --figure-zero:var(--neutral-550);
  --ledger-balanced:var(--green-600);
  --ledger-unbalanced:var(--red-600);
}
