/* ============================================================
   CCD Design System v2 — TOKENS
   Ground-up rebuild 2026-07-01, re-derived by eye (decide-0N-*.html).
   Foundation only: color · type · spacing · radius · shadow.
   4 surviving facts: navy #142549 · bilingual Thai+Latin · low-res
   1366 Windows · internal-tool register. Everything else re-judged.
   ============================================================ */
:root {

  /* ── COLOR ─────────────────────────────────────────────── */

  /* Canvas + ink — flat white, separation by line only.
     INVARIANT: a block of content is never given a background. Group it with a
     line, a gap, or a heading. Every tinted rectangle a consumer has reached for
     — a --canvas panel, an --accent-tint strip, a --divider band — was rejected
     on sight. Fills belong to controls and state (hover, selection, disabled,
     tracks, pills), never to structure.
     The law is a LIGHTNESS STEP WITH A NO-HUE CLAUSE, not "no fills" — light
     mode's step is zero only because white is the ceiling. See PRODUCT.md
     composition rule 1. Dark takes real steps and breaks nothing. */
  --canvas:  #FFFFFF;   /* page background */
  --card:    #FFFFFF;   /* card / panel surface (same as canvas; a line separates it) */
  --float:   #FFFFFF;   /* TRANSIENT overlay — menu, modal, toast. Same as card on light
                           (white is the ceiling), a third step up on dark.
                           Test: does it appear and then go away? Then it floats. Page
                           furniture does NOT — the form bar was tried here and moved back
                           to --card. Cross-check: everything on this tier casts a shadow;
                           everything on --card casts none. */
  --ink:     #142549;   /* primary text — Boonrawd navy (the fixed brand anchor) */
  --muted:   #61697A;   /* secondary text — slate; AA 5.51:1 on white */

  /* Neutral ramp (OKLCH navy hue, lightness-stepped). muted=text · rule=border · divider=row line */
  --rule:    #CBD1DF;   /* borders — card edge, header underline */
  --divider: #EDEFF1;   /* faint row hairline / control track — NEAR-NEUTRAL gray (split from accent-tint so structure ≠ selection).
                           AS A FILL, small elements ONLY: disabled control, .meter and .seg tracks, .st-pill--none,
                           and the thin .tbl__group header row. NEVER a content panel — see the flat-white rule above.
                           NOT A HOVER — that is --ctl-raise-hover below, and the split is load-bearing on dark: a LINE
                           has to sink into its surface, a PRESSABLE has to rise off it. Opposite directions, so one
                           value cannot do both. (Five components used this as their hover fill until 2026-07-30.) */

  /* Accent — the ONE meaning color: actions, links, selection, meter. Never decoration (≤10%) */
  --accent:      #4068CA;   /* TEXT + decorative fill: links, selection, meter bar, info dot. LIFTS on dark. */
  --accent-tint: #E9F1FC;   /* selection + sidebar hover fill — soft blue, distinct from the neutral divider (which draws lines/tracks) */

  /* FILLS DON'T INVERT — a fill carries its own contrast, so it never moves.
     --accent and --neg-strong each do two jobs: coloured TEXT on the page, and a
     coloured FILL with text on top. Dark moves those in OPPOSITE directions —
     text must lighten, a fill must stay dark enough to hold white text. Lifting
     one token for the text job silently wrecks the fill job: it turned the
     destructive button into a pale salmon slab with dark text, the only inverted
     element on the screen and LOUDER than the primary (6.73:1 vs 5.18:1 — white
     on --accent-fill #4068CA measures 5.18, not the 5.57 first written here).
     Destroy was out-shouting save. Found by rendering, 2026-07-29 (CCD-174).
     These three are identical in both modes ON PURPOSE — that is the rule, stated
     as a value. Decorative fills with nothing on top (.meter b, .toast--info dot)
     keep the LIFTED --accent instead: more visible is strictly better there. */
  --accent-fill: #4068CA;   /* .btn--primary, selected .sbtnN — white text sits on this */
  --neg-fill:    #C4372C;   /* .btn--danger — the accessible red, never the --neg dot red */
  --on-fill:     #FFFFFF;   /* text on any of the above. Replaced 4 hardcoded #FFFFFF. */

  /* CONTROL SURFACES — surfaces take a solid step, everything else takes a FILM.
     A surface (--card, --float) must be opaque: a modal has to hide what is behind
     it. A control cannot use a fixed step, because it appears at many depths — a
     button sits on a card, in a modal, on a toolbar, and one absolute value is
     correct at exactly one of them. Found the hard way: a --float secondary button
     inside a --float modal was LITERALLY the same colour.
     So on dark these are translucent and land one step off whatever they sit on
     (verified: secondary lifts 1.23:1 on a card, 1.24:1 in a modal). Direction
     follows light falling from above — a field you type into recesses, a button
     you press rises. On light all three are today's values, unchanged. */
  --ctl-face:  #FFFFFF;   /* face of a control you type in or press — .control, .sbtnN */
  --ctl-track: #EDEFF1;   /* the trough a control rides in — .seg. (= --divider on light;
                             they must split on dark, where a hairline has to be LIGHTER
                             than the card and a trough has to be DARKER.) */
  --ctl-raise: #FFFFFF;   /* pressable sitting above its surface — .btn--secondary */
  /* Hover face for anything on --ctl-raise (and for the transparent pressables:
     .btn--ghost, .modal__close, .toast__close). On light this is the same value
     --divider already was, so light renders byte-identical. On dark it MUST be a
     stronger film than --ctl-raise, not a solid, because hover means "closer to
     you" and a film is the only thing that composites correctly on --card AND
     --float. Measured with a real cursor 2026-07-30: dark .btn--secondary on a
     card rested at 1.23:1 above the card and hovered to 1.066:1 — flatter than
     the 1.14:1 that --dk-float already rejected as "read as flush", so the hover
     state was very nearly invisible, and .modal__close hovered DARKER than the
     modal (a hole, not a hover). With this token the lift is ~1.49-1.52:1 on all
     three surfaces, because a film steps off whatever is under it. */
  --ctl-raise-hover: #EDEFF1;
  /* The select chevron, as a TOKEN and not a literal in forms.css. A colour baked
     into a data-URI cannot follow a var(), so the old hardcoded #61697A stroke
     stayed light-grey on a dark field — 2.64:1 over a dark modal, against 5.95:1
     for the mapped --muted it was copied from (Codex audit 2026-07-30). Holding
     the whole URL as a token means dark needs no component-level override: it maps
     through the same two blocks and the same print reset as every other value, so
     the "dark lives only in tokens.css" property survives. The stroke here must
     stay in step with --muted / --dk-muted by hand — %23 is a URL-escaped '#'. */
  --ctl-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2361697A' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --scrim: rgba(20, 37, 73, .32);   /* modal backdrop. Must differ by mode: this navy wash
                                       is tuned to dim WHITE and barely touches a dark page,
                                       so the modal stops reading as blocking. */

  /* Status — macOS traffic-light trio (owner pick 2026-07-01). Type-1 = dot + ink text.
     Bright hues are fine as DOTS; they'd fail AA as text, so keep dot-only (darken for any pill). */
  --pos: #27C93F;   /* done / positive — terminal green */
  --cau: #FFBD2E;   /* in-progress / caution — terminal yellow */
  --neg: #FF5F56;   /* overdue / error — terminal red (DOT-ONLY; fails AA as text/fill) */
  --neg-strong: #C4372C;   /* accessible red for TEXT/FILLS — danger button, delta down, error pill (5.34:1 white) */

  /* Delta / trend colors — the accessible "darken for text/pill" tier of the status hues.
     Bright --pos/--neg stay dot-only; these carry meaning as small colored text + tinted pills. */
  --pos-strong: #1A7F37;   /* accessible green text — 5.08:1 on white, 4.52:1 on --pos-tint */
  --pos-tint:   #E7F5EC;   /* green trend-pill / status-pill background */
  --neg-tint:   #FBE9E7;   /* red trend-pill / status-pill background (pairs with --neg-strong) */
  /* CAUTION — the one tier that does NOT hold a single hue, and deliberately.
     The dot stays yellow at 41°; strong and tint moved to orange at ~28°.
     Why the split: at 41° every value dark enough to read is gold or olive —
     dark yellow is physically mud, the same wall the dark-mode tints hit. And
     the dot cannot follow the strong into orange. Measured in CIEDE2000 against
     the --neg dot #FF5F56: the yellow dot as shipped is ΔE00 38.6 normal / 14.6
     deuteranope, and an orange dot at the strong's hue (#CF741E) collapses to
     21.7 / 6.6. (This line previously read "ΔE 22 to 15, colour-blind 15.2 to
     10.1" while naming neither the metric nor the rejected orange, so nobody
     could re-derive it — Codex flagged exactly that. Recomputed and restated
     2026-07-30; the conclusion held, the numbers were from an unrecorded
     metric.) Confusing
     "caution" with "overdue" is the one mistake this palette exists to prevent.
     It costs nothing because the two never meet — the dot lives in pills, whose
     text is ink; the strong lives in glyphs and marks. (Owner call 2026-07-30,
     picked by eye from a 42° / 43° / 29° render; pos already runs a 12° spread.)
     GLYPHS AND MARKS ONLY — 3.40:1 clears the 3:1 icon bar, NOT the 4.5:1 text
     bar. Never set a paragraph in it. Holding it to 4.5:1 is exactly what
     dragged the old #8A6100 to olive. */
  --cau-strong: #CF741E;   /* warning glyph, caution mark — 3.40:1 white, 3.00:1 on its tint */
  --cau-tint:   #FDEEE2;   /* amber status-pill background (pill text is ink + dot, not amber).
                              Same lightness step as the cream it replaced (1.13:1 off white),
                              so the tint family keeps its even spacing — only the hue moved. */

  /* ── CHART · CATEGORICAL — ONE OF FOUR JOBS, not "the chart colours" ──
     This set answers exactly one question: "WHICH ONE is this" — Ubon vs Pathum,
     HR vs Ops. Identity, where the colour means nothing beyond "a different one".
     It is the wrong tool for the other three colour jobs, and reaching for it by
     default is the mistake this comment exists to stop:
       sequential (a magnitude — headcount, spend, % done) wants ONE hue running
         light to dark, because the size of the number lives in the lightness;
       diverging (a polarity — agree/disagree, over/under target, YoY) wants two
         hues off a neutral middle. LIKERT IS ALWAYS DIVERGING. Painting nine
         competencies --chart-1..7 throws away the only thing rating data has,
         which is its order, and no legend gives that back;
       status (done / late / at risk) is already owned above — never a chart hue.
     Neither ramp is built. The first real chart sizes them; do not invent values
     in a mock. Full rule with the worked cases: PRODUCT.md composition rule 6.

     WITHIN this categorical job, INDEX ORDER IS THE API — series 1 takes
     --chart-1, series 2 --chart-2. That part is not a taste decision: pick a hue
     per chart and the same plant is blue on one card and green on the next, so the
     reader re-learns every chart on the page. --chart-7 grey is Other/Unassigned
     and never a real series.

     MOST CHARTS WANT ONE HUE, NOT SEVEN. Subject in --chart-1, everything else in
     --chart-7 grey. A dashboard chart usually has a subject and context, not seven
     equal competitors; reach for the full set only when every series is genuinely
     being compared. (Owner call 2026-07-30, correcting an earlier version of this
     comment that stated index-order as a universal law for all charts.)

     LABEL THE MARKS — and a legend is the FALLBACK, not the licence. It makes the
     reader look away to decode and back again, so put the value on the mark where
     it fits. Against WHITE, four of the seven sit under the 3:1 fill bar: yellow
     1.51:1, grey 1.68:1, teal 2.24:1, orange 2.29:1. Accepted, because a chart is
     not read by colour alone — but it is why the label is mandatory rather than
     nice, and why rule 2 below exists.

     One set for BOTH modes, no --dk- partners: verified 2026-07-30, all seven clear
     3:1 on dark canvas AND dark card — it is only white they struggle against.
     Chosen over Tableau 20, which was two-tone by construction and would have
     needed a mode swap this does not.

     ── ALL SEVEN VALUES ARE FROZEN. THE COLLISIONS ARE FIXED BY RULE, NOT BY HEX ──
     Owner call 2026-07-30, made after seeing three replacements rendered side by side
     (v2/chart-fix-explore.html) and rejecting them: the palette stays as picked. So
     the three collisions the Codex audit measured are real, known, and STILL HERE:
       --chart-1  ΔE00 4.3 from the DARK --accent. On a dark page series one is the
                  selection blue — and --accent also paints .meter, itself a data mark.
       --chart-6  ΔE00 8.1 from the --cau dot, and 1.51:1 on white.
       --chart-7  1.68:1 on white.
       (--chart-4 is also 9.1 from the dark --cau-strong.) Under ΔE00 ~10, two colours
       are the same colour to anyone; under 3:1 a fill is a shape you cannot locate.
     Three rules carry that weight instead. They are not softeners — a chart obeying
     them is genuinely unambiguous, and a chart breaking them is broken whatever its
     hexes are:

       1. COLOUR IS NEVER THE ONLY CHANNEL. Position identifies a bar, its own path
          identifies a line, and the direct label says which is which; colour is the
          lookup aid layered on top. Test: would the chart still be readable printed
          greyscale? If no, it is already wrong, collisions or not. This is why the
          hexes CAN be frozen — a palette only has to carry identity alone when
          nothing else does, and here something else always does.
       2. A FILL UNDER 3:1 ON ITS GROUND TAKES A HAIRLINE, NOT A DARKER FILL. On
          white, series 6 (1.51:1) and series 7 (1.68:1) are bars you cannot find.
          Give the shape a 1px stroke of its own hue, darkened; the fill keeps the
          colour that was chosen. Never darken the fill itself — for yellow that is
          the olive trap --cau-strong already fell into.
       3. A STATUS MARK AND A CHART MARK DO NOT SHARE A CARD. This is the rule that
          actually neutralises collisions 1 and 6: a card boundary means the two are
          never seen side by side, so a 4.3 or an 8.1 never gets compared. In one card
          on a dark page, both mislead — and no legend fixes it, because the reader
          is not confusing two series, they are confusing a series with the UI.
       And the one-hue default below does most of the work on its own: with a subject
       in --chart-1 and context in --chart-7, five of the seven never appear at all.
     Re-open the values only if a chart ever needs all seven AND has to sit beside
     status marks. That combination is what the frozen palette cannot survive.

     Colour-blind separation was measured across protanopia/deuteranopia/tritanopia
     and deliberately NOT treated as a constraint (owner call: known internal
     audience, worst-case dichromacy). If a chart ever ships to a wide outside
     audience, re-open it — 1↔5 and 3↔7 were the failing pairs.
     Renders: v2/chart-fix-explore.html (this decision), v2/chart-palette-explore.html
     (the original palette-vs-Tableau pick). */
  --chart-1: #5B8DEF;   /* blue    — series 1 */
  --chart-2: #E8637F;   /* rose    — series 2 */
  --chart-3: #5CBDB4;   /* teal    — series 3 */
  --chart-4: #E89B3C;   /* orange  — series 4 */
  --chart-5: #9B51F0;   /* violet  — series 5 */
  --chart-6: #F4CF4A;   /* yellow  — series 6 */
  --chart-7: #C6C8CC;   /* grey    — Other / Unassigned, never a real series */

  /* ── TYPE ──────────────────────────────────────────────── */
  --font-sans: "Inter", "Noto Sans Thai", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* 5 sizes. Body 14 is the workhorse; 12 used sparingly (Thai floor is 14-gray). */
  --fs-kpi:     30px;   /* hero number — the ONLY 700 */
  --fs-title:   20px;   /* page title — once per page */
  --fs-section: 17px;   /* section header */
  --fs-body:    14px;   /* body text AND data — the workhorse */
  --fs-caption: 12px;   /* rare meta only; default small text up to 14-gray */

  /* 3 weights */
  --fw-regular: 400;    /* body + all small text */
  --fw-semi:    600;    /* headings, emphasis, interactive, label column */
  --fw-bold:    700;    /* KPI hero number ONLY */

  --lh-tight:   1.1;    /* KPI number */
  --lh-snug:    1.3;    /* headings */
  --lh-normal:  1.6;    /* body (Thai needs the leading) */

  /* ── SPACING (4px scale) ───────────────────────────────── */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;

  /* Density defaults (semantic — tune here, applies app-wide). Balanced: Thai breathes, still dense. */
  --card-pad: var(--s-4);   /* 16 */
  --card-gap: var(--s-3);   /* 12 */
  --row-y:    9px;          /* table row vertical padding */

  /* CONTROL HEIGHT — the one number every control on a shared line reads.
     .btn · .control · select.control · .menu-btn · .menu-btn--kebab · .seg
     all set `height: var(--ctl-h)` and zero their vertical padding. Height is
     set OUTRIGHT, never derived from padding: an <input> refuses to shrink
     below its font's natural line box (~17px at 14px Inter), so a padding-based
     rule silently leaves fields taller than the buttons beside them — which is
     exactly how v2 drifted to five different control heights.
     --ctl-h-sm is the ONE exception and has ONE job: a compact table row, where
     a control must stay near the 22.4px line of text it rides. .btn--sm and
     .menu-btn--sm live here. Nothing else may use it.
     Out of scope on purpose: .tabs and the sidebar own their own line (nav.css
     forbids tabs and seg sharing a shape), .menu__item lives inside the pane,
     and .st / .check are already 22.4px — exactly the text line. */
  --ctl-h:    34px;         /* bar family — button, field, trigger, seg.
                               34, not 30 (owner call 2026-07-29, picked by eye from a
                               30/32/34/36 render): at 30 a field reads as a slot, not
                               something you type a sentence into. 34 is also the height
                               .btn already had before v2.1.9, so buttons across every
                               consumer stay pixel-identical — this release moves fields
                               (42.4 → 34) and nothing else. */
  --ctl-h-sm: 24px;         /* compact table rows only */

  /* ── RADIUS ────────────────────────────────────────────── */
  /* Base and large move TOGETHER (owner call 2026-07-29, picked by eye at 2.5×
     against the new 34px control height). 6 → 8 softens the signature without
     crossing into consumer-SaaS round (10 was tried and rejected: the primary
     button starts reading friendly, which is on PRODUCT.md's anti-references).
     --r-large follows to 12 to keep the gap — at base 8 / large 10 a card and a
     modal read as the same shape, and "this floats above you" stops landing. */
  --r-tiny:  3px;      /* micro tags <12px (rare) */
  --r-base:  8px;      /* card, button, input, swatch — the signature */
  --r-large: 12px;     /* modal, large overlay — must stay clear of --r-base */
  --r-pill:  999px;    /* status chips, round badges */

  /* ── SHADOW ────────────────────────────────────────────── */
  /* Flat system: cards/containers get NO shadow (a line separates them).
     Shadow is reserved for things that FLOAT above the page. */
  --shadow-none:  none;
  --shadow-float: 0 2px 8px rgba(20, 37, 73, 0.06);    /* floating shell sidebar — tight lift on white (pairs with a faint divider edge) */
  --shadow-pop:   0 4px 12px rgba(20, 37, 73, 0.08);   /* dropdown, menu, popover, toast — softened (was .12) */
  --shadow-modal: 0 12px 32px rgba(20, 37, 73, 0.10);  /* modal / sheet — softened (was 16/40/.16) */

  /* ── FOCUS ─────────────────────────────────────────────── */
  /* No focus token, on purpose (owner call 2026-07-29): the only part that
     could ever change is the colour, and that is already --accent. What is
     left — "2px solid" — has not drifted once across 13 sites in 7 files.
     And there is no single look to store, because there are TWO:

       PRESSABLE — .btn .menu-btn .tab .seg__opt .menu__item .shell__*
         outline: 2px solid var(--accent).
       FIELD — .control only
         no visible outline; its own border goes accent + inset 1px shadow,
         reading as a crisp 2px with no halo and no layout shift (forms.css).
         A ring around a field draws a second box around the box it already
         has — two frames with a gap, which reads as a rendering fault.

     OFFSET is the part that gets picked wrong, so:
       +2px  free-standing on a surface — the default, and the only choice
             you should need to think about.
       -2px  a row in a STACKED list: .menu__item, .shell__item,
             .shell__user, .shell__collapse. List rows touch each other, so a
             ring drawn outside lands on the rows above and below instead of
             framing its own. (It is NOT about clipping — .shell__nav has
             8px of padding and a 2px ring would fit fine.)
       +1px  .seg__opt only — it sits in a 3px trough; +2 touches the wall. */

  /* ── Z-INDEX SCALE ─────────────────────────────────────────
     Four tiers, and every component must take one. Three components used to
     hardcode 10, 1 and 20 while PRODUCT.md claimed this scale was the single
     source — which meant the scale was decoration and the real order lived in
     three unrelated magic numbers (Codex audit 2026-07-30). Two of those were
     both "sticky page furniture" wearing different values for no reason.
     --z-modal exists but the DS's own .modal does NOT use it, and that is
     correct, not dead: a native <dialog> is promoted to the browser's TOP LAYER,
     which sits above every z-index on the page and ignores the property entirely.
     The token stays for a consumer's own non-dialog overlay. Don't "adopt" it in
     modals.css to tidy the books — it would do nothing there. */
  --z-sticky:   50;    /* sticky page furniture — .fbar, .shell__head */
  --z-drawer:   75;    /* an overlay PANEL of page furniture — the mobile sidebar.
                          Cannot share --z-sticky: it has to cover the sticky head
                          it slides over. Below dropdown so a menu opened INSIDE
                          the drawer still lands on top. */
  --z-dropdown: 100;
  --z-modal:    200;
  --z-toast:    300;
}

/* ============================================================
   DARK — the second generation of the SAME system (CCD-174, 2026-07-29)
   ============================================================
   THREE MODES, all opt-in by attribute on <html>:
       data-theme="light"    forced light (or just omit the attribute)
       data-theme="dark"     forced dark
       data-theme="system"   follows the OS setting, live

   Nothing anywhere changes until an app sets the attribute — the same safety
   property as the v1/v2 folder split. An app that never opts in cannot break.
   Note "system" is opt-in too: a bare prefers-color-scheme rule would have
   flipped every existing v2 app the moment a user's laptop was in dark mode.

   WHO GETS DARK: signed-in, registered users only. An outward-facing page —
   anything an unregistered respondent opens from a token link — stays light and
   must not set this attribute. That is not a restriction bolted on afterwards;
   it falls out of where the switch lives. The control is an account preference
   in the sidebar user menu, so a page with no user has no menu and therefore no
   way to turn dark on. PRODUCT.md composition rule 2 (owner call 2026-07-30).

   "System" NEEDS NO JAVASCRIPT. That was not obvious — the plan carried three
   options for a theme script, all of which assumed JS had to read the OS
   setting. CSS reads it directly, so the DS stays CSS-only as intended, there
   is no first-paint flash to defend against, and the OS switching at dusk
   updates a page that is already open. All an app has to do is remember the
   user's CHOICE (a cookie, rendered server-side) — never resolve it.

   Grey, not navy: the brand navy was tried as the canvas and rejected — it
   tinted the status hues and muddied the accent. Grey lets them read true.
   Not black either: #191A1D sits 1.21:1 off true black, so the surfaces above
   it have somewhere to go.

   Every value below was picked by RENDERING, not derived on paper. Two of the
   three rules that shape it (fills don't invert; surfaces solid / controls film)
   exist because the arithmetic said a screen was fine and the eye said it wasn't.

   What is NOT here, on purpose: --accent-fill, --neg-fill, --on-fill,
   --card-pad, type, spacing, radius. A token absent from this block is a token
   that does not change, and that is a claim worth being able to read at a glance.
   ============================================================ */
/* ── The dark VALUES, defined once ────────────────────────────────────
   Inert on their own — they do nothing until one of the two mapping blocks
   below points a real token at them. They live here, and only here, so that
   changing a dark colour is a one-line edit that cannot half-apply. */
:root {
  /* Surfaces — three real lightness steps. canvas -> card -> float. */
  --dk-canvas:  #191A1D;
  --dk-card:    #212327;
  --dk-float:   #32353C;      /* 1.28:1 off card. 1.14 was tried and read as flush. */

  /* Text — never pure #FFF: it halates on dark and thins Thai strokes. */
  --dk-ink:     #E9EAEC;      /* 14.5:1 on canvas */
  --dk-muted:   #A2A6AD;      /* 7.12:1 */

  /* Lines. NOTE the inversion vs light: --rule is now LIGHTER than --divider,
     because on dark a border has to climb off the surface, not sink into it. */
  --dk-rule:    #383B41;
  --dk-divider: #26282C;

  /* Accent lifts — it is text as often as it is a fill. The FILL stays put; see
     --accent-fill above, which is the same value in both modes on purpose. */
  --dk-accent:      #6E9BF0;  /* 5.70:1 on card, 6.31:1 on canvas (recomputed 2026-07-30 —
                                 the comment said 5.57, which was the light primary's number) */
  --dk-accent-tint: #222D3A;  /* selection / sidebar hover — blue held back */

  /* Dots (--pos/--cau/--neg) keep their EXACT light hue and so are absent here:
     designed for white, they already read on dark, and moving them would break
     the one thing that ties the two modes together. */

  /* The "-strong" TEXT tier inverts: darkened on white, lightened here. */
  --dk-pos-strong: #5FD787;
  --dk-cau-strong: #FFB978;   /* orange, matching the light tier's 28-29 deg */
  --dk-neg-strong: #FF8A80;

  /* TINTS ARE A VEIL, NOT A PAINT — the bright dot hue at low alpha over
     whatever is beneath. Darkening the light tints produced mud: yellow at low
     lightness IS the colour of mud, and there is no solid dark amber that reads
     as amber. status.css:30 already names this problem and dodges it on light by
     keeping pill TEXT ink; it could not dodge the fill. A veil also composites
     correctly on --card AND --float, which no single solid value can.
     Consequence, and it is a real one: on dark a tint CANNOT carry meaning
     alone. The status dot is mandatory, not decorative — PRODUCT.md rule 1. */
  --dk-pos-tint: rgba(39,201,63,.14);
  --dk-cau-tint: rgba(255,170,90,.14);
  --dk-neg-tint: rgba(255,95,86,.14);

  /* Controls — the film. Recess what you type in, raise what you press. */
  --dk-ctl-face:  rgba(0,0,0,.22);
  --dk-ctl-track: rgba(0,0,0,.22);
  --dk-ctl-raise: rgba(255,255,255,.07);
  --dk-ctl-raise-hover: rgba(255,255,255,.13);   /* ~1.49-1.52:1 off canvas / card / float */
  --dk-ctl-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23A2A6AD' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --dk-scrim: rgba(0,0,0,.66);

  /* Navy-alpha shadows are invisible on dark. Black, and heavier. */
  --dk-shadow-float: 0 2px 10px rgba(0,0,0,.45);
  --dk-shadow-pop:   0 6px 18px rgba(0,0,0,.55);
  --dk-shadow-modal: 0 16px 40px rgba(0,0,0,.60);
}

/* ── The two mapping blocks ───────────────────────────────────────────
   These MUST stay identical. They exist twice for one reason: an OS colour
   preference is a fact only the browser knows, so it can only be tested by a
   media query — and a media query cannot be added to an existing selector.
   Because both blocks are pure `--token: var(--dk-token)` lines with no values
   of their own, a drift between them is a visible diff, not a wrong colour.

   BOTH ARE SCOPED TO :root, AND THAT IS LOAD-BEARING. Written bare as
   `[data-theme="dark"]` these matched ANY element carrying the attribute, so a
   control whose own markup used data-theme as its click target re-themed ITSELF
   — the showroom's theme switch had a "Dark" button painting its label in
   --dk-ink, near-white, on a light track, which read as disabled. Nothing about
   the component was wrong. The attribute is a MODE DECLARATION for the document,
   never a data slot: if a widget needs to remember which mode a button selects,
   it uses its own name (the switch now uses data-mode). :root makes that a rule
   the CSS enforces instead of a convention every consumer has to know. There is
   no subtree theming to lose — a dark island on a light page was never a use
   case, since the mode is one account preference for the whole app. */
:root[data-theme="dark"] {
  color-scheme: dark;        /* native scrollbars, form controls, spellcheck underlines */
  --canvas: var(--dk-canvas);   --card: var(--dk-card);     --float: var(--dk-float);
  --ink: var(--dk-ink);         --muted: var(--dk-muted);
  --rule: var(--dk-rule);       --divider: var(--dk-divider);
  --accent: var(--dk-accent);   --accent-tint: var(--dk-accent-tint);
  --pos-strong: var(--dk-pos-strong);
  --cau-strong: var(--dk-cau-strong);
  --neg-strong: var(--dk-neg-strong);
  --pos-tint: var(--dk-pos-tint);
  --cau-tint: var(--dk-cau-tint);
  --neg-tint: var(--dk-neg-tint);
  --ctl-face: var(--dk-ctl-face);
  --ctl-track: var(--dk-ctl-track);
  --ctl-raise: var(--dk-ctl-raise);
  --ctl-raise-hover: var(--dk-ctl-raise-hover);
  --ctl-chevron: var(--dk-ctl-chevron);
  --scrim: var(--dk-scrim);
  --shadow-float: var(--dk-shadow-float);
  --shadow-pop:   var(--dk-shadow-pop);
  --shadow-modal: var(--dk-shadow-modal);
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="system"] {
    color-scheme: dark;
    --canvas: var(--dk-canvas);   --card: var(--dk-card);     --float: var(--dk-float);
    --ink: var(--dk-ink);         --muted: var(--dk-muted);
    --rule: var(--dk-rule);       --divider: var(--dk-divider);
    --accent: var(--dk-accent);   --accent-tint: var(--dk-accent-tint);
    --pos-strong: var(--dk-pos-strong);
    --cau-strong: var(--dk-cau-strong);
    --neg-strong: var(--dk-neg-strong);
    --pos-tint: var(--dk-pos-tint);
    --cau-tint: var(--dk-cau-tint);
    --neg-tint: var(--dk-neg-tint);
    --ctl-face: var(--dk-ctl-face);
    --ctl-track: var(--dk-ctl-track);
    --ctl-raise: var(--dk-ctl-raise);
    --ctl-raise-hover: var(--dk-ctl-raise-hover);
    --ctl-chevron: var(--dk-ctl-chevron);
    --scrim: var(--dk-scrim);
    --shadow-float: var(--dk-shadow-float);
    --shadow-pop:   var(--dk-shadow-pop);
    --shadow-modal: var(--dk-shadow-modal);
  }
}

/* PRINT — --ink is near-white and would print invisible. A page printed from a
   dark app must come out as the light app.
   This works by resetting the dark VALUES rather than the mapped tokens, so one
   block covers forced-dark and system-dark alike, and it cannot go stale when a
   new token is added to the mapping. */
@media print {
  :root {
    --dk-canvas: #FFFFFF; --dk-card: #FFFFFF; --dk-float: #FFFFFF;
    --dk-ink: #142549; --dk-muted: #61697A;
    --dk-rule: #CBD1DF; --dk-divider: #EDEFF1;
    --dk-accent: #4068CA; --dk-accent-tint: #E9F1FC;
    --dk-pos-strong: #1A7F37; --dk-cau-strong: #CF741E; --dk-neg-strong: #C4372C;
    --dk-pos-tint: #E7F5EC; --dk-cau-tint: #FDEEE2; --dk-neg-tint: #FBE9E7;
    --dk-ctl-face: #FFFFFF; --dk-ctl-track: #EDEFF1; --dk-ctl-raise: #FFFFFF;
    --dk-ctl-raise-hover: #EDEFF1;
    --dk-ctl-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2361697A' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --dk-scrim: rgba(20,37,73,.32);
    --dk-shadow-float: 0 2px 8px rgba(20,37,73,.06);
    --dk-shadow-pop:   0 4px 12px rgba(20,37,73,.08);
    --dk-shadow-modal: 0 12px 32px rgba(20,37,73,.10);
  }
  /* color-scheme is a PROPERTY, not a --dk-* value, so resetting the values above
     does not reach it — the two mapping blocks set `color-scheme: dark` directly
     and it survived into print. Result: the authored colours printed light while
     the browser still painted native controls, scrollbars and spellcheck dark, so
     a printed select was a dark slab in a white page (Codex audit 2026-07-30).
     THE SELECTOR LIST IS DELIBERATE. `:root` alone loses — it is specificity
     (0,1,0) against the (0,2,0) of `:root[data-theme="dark"]`. Matching those
     selectors exactly ties the specificity, and this block is later in the file,
     so it wins. Keep this list in step if a fourth mode is ever added. */
  :root,
  :root[data-theme="dark"],
  :root[data-theme="system"] {
    color-scheme: light;
  }
}
