/* =============================================================================
   A-EMR DESIGN TOKENS
   Aegis IT Advisors LLC — healthcare capability demonstration
   Owner: Product & Brand Design. Spec: docs/02-product/ui-direction.md

   HOW TO USE
   Import this file once, first, in app/globals.css (or the root layout).
   Every value a component needs is here. A component that hard-codes a colour,
   a font size, a radius or a duration is a defect — raise it, do not patch it
   locally. New need? Add a semantic alias here, pointing at an existing
   primitive. Adding a new primitive is a design decision, not an implementation
   one.

   STRUCTURE
     1. Primitives            raw ramps, no meaning attached
     2. Typography            families, sizes, line heights, weights
     3. Space / radius / line / elevation / z / motion
     4. Semantic aliases      LIGHT (default)
     5. Density               comfortable (default) and compact
     6. Semantic aliases      DARK  — :root[data-theme="dark"]
     7. Reduced motion, forced colors, print

   THEMING
   The dark theme is applied by the attribute data-theme="dark" on <html>.
   It is NOT applied by prefers-color-scheme alone: the app must set the
   attribute from a small blocking script in the document head (read
   localStorage, fall back to matchMedia). Without that script the app renders
   light. This is deliberate — it removes the flash of wrong theme, and it keeps
   one dark block in this file instead of two that can drift apart.

   CONTRAST
   Every foreground/background pairing that actually occurs in A-EMR is
   measured and tabulated in ui-direction.md (83 pairs, WCAG 2.1 relative
   luminance). No colour appears in this file that is not listed there.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. PRIMITIVES — raw ramps
   These carry no meaning. Never reference a primitive from a component; a
   component reads a semantic alias from section 4/6. Ramps run light (lower
   number) to dark (higher number) by relative luminance.
   -------------------------------------------------------------------------- */
:root {
  /* Neutral — cool, very slightly navy-tinted greys. Light-theme surfaces,
     borders and text; also the light foregrounds used on dark surfaces. */
  --color-neutral-0: #ffffff;
  --color-neutral-25: #fafbfd;
  --color-neutral-50: #f4f6f9;
  --color-neutral-100: #e9edf3;
  --color-neutral-200: #d8dee8;
  --color-neutral-300: #bfc8d6;
  --color-neutral-400: #94a0b3;
  --color-neutral-450: #7d8a9e; /* lowest neutral that clears 3:1 on white */
  --color-neutral-500: #6b7789;
  --color-neutral-600: #5b6676;
  --color-neutral-700: #4e5969;
  --color-neutral-800: #37414f;
  --color-neutral-900: #151c26;

  /* Ink — dark-theme work surfaces. Desaturated on purpose: the work surface
     must not tint clinical colour. */
  --color-ink-950: #0b1119;
  --color-ink-900: #10161f;
  --color-ink-850: #18202b;
  --color-ink-800: #1e2732;
  --color-ink-750: #232d3a;
  --color-ink-700: #2c3846;

  /* Navy — Aegis brand chrome, measured from the shipped aegisit.ai stylesheet.
     Used for the global nav rail and the patient banner in BOTH themes, so the
     brand anchor does not move when the theme changes. */
  --color-navy-950: #050b1f;
  --color-navy-900: #071129;
  --color-navy-800: #0a1a4a;

  /* Accent — Aegis brand purple, ramp taken verbatim from the shipped
     aegisit.ai stylesheet (brand hue at 600). Purple carries no clinical
     meaning in US practice, which is precisely why it is the interactive
     colour here: it leaves red, amber, blue and green free to mean something. */
  --color-accent-50: #faf5ff;
  --color-accent-100: #f3e8ff;
  --color-accent-200: #e9d5ff;
  --color-accent-300: #d8b4fe;
  --color-accent-400: #c084fc;
  --color-accent-500: #a855f7;
  --color-accent-600: #9333ea;
  --color-accent-700: #7e22ce;
  --color-accent-800: #6b21a8;
  --color-accent-900: #581c87;
  --color-accent-950: #3b0764;

  /* Brand pink — the SECOND stop of the Aegis wordmark gradient, and the only
     colour in this file that is neither a clinical hue nor part of a ramp. It
     is read from the same shipped aegisit.ai stylesheet as the accent ramp
     above (`aegis-site/src/app/globals.css`, the `text-gradient` utility:
     `linear-gradient(to right, #9333ea, #db2777)`), so it is matched rather
     than invented — ui-direction.md section 2 names that stylesheet as the
     source of the brand.

     IT IS BRAND CHROME AND IT IS NOT A CLINICAL COLOUR. It appears only where
     the Aegis identity is being presented to a viewer who is not yet inside
     the application — the sign-in screen and the demo cover, the same two
     surfaces section 4 reserves the display face for. It must never reach a
     chart, a result, a claim or a schedule: red already means critical there,
     and a second warm hue on a clinical surface is exactly the ambiguity
     Decision 3 exists to prevent. */
  --color-brand-pink-600: #db2777;

  /* Red — critical only. Never used for a delete button, a required-field
     marker, or a validation message. In A-EMR red means clinical
     criticality INSIDE THE APPLICATION, and a genuine failure on the two brand
     surfaces - the sign-in page and the portal - and nothing else. The second
     half is not a widening: site/signin.html's .notice-problem already reads
     --clin-critical-border for a failed sign-in, and portal-direction.md
     pattern 14 binds --portal-tone-problem to --color-red-300 for a launch
     step that failed. The rule and the code disagreed; this is the rule
     catching up, written down so the next reader does not have to discover it.
     No clinical surface may use red for anything but criticality. */
  --color-red-50: #fef3f2;
  --color-red-100: #fee4e2;
  --color-red-200: #fccfca;
  --color-red-300: #ff9b8f;
  --color-red-400: #f97066;
  --color-red-500: #dc3b2c;
  --color-red-600: #c0281c;
  --color-red-700: #b42318;
  --color-red-800: #9a1c10;
  --color-red-900: #5e1109;
  --color-red-950: #2a1512;

  /* Amber — attention. Abnormal-high results and unsigned/pending workflow. */
  --color-amber-50: #fff6ed;
  --color-amber-100: #ffead5;
  --color-amber-200: #fddcab;
  --color-amber-300: #fdb022;
  --color-amber-400: #f79009;
  --color-amber-500: #dc6803;
  --color-amber-600: #c4530a;
  --color-amber-700: #b54708;
  --color-amber-800: #93370d;
  --color-amber-900: #5c2408;
  --color-amber-950: #291a08;

  /* Blue — abnormal-low. Blue against amber is the pairing that survives
     protan and deutan colour vision deficiency; it is not a link colour here
     precisely so it can mean "low". */
  --color-blue-50: #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-200: #bfdbfe;
  --color-blue-300: #84caff;
  --color-blue-400: #3d95e8;
  --color-blue-500: #1570cf;
  --color-blue-600: #0b5aa6;
  --color-blue-700: #08497f;
  --color-blue-800: #073a66;
  --color-blue-900: #062b4c;
  --color-blue-950: #0e1f33;

  /* Green — in-range, confirmed, posted. Green is an affirmation, never the
     sole signal, and never applied to a numeric result cell. */
  --color-green-50: #ecfdf3;
  --color-green-100: #d1fadf;
  --color-green-200: #a6f4c5;
  --color-green-300: #47cd89;
  --color-green-400: #17b26a;
  --color-green-500: #099250;
  --color-green-600: #0f7038;
  --color-green-700: #0a5a2d;
  --color-green-800: #08461f;
  --color-green-900: #06331a;
  --color-green-950: #0d2118;

  /* Shadow / scrim base — the rgb channels of --color-ink-950. Declared as a
     space-separated triple so alpha can be applied without a new colour. */
  --shadow-rgb: 11 17 25;
}

/* -----------------------------------------------------------------------------
   2. TYPOGRAPHY
   Two faces. Inter for everything read as language; IBM Plex Mono for strings
   read as identifiers (MRN, claim number, ICD-10 / CPT codes, NDC, accession).
   Both are self-hosted via next/font/local — no CDN, no font service. The
   fallback stack is metric-adjacent and never resolves to a serif: a clinic
   with no network must not render a chart in Times.
   -------------------------------------------------------------------------- */
:root {
  --font-sans: "Inter", "Segoe UI", system-ui, -apple-system, "Helvetica Neue",
    Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "Cascadia Mono", "SFMono-Regular",
    Consolas, "Liberation Mono", monospace;
  /* Display face is used only on the sign-in screen and the demo cover, never
     inside the chart. */
  --font-display: "Space Grotesk", "Inter", "Segoe UI", system-ui, sans-serif;

  /* Size / line-height pairs. Sizes are whole pixels — a clinical table with
     fractional line boxes shimmers on a 1x display. */
  --size-micro: 0.6875rem; /* 11px — column headers, pill text, uppercase only */
  --line-micro: 1rem; /* 16px */
  --size-caption: 0.75rem; /* 12px — metadata, timestamps, units */
  --line-caption: 1rem; /* 16px */
  --size-compact: 0.8125rem; /* 13px — table cell text at compact density */
  --line-compact: 1.125rem; /* 18px */
  --size-body: 0.875rem; /* 14px — default UI and table text */
  --line-body: 1.25rem; /* 20px */
  --size-body-lg: 1rem; /* 16px — dialog body, note prose */
  --line-body-lg: 1.5rem; /* 24px */
  --size-heading-sm: 1.125rem; /* 18px — sub-section heading */
  --line-heading-sm: 1.625rem; /* 26px */
  --size-heading: 1.25rem; /* 20px — panel title */
  --line-heading: 1.75rem; /* 28px */
  --size-title: 1.5rem; /* 24px — page title */
  --line-title: 2rem; /* 32px */
  --size-patient: 1.875rem; /* 30px — patient name in the banner */
  --line-patient: 2.375rem; /* 38px */
  --size-display: 2.25rem; /* 36px — sign-in and demo cover only */
  --line-display: 2.75rem; /* 44px */

  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  --tracking-micro: 0.06em; /* uppercase micro labels only */
  --tracking-tight: -0.01em; /* 24px and above */
  --tracking-normal: 0;

  /* Prose measure for note bodies and patient instructions: 60–75 characters. */
  --measure-prose: 68ch;
}

/* -----------------------------------------------------------------------------
   3. SPACE, RADIUS, LINE, ELEVATION, Z-INDEX, MOTION
   -------------------------------------------------------------------------- */
:root {
  /* 4px base. Half-steps exist only for icon/text optical alignment. */
  --space-0: 0;
  --space-05: 0.125rem; /* 2 */
  --space-1: 0.25rem; /* 4 */
  --space-2: 0.5rem; /* 8 */
  --space-3: 0.75rem; /* 12 */
  --space-4: 1rem; /* 16 */
  --space-5: 1.25rem; /* 20 */
  --space-6: 1.5rem; /* 24 */
  --space-8: 2rem; /* 32 */
  --space-10: 2.5rem; /* 40 */
  --space-12: 3rem; /* 48 */
  --space-16: 4rem; /* 64 */
  --space-20: 5rem; /* 80 */

  /* Radii are decisions: square where data is tabulated, soft where a surface
     floats. Nothing here is a circle except an avatar and a dot. */
  --radius-none: 0;
  --radius-xs: 2px; /* inline chips inside a table row */
  --radius-sm: 4px; /* inputs, buttons, selects */
  --radius-md: 6px; /* cards, panels, popovers */
  --radius-lg: 10px; /* dialogs */
  --radius-pill: 999px; /* status pills, avatars */

  --line-hairline: 1px;
  --line-rule: 2px; /* clinical state rails and the focus ring */
  --line-heavy: 3px; /* allergy alert strip left edge */

  /* Elevation. Level 1 is a border, not a shadow: a data panel that floats is
     a data panel you cannot align to. Shadows begin at popovers. */
  --elevation-0: none;
  --elevation-1: none;
  --elevation-2: 0 2px 6px rgb(var(--shadow-rgb) / 0.12),
    0 1px 2px rgb(var(--shadow-rgb) / 0.08);
  --elevation-3: 0 12px 32px rgb(var(--shadow-rgb) / 0.24),
    0 2px 8px rgb(var(--shadow-rgb) / 0.12);
  --elevation-4: 0 8px 24px rgb(var(--shadow-rgb) / 0.28);

  /* THE STACKING SCALE. Every layer in the system is one of the values below
     and nothing writes a bare z-index, because a bare number is a decision
     nobody can find again.

     TWO BOXES ON ONE LAYER ARE ORDERED BY DOCUMENT POSITION. Sharing a layer
     is harmless where the sharers never occupy the same pixels; it is a defect
     the moment two of them are both stuck to the top of the viewport, because
     then the one written later in the markup wins and nothing in either
     stylesheet says so. */
  --z-base: 0;

  /* THE STICKY LAYER, AND IT IS THE PATIENT BANNER'S. Nothing that can appear
     BELOW the banner in the document may claim it: equal layers are resolved
     by document order, so a table's column-header strip on this layer painted
     across the patient's name at mid-scroll. That reasoning is written out in
     full on .dtHead in app/src/components/shell/DataTable.module.css, and
     design/nothing-paints-over-the-patient-banner counts the holders. */
  --z-sticky: 100;

  /* THE DEMONSTRATION MARKER, ABOVE STICKY, FOR A CONSTITUTIONAL REASON.
     Constitution V requires every customer-visible surface to carry the line
     saying this is a demonstration running on synthetic data, and the
     application carries it in the chrome bar. The chrome bar and the patient
     banner are both stuck to the top of the viewport, so at mid-scroll they
     want the same strip of screen - and while they shared --z-sticky the
     banner won on document order and painted over the marker. A marker that is
     painted over is not visible, which makes this the constitution failing
     rather than two boxes overlapping.

     IT IS A TOKEN AND NOT A z-index AT THE CALL SITE. A number typed into
     AppShell.module.css is a number the next sticky component can match
     without ever reading this comment. A named layer is something the gate can
     count the holders of, which is what tests/checks/layering.js does.

     BELOW --z-dropdown ON PURPOSE. The persona and theme controls live in the
     chrome, and a menu opened from a control has to open over the bar that
     carries it. */
  --z-demo-marker: 150;

  --z-dropdown: 200;
  --z-popover: 300;
  --z-dialog: 400;
  --z-toast: 500;

  /* THE HEIGHT OF THE CHROME BAR, SO THE STICKY BOX UNDER IT CAN CLEAR IT.
     The bar carries the demonstration marker and pins at the top of the
     viewport; the patient banner pins to the same place. --z-demo-marker
     decided WHICH of the two paints on top, which made the marker visible
     without moving either box - so the bar still covered the banner's 12px of
     padding and the top 13px of the patient name's 38px line box, and the
     reader met that as a name with its capitals chopped.

     IT IS DECLARED HERE BECAUSE IT IS ONE PIECE OF GEOMETRY THAT TWO
     STYLESHEETS NEED. CSS gives an element no way to read another element's
     height, so the offset has to be written down somewhere; a literal in
     either stylesheet is a number the other one has to match without ever
     reading why, which is the argument that made the stacking scale a token
     rather than a set of z-indexes.

     IT IS A calc OVER THE BAR'S OWN TOKENS RATHER THAN A MEASURED PIXEL COUNT.
     The marker draws padding-block: var(--space-1), one --line-caption line,
     and a --line-hairline bottom border, so this expression is the bar's
     construction rather than a snapshot of it - change the marker's padding
     and the clearance follows. None of the three inputs is redeclared by a
     density block, and a custom property substitutes at use time in any case,
     so it resolves to the same 25px comfortable and compact. Measured in
     Chrome against these declarations at 1578, 1002 and 778 CSS px of column
     width, at both densities: 25px every time.

     WHERE IT STOPS HOLDING, STATED SO IT IS NOT DISCOVERED. The marker is one
     line down to roughly 560px of column width; below that it wraps, the bar
     becomes 41px, and this clearance is short by 16. The smallest supported
     surface is tablet at 1024 landscape (ui-direction.md section 11), which
     leaves 784px beside the 240px rail, so the wrap sits outside the supported
     range rather than merely being unlikely. If the marker's wording grows,
     re-measure before assuming that is still true.

     NOT EXTENDED TO THE TWO OFFSETS THAT USE --section-gap. .shellContext and
     .chartRail pin at var(--section-gap), which is 24px comfortable and 16px
     compact and therefore under-clears this bar in both. That is a separate
     decision about where those two columns sit on every screen and it is not
     taken here. */
  --chrome-bar-block-size: calc(var(--space-1) * 2 + var(--line-caption) + var(--line-hairline));

  --dur-instant: 0ms;
  --dur-fast: 120ms; /* hover, focus, checkbox */
  --dur-medium: 200ms; /* popover, drawer, tab panel */
  --dur-slow: 320ms; /* dialog entry only */
  --ease-standard: cubic-bezier(0.2, 0, 0.2, 1);
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);
}

/* -----------------------------------------------------------------------------
   4. SEMANTIC ALIASES — LIGHT (default)
   These are what components read. Read the contrast table in ui-direction.md
   before changing any value below.
   -------------------------------------------------------------------------- */
:root {
  color-scheme: light;

  /* Surfaces */
  --surface-canvas: var(--color-neutral-50); /* behind panels */
  --surface-raised: var(--color-neutral-0); /* panels, cards, table body */
  --surface-sunken: var(--color-neutral-100); /* table header, inset regions */
  --surface-overlay: var(--color-neutral-0); /* dialog, popover, menu */
  --surface-hover: var(--color-neutral-50);
  --surface-selected: var(--color-accent-50);
  --surface-disabled: var(--color-neutral-100);
  --surface-chrome: var(--color-navy-950); /* global nav rail */
  --surface-banner: var(--color-navy-800); /* patient banner */
  --surface-scrim: rgb(var(--shadow-rgb) / 0.6); /* behind a modal dialog */

  /* Text */
  --text-primary: var(--color-neutral-900);
  --text-secondary: var(--color-neutral-700);
  --text-muted: var(--color-neutral-600);
  --text-disabled: var(--color-neutral-400); /* WCAG 1.4.3 exempt; see spec */
  --text-on-chrome: var(--color-neutral-0);
  --text-on-chrome-muted: var(--color-neutral-300);
  --text-link: var(--color-accent-700);
  --text-on-accent: var(--color-neutral-0);

  /* Borders. --border-control is the only border permitted where the border is
     what identifies an interactive element; it clears 3:1 on every surface it
     sits on. --border-subtle is decorative (row rules) and is not required to. */
  --border-subtle: var(--color-neutral-200);
  --border-default: var(--color-neutral-300);
  --border-control: var(--color-neutral-450);
  --border-strong: var(--color-neutral-700);
  --border-focus: var(--color-accent-700);
  --focus-halo: var(--color-neutral-0); /* inner ring, works on any surface */

  /* Aegis brand gradient. Chrome, not theme: identical in both themes for the
     same reason --surface-chrome is, so the brand anchor does not move when
     the theme does. Consumed by site/signin.html and nothing else today. The
     two stops measure 3.63 (purple) and 4.25 (pink) against --surface-chrome,
     so gradient-filled TEXT is permitted only at large-text size (WCAG 1.4.3
     floor 3.0); white ON the gradient measures 5.38 at the purple stop and
     4.60 at the pink stop, which clears the 4.50 normal-text floor at its
     worst point. Any surface that paints text into this gradient must also
     unpaint it under @media print and (forced-colors: active), where
     background-clip:text plus color:transparent renders nothing at all. */
  --brand-gradient: linear-gradient(
    90deg,
    var(--color-accent-600),
    var(--color-brand-pink-600)
  );

  /* Accent / interactive */
  --accent-base: var(--color-accent-600);
  --accent-hover: var(--color-accent-700);
  --accent-active: var(--color-accent-800);
  --accent-subtle: var(--color-accent-50);
  --accent-subtle-text: var(--color-accent-800);

  /* The portal - the demonstration's cover surfaces, and theme-invariant.

     portal-direction.md section 8.2 names these nine and gives the reasoning.
     They exist because the portal renders on --surface-chrome in BOTH themes,
     while --text-muted, --surface-raised, --border-default, --border-focus and
     --focus-halo all flip underneath it. A rule that reads a flipping alias on
     a fixed ground is correct in one theme by luck. So the portal reads this
     family instead, and every one of them is restated verbatim in section 6 -
     the same deliberate restatement --brand-gradient already uses.

     Every ratio below is against --surface-chrome #050B1F and was computed for
     portal-direction.md section 6.1. Text floors: 4.5 normal, 7.0 AAA;
     non-text floor 3.0.

     Tokens 4 and 8 share a primitive, and 5 and 7 share a primitive. That is
     deliberate: a state colour and a text colour are different decisions that
     happen to have the same answer today, and the separate alias is the seam
     that lets one move later without dragging the other with it. */
  --portal-panel: var(--color-ink-850); /* card and honesty-panel fill; 1.19 lift off the ground */
  --portal-border: var(--color-neutral-500); /* card, badge and control edge; 4.31 */
  --portal-rule: var(--color-neutral-700); /* decorative divider; 2.75, decoration, SC 1.4.11 does not reach it */
  --portal-text-quiet: var(--color-neutral-400); /* eyebrow, timestamp, attribution; 7.39 */
  --portal-accent-text: var(--color-accent-400); /* accent as TEXT and as the focus ring's outer stroke; 7.40 */
  --portal-tone-ready: var(--color-green-300); /* running; a step that is done; 9.65 */
  --portal-tone-working: var(--color-accent-400); /* starting, stopping; a step being checked; 7.40 */
  --portal-tone-idle: var(--color-neutral-400); /* stopped; a step that is waiting; 7.39 */
  --portal-tone-problem: var(--color-red-300); /* unknown; a step that failed; 9.62 */

  /* Clinical state — CRITICAL. Redundant encoding: filled fill + the word
     "Critical" + a solid triangle-with-bang icon. Never colour alone. */
  --clin-critical-fg: var(--color-red-700);
  --clin-critical-bg: var(--color-red-50);
  --clin-critical-fg-on-bg: var(--color-red-800);
  --clin-critical-border: var(--color-red-500);
  --clin-critical-solid: var(--color-red-700);
  --clin-critical-on-solid: var(--color-neutral-0);

  /* Clinical state — ABNORMAL HIGH. Redundant: letter "H" + upward chevron. */
  --clin-high-fg: var(--color-amber-700);
  --clin-high-bg: var(--color-amber-50);
  --clin-high-fg-on-bg: var(--color-amber-800);
  --clin-high-border: var(--color-amber-500);
  --clin-high-solid: var(--color-amber-700);
  --clin-high-on-solid: var(--color-neutral-0);

  /* Clinical state — ABNORMAL LOW. Redundant: letter "L" + downward chevron. */
  --clin-low-fg: var(--color-blue-600);
  --clin-low-bg: var(--color-blue-50);
  --clin-low-fg-on-bg: var(--color-blue-800);
  --clin-low-border: var(--color-blue-500);
  --clin-low-solid: var(--color-blue-600);
  --clin-low-on-solid: var(--color-neutral-0);

  /* Clinical state — NORMAL / IN RANGE. Redundant: the absence of a flag, plus
     the words "In range" where an affirmation is actually required. */
  --clin-normal-fg: var(--color-green-600);
  --clin-normal-bg: var(--color-green-50);
  --clin-normal-fg-on-bg: var(--color-green-700);
  --clin-normal-border: var(--color-green-500);
  --clin-normal-solid: var(--color-green-600);
  --clin-normal-on-solid: var(--color-neutral-0);

  /* Clinical state — STALE. Data whose recency cannot be relied on. Redundant:
     a dashed border, an "as of <date>" string, and reduced density weight. */
  --clin-stale-fg: var(--color-neutral-600);
  --clin-stale-bg: var(--color-neutral-50);
  --clin-stale-fg-on-bg: var(--color-neutral-700);
  --clin-stale-border: var(--color-neutral-450);

  /* Clinical state — UNSIGNED / PENDING. Shares the amber primitive with
     abnormal-high by design (both mean "needs a human"), but the two never
     appear in the same region. Redundant: a 45-degree hatched left rail, the
     word "Unsigned", and an open-padlock icon. */
  --clin-unsigned-fg: var(--color-amber-700);
  --clin-unsigned-bg: var(--color-amber-50);
  --clin-unsigned-fg-on-bg: var(--color-amber-800);
  --clin-unsigned-border: var(--color-amber-500);

  /* Data visualisation — single-analyte trend. One series, one reference band,
     flagged points inherit the clinical state tokens above. No gradients, no
     quadrant overlays, no desirability ramp. */
  --viz-line: var(--color-neutral-800);
  --viz-grid: var(--color-neutral-200);
  --viz-axis: var(--color-neutral-600);
  --viz-band: var(--color-green-50); /* reference-range band */
  --viz-band-edge: var(--color-green-500);
  --viz-point: var(--color-neutral-800);
}

/* -----------------------------------------------------------------------------
   5. DENSITY
   Comfortable is the default and is what a front-desk user and a demo audience
   see. Compact is opt-in per user and persists; it is what a physician running
   a full clinic uses. Both keep a 24x24 CSS px minimum pointer target
   (WCAG 2.2 SC 2.5.8) — in compact the target is achieved with an invisible
   inset hit area, not by growing the row.
   -------------------------------------------------------------------------- */
:root,
:root[data-density="comfortable"] {
  --density-text-size: var(--size-body);
  --density-text-line: var(--line-body);
  --row-height: 40px;
  --row-height-compact-inner: 40px;
  --cell-pad-y: var(--space-2); /* 8 */
  --cell-pad-x: var(--space-3); /* 12 */
  --control-height: 36px;
  --control-pad-x: var(--space-3);
  --icon-size: 20px;
  --section-gap: var(--space-6); /* 24 */
  --panel-pad: var(--space-5); /* 20 */
  --hit-target-min: 24px;
}

:root[data-density="compact"] {
  --density-text-size: var(--size-compact);
  --density-text-line: var(--line-compact);
  --row-height: 28px;
  --row-height-compact-inner: 28px;
  --cell-pad-y: var(--space-1); /* 4 */
  --cell-pad-x: var(--space-2); /* 8 */
  --control-height: 30px;
  --control-pad-x: var(--space-2);
  --icon-size: 16px;
  --section-gap: var(--space-4); /* 16 */
  --panel-pad: var(--space-3); /* 12 */
  --hit-target-min: 24px;
}

/* -----------------------------------------------------------------------------
   6. SEMANTIC ALIASES — DARK
   Same alias names, same structure, different primitives. Applied by
   <html data-theme="dark">. Chrome and banner stay navy in both themes.
   -------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  --surface-canvas: var(--color-ink-900);
  --surface-raised: var(--color-ink-850);
  --surface-sunken: var(--color-ink-950);
  --surface-overlay: var(--color-ink-800);
  --surface-hover: var(--color-ink-750);
  --surface-selected: var(--color-accent-950);
  --surface-disabled: var(--color-ink-800);
  --surface-chrome: var(--color-navy-950);
  --surface-banner: var(--color-navy-800);
  --surface-scrim: rgb(var(--shadow-rgb) / 0.72);

  --text-primary: var(--color-neutral-100);
  --text-secondary: var(--color-neutral-300);
  --text-muted: var(--color-neutral-400);
  --text-disabled: var(--color-neutral-500);
  --text-on-chrome: var(--color-neutral-0);
  --text-on-chrome-muted: var(--color-neutral-300);
  --text-link: var(--color-accent-400);
  --text-on-accent: var(--color-neutral-0);

  --border-subtle: var(--color-ink-700);
  --border-default: var(--color-ink-700);
  --border-control: var(--color-neutral-500);
  --border-strong: var(--color-neutral-400);
  --border-focus: var(--color-accent-400);
  --focus-halo: var(--color-ink-950);

  /* Brand chrome, restated so the alias is defined in both themes as the
     rule in section 3 of ui-direction.md requires. Same value, on purpose. */
  --brand-gradient: linear-gradient(
    90deg,
    var(--color-accent-600),
    var(--color-brand-pink-600)
  );

  --accent-base: var(--color-accent-600);
  --accent-hover: var(--color-accent-500);
  --accent-active: var(--color-accent-400);
  --accent-subtle: var(--color-accent-950);
  --accent-subtle-text: var(--color-accent-300);

  /* The portal family, restated verbatim. Same values, on purpose: the cover
     is dark in both themes, so an alias that changed here would give the
     portal a second appearance nobody asked for. portal-direction.md
     section 4.1. */
  --portal-panel: var(--color-ink-850);
  --portal-border: var(--color-neutral-500);
  --portal-rule: var(--color-neutral-700);
  --portal-text-quiet: var(--color-neutral-400);
  --portal-accent-text: var(--color-accent-400);
  --portal-tone-ready: var(--color-green-300);
  --portal-tone-working: var(--color-accent-400);
  --portal-tone-idle: var(--color-neutral-400);
  --portal-tone-problem: var(--color-red-300);

  --clin-critical-fg: var(--color-red-300);
  --clin-critical-bg: var(--color-red-950);
  --clin-critical-fg-on-bg: var(--color-red-300);
  --clin-critical-border: var(--color-red-400);
  --clin-critical-solid: var(--color-red-700);
  --clin-critical-on-solid: var(--color-neutral-0);

  --clin-high-fg: var(--color-amber-300);
  --clin-high-bg: var(--color-amber-950);
  --clin-high-fg-on-bg: var(--color-amber-300);
  --clin-high-border: var(--color-amber-400);
  --clin-high-solid: var(--color-amber-700);
  --clin-high-on-solid: var(--color-neutral-0);

  --clin-low-fg: var(--color-blue-300);
  --clin-low-bg: var(--color-blue-950);
  --clin-low-fg-on-bg: var(--color-blue-300);
  --clin-low-border: var(--color-blue-400);
  --clin-low-solid: var(--color-blue-600);
  --clin-low-on-solid: var(--color-neutral-0);

  --clin-normal-fg: var(--color-green-300);
  --clin-normal-bg: var(--color-green-950);
  --clin-normal-fg-on-bg: var(--color-green-300);
  --clin-normal-border: var(--color-green-400);
  --clin-normal-solid: var(--color-green-600);
  --clin-normal-on-solid: var(--color-neutral-0);

  --clin-stale-fg: var(--color-neutral-400);
  --clin-stale-bg: var(--color-ink-800);
  --clin-stale-fg-on-bg: var(--color-neutral-400);
  --clin-stale-border: var(--color-neutral-500);

  --clin-unsigned-fg: var(--color-amber-300);
  --clin-unsigned-bg: var(--color-amber-950);
  --clin-unsigned-fg-on-bg: var(--color-amber-300);
  --clin-unsigned-border: var(--color-amber-400);

  --viz-line: var(--color-neutral-100);
  --viz-grid: var(--color-ink-700);
  --viz-axis: var(--color-neutral-400);
  --viz-band: var(--color-green-950);
  --viz-band-edge: var(--color-green-400);
  --viz-point: var(--color-neutral-100);

  /* Shadows read as noise on a dark canvas; overlays are separated by surface
     step and border instead. */
  --elevation-2: 0 2px 6px rgb(var(--shadow-rgb) / 0.5);
  --elevation-3: 0 12px 32px rgb(var(--shadow-rgb) / 0.6);
  --elevation-4: 0 8px 24px rgb(var(--shadow-rgb) / 0.6);
}

/* -----------------------------------------------------------------------------
   7a. REDUCED MOTION
   Durations collapse to zero at the token layer so no component has to
   remember. Transitions that only communicate state change (focus, hover) are
   allowed to remain instant; nothing animates position or opacity.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0ms;
    --dur-medium: 0ms;
    --dur-slow: 0ms;
  }
}

/* -----------------------------------------------------------------------------
   7b. FORCED COLORS (Windows High Contrast)
   Hand colour back to the OS, but keep the structural encoding: the clinical
   state rails and the focus ring must survive as borders, because in forced
   colours our hues are discarded and the shape is all that is left.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  /* BOTH SELECTORS, FOR THE REASON SECTION 7c ALREADY RECORDS BELOW, and this
     block had the identical defect until 2026-08-27. `:root` alone is
     specificity (0,1,0); section 6 declares every alias listed here on
     `:root[data-theme="dark"]`, which is (0,2,0), and @media adds no
     specificity of its own. So on any surface that pins the dark theme -
     site/index.html, site/signin.html and site/portal/index.html all carry
     <html data-theme="dark"> - the dark block outranked this one and NOT ONE
     of these declarations took effect. The nine --portal-* aliases were dead
     from the day they were added, and --border-*, --focus-halo and the six
     --clin-*-border aliases were dead in the dark theme before that.

     The cascade, spelled out, because "specificity" asserted is how this got
     shipped twice: matching `:root[data-theme="dark"]` here raises this rule
     to (0,2,0), which ties section 6; ties are broken by document order, and
     this block is later in the file. `:root` on its own still carries the
     light theme and the no-attribute default, where nothing outranks it.
     Held by auth-gate/the-signin-page-survives-paper-and-forced-colours,
     which reads the print block's selector list; that check is the reason the
     print block was correct and this one was not. */
  :root,
  :root[data-theme="dark"] {
    --border-subtle: CanvasText;
    --border-default: CanvasText;
    --border-control: CanvasText;
    --border-strong: CanvasText;
    --border-focus: Highlight;
    --focus-halo: Canvas;
    --clin-critical-border: CanvasText;
    --clin-high-border: CanvasText;
    --clin-low-border: CanvasText;
    --clin-normal-border: CanvasText;
    --clin-stale-border: GrayText;
    --clin-unsigned-border: CanvasText;
    /* The portal, portal-direction.md section 8.2. Every hairline it draws is
       a border, so handing these to CanvasText keeps the card edges, the rule
       stub, the eyebrow tick, the link-arrow rule and the step connector.
       --portal-tone-idle goes to GrayText for the same reason --clin-stale-border
       does: "waiting" and "stopped" are the two states that mean nothing is
       happening, and GrayText is what the OS calls that. */
    --portal-border: CanvasText;
    --portal-rule: CanvasText;
    --portal-text-quiet: CanvasText;
    --portal-accent-text: CanvasText;
    --portal-tone-ready: CanvasText;
    --portal-tone-working: CanvasText;
    --portal-tone-problem: CanvasText;
    --portal-tone-idle: GrayText;
    --elevation-2: none;
    --elevation-3: none;
    --elevation-4: none;
  }
}

/* -----------------------------------------------------------------------------
   7c. PRINT
   A day sheet, a superbill and a CMS-1500 all get printed. On paper the tint
   fills disappear, so the state rails become heavier rules and every state
   keeps its letter and its word. Surfaces go white, text goes near-black, and
   nothing depends on a background colour the printer may drop.
   -------------------------------------------------------------------------- */
@media print {
  /* BOTH SELECTORS, AND THE SECOND ONE IS THE POINT. `:root` alone is
     specificity (0,1,0) and `:root[data-theme="dark"]` in section 6 is
     (0,2,0), so for the whole life of this file the dark theme quietly
     outranked the print block and a page printed in dark mode came out
     near-black on paper - or, once the printer dropped the background,
     pale grey text on white. @media does not raise specificity. Found by
     rendering the sign-in page with print media emulated; held by
     auth-gate/the-signin-page-survives-paper-and-forced-colours, which reads
     this selector list and the page that pins the attribute together. */
  :root,
  :root[data-theme="dark"] {
    color-scheme: light;
    --surface-canvas: var(--color-neutral-0);
    --surface-raised: var(--color-neutral-0);
    --surface-sunken: var(--color-neutral-0);
    --surface-overlay: var(--color-neutral-0);
    --surface-hover: var(--color-neutral-0);
    --surface-selected: var(--color-neutral-0);
    --surface-chrome: var(--color-neutral-0);
    --surface-banner: var(--color-neutral-0);

    --text-primary: var(--color-neutral-900);
    --text-secondary: var(--color-neutral-900);
    --text-muted: var(--color-neutral-700);
    --text-on-chrome: var(--color-neutral-900);
    --text-on-chrome-muted: var(--color-neutral-700);
    --text-link: var(--color-neutral-900);

    --border-subtle: var(--color-neutral-700);
    --border-default: var(--color-neutral-900);
    --border-control: var(--color-neutral-900);

    --clin-critical-bg: var(--color-neutral-0);
    --clin-high-bg: var(--color-neutral-0);
    --clin-low-bg: var(--color-neutral-0);
    --clin-normal-bg: var(--color-neutral-0);
    --clin-stale-bg: var(--color-neutral-0);
    --clin-unsigned-bg: var(--color-neutral-0);
    --clin-critical-fg-on-bg: var(--color-neutral-900);
    --clin-high-fg-on-bg: var(--color-neutral-900);
    --clin-low-fg-on-bg: var(--color-neutral-900);
    --clin-normal-fg-on-bg: var(--color-neutral-900);
    --clin-stale-fg-on-bg: var(--color-neutral-900);
    --clin-unsigned-fg-on-bg: var(--color-neutral-900);

    /* The portal on paper. The cover inverts to a white ground with near-black
       text - 17.13:1 - and every state tone collapses to ink, exactly as the
       clinical block above does: the word and the glyph carry the state, so a
       printed launch screen that has lost its colour still says Failed.
       portal-direction.md section 4.4. */
    --portal-panel: var(--color-neutral-0);
    --portal-border: var(--color-neutral-900);
    --portal-rule: var(--color-neutral-700);
    --portal-text-quiet: var(--color-neutral-700);
    --portal-accent-text: var(--color-neutral-900);
    --portal-tone-ready: var(--color-neutral-900);
    --portal-tone-working: var(--color-neutral-900);
    --portal-tone-idle: var(--color-neutral-900);
    --portal-tone-problem: var(--color-neutral-900);

    --line-rule: 3px;
    --line-heavy: 4px;
    --elevation-2: none;
    --elevation-3: none;
    --elevation-4: none;
    --dur-fast: 0ms;
    --dur-medium: 0ms;
    --dur-slow: 0ms;
  }
}
