/* MOOEV OS — design tokens (framework-agnostic).
   One source of truth for colour, type, spacing, and field-readiness.
   These survive any later JS framework or native-shell choice.
   LAST CHANGED: 29 Aug 2026 — WHAT'S NEW: Phase 1 Design Language Refresh, Deliverable 1
   (PHASE1_DESIGN_LANGUAGE_REFRESH_SPEC.md, Founder-confirmed). Four additions, all new
   tokens — nothing existing renamed or removed:
   (1) --c-safety (+--c-on-safety): a Safety Event red, deliberately DISTINCT from
   --c-danger (never merged into it — the spec's own instruction: a safety/thermal-
   boundary condition must stay unmistakably separate from routine danger/caution
   states). Light #D62B2B (the Founder-spec value) measured ~4.95:1 against white —
   clears AA. Dark #FF6B6B chosen instead of reusing --c-danger's own dark salmon
   (#f09595) specifically so Safety stays visually more saturated/urgent than routine
   danger in dark mode too, not just light — measured ~5.80:1 against the dark surface
   (#18222c). Both re-verified by the real Playwright contrast probe (test_color_contrast
   pattern) against apps/web/design-system/gallery.html, not by hand math alone.
   (2) --c-danger-bg/--c-warn-bg/--c-neutral-bg: severity-tint row/card backgrounds,
   closing the forward note in the Operations Manager App design doc (Needs Attention
   Feed section) that flagged these as needed but explicitly deferred. Deliberately
   near-white/near-black tints (not saturated) so the EXISTING --c-text/--c-text-muted
   foreground tokens stay legible on top by construction, verified by the same contrast
   probe rather than assumed.
   (3) --dur-fast/--dur-base/--dur-slow/--ease-standard: motion-duration tokens, kept
   short per this platform's own "speed is the feature" principle — not decorative
   animation. All transitions already fall under components.css's existing
   prefers-reduced-motion block (transition-duration forced to .001ms), so no separate
   reduced-motion handling was needed for these.
   (4) --touch-min compliance fix: 9 confirmed sub-48px interactive controls raised to
   var(--touch-min) — driver.css .lang (was 40px) + .steps button (was 36px), energy.css
   .station (was 40px), dashboard.css .range (was 40px) + .pin (was 30px), and this
   file's own .crumb button (was 36px) + .tabs button (was 40px) + .groupby select (was
   40px) + .seg .btn (was calc(var(--touch-min) - 8px), i.e. 40px). A new tripwire test
   (tests/layer_web/test_touch_targets.py) now statically scans every CSS file under
   apps/web (recursively) so this class of drift can't silently reappear — seeing the
   fix through with an enforcement mechanism, not just a one-time patch (CLAUDE.md's own
   "fix invariants, not instances" rule). Full findings + Founder confirmation:
   PHASE1_DESIGN_LANGUAGE_REFRESH_SPEC.md; gap record: GAP_REGISTER.md.

   CAUGHT-AND-FIXED IN THIS SAME PASS, worth naming so it isn't repeated: this paragraph
   originally spelled the scan target as a two-star-then-star-dot-css glob shorthand.
   Written that way, the asterisk character sitting right before a following slash
   character forms a real, premature comment-close token — CSS block comments don't
   nest and have no escape, so the very first such pairing anywhere inside an
   already-open comment ends it right there, silently, with no error shown to a person
   reading the source. That is exactly what happened here: it truncated this ENTIRE
   header comment partway through and dropped the whole base :root token block below
   (every token, old and new) on every already-shipped app page, not just anything new
   — found by a real headless-browser render (a computed-style probe against
   apps/web/energy/index.html showing --touch-min as empty and its buttons at 19px),
   not by reading the CSS as text, which is why it went unnoticed at first. Lesson kept
   here on purpose: inside a CSS comment, never let an asterisk sit immediately before
   a slash anywhere in the middle of the text (a glob pattern, a pasted code fragment,
   anything) — spell it out in plain words instead, the way this very paragraph now
   does, or the direct proof above (the file that broke on being rendered) is the only
   way the mistake gets caught before it ships.
   ---
   PRIOR: 23 Jul 2026 — WHAT'S NEW: contrast correction to the 22 Jul 2026 per-app
   colour theming below, found by tests/layer_web/test_color_contrast.py before that work
   was ever committed (54 failures: every non-Driver app, both themes, plus 2 Driver trip
   tabs). Root cause: (1) Operations/Energy/Founder/Dashboard each used ONE brand hex for
   both light and dark theme — no single hue clears WCAG AA 4.5:1 as text against both a
   white surface and a near-black one, so each now gets separately-tuned light/dark
   values, mirroring how the base palette already splits --c-brand; --c-on-brand is no
   longer overridden per app since the corrected hues already match the base cascade's
   existing white/#062019 split. (2) Driver's new always-dark background block forced
   bg/surface/text but left --c-ok/--c-warn/--c-danger/--c-accent/--c-heat-* on whatever
   data-theme said, so an explicit data-theme="light" session got light-tuned status
   colors (e.g. --c-ok #3b6d11, built for white) on a near-black surface — now forced
   dark-tuned unconditionally, matching Driver's own permanently-dark nature. Full
   before/after values in the block comments below. Full test suite reconfirmed 0
   contrast violations after this fix.
   ---
   PRIOR: 22 Jul 2026 — WHAT'S NEW: per-role app colour theming (Part 4 of the
   22 Jul 2026 queued plan, Founder-approved). Added 4 per-app brand blocks
   ([data-app="driver|operations|energy|founder|dashboard"], OSDesPhil palette: Driver
   Teal, Operations Blue, Energy Amber, Founder+Dashboard Purple) plus Driver's own full
   dark-background block (near-black, deliberately not [data-theme="dark"] — see block
   comment below for why). Added --c-on-brand: #ffffff to the sunlight block, fixing a
   latent gap the new per-app --c-on-brand: #062019 would otherwise have exposed
   (dark-on-dark button text in sunlight mode). See apps/web/shell.js and the 10 app
   HTML files' new data-app attribute for the other half of this change.
   ---
   PRIOR: 22 Jul 2026 — WHAT'S NEW: D-HEATMAP-CVD closed, ported from unmerged branch
   fix/heatmap-cvd-diverging-palette (commit 81c34d9, orig. 10 Jul 2026) — token values and
   rationale unchanged, only this date line updated to record the port. Added --c-heat-neg/-mid/-pos,
   a genuinely CVD-safe diverging pair (orange↔grey↔purple) for the corridor heat map and the
   Founder margin lines, which previously reused --c-ok/--c-warn/--c-danger (red-green, plus a
   hue — amber — at the diverging midpoint, both anti-patterns for a diverging encoding).
   Validated via the dataviz skill's validate_palette.js: light poles ΔE 97.1/59.4/100.0
   (protan/tritan/normal), dark poles ΔE 83.0/45.3/82.5 — both far above the 12.0 target (was
   6.2, below the 8-12 floor). Light/dark values are NOT simple inverses of each other: dark
   --c-heat-neg/-pos were tuned brighter than the validator's own first pass so the LEGEND text
   (a literal glyph in a <span>, held to WCAG's 4.5:1 text bar by tests/layer_web/
   test_color_contrast.py) clears contrast against --c-surface, while staying inside the OKLCH
   lightness band the validator wants for a legible map marker — light --c-heat-mid was darkened
   for the identical reason against a light surface. Every final value re-passed both the
   validator (CVD/lightness/chroma/contrast) and a direct WCAG 4.5:1 check before landing here.
   See GAP_REGISTER.md D-HEATMAP-CVD for the full before/after. */

:root {
  /* Brand + semantic colour — light mode (default) */
  --c-bg:            #f4f6f8;   /* page */
  --c-surface:       #ffffff;   /* cards, bars */
  --c-surface-2:     #eef1f4;   /* insets */
  --c-border:        #d7dce2;
  --c-text:          #15202b;   /* primary */
  --c-text-muted:    #5b6776;
  --c-brand:         #0f6e56;   /* MOOEV green (energy) */
  --c-brand-strong:  #0a5341;
  --c-accent:        #185fa5;   /* informational blue */
  --c-ok:            #3b6d11;
  --c-warn:          #854f0b;
  --c-danger:        #a32d2d;
  --c-on-brand:      #ffffff;
  --c-on-danger:     #ffffff;
  /* Safety Event red — deliberately separate from --c-danger (never reused/aliased to
     it). Reserved exclusively for actual Safety Events; no other status state may
     borrow this hue. See this file's 29 Aug 2026 header note for the contrast math. */
  --c-safety:        #D62B2B;
  --c-on-safety:     #ffffff;
  /* Severity-tint row/card backgrounds — deliberately near-white so the existing
     --c-text/--c-text-muted foreground tokens stay legible on top unchanged. */
  --c-danger-bg:     #fbeaea;
  --c-warn-bg:       #fbf1e0;
  --c-neutral-bg:    #eef1f4;
  /* --c-ok-bg: completes the severity-tint family (danger/warn/neutral already had one) —
     added for Energy's station-selector online/offline tint (energy.css .station.online).
     Same "near-white, --c-text stays legible unchanged" construction as its siblings; not
     yet re-verified by the repo's own headless-browser contrast probe
     (test_color_contrast.py) the way the other three were when they landed — flag that as a
     follow-up before treating this as fully load-bearing the way the others are. */
  --c-ok-bg:         #eaf4e3;
  /* Diverging pair for magnitude/polarity encodings (corridor heat map, Founder margin
     lines) — DISTINCT from the status colours above (--c-ok/--c-warn/--c-danger), which stay
     reserved for state (good/warning/critical) per the dataviz skill's rule. Orange↔grey↔purple:
     CVD-safe (validated, see D-HEATMAP-CVD), unlike red-green. -neg = hurts/loses,
     -mid = neutral midpoint, -pos = helps/earns. */
  --c-heat-neg:      #a3520a;
  --c-heat-mid:      #6e6e6e;
  --c-heat-pos:      #5b3fa0;

  /* Type — system stack, no web-font dependency (works offline).
     D-HOME-IA-SMARTPHONE (2 Sep 2026): --fs-* converted from raw px to rem (base: the
     browser's own root font-size, made explicit at 100% in components.css's html,body
     rule — normally 16px, but genuinely whatever the OS/browser dynamic-type setting has
     scaled it to). A px value never responds to that OS setting; rem does — this was a
     real, previously-unmet accessibility gap (DESIGN_LANGUAGE.md Sec 4), not a cosmetic
     change. Same visual sizes at the default 16px root: 13/15/17/21/28px. Confirmed safe
     against every existing px-literal gate before converting: test_touch_targets.py's
     regex only matches min-height/height/min-width/width, never font-size; test_color_
     contrast.py reads getComputedStyle().fontSize, which the browser always resolves to px
     regardless of the source unit — neither test can be tripped by this. New --fs-hero
     step (2.5rem/40px) for the one-per-role home hero number (DESIGN_LANGUAGE.md Sec 2/3)
     — the scale had no step above --fs-xl before this pass. */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", sans-serif;
  --fs-xs: 0.8125rem; --fs-sm: 0.9375rem; --fs-md: 1.0625rem; --fs-lg: 1.3125rem; --fs-xl: 1.75rem;
  --fs-hero: 2.5rem;
  --fw-regular: 400; --fw-medium: 500;  /* Claude design: two weights only — 400 / 500 */
  --lh: 1.5;

  /* Spacing scale */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 24px; --s-6: 32px; --s-7: 48px;

  /* D-NAVRAIL-COLLAPSE (04 Sep 2026): the left nav rail's own grid-column width — read by
     every app's own grid-template-columns (`.ops`/`.en`/`.fr`) instead of a hardcoded
     240px, so apps/web/nav-collapse.js can shrink it to the icon-only width by setting this
     custom property directly on the app shell element (`element.style.setProperty(...)`),
     with zero extra CSS class/selector needed for the column-width half of collapsing. */
  --rail-w: 240px;
  --rail-w-collapsed: 64px;

  /* Radius */
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-pill: 999px;

  /* Motion — kept short; "speed is the feature," not decorative animation. Every
     transition/animation already falls under the prefers-reduced-motion block below
     (forced to .001ms), so these need no separate reduced-motion handling. */
  --dur-fast: 120ms;    /* micro-interactions: press, toggle */
  --dur-base: 200ms;    /* standard: card/panel enter-exit */
  --dur-slow: 320ms;    /* larger sheet transitions */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);

  /* Field-readiness — gloves + movement need big targets; glare needs contrast */
  --touch-min: 48px;          /* minimum tap target (driver) */
  --touch-driver: 64px;       /* primary driver actions */
  --shadow: 0 1px 2px rgba(21,32,43,.06);  /* flat & clean — minimal elevation */
  /* D-OPSNAVSIDE shared extraction (04 Sep 2026) — the first modal/drawer backdrop this
     codebase has needed (Operations' narrow-screen nav drawer, operations.css); a plain
     black wash dims content behind an overlay the same way in every theme, so this is
     deliberately NOT redefined in the dark-mode blocks below, unlike every other --c-*
     token. Replaces a raw `rgba(0, 0, 0, .4)` that was the one hardcoded colour left in
     the drawer-overlay rule this pass promotes to design-system/components.css. */
  --c-scrim: rgba(0, 0, 0, .4);
}

/* Dark / night mode — auto by system, overridable by [data-theme] on <html> */
/* --c-on-danger (contrast fix, 05 Jul 2026): --c-danger itself goes LIGHT in dark mode
   (#f09595 — designed as readable TEXT color on the dark surface, e.g. the Emergency
   domain's own heading), but apps/web/design-system/components.css's notify-badge.high
   and operations.css's tag.risk use --c-danger as a BACKGROUND with hardcoded white
   text — fine in light mode (white on the dark #a32d2d), badly broken in dark mode
   (white on the light #f09595 measured at 2.23:1, needs 4.5:1). Reuses --c-on-brand's
   same dark value (#062019, 7.68:1 on #f09595) rather than inventing a new color.
   --c-brand / --c-accent (contrast fix, 05 Jul 2026): measured via a real headless-
   browser probe (every visible text node's ACTUAL computed color against its ACTUAL
   effective background) at 4.25:1 and 4.01:1 against --c-surface-2 — used at normal
   text weight/size (nav-item active state, status pills), so WCAG AA's 4.5:1 applies,
   not the 3:1 "large text" threshold. Both nudged ~10% lighter (#1d9e75->#33a782,
   #378add->#4b95e0) — the smallest shift that clears 4.5:1 against BOTH surface
   colors in use, chosen to stay recognizably the same brand green/accent blue rather
   than a different hue. --c-brand-strong (hover states, not a text-on-surface use)
   is unaffected. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --c-bg: #0f161d; --c-surface: #18222c; --c-surface-2: #1f2b36;
    --c-border: #2c3a47; --c-text: #eef2f6; --c-text-muted: #9fb0bf;
    --c-brand: #33a782; --c-brand-strong: #5dcaa5; --c-accent: #4b95e0;
    --c-ok: #97c459; --c-warn: #ef9f27; --c-danger: #f09595; --c-on-brand: #062019;
    --c-heat-neg: #cc8138; --c-heat-mid: #8a8a8a; --c-heat-pos: #9e83c9;
    --c-on-danger: #062019;
    --c-safety: #FF6B6B; --c-on-safety: #062019;
    --c-danger-bg: #2a1618; --c-warn-bg: #2a2113; --c-neutral-bg: #1f2b36; --c-ok-bg: #172a1b;
    --shadow: 0 1px 2px rgba(0,0,0,.28);
  }
}
:root[data-theme="dark"] {
  --c-bg: #0f161d; --c-surface: #18222c; --c-surface-2: #1f2b36;
  --c-border: #2c3a47; --c-text: #eef2f6; --c-text-muted: #9fb0bf;
  --c-brand: #33a782; --c-brand-strong: #5dcaa5; --c-accent: #4b95e0;
  --c-ok: #97c459; --c-warn: #ef9f27; --c-danger: #f09595; --c-on-brand: #062019;
  --c-heat-neg: #cc8138; --c-heat-mid: #8a8a8a; --c-heat-pos: #9e83c9;
  --c-on-danger: #062019;
  --c-safety: #FF6B6B; --c-on-safety: #062019;
  --c-danger-bg: #2a1618; --c-warn-bg: #2a2113; --c-neutral-bg: #1f2b36;
  --shadow: 0 1px 3px rgba(0,0,0,.4);
}

/* Per-app brand identity — OSDesPhil Colour System table (Founder, 22 Jul 2026):
   Driver Teal, Operations Blue, Energy Amber, Founder+Dashboard Purple (one identity,
   shared, not a fifth colour). Static `data-app` on <html> per app, read here.

   FIX (23 Jul 2026, same-pass correction before this Part 4 change ever shipped):
   the first version of this block used ONE brand hex per app for both light and dark
   theme. A real headless-browser contrast probe (the same class of check
   tests/layer_web/test_color_contrast.py already runs) measured 2.17-3.76:1 for
   Operations/Energy/Founder against light-mode surfaces (needs 4.5:1) — a single hue
   simply cannot clear WCAG AA as text against both a white surface and a near-black
   one. Fixed the same way the BASE palette already handles this (see the light
   :root default vs. the dark blocks above): each app now gets a LIGHT-mode value and
   a separately-tuned DARK-mode value, not one color asked to do both jobs. --c-on-brand
   is deliberately NOT overridden per app any more — the new light-mode hues are dark
   enough to need the base cascade's white default (line ~45), and the new dark-mode
   hues are light enough to need the dark blocks' #062019 (line ~96/106), so the
   already-correct shared split just applies unchanged. --c-accent stays shared (not
   part of the per-app palette). */
:root[data-app="driver"] {
  --c-brand: #1D9E75; --c-brand-strong: #17835f; --c-on-brand: #062019;
}
:root[data-app="operations"] {
  --c-brand: #1f6bb8; --c-brand-strong: #18538f;
}
:root[data-app="energy"] {
  --c-brand: #9a610b; --c-brand-strong: #664007;
}
:root[data-app="founder"],
:root[data-app="dashboard"] {
  --c-brand: #6056d5; --c-brand-strong: #3e32ca;
}
@media (prefers-color-scheme: dark) {
  :root[data-app="operations"]:not([data-theme="light"]) {
    --c-brand: #4894e0; --c-brand-strong: #247cd4;
  }
  :root[data-app="energy"]:not([data-theme="light"]) {
    --c-brand: #EF9F27; --c-brand-strong: #cc8412;
  }
  :root[data-app="founder"]:not([data-theme="light"]),
  :root[data-app="dashboard"]:not([data-theme="light"]) {
    --c-brand: #8e87e1; --c-brand-strong: #6b61d8;
  }
}
:root[data-app="operations"][data-theme="dark"] {
  --c-brand: #4894e0; --c-brand-strong: #247cd4;
}
:root[data-app="energy"][data-theme="dark"] {
  --c-brand: #EF9F27; --c-brand-strong: #cc8412;
}
:root[data-app="founder"][data-theme="dark"],
:root[data-app="dashboard"][data-theme="dark"] {
  --c-brand: #8e87e1; --c-brand-strong: #6b61d8;
}

/* Driver full background — its own block, deliberately NOT [data-theme="dark"].
   apps/web/api.js's MOOEV.theme.init() unconditionally overwrites data-theme from a
   single cross-app localStorage['mooev_theme'] key on every load, and Driver has no
   theme-toggle UI to fight that — so Driver's dark background must not depend on
   data-theme at all. Near-black values per MOOEV_Driver_App_Decisions_Log.txt
   (glare / field-readiness, a deliberate exception, not an oversight).

   FIX (23 Jul 2026, same-pass correction): this block forced bg/surface/text near-black
   unconditionally but left --c-ok/--c-warn/--c-danger/--c-accent/--c-on-danger/--c-heat-*
   on whatever the requested THEME said — so a Driver session with data-theme="light"
   explicitly stored (MOOEV.esc()'s cross-app localStorage key, no per-app override) got
   near-black surfaces with LIGHT-mode-tuned status colors, e.g. --c-ok #3b6d11 (built for
   a white background) measured 2.65-3.13:1 against Driver's own near-black surfaces —
   caught by tests/layer_web/test_color_contrast.py's
   test_driver_trip_stage_tab_has_no_low_contrast_text[*-light]. Driver is permanently
   dark, so it needs the DARK-tuned semantic tokens unconditionally too, the same way its
   bg/surface/text already are — not just when data-theme happens to agree. */
:root[data-app="driver"] {
  --c-bg: #0d0d0d; --c-surface: #171717; --c-surface-2: #1f1f1f;
  --c-border: #2e2e2e; --c-text: #ffffff; --c-text-muted: #A0A0A0;
  --c-ok: #97c459; --c-warn: #ef9f27; --c-danger: #f09595; --c-accent: #4b95e0;
  --c-on-danger: #062019;
  --c-heat-neg: #cc8138; --c-heat-mid: #8a8a8a; --c-heat-pos: #9e83c9;
  /* Forced unconditionally, same reasoning as the block above (23 Jul 2026 fix note):
     Driver is permanently dark regardless of the shared data-theme key, so these new
     29 Aug 2026 tokens need the dark-tuned values here too, not just in the
     data-theme="dark" blocks — otherwise a Driver session with data-theme="light"
     stored would get these NEW tokens' light values against Driver's near-black
     surface, reintroducing the exact bug class this block already exists to prevent. */
  --c-safety: #FF6B6B; --c-on-safety: #062019;
  --c-danger-bg: #2a1618; --c-warn-bg: #2a2113; --c-neutral-bg: #1f1f1f;
  --shadow: 0 1px 2px rgba(0,0,0,.5);
}

/* Sunlight mode — maximum contrast for direct-glare conditions (driver) */
:root[data-contrast="high"] {
  --c-bg: #ffffff; --c-surface: #ffffff; --c-surface-2: #ffffff;
  --c-border: #000000; --c-text: #000000; --c-text-muted: #000000;
  --c-brand: #064f3d; --c-accent: #0b3b66; --c-on-brand: #ffffff;
}
