/* Gervasi Lab — design tokens. See contracts/design-tokens.md.
 *
 * THIS IS THE ONLY FILE THAT MAY CONTAIN A LITERAL COLOUR.
 * Layer 1 (--raw-*) is declared here and consumed only by layer 2, in this file.
 * Components reference layer 2 and nothing else. Enforced by a grep test.
 *
 * There is deliberately NO green and NO red. The plan ships no safe/unsafe
 * indicator (R-17: the likeliest path to real liability is not a designed
 * feature but a well-meant one), so severity is carried by tier, wording and
 * position — never by a hue the palette does not contain.
 *
 * That rule is about SEVERITY, and it is unchanged. The SOP phase accents
 * added below do contain a green (MEASURE) and a red (HAZARD), and neither is
 * a safe/unsafe indicator: they name what a step IS, not whether it went well.
 * Completion is carried by fill, and HAZARD is identified by its hatch, not
 * its hue (spec 03 §4.4). No token in this file means "safe" or "unsafe".
 */

:root {
  /* --- layer 1: raw. Locked by the PI. Referenced ONLY by layer 2. --- */
  --raw-celeste: #6cace4;
  --raw-gold: #f6b40e;
  --raw-white: #ffffff;
  --raw-neutral: #0f212d;

  /* Derived, each stated explicitly so a palette change shows in a diff. */
  --raw-neutral-900: #0a1720;
  --raw-neutral-800: #16303f;
  --raw-neutral-700: #1e3d50;
  --raw-celeste-200: #a9cfee;
  --raw-celeste-700: #4c7aa1;
  --raw-gold-200: #ffd466;
  --raw-gold-700: #986f08;
  --raw-mist: #c7d6df;

  /* The eight SOP phase accents, DARK column, verbatim from
   * `docs/03-sop-engine-spec.md` §4.5. Re-derived, not trusted:
   * `scripts/verify/sop_palette.py` parses these very lines and re-computes
   * every contrast ratio and the whole CIEDE2000 separation matrix from them.
   *
   * WHY THE PALETTE IS SHAPED LIKE THIS
   *
   * §4.2 — PHASE IS THE HUE AXIS. It is the only stable a-priori partition of
   * all 656 steps in the corpus. Audience has zero variance; resource is 279
   * distinct strings (a filter, not a palette); timing rigidity became the
   * *shape* channel instead. Phase leaves the lightness/area channel free for
   * run state, so showing progress costs no hue.
   *
   * §4.3 — CHROMA IS BY CONSEQUENCE, NOT FREQUENCY. LOUD (HAZARD, INCUBATE,
   * WASH, MEASURE) is 192 steps, 29% of the corpus, and is every step where
   * the experiment can be destroyed. QUIET (PREP, CHECK, MIX, CLEAN) is the
   * other 464, 71% — including the two commonest phases, CHECK 183 and PREP
   * 174. Badging the majority badges nothing, so a run reads as a calm field
   * with bright bars only where the work is destructive.
   *
   * §4.3/§4.4 — CLEAN IS DELIBERATELY ACHROMATIC (C 2.9). It is the one phase
   * that survives greyscale print and every colour-vision deficiency on
   * lightness alone, with no hue at all.
   *
   * §4.4 — THIS IS NOT A RE-INTRODUCTION OF RED/GREEN (see the file header,
   * and design-tokens.md rule D2). MEASURE's green means "measure", not
   * "success" — completion is carried by FILL, so nothing here is green for
   * "go". HAZARD's identity is the 45° hatch, squared corners and the
   * triangle-bang glyph; its red is reinforcement for trichromats only. No
   * safe/unsafe hue exists in the product, and none of these eight is one.
   *
   * §4.14 — HUE IS NEVER THE ONLY CHANNEL. Every phase also carries a ≥16 px
   * glyph and legend text. If the eight glyphs are not built, the run map does
   * not ship — these tokens alone are not an accessible encoding.
   */
  --raw-phase-hazard: #ee645a;
  --raw-phase-incubate: #ae6ac9;
  --raw-phase-wash: #92b6fc;
  --raw-phase-measure: #98ea82;
  --raw-phase-prep: #8397a6;
  --raw-phase-check: #89ccd4;
  --raw-phase-mix: #c5b097;
  --raw-phase-clean: #ede6e3;

  /* The 14% tints, also verbatim from §4.5 — NOT computed here. §4.5 ships two
   * fill strengths because one accent cannot be both dark enough for 3:1 as a
   * fill and light enough for 4.5:1 under ink: map tiles carry no text and use
   * the accent at 100%; list rows carry text and use the tint, with the accent
   * demoted to rail and glyph chip. Body text on these tints is 10.3–13.4:1.
   */
  --raw-phase-hazard-tint: #2f2229;
  --raw-phase-incubate-tint: #25233b;
  --raw-phase-wash-tint: #213043;
  --raw-phase-measure-tint: #223830;
  --raw-phase-prep-tint: #1e2b35;
  --raw-phase-check-tint: #1f333d;
  --raw-phase-mix-tint: #292f33;
  --raw-phase-clean-tint: #2f373f;

  /* --- layer 2: semantic. The ONLY names a component may reference. --- */
  --surface: var(--raw-neutral);
  --surface-raised: var(--raw-neutral-800);
  --surface-recessed: var(--raw-neutral-900);
  --border: var(--raw-neutral-700);
  --ink: var(--raw-white);
  --ink-secondary: var(--raw-mist);
  --accent: var(--raw-celeste);
  --accent-ink: var(--raw-celeste-200);
  --on-accent: var(--raw-neutral);
  --warn: var(--raw-gold);
  --warn-ink: var(--raw-gold-200);
  --on-warn: var(--raw-neutral);
  --focus-ring: var(--raw-white);

  /* Phase, layer 2. A template interpolates the phase name into all three:
   * `var(--phase-{{ step.phase }})`, `-tint`, and the two shared ink tokens.
   *
   * ONLY THE DARK COLUMN SHIPS. The app is dark-default (base.html:
   * `<meta name="color-scheme" content="dark">`) and this file has no
   * `prefers-color-scheme: light` block to hang §4.5's LIGHT column on. The
   * only light ground in the file is `@media print`, whose ground is white,
   * not §4.5's `#F7F9FB` — so LIGHT is deliberately NOT shipped here rather
   * than shipped against a ground it was not solved for. §4.14 already says
   * greyscale print does not carry phase (min adjacent L* gap 0.4): on paper
   * the glyph plus a mandatory per-page legend carries it, not the hue.
   *
   * TWO GROUNDS, STATED. §4.5 solved DARK against ground `#0B1620`; this app's
   * `--surface` is the locked neutral `#0f212d`, which is lighter, so every
   * accent measures lower here than §4.5's "vs bg" column — it lands on
   * §4.5's "on-accent" column instead, 4.47–13.35:1. Still above the 3:1
   * graphical floor everywhere. `scripts/verify/sop_palette.py` asserts both
   * grounds so the gap stays visible rather than being discovered on a bench.
   */
  /* Ink ON a filled phase tile. Dark theme: every accent is light, so the ink
   * is the ground colour. This is not decoration — a done tile fills with its
   * accent, and progress is expressed as fill and area rather than a second
   * hue, so the ink has to follow the fill. */
  --phase-hazard-ink: #0b1620;
  --phase-incubate-ink: #0b1620;
  --phase-wash-ink: #0b1620;
  --phase-measure-ink: #0b1620;
  --phase-prep-ink: #0b1620;
  --phase-check-ink: #0b1620;
  --phase-mix-ink: #0b1620;
  --phase-clean-ink: #0b1620;
  --phase-hazard: var(--raw-phase-hazard);
  --phase-incubate: var(--raw-phase-incubate);
  --phase-wash: var(--raw-phase-wash);
  --phase-measure: var(--raw-phase-measure);
  --phase-prep: var(--raw-phase-prep);
  --phase-check: var(--raw-phase-check);
  --phase-mix: var(--raw-phase-mix);
  --phase-clean: var(--raw-phase-clean);

  --phase-hazard-tint: var(--raw-phase-hazard-tint);
  --phase-incubate-tint: var(--raw-phase-incubate-tint);
  --phase-wash-tint: var(--raw-phase-wash-tint);
  --phase-measure-tint: var(--raw-phase-measure-tint);
  --phase-prep-tint: var(--raw-phase-prep-tint);
  --phase-check-tint: var(--raw-phase-check-tint);
  --phase-mix-tint: var(--raw-phase-mix-tint);
  --phase-clean-tint: var(--raw-phase-clean-tint);

  /* One value for all eight, because §4.5 specifies one. Text on a phase tint
   * is `#E6EEF4` in the spec; the locked palette's white is lighter, so every
   * ratio comes out ABOVE the spec's stated 10.29–12.96 rather than below —
   * asserted, not assumed. Text on a 100% accent fill is the locked neutral,
   * which is exactly what §4.5's "on-accent" column measures. */
  --on-phase-tint: var(--raw-white);
  --on-phase-accent: var(--raw-neutral);

  /* Type. No weight below 400 — hairline weights vanish under cold-room glare. */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Monospace for catalog numbers, lot numbers and mfr_catalog_raw, where 0/O
   * and 1/l must not be confusable at arm's length. */
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
  --weight-regular: 400;
  --weight-medium: 600;
  --weight-bold: 700;

  --text-xs: 0.8125rem;
  --text-sm: 0.9375rem;
  --text-base: 1.0625rem;
  --text-lg: 1.25rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;
  /* The validated dilution, the gauge numeral, the lot quantity on the
   * decrement screen. Gate 5 requires `1:1000 in BSA 1% TTBS` legible at arm's
   * length in a cold room. */
  --text-display: 2.75rem;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;

  /* WCAG 2.5.5 AAA. A nitrile-gloved thumb contact patch is ~19-20 mm, so 44 px
   * abutting 44 px reads as one target — hence the mandatory gap. */
  --target-min: 44px;
  --target-bench: 56px;
  --target-gap: 8px;

  --radius: 8px;
  --radius-lg: 12px;
}

/* The print/export theme. Gold on white is 1.83:1 and celeste on white is
 * 2.43:1 — both fail WCAG AA, and gold fails even the 3:1 non-text floor. The
 * palette is locked, so the raw hues become FILLS and a darker shade of the
 * same hue becomes INK. This matters most off screen: the laminated role cards
 * (gate 10) and the printed weekend duty card carrying the Airgas contact and
 * the PI's mobile (gate 11) are what someone reads at 02:00 in a basement.
 */
@media print {
  :root {
    --surface: var(--raw-white);
    --surface-raised: var(--raw-white);
    --surface-recessed: var(--raw-white);
    --border: var(--raw-neutral-700);
    --ink: var(--raw-neutral);
    --ink-secondary: var(--raw-neutral);
    --accent-ink: var(--raw-celeste-700);
    --warn-ink: var(--raw-gold-700);
    --on-accent: var(--raw-white);
    --on-warn: var(--raw-neutral);
  }
}

/* ===========================================================================
 * LIGHT, AND HOW YOU CHOOSE
 * ===========================================================================
 * The app has been dark-default since the first screen, and that is the right
 * default for this lab: it images sperm and embryos, the microscope room is
 * kept dark, and the run view is the screen most likely to be propped on a
 * bench in there. A white screen at full brightness beside a scope costs dark
 * adaptation, and it is the one place somebody cannot simply look away.
 *
 * What was missing was everything else. `<meta name="color-scheme">` pinned the
 * app dark for everyone, so a person who works in a bright room, or who needs
 * a light ground to read comfortably, had no way to say so.
 *
 * TWO MECHANISMS, AND THE ORDER MATTERS:
 *
 *   `prefers-color-scheme: light`   what your machine already says about you.
 *   `:root[data-theme="..."]`       what you said on THIS device, which wins.
 *
 * The override is not a nicety. A shared bench laptop follows nobody's
 * preference in particular, and the person at the scope at 22:00 is not the
 * person who set up that machine. Pinning is per-browser and survives a reload.
 *
 * DERIVED, NOT INVERTED. Every accent below is §4.5's LIGHT column verbatim —
 * ground `#F7F9FB`, ink `#0F212D` — solved against paper rather than flipped
 * from the dark values. Inverting a hue-and-chroma solution changes both its
 * lightness relationship to the ground and its separation from its neighbours,
 * which is precisely what the colour-vision work was for. The two columns are
 * two solutions to two problems.
 *
 * `scripts/verify/sop_palette.py` checks BOTH grounds on every CI run.
 * =========================================================================== */

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --surface: #f7f9fb;
    --surface-raised: #ffffff;
    --surface-recessed: #edf1f5;
    --border: #ccd6de;
    --ink: #0f212d;
    /* §4.5's PREP accent: L* 38.0, 6.58:1 on paper. A secondary ink has to
     * stay readable rather than merely look quieter. */
    --ink-secondary: #495c6a;
    --accent: #2473c7;
    --accent-ink: #1c5b9e;
    --on-accent: #ffffff;
    --warn: #986f08;
    --warn-ink: #7a5906;
    --on-warn: #ffffff;
    --focus-ring: #0f212d;

    /* §4.5 LIGHT, verbatim. Loud phases keep their chroma; quiet phases keep
     * their restraint. The consequence ordering is preserved across grounds —
     * HAZARD and INCUBATE are the two loudest in both. */
    --phase-hazard: #a12526;
    --phase-incubate: #5d2675;
    --phase-wash: #2473c7;
    --phase-measure: #3b9f2c;
    --phase-prep: #495c6a;
    /* CHECK DEPARTS FROM §4.5's LIGHT COLUMN, and this is the one value that does.
     * The spec's `#468188` separates from WASH `#2473C7` by only deltaE2000 5.80
     * under tritanopia on paper — both are blue-family in the light column, and
     * tritanopia is the axis that collapses blue against teal. The DARK column
     * has no such collision (its floor is 11.35), so this is a defect in one
     * column rather than a property of the palette.
     *
     * `#667a7f` is the same quiet teal moved off the blue axis. Measured:
     * worst pair rises 5.80 -> 10.51, contrast on paper 4.18 -> 4.27, and the
     * residual tightest pair becomes CHECK/CLEAN under protanopia — quiet
     * against quiet, which is the shape §4.5 already accepts, because confusing
     * "look at it" with "wipe the bench" costs nothing. `light_palette.py`
     * measures it on every CI run. */
    --phase-check: #667a7f;
    --phase-mix: #87745d;
    --phase-clean: #948f8b;

    --phase-hazard-tint: #e9d7d9;
    --phase-incubate-tint: #ded7e6;
    --phase-wash-tint: #d5e4f3;
    --phase-measure-tint: #d9ebda;
    --phase-prep-tint: #dbe0e4;
    --phase-check-tint: #dbe6e9;
    --phase-mix-tint: #e5e4e2;
    --phase-clean-tint: #e7e8e9;

    /* MEASURE and CLEAN are light enough that white on them fails; the spec
     * puts dark ink on exactly those two. Meaning is never carried by hue
     * alone in either theme — the glyph and the label do that — but the ink
     * still has to be readable. */
    /* Light accents are DARK, so the ink is white — except MEASURE and CLEAN,
     * which §4.5 gives dark ink because they are light enough that white fails. */
    --phase-hazard-ink: #ffffff;
    --phase-incubate-ink: #ffffff;
    --phase-wash-ink: #ffffff;
    --phase-prep-ink: #ffffff;
    --phase-check-ink: #ffffff;
    --phase-mix-ink: #ffffff;
    --phase-measure-ink: #0f212d;
    --phase-clean-ink: #0f212d;

    --on-phase: #ffffff;
    --on-phase-light: #0f212d;
  }
}

/* An explicit choice beats the machine's, in both directions. Duplicated
 * rather than shared with the media query because a media query cannot be
 * overridden by a later selector of equal specificity — the pin has to be able
 * to force LIGHT on a dark-preferring machine as well as the reverse. */
:root[data-theme="light"] {
  --surface: #f7f9fb;
  --surface-raised: #ffffff;
  --surface-recessed: #edf1f5;
  --border: #ccd6de;
  --ink: #0f212d;
  --ink-secondary: #495c6a;
  --accent: #2473c7;
  --accent-ink: #1c5b9e;
  --on-accent: #ffffff;
  --warn: #986f08;
  --warn-ink: #7a5906;
  --on-warn: #ffffff;
  --focus-ring: #0f212d;

  --phase-hazard: #a12526;
  --phase-incubate: #5d2675;
  --phase-wash: #2473c7;
  --phase-measure: #3b9f2c;
  --phase-prep: #495c6a;
  /* See the note in the media-query block: this one value departs from
   * §4.5's LIGHT column to fix a tritan collision with WASH. */
  --phase-check: #667a7f;
  --phase-mix: #87745d;
  --phase-clean: #948f8b;

  --phase-hazard-tint: #e9d7d9;
  --phase-incubate-tint: #ded7e6;
  --phase-wash-tint: #d5e4f3;
  --phase-measure-tint: #d9ebda;
  --phase-prep-tint: #dbe0e4;
  --phase-check-tint: #dbe6e9;
  --phase-mix-tint: #e5e4e2;
  --phase-clean-tint: #e7e8e9;

  /* Light accents are DARK, so the ink is white — except MEASURE and CLEAN,
   * which §4.5 gives dark ink because they are light enough that white fails. */
  --phase-hazard-ink: #ffffff;
  --phase-incubate-ink: #ffffff;
  --phase-wash-ink: #ffffff;
  --phase-prep-ink: #ffffff;
  --phase-check-ink: #ffffff;
  --phase-mix-ink: #ffffff;
  --phase-measure-ink: #0f212d;
  --phase-clean-ink: #0f212d;

  --on-phase: #ffffff;
  --on-phase-light: #0f212d;
}
