:root {
  /* Brand & Neutrals — Effectiv Platform Color Scheme (July 2026)
     Official hexcodes; text-role colors adjusted where the brand value
     fails WCAG AA (4.5:1) on white. Fills/badges keep the official hex. */
  --navy: #011d60;          /* Navy 900 — sidebar, headings, wordmark */
  --navy-light: #1b3f86;    /* Navy 700 */
  --primary: #1b3f86;       /* interactive: buttons, links, focus, selection */
  --primary-light: #e8effa; /* pale tint for selected/highlight surfaces */
  --accent: #2ca3e2;        /* Bright Blue 500 — fills, charts, large graphics
                               only (2.8:1 on white — never small text) */
  --accent-soft: #b4cde3;   /* Light Blue — tints on dark, selected states */
  --text: #233042;
  /* Darkened from #667483 by P3-23's a11y run, for the same reason as --blue:
     the old value's "4.8:1" was measured on WHITE, and muted text is routinely
     placed on the pale tints (`--bg`, `--primary-light`, `--blue-light`), where
     it fell to 4.13–4.22. This value clears 4.5 on every surface it is used
     on — 5.21 white, 4.98 --bg, 4.51 --primary-light, 4.60 badge-open,
     4.52 --blue-light. */
  --muted: #616e7c;         /* AA on white AND on every pale tint */
  --border: #e6e5e5;
  --bg: #f8fafc;
  --card: #ffffff;
  /* UF-03: text/icons on a dark fill (navy rail, primary button, drawer).
     Distinct from --card despite the identical value: one is a surface, the
     other is ink. They were both `#fff` in fifteen places, which is how a
     future change to the card surface would have silently repainted the text
     on every navy background. */
  --text-on-dark: #ffffff;

  /* UF-03: the grays Button.css had inline, at their exact previous values —
     this task moves color into tokens, it does not restyle anything.
     `--text-disabled` on `--surface-disabled` is ~2.3:1, which is deliberate
     and permitted: WCAG 1.4.3 exempts disabled controls, and raising it would
     make a disabled button read as enabled. */
  --surface-disabled: #f1f2f4;
  --text-disabled: #a7adb5;
  --border-disabled: #e2e6ea;
  --border-strong: #cbd5e1;  /* hover border, one step darker than --border */

  /* Print only. Deliberately darker than --border (#e6e5e5), which nearly
     vanishes on paper. See MonthlyInsights.css @media print. */
  --print-border: #ccc;

  /* Semantic - Severity (text-safe / official-fill pairs) */
  --red: #c53838;           /* critical text (brand #d64545 is 4.4:1) */
  --red-fill: #d64545;
  --red-light: #fbeaea;
  --amber: #8a5f00;         /* urgent text (brand #f5a623 is 1.9:1) */
  --amber-fill: #f5a623;
  --amber-light: #fdf3e0;
  --teal: #257065;          /* routine text (brand #3ba99c is 2.9:1) */
  --teal-fill: #3ba99c;
  --teal-light: #e4f4f1;

  /* Semantic - Status (alert lifecycle) */
  /* Darkened from #1c7ab8 by P3-23's a11y run. The old value was verified
     against WHITE (4.9:1) but every badge puts it on `--blue-light`, where it
     measured 4.03 — the token was AA for the background it was checked on and
     not for the one it is used on. 5.26 on white, 4.56 on --blue-light. */
  --blue: #1a71ab;          /* acknowledged/info text (brand #2ca3e2 fails) */
  --blue-fill: #2ca3e2;
  --blue-light: #e3f1fa;
  --green: #23744a;         /* resolved/success text (brand #2e8b57 is 4.25:1) */
  --green-fill: #2e8b57;
  --green-light: #e6f4ec;

  /* ── Chart tokens (P7-04, reworked by UF-20) ──────────────────────────
     No new hues. Every value is an existing brand token repeated by name.

     ── The rule: the FILL is the brand, the STROKE carries the contrast ──

     P7-04 found, correctly, that the `-fill` tokens fail WCAG 1.4.11 as chart
     marks: --accent is 2.82:1 on --card, --amber-fill 2.03, --teal-fill 2.86.
     That finding stands. Its remedy did not. Every mark was swapped to the
     TEXT-SAFE token — colors darkened to clear 4.5:1 for TEXT — and the result
     was reported by the owner as "wrong color scheme, and too dark - really
     bad". Optimizing a chart palette for text contrast produces a chart that
     looks like text.

     The way out is that a filled shape does not have to carry its contrast in
     the fill. 1.4.11 is about perceiving the object, and an outline delineates
     it: a bright fill with a darker same-hue stroke at >=3:1 against the
     surface is both conformant and on-brand. So each series is a PAIR — the
     brand fill everyone recognizes, and a stroke that makes its boundary
     unambiguous. `colorTokens.test.ts` asserts the strokes, not the fills.

     The reference for "on-brand" was in the product the whole time:
     `FunnelChart` draws with --blue-fill / --accent / --green-fill and
     reads correctly beside everything else. These match it.

     Four series, not six: the brand is blue-dominated and the genuinely
     distinguishable hues run out. `--navy` remains the only dark token with no
     semantic role, so chart-2/3/4 still reuse meaning (acknowledged, routine,
     resolved) — recorded here and pinned by a test rather than denied, pending
     a brand decision. Severity FILLS are no longer held back: severity IS the
     meaning in the one chart that uses them, so using the badge colors there
     is the point, not a collision. */
  --chart-1: #2ca3e2;         /* = --accent (the signature blue) */
  --chart-1-stroke: #1a71ab;  /* = --blue    ·  5.26 card / 5.03 bg */
  --chart-2: #1b3f86;         /* = --navy-light */
  --chart-2-stroke: #011d60;  /* = --navy    · 15.60 / 14.91 */
  --chart-3: #3ba99c;         /* = --teal-fill */
  --chart-3-stroke: #257065;  /* = --teal    ·  5.86 / 5.60 */
  --chart-4: #2e8b57;         /* = --green-fill */
  --chart-4-stroke: #23744a;  /* = --green   ·  5.72 / 5.47 */

  /* Severity as a chart mark — now the BADGE colors, so a reader who has
     learned the alert badges reads the chart without re-learning anything.
     The stroke does the conformance work the fill cannot. */
  --chart-critical: #d64545;         /* = --red-fill */
  --chart-critical-stroke: #c53838;  /* = --red   · 5.25 */
  --chart-urgent: #f5a623;           /* = --amber-fill */
  --chart-urgent-stroke: #8a5f00;    /* = --amber · 5.65 */
  --chart-routine: #3ba99c;          /* = --teal-fill */
  --chart-routine-stroke: #257065;   /* = --teal  · 5.86 */

  /* Sequential ramp for intensity encodings — monotonic in luminance, so it
     survives the grayscale printing the Monthly Insights artifact needs. */
  --chart-seq-1: #e8effa;  /* = --primary-light */
  --chart-seq-2: #b4cde3;  /* = --accent-soft */
  --chart-seq-3: #2ca3e2;  /* = --accent */
  --chart-seq-4: #1b3f86;  /* = --navy-light */
  --chart-seq-5: #011d60;  /* = --navy */

  /* Furniture. Grid and axis are deliberately low contrast: they are
     decoration, 1.4.11 exempts them, and a grid that competes with the data is
     a worse chart rather than a more accessible one. */
  --chart-grid: #e6e5e5;        /* = --border */
  --chart-axis: #616e7c;        /* = --muted */
  --chart-separator: #ffffff;   /* = --card · the hairline between touching marks */

  /* Reference lines are content — a reader acts on them — so each clears 3:1,
     and the primitives distinguish them by dash pattern as well as hue. */
  --chart-median: #011d60;      /* = --navy */
  --chart-annotation: #8a5f00;  /* = --amber */
  --chart-benchmark: #257065;   /* = --teal */
  /* P7-13: an operational TARGET, distinct from a clinical benchmark. A run
     chart's median says where this measure usually sits; a trend chart's target
     says what somebody committed to. Different claims, so different lines. */
  --chart-target: #8a5f00;      /* = --amber · 4.86 card / 4.65 bg */

  /* UF-56: a clinical BAND boundary — where one severity ends and the next
     begins. A third distinct claim: the median says where a measure usually
     sits, the target says what somebody committed to, and a band boundary says
     what the INSTRUMENT defines. None of the three may borrow another's line.

     Same value as --chart-axis today, and a separate token deliberately — the
     same reasoning as --text-on-dark vs --card. One is the chart's frame and
     the other is a marking inside it; a future change to the axis color must
     not silently repaint every band boundary in the product. */
  --chart-band: #616e7c;        /* = --muted · 5.21 card / 4.98 bg */

  /* Typography Scale */
  /* UF-77 (September 26, 2026): the typeface is Atkinson Hyperlegible Next,
     owner decision. Designed by the Braille Institute for legibility: every
     character people confuse in doses and IDs is distinct (I l 1, 0 O), which
     the low-literacy patient surfaces and clinical reading both need. Served
     with the app (index.css), so every device, the report PDF and email render
     the same face; before, the stack below WAS the design, and each device drew
     its own font. The second name matches the face where it is INSTALLED
     rather than served, as on the PDF renderer (api/_lib/brandFonts.ts). The
     stack stays only as the fallback while the font loads. */
  --font-family: "Atkinson Hyperlegible Next Variable", "Atkinson Hyperlegible Next", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* UF-77 step 4: the three weights the scale uses, as primitives, so a
     component can say "semibold" without borrowing a heading's semantic token. */
  --font-weight-regular: 400;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  /* A functional floor, not a step of the scale: iOS Safari zooms the page
     when a focused input's text is smaller than 16px. Inputs use this so a
     future change to the scale can't reintroduce the zoom. */
  --font-size-input-min: 16px;
  --font-size-display: 26px;
  --font-weight-display: 700;

  --font-size-h1: 21px;
  --font-weight-h1: 700;

  --font-size-h2: 16px;
  --font-weight-h2: 600;

  --font-size-label: 14.5px;
  --font-weight-label: 600;

  --font-size-body: 14.5px;
  --font-weight-body: 400;

  --font-size-caption: 12.5px;
  --font-weight-caption: 400;

  /* Spacing Scale */
  /* UF-77 step 4: the one sub-step value, for hairline nudges (a 1-2px
     offset to align an icon or a border). Never for layout rhythm. */
  --space-hairline: 2px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 10px;
  --radius-pill: 20px;

  /* ── P3-19 additions ──────────────────────────────────────────────────
     No new colors: the palette above is fixed (WS-A, WCAG-annotated).
     Each token below replaces a literal that was already inline in the
     codebase, or fills a gap the two-shell layout made unavoidable. */

  /* Line height — only a global 1.55 on body existed, so headings and
     dense table rows had no way to differ from body copy. */
  /* UF-77 step 3: letter-spacing. Two values only: normal text is never
     tracked, and uppercase labels (badges, table headers, filter chips) open up
     slightly so capitals don't crowd. The ten hand-typed values it replaces
     were 0.2px to 0.6px, seven of them on uppercase. */
  --tracking-normal: 0;
  --tracking-caps: 0.04em;

  /* UF-77 step 3: icon sizes (lucide, stroke 2). Four, down from fifteen in use.
     Mirrored for React props in src/lib/iconSize.ts; a test keeps them equal. */
  --icon-sm: 14px;  /* inline with text, badges, table cells */
  --icon-md: 16px;  /* buttons, form controls, nav */
  --icon-lg: 20px;  /* card and section headers */
  --icon-xl: 32px;  /* empty states */

  --leading-none: 0;       /* image and icon wrappers: removes the inline gap */
  --leading-tight: 1.25;   /* headings, stat figures */
  --leading-snug: 1.4;     /* table rows, nav items, compact UI */
  --leading-body: 1.55;    /* matches today's body default */

  /* Elevation — was inline: `0 4px 12px rgba(0,0,0,0.1)` (AppLayout.css). */
  --shadow-sm: 0 1px 2px rgba(2, 18, 61, 0.06);
  --shadow-md: 0 4px 12px rgba(2, 18, 61, 0.10);
  --shadow-lg: 0 12px 32px rgba(2, 18, 61, 0.16);

  /* Motion — 0.2s/0.3s were repeated across six files. */
  --motion-fast: 120ms;
  --motion-base: 200ms;
  --motion-slow: 300ms;
  --motion-instant: 0.01ms; /* the reduced-motion rule: "done", without travel */
  --motion-pulse: 1.4s;      /* a loading pulse, one cycle */
  --motion-highlight: 4s;    /* a just-arrived highlight fading out */
  --ease-standard: cubic-bezier(0.2, 0, 0.2, 1);

  /* Z-index — was 10 and 100, bare. Two shells plus banners, dropdowns,
     modals and toasts now have to stack predictably. */
  --z-base: 0;    /* local stacking inside a component */
  --z-raised: 1;  /* one element above its siblings, locally */
  --z-sidebar: 10;
  --z-topbar: 20;
  --z-dropdown: 100;
  --z-modal: 1000;
  --z-toast: 1100;

  /* Focus ring — none existed; keyboard focus was the browser default or
     nothing. Uses --primary, which is AA on white. */
  --focus-ring: 0 0 0 2px var(--card), 0 0 0 4px var(--primary);
  --focus-ring-offset-dark: 0 0 0 2px var(--navy), 0 0 0 4px var(--accent);

  /* Content width — pages hardcoded `maxWidth: 1000` / `800` inline. */
  --content-narrow: 800px;   /* forms, single-column editors */
  --content-default: 1100px; /* standard page */
  --content-wide: 1400px;    /* tables, dashboards */

  /* Shell metrics — the rail is persistent now rather than a 70px strip
     that only reveals its labels on hover (see redesign doc §1.5).
     UF-01 removed --rail-width-compact along with the compact rail itself:
     below --bp-tablet the nav is a drawer, so there is no narrow rail left
     to size. The drawer reuses --rail-width. */
  --rail-width: 232px;
  --topbar-height: 64px;

  /* ── UF-01: breakpoints ───────────────────────────────────────────────
     Every responsive rule in the app used to be a hand-typed literal, and
     they disagreed — 640, 720, 900, 1024, 1100 across nine files, because
     there was nowhere to look up "what width do we break at".

     1024 is the figure `docs/Effectiv_Platform_Accessibility_Constraints.md`
     already names as the floor for staff surfaces, so it is where the nav
     drawer and the two-pane layouts engage.

     These custom properties are the readable/JS-visible form. Media queries
     must use the `@custom-media` rules below instead: a CSS variable cannot
     be evaluated inside `@media`, so `--bp-tablet` alone could never be the
     source of truth. `src/lib/breakpoints.ts` holds the same numbers for
     JS, and `breakpoints.test.ts` fails if the three ever disagree. */
  --bp-compact: 640px;
  --bp-tablet: 1024px;
  --bp-desktop: 1280px;

  /* Scrim behind overlays. A token because the alternative is every overlay
     hardcoding its own rgba(), which is what Modal.css already does — and no
     two of them would agree on the dim. */
  --overlay-scrim: rgba(1, 29, 96, 0.45);
}

/* Resolved by postcss-custom-media at build time. Component CSS writes
   `@media (--bp-tablet)`, never a bare pixel value. */
@custom-media --bp-compact (max-width: 640px);
@custom-media --bp-tablet (max-width: 1024px);
@custom-media --bp-desktop (max-width: 1280px);
