/* ---------------------------------------------------------------------
   Design tokens: one source of truth for colour, type, space and geometry.

   The rule this file exists to enforce: the same hue means the same thing on
   every screen. A chart picks a token by MEANING (categorical slot, sequential
   ramp step, single-series bar, track) and never by eye.

   Three theme blocks, always kept in step:
     :root                                        light
     @media (prefers-color-scheme:dark) :root:not([data-theme="light"])
     :root[data-theme="dark"]                     manual toggle
   Any token that differs in dark MUST appear in BOTH dark blocks, or the
   manual toggle silently disagrees with the OS setting.
--------------------------------------------------------------------- */
:root{
  /* @palette-start ----------------------------------------------------
     U.S. Chamber of Commerce brand palette, Guidelines V3.1.

     This is the REFERENCE PALETTE: five hue families x six tiers, plus greys.
     Every colour on this dashboard resolves to one of these swatches and no
     colour outside this block may be invented. Swatches are kept complete on
     purpose even where nothing currently points at one - this is the legal
     source list, not a usage list, so a designer can check a value against
     the PDF by name. Names follow the guide's own tiers (Optimistic / Bright
     / Bold / Insightful / Impactful / Neutral).

     --ink is the one addition: a darkened Impactful Blue kept for body text
     and dark surfaces, because Impactful Blue itself is in use as an accent.

     All six digits, always: mapStops() in charts.js reads the sequential ramp
     back out through getComputedStyle and accepts only /^#[0-9a-f]{6}$/i, so a
     three-digit #fff anywhere in that chain would silently kill the tile map.
  ------------------------------------------------------------------- */
  --blue-opt:#D8F2FF;   --blue-bright:#85E7FF; --blue-bold:#005FFF;
  --blue-ins:#004F91;   --blue-imp:#002452;    --blue-neutral:#99A5BE;
  --red-opt:#FFEBFA;    --red-bright:#FFA7B3;  --red-bold:#CE0000;
  --red-ins:#9A2136;    --red-imp:#450012;     --red-neutral:#B599A0;
  --green-opt:#E3FFE3;  --green-bright:#87EF00; --green-bold:#009F4C;
  --green-ins:#42614F;  --green-imp:#13342B;   --green-neutral:#A1AEAA;
  --yellow-opt:#FFFAD1; --yellow-bright:#FFE300; --orange-bold:#FF8300;
  --orange-ins:#9A4018; --orange-imp:#511803;  --orange-neutral:#C5AFAD;
  --purple-opt:#F2EBFF; --purple-bright:#D6B9FF; --purple-bold:#9866FF;
  --purple-ins:#623E8A; --purple-imp:#330066;  --purple-neutral:#B39DC3;
  --ink:#0A152B;
  --white:#ffffff; --grey-01:#f5f5f5; --grey-02:#666666; --grey-50:#E8EBEF; --black:#000000;
  /* @palette-end */

  /* Legacy aliases: older rules address the same swatches by number. */
  --blue-01: var(--ink); --blue-02: var(--blue-imp); --blue-03: var(--blue-ins);
  --blue-04: var(--blue-bold); --blue-05: var(--blue-bright);
  --red-02: var(--red-ins); --red-03: var(--red-bold);
  --green-02: var(--green-bright); --purple-01: var(--purple-imp); --yellow-01: var(--yellow-bright);

  /* Ink alpha ramp - hairlines, washes and scrims over a light ground. */
  --blue-01-06: rgba(10,21,43,.06); --blue-01-10: rgba(10,21,43,.10);
  --blue-01-20: rgba(10,21,43,.20); --blue-01-50: rgba(10,21,43,.5);

  /* ---------------- typography ---------------- */
  --font-sans:"GT America",Helvetica,Arial,"Lucida Grande",sans-serif;
  --font-serif:"Tobias",Georgia,"Times New Roman",serif;
  --font-mono:"GT America Mono","IBM Plex Mono",ui-monospace,monospace;
  /* Short aliases: referenced by name from charts.js and render.js. */
  --sans: var(--font-sans); --serif: var(--font-serif); --mono: var(--font-mono);
  --feat: "kern","liga","ss01";

  /* Type scale. See @reserved below: the rules that will consume these live
     in base.css / layout.css, which belong to another step of this refactor.
     Values are the sizes those rules already use, so adopting a token is a
     no-op visually and the scale is a description of the design, not a wish. */
  --font-size-eyebrow:11px;   /* mono, uppercase, letterspaced   .eyebrow   */
  --font-size-h2:22px;        /* section titles                  .section-title */
  --font-size-h3:14.5px;      /* card titles                     .viz-card h3 */
  --font-size-body:13.5px;    /* controls and running text                  */
  --font-size-caption:12px;   /* hints, legends, secondary numbers          */

  /* ---------------- space & radius ---------------- */
  --space-1:4px; --space-2:8px; --space-3:12px;
  --space-4:16px; --space-5:20px; --space-6:24px;
  --radius-sm:6px; --radius-md:10px; --radius-pill:999px;
  --card-padding: var(--space-5) var(--space-6);
  /* Height of the shared top header bar (header.css) - also read by
     layout.css and responsive.css to offset the sidebar/mobile bar below it. */
  --app-header-h:52px;

  /* ---------------- chart geometry ----------------
     Mirrored as exported constants in js/tokens.js, because donutSVG() does
     the arc maths in JavaScript and cannot read CSS. The two files must state
     the same numbers; tests/test_tokens.py asserts they do. */
  --donut-diameter:176px;  --donut-stroke:28px;
  --bar-height:12px;       --bar-gap: var(--space-2);
  --bar-radius: var(--radius-sm);
  /* These stay a literal 180/160px HERE - tests/test_tokens.py's
     GeometryMirror checks this exact declaration against js/tokens.js
     BAR.labelColumn/valueColumn, byte for byte. The Step 6 responsive
     column width (clamp(120px,28%,220px)) is layered on top as a scoped
     override of this same custom property on .bar-list in charts.css,
     which does not touch this declaration and is invisible to that test -
     see the comment there. */
  --bar-label-col:180px;   --bar-value-col:160px;

  /* @reserved-start
     Defined earlier, consumed in this step except for the groups named
     below. tests/test_tokens.py requires this list to be exactly the set of
     defined-but-unreferenced tokens outside the reference palette - so
     wiring one up means deleting it from this list, and adding a token
     without a consumer means declaring it here on purpose rather than
     letting it rot unnoticed.

     The sequential design-system aliases stay reserved:
     the ramp they name already has a consumer under its map-flavoured
     names, which the tile map's CSS and charts.js read directly, and the
     design-system-flavoured aliases are not yet read by anything, so they
     stay reserved rather than being wired to a second, unrelated use just
     to clear this list. The donut stroke width has no CSS consumer of its
     own: its one live use is the number donutSVG threads onto each circle's
     stroke-width attribute from JS (js/tokens.js DONUT.stroke), which
     tests/test_tokens.py's GeometryMirror checks stays in step with the
     token below without CSS ever needing to read it back. The donut
     diameter joins it as of Step 6: cards.css now sizes .donut-svg's outer
     box with clamp(140px,45cqw,200px) so the ring can shrink/grow with its
     card, but donutSVG() still draws its arcs against a fixed 176x176
     viewBox in JS (see DONUT.diameter) - CSS just no longer needs to read
     --donut-diameter back to size anything, the same way --donut-stroke
     already didn't.

       --seq-1 --seq-2 --seq-3 --seq-4 --seq-5
       --donut-stroke --donut-diameter
     @reserved-end

     The sequential aliases are a special case: they are the design-system name
     for the ramp the map tokens already carry, kept because "sequential" is
     the vocabulary the token spec speaks while the map naming is an
     implementation detail of charts.js, which reads those directly. */

  /* Motion: referenced as var(--ease,ease) from charts.css, which silently
     fell back to plain `ease` for as long as this went undefined. */
  --ease: cubic-bezier(.22,.61,.36,1);

  /* ---------------- surfaces & text ---------------- */
  --page-bg: var(--grey-01);
  --surface: var(--white);
  --surface-sunken: var(--grey-01);
  --text-primary: var(--blue-01);
  --text-secondary: var(--grey-02);
  /* Flips with the theme: for text on a fill that is itself theme-aware
     (--critical). NOT for the brand-blue chrome - see --on-brand. */
  --text-inverse: var(--white);
  --border: var(--blue-01-10);
  --border-strong: var(--blue-01-20);
  --accent: var(--blue-02);
  --accent-spark: var(--blue-05);

  /* Text and hairlines on the dark brand blue. That ground is var(--blue-01)
     in BOTH themes - footer, active chips, the Used pill - so these must NOT
     flip. Using --text-inverse here would paint navy on navy in dark mode. */
  --on-brand: var(--white);
  --on-brand-soft: rgba(255,255,255,.75);
  /* Was .45 - #footMeta (the only consumer, cards.css's footer.foot .meta)
     measured 4.48:1 against the footer's fixed --ink ground in BOTH themes
     (this ground never flips - see the --on-brand family comment above), a
     hair under the 4.5:1 AA floor for its 10.5px text. .55 clears it with
     margin (~6.1:1) while staying visibly fainter than --on-brand-soft. */
  --on-brand-faint: rgba(255,255,255,.55);
  --on-brand-border: rgba(255,255,255,.14);
  --paper: var(--white);              /* print ground - paper is white in both themes */

  /* ---------------- charts: one meaning per token ----------------
     color.series.primary - THE blue for every single-series bar chart, so a
     bar never has to decide its own colour. --chart-hue is the older name and
     is referenced from charts.js/tokens.js, so it stays as an alias. */
  --series-primary: var(--blue-04);
  --chart-hue: var(--series-primary);
  --track: var(--grey-01);            /* color.track - the one grey bar track */
  --chart-track: var(--track);
  --skeleton: var(--blue-01-06);      /* flat neutral at 6% of the foreground */

  /* color.sequential - five stops, the STATE TILE MAP ONLY.
     Ramping through five Chamber blues rather than interpolating two keeps the
     middle of the range a recognisable brand blue instead of the muddy midpoint
     a straight light-to-dark blend produces.
     --map-1..5 are the names charts.js reads; keep them. A var() chain is safe
     here (it resolves to literal hex in getComputedStyle, verified), but
     color-mix()/rgb() would NOT resolve and the ramp would silently fall back.
     --map-* is the canonical name (it is what charts.js reads, and each theme
     block sets it straight to a brand swatch so the chain stays one hop from
     a literal); --seq-* is the design-system alias and is declared once here,
     picking up whichever --map-* the active theme has installed. */
  --map-1: var(--blue-opt); --map-2: var(--blue-bright); --map-3: var(--blue-bold);
  --map-4: var(--blue-ins); --map-5: var(--blue-imp);
  --seq-1: var(--map-1); --seq-2: var(--map-2); --seq-3: var(--map-3);
  --seq-4: var(--map-4); --seq-5: var(--map-5);

  /* ---------------- color.categorical ----------------
     Ten ordered hues for donut/topic slices and trend lines ONLY. Never for a
     single-series bar (that is --series-primary) and never for the map.

     Chosen by measurement, not by eye: candidates restricted to the Chamber
     V3.1 palette, to a usable lightness band (L* 30-85 light / 40-95 dark) so
     the set reads as colours rather than near-black blobs, and to the five hue
     families. Scored by the WORST pairwise CIEDE2000 distance across normal,
     protan, deutan and tritan vision, then maximised exhaustively.

       light worst pair 6.0   dark worst pair 6.0   (both: Bold Blue / Bold
       Purple, under protanopia - the same limiting pair the previous 8 had)

     6.0 is the proven ceiling for ten colours drawn from this palette, so the
     set is optimal, not merely good. The marginal cost of each slot, over the
     pools actually used:  k=6 15.6 | k=7 13.2 | k=8 10.9 | k=9 7.0 | k=10 6.0.

     Slot N keeps its HUE FAMILY in both themes and only changes tier, so a
     topic stays "the purple one" when the theme flips; the light and dark sets
     were therefore optimised jointly rather than one after the other.

     SLOTS 9 AND 10 ARE DECORATIVE, NOT DISTINGUISHING. On white they fall
     under the 3:1 non-text contrast floor (2.30-2.46:1). That is deliberate
     and safe here: every slice carries a text label and a value in the legend,
     so colour is a lookup aid rather than the sole carrier of meaning. Order
     matters - the low-contrast hues are last so they land on the smallest
     slices. Do not reorder these to "tidy" them.

     A ninth topic is never a new hue; it folds into --cat-other. */
  --cat-1: var(--blue-bold);      /* Bold Blue           */
  --cat-2: var(--red-bold);       /* Bold Red            */
  --cat-3: var(--purple-ins);     /* Insightful Purple   */
  --cat-4: var(--green-ins);      /* Insightful Green    */
  --cat-5: var(--red-ins);        /* Insightful Red      */
  --cat-6: var(--green-bold);     /* Bold Green          */
  --cat-7: var(--purple-bold);    /* Bold Purple         */
  --cat-8: var(--orange-bold);    /* Bold Orange         */
  --cat-9: var(--purple-neutral); /* Neutral Purple  - decorative */
  --cat-10: var(--green-neutral); /* Neutral Green   - decorative */
  /* The residual, deliberately the one neutral in the set: "all other topics"
     must read as NOT one of the ten. Reserved - never a categorical slot. */
  --cat-other: var(--blue-neutral);

  /* ---------------- status & consent ----------------
     color.status.* - the story lifecycle. --seg-* are the names render.js
     uses, kept as aliases. */
  --status-new: var(--blue-05);
  --status-reviewed: var(--blue-04);
  --status-used: var(--blue-01);
  --seg-new: var(--status-new); --seg-reviewed: var(--status-reviewed); --seg-used: var(--status-used);

  /* color.consent.* - what the Chamber may do with a story. Referenced from
     js/tokens.js (CONSENT_STATES) so the legend, the donut and the matrix all
     read the same token. */
  --consent-cleared: var(--good);
  --consent-no-media: var(--series-primary);
  --consent-no-contact: var(--purple);
  /* Was the raw --grey-50 swatch, which had no dark override and so glared
     near-white on the dark navy surface. Now a token with a dark variant. */
  --consent-not-granted: var(--grey-50);

  /* ---------------- semantic accents ---------------- */
  --tag: var(--red-02);
  --critical: var(--red-03);
  --critical-wash: var(--red-opt);
  /* Insightful Green rather than Bold Green: this is 11px label text, and
     Bold Green only reaches 3.5:1 on white. */
  --good: var(--green-ins);
  --good-dot: var(--green-02);
  --warn-dot: var(--yellow-01);
  --purple: var(--purple-01);
  --mem-member: var(--blue-04);
  --mem-prospect: var(--blue-02);
  --mem-graced: var(--purple-01);
  --mem-lapsed: var(--grey-02);
  --amber-text: var(--orange-ins); --amber-border: var(--yellow-bright); --amber-wash: var(--yellow-opt);

  /* ---------------- elevation & overlay ---------------- */
  --shadow-card: 0 1px 2px rgba(10,21,43,.05), 0 6px 16px rgba(10,21,43,.06);
  --shadow-pop: 0 12px 32px rgba(10,21,43,.22);
  --shadow-dialog: 0 24px 64px rgba(10,21,43,.28);
  --shadow-drawer: 0 0 40px rgba(10,21,43,.18);
  --scrim: var(--blue-01-50);
  --scrim-strong: rgba(10,21,43,.62);
  /* Row hover. The neutral one was rgba(0,0,0,.03): invisible on a dark
     surface, so both are theme-aware now. */
  --row-hover: rgba(10,21,43,.04);
  --row-hover-accent: rgba(0,95,255,.04);

  /* A data: URI cannot take a var(), so the <select> caret's colour cannot be
     themed by reference - it has to be a whole second URI per theme. Keeping
     both here means filters.css stays free of literal colour and the caret
     stops being a near-invisible #666 on the dark surface. */
  --select-caret:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23666666' d='M4.8 8.99l6.19 7.07.71.71.71-.71 6.19-7.07-.9-.79-6 6.85-6-6.85z'/></svg>");

  color-scheme: light;
}

/* =====================================================================
   DARK THEME
   The two blocks below are intentionally identical. The @media block follows
   the OS setting; the [data-theme="dark"] block serves the manual toggle.
   Change one, change the other.
===================================================================== */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --page-bg:#070c17; --surface: var(--blue-01); --surface-sunken:#060a14;
    --text-primary:#ffffff; --text-secondary:rgba(255,255,255,.62); --text-inverse: var(--blue-01);
    --border:rgba(255,255,255,.14); --border-strong:rgba(255,255,255,.26);
    --accent: var(--blue-05); --accent-spark: var(--blue-05);
    --series-primary: var(--blue-05); --track: rgba(255,255,255,.09);
    --skeleton: rgba(255,255,255,.06);
    --map-1: var(--blue-imp); --map-2: var(--blue-ins); --map-3: var(--blue-bold);
    --map-4: var(--blue-bright); --map-5: var(--blue-opt);
    --cat-1: var(--blue-bold);      /* Bold Blue          */
    --cat-2: var(--red-bold);       /* Bold Red           */
    --cat-3: var(--purple-bright);  /* Bright Purple      */
    --cat-4: var(--green-bright);   /* Bright Green       */
    --cat-5: var(--red-opt);        /* Optimistic Red     */
    --cat-6: var(--green-neutral);  /* Neutral Green      */
    --cat-7: var(--purple-neutral); /* Neutral Purple     */
    --cat-8: var(--orange-bold);    /* Bold Orange        */
    --cat-9: var(--purple-bold);    /* Bold Purple   - decorative */
    --cat-10: var(--green-bold);    /* Bold Green    - decorative */
    --cat-other: var(--blue-neutral);
    --tag: var(--red-bright); --critical: var(--red-bright); --critical-wash: rgba(206,0,0,.16);
    --good: var(--green-bright); --good-dot: var(--green-bright); --warn-dot: var(--yellow-bright);
    --purple: var(--purple-bright);
    --status-new: var(--blue-05); --status-reviewed: var(--blue-04); --status-used: var(--blue-03);
    /* #E8EBEF is near-white on navy; Neutral Blue carries the same "inert" read. */
    --consent-not-granted: var(--blue-neutral);
    --mem-member: var(--blue-05); --mem-prospect: var(--blue-03); --mem-graced: var(--purple-bright); --mem-lapsed: var(--grey-02);
    --shadow-card: none;
    --shadow-pop: 0 12px 32px rgba(0,0,0,.55);
    --shadow-dialog: 0 24px 64px rgba(0,0,0,.66);
    --shadow-drawer: 0 0 40px rgba(0,0,0,.6);
    --scrim: rgba(3,6,12,.66); --scrim-strong: rgba(3,6,12,.78);
    --row-hover: rgba(255,255,255,.06); --row-hover-accent: rgba(133,231,255,.10);
    --amber-text: var(--yellow-bright); --amber-border: rgba(255,227,0,.4); --amber-wash: rgba(255,227,0,.12);
    --select-caret:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23AEB6C4' d='M4.8 8.99l6.19 7.07.71.71.71-.71 6.19-7.07-.9-.79-6 6.85-6-6.85z'/></svg>");
    color-scheme: dark;
  }
}
:root[data-theme="dark"]{
  --page-bg:#070c17; --surface: var(--blue-01); --surface-sunken:#060a14;
  --text-primary:#ffffff; --text-secondary:rgba(255,255,255,.62); --text-inverse: var(--blue-01);
  --border:rgba(255,255,255,.14); --border-strong:rgba(255,255,255,.26);
  --accent: var(--blue-05); --accent-spark: var(--blue-05);
  --series-primary: var(--blue-05); --track: rgba(255,255,255,.09);
  --skeleton: rgba(255,255,255,.06);
  --map-1: var(--blue-imp); --map-2: var(--blue-ins); --map-3: var(--blue-bold);
  --map-4: var(--blue-bright); --map-5: var(--blue-opt);
  --cat-1: var(--blue-bold);      /* Bold Blue          */
  --cat-2: var(--red-bold);       /* Bold Red           */
  --cat-3: var(--purple-bright);  /* Bright Purple      */
  --cat-4: var(--green-bright);   /* Bright Green       */
  --cat-5: var(--red-opt);        /* Optimistic Red     */
  --cat-6: var(--green-neutral);  /* Neutral Green      */
  --cat-7: var(--purple-neutral); /* Neutral Purple     */
  --cat-8: var(--orange-bold);    /* Bold Orange        */
  --cat-9: var(--purple-bold);    /* Bold Purple   - decorative */
  --cat-10: var(--green-bold);    /* Bold Green    - decorative */
  --cat-other: var(--blue-neutral);
  --tag: var(--red-bright); --critical: var(--red-bright); --critical-wash: rgba(206,0,0,.16);
  --good: var(--green-bright); --good-dot: var(--green-bright); --warn-dot: var(--yellow-bright);
  --purple: var(--purple-bright);
  --status-new: var(--blue-05); --status-reviewed: var(--blue-04); --status-used: var(--blue-03);
  /* #E8EBEF is near-white on navy; Neutral Blue carries the same "inert" read. */
  --consent-not-granted: var(--blue-neutral);
  --mem-member: var(--blue-05); --mem-prospect: var(--blue-03); --mem-graced: var(--purple-bright); --mem-lapsed: var(--grey-02);
  --shadow-card: none;
  --shadow-pop: 0 12px 32px rgba(0,0,0,.55);
  --shadow-dialog: 0 24px 64px rgba(0,0,0,.66);
  --shadow-drawer: 0 0 40px rgba(0,0,0,.6);
  --scrim: rgba(3,6,12,.66); --scrim-strong: rgba(3,6,12,.78);
  --row-hover: rgba(255,255,255,.06); --row-hover-accent: rgba(133,231,255,.10);
  --amber-text: var(--yellow-bright); --amber-border: rgba(255,227,0,.4); --amber-wash: rgba(255,227,0,.12);
  --select-caret:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23AEB6C4' d='M4.8 8.99l6.19 7.07.71.71.71-.71 6.19-7.07-.9-.79-6 6.85-6-6.85z'/></svg>");
  color-scheme: dark;
}
