/* ============================================================================
   GB ELECTRICITY — DESIGN TOKENS  (css/tokens.css, created v0.80 / rev 74 Phase 0)

   Every custom property the page declares on :root lives HERE and nowhere else —
   both themes, every responsive override, and every comment that justified a
   value, moved verbatim from the dashboard's <style> block where they had lived
   since the fork. Scope §2: "the 69 design tokens, both themes … move to
   css/tokens.css unchanged." The values are unchanged; so is their cascade
   order — this sheet is linked BEFORE the page's own <style>, and nothing after
   it declares a token on :root, so the computed values are byte-for-byte the
   ones v0.79 resolved.

   Component-scoped custom properties (--fill/--bar on .insight-item, --odo-t on
   .odo) are NOT tokens and stay beside their components.

   ⚠ THE COMMENTS ARE LOAD-BEARING. 44.3% of the dashboard is prose carrying the
   reasoning and the measurements behind these values (scope §1); a comment
   separated from its token is worthless, which is why each one travelled.
   ============================================================================ */

  :root{
    /* ⚠ v1.60 (rev 155): `color-scheme` IS NOT A TOKEN, AND THAT IS WHY IT WAS ALMOST MISSED.
       The redesign sheet declares it per theme (lines 47 and 118 of
       `tokens-neobrutalist-v1.4.css`) and the first pass of the merge dropped both, because
       every check written for that merge walked `--*` custom properties and this is a plain
       CSS property — invisible to the parser, to the token-count diff and to the resolve probe.
       Found only by rendering a page and reading `getComputedStyle(root).colorScheme`, which
       came back `normal`.
       ⚠ WHAT IT BUYS. The document has `<meta name="color-scheme" content="light dark">`, which
       tells the browser the page handles both — so without this property the CANVAS, the
       SCROLLBARS and the form controls follow the OPERATING SYSTEM, not the theme the reader
       chose here. A reader on a light OS who picks Dark gets a dark page with light scrollbars,
       and a white canvas anywhere the body does not paint. `data-theme` drives the tokens;
       this drives everything the tokens cannot reach.
       ⚠ IT IS DECLARED PER THEME, NOT AS `light dark`, deliberately: the resolved theme is
       always written to `html[data-theme]` and the site's control is a three-way radio whose
       System option follows the OS itself (M-01). Naming both here would hand the decision back
       to the OS and break that contract. */
    color-scheme:light;
    /* ---- Interface tokens. Identity lives here and nowhere else. ---- */
    /* v0.74: the odometer's digit window, hoisted from `.odo` because the flow band's figures
       now set their line box to it so their digits centre where their boxes centre. */
    --odo-h:.8em;
    /* ⚠ v0.74b: THE TOP PADDING OF A CARD THAT CARRIES A LABEL OVER A FIGURE. It exists as a
       token because the flow band puts three such cards side by side and their labels have to
       start on one line — and they were set in two places at two values (`.tile` 13px,
       `.balancebox` 15px) which is a 2px drift waiting to be noticed, on top of the 13px one
       that actually shipped. Side-by-side cards cannot each own this number. */
    --card-pad-t:13px;
    /* ⚠ v1.30 (rev 124), CANDIDATE 14: THE VALUE SCALE — four steps, declared once. The
       dashboard sheet carried thirty-eight rules on `.value` with fourteen distinct font sizes,
       nearly all of them one figure stepping down through media blocks. A breakpoint now moves
       these tokens; the rules only consume them. hero = the flow band's three headline figures
       (fluid between 1180 and 890px, 40px once the band stacks); tile = the condition tiles;
       mid = the balance figure and the two-up phone tiles; small = an inline figure in prose. */
    /* ⚠ v1.34 (rev 128): THE FUEL-MIX BAR HEIGHT, DECLARED ONCE BECAUSE TWO RULES NEED IT.
       `.track` sets the collapsed height and `#panel-mix.is-expanded .track` sets the FLOOR the
       expanded bar may not go below, and the rule that sets the floor says so in its own comment:
       "min-height is the ordinary collapsed height, so the expanded view can never render a
       THINNER bar than the collapsed one it came from". It was 14px against a collapsed 19px —
       the two numbers had drifted apart, and the property the comment claims had not been true
       for some time. Candidate 77's low-carbon line took 8% of the expanded panel's height and
       the bars fell to 16px, which is how it surfaced: thinner expanded than collapsed, exactly
       what the floor exists to prevent. One token, consumed by both rules, cannot drift again. */
    --track-h:19px;
    --val-hero:44px;
    --val-tile:32px;
    --val-mid:27px;
    --val-small:16px;
    --val-unit-hero:16px;
    --val-unit:14px;
    --val-unit-mid:13.5px;

    /* ========================================================================================
       REDESIGN PHASE 1 — THE NEO-BRUTALIST PRIMITIVES (rev 155, 18 September 2026)
       Merged from `docs/redesign/implementation/tokens-neobrutalist-v1.4.css`. That sheet is a
       SOURCE, not a replacement: this file was MERGED, so every live-only token keeps its value
       and its comment, and the replacement was never run over this sheet itself (Handoff §4 —
       `--line-soft:var(--line-soft)` is a cycle that silently invalidates every border using it,
       and it only showed up in a screenshot diff when the mockup build hit it).
       Register C-07, C-08, C-09, T-01, P-02. Decisions D4, D7 (as amended by 7f), D13, D14.

       ⚠ THE CHROME TOKENS FURTHER DOWN NOW ALIAS INTO THESE, EACH DECLARED EXACTLY ONCE.
       `--page`, `--surface`, `--foot-bg` and the rest resolve through a theme-scoped primitive,
       so ONE declaration on :root serves both themes and the dark block no longer declares them
       at all. That is not tidiness. The alias block the source sheet ships is plain `:root`
       (specificity 0,1,0) and this file's dark block is `html[data-theme="dark"]` (0,1,1), so
       any dark declaration left in place would OUTRANK the alias and dark mode would silently
       keep the entire old palette while light moved. Deleting them is what makes the merge work.

       ⚠ THE SERIES PALETTE IS NOT IN HERE AND DOES NOT MOVE (P-01/P-02, D1). `--c-*`,
       `--c-*-ink`, `--ci*`, `--ciw*`, `--solar-*` and `--flow-*` keep their live values and the
       measurements that justified them — checked byte-for-byte against the source sheet, which
       declares them identically. The four `--c-{solar,battery,pumped,inter}` names below are
       ALIASES so the mockups' names resolve onto the validated palette; they are not new hues.
       ======================================================================================== */

    /* GROUNDS. The three same-hue blue-greys the site stacked (`--page` #F4F6FA at hue 220.0°,
       `--grey-bg` #EDF0F7 at 222.0°, `--foot-bg` #DFE5F2 at 221.1°) collapse to one warm family
       at hue ~44°, and the footer stops having a ground of its own. That was candidate 87's
       complaint, in Kyle's words: "the website looks dated … too much like Elexon". */
    /* ⚠ rev 208 (Kyle, 5 October 2026 — Panel_Border_Study.html, "Decided"): #F5F2EA -> #F8F6F0, one step lighter in the
       same warm hue. Every ink gains a little (--ink-2 9.89 -> 10.23:1, --ink-4 4.58 -> 4.74:1, --blue-text 4.61 -> 4.78:1);
       the white card's step off the page falls 1.12 -> 1.08:1, which is why the thinner edges stay in ink. ⚠ `--tint`
       (#F3F0E6) is now 1.06:1 DARKER than the page where it sits on it directly (it was 1.02:1, near invisible) — a
       faint band, not a lift; inside a white panel nothing changes. Dark mode keeps #0F111A (its own decision). */
    --paper:#F8F6F0;
    --card:#FFFFFF;
    --card-2:#FAF8F0;
    /* ⚠ rev 156 (Phase 2, decision 7h): #EFEADC -> #F3F0E6. Every ink on the old tint had been
       solved to the MINIMUM that cleared the floor, so the tinted well was the one ground that
       failed: --ink-4 4.27:1, --blue-text 4.29:1, the light carbon bands 2.89:1 at worst. Moving
       the ground rather than the inks clears all of them at once and keeps the paper/tint/card
       steps. Measured on the built site (rev 155 audit): 28 of 37 sub-AA text nodes were this. */
    --tint:#F3F0E6;

    /* INK STEPS. Four, each with its measured floor, replacing the live three.
       ⚠ RE-MEASURED IN THIS BUILD, NOT CARRIED FROM THE SOURCE SHEET'S COMMENT, which quotes
       paper and card only and never mentions `--tint`: --ink-4 is 4.58:1 on paper and 5.13:1 on
       card, but **4.27:1 on --tint** — under AA, and within 0.01 of the 4.26:1 that got
       candidate 9's #EAEFF8 ground rejected. Recorded as an expected A-1 failure at Phase 1 and
       fixed at Phase 2 (register U-09); `--tint` is the surface to move, not the inks. */
    --ink-2:#3a3d35;      /* body copy: 9.9:1 on paper                                        */
    --ink-3:#62665C;      /* secondary text: 5.3:1 paper, 5.9:1 card                          */
    --ink-4:#6C6F64;      /* meta/caption: 4.58:1 paper, 5.13:1 card; was 4.27:1 on the old tint  */

    /* EDGES AND SHADOWS SHARE ONE COLOUR, in both themes, and that is the whole neo-brutalist
       device: a hard offset with no blur, reading as a printed edge rather than a light source.
       ⚠ A BORDER WRITTEN AS `var(--ink)` LOOKS RIGHT IN LIGHT AND GLARES IN DARK — the mockups
       had 127 borders and 10 map outlines on `--ink` that had to be moved. Structural borders
       take `--line`. ⚠ AND A BLACK SHADOW IS INVISIBLE ON DARK PAPER (measured 1.1:1), which is
       why `--shadow-c` follows `--line` in dark rather than staying ink. */
    --line:var(--ink);
    --line-mid:#C9C4B4;
    --line-soft:#E3DFD3;
    --shadow-c:var(--ink);
    /* ⚠ rev 208 (Kyle, 5 October 2026 — Panel_Border_Study.html, the "Decided" and "Revised later on 5 October" boxes;
       Panel_Border_Prototype.html, "Thin + 2px base"): THE EDGE IS HALVED AND THE LOWER EDGE STAYS SOLID. Every border
       drawn in the edge colour reads its width from one of four tokens — until this rev each rule wrote its own number
       and `--border` / `--border-thin` were declared and consumed by nothing on the site:
         --edge-w        panels, tiles, the footer card, the masthead bar   3px   -> 1.5px
         --edge-w-ctl    buttons and controls                               2.5px -> 1.5px
         --edge-w-inner  inner rules, small controls and boxes              2px   -> 1.5px
         --edge-w-fine   the finest edges (swatch rims, tags, dots)         1.5px -> 1.25px
       Three of the four are equal today; they stay four because they are four jobs, and the next change to one of
       them should not have to find which 1.5s are which. The thicker LOWER edge is still a hard, unblurred offset in
       `--shadow-c` — ink in light, #A5A399 in dark, the same colour as the edge, so the sentence above this block stays
       true — and it is halved with the sides: panels 1.5px + 2px (was 3 + 4), buttons 1.5px + 2px (was 2.5 + 3).
       ⚠ A 1.5px edge DRAWS AS 1px on a 100% desktop display (Chrome rounds a border down to whole device pixels) and
       1.5px on a 2x screen; 1.5 is the value to specify, it will not look identical everywhere. Today's 2.5px buttons
       already drew at 2px on a 100% display. */
    --edge-w:1.5px;
    --edge-w-ctl:1.5px;
    --edge-w-inner:1.5px;
    --edge-w-fine:1.25px;
    --shadow: 0 2px 0 var(--shadow-c);
    --shadow-sm: 0 2px 0 var(--shadow-c);
    /* rev 208: `--border` follows the panel edge; the site's own rules write `var(--edge-w) solid var(--line)` and the
       pair is consumed only by the design guide's chrome, which describes the site and so moves with it. */
    --border: var(--edge-w) solid var(--line);
    --border-thin: 1px solid var(--line-soft);

    /* THE BLUE'S COMPANIONS. `--blue-soft` is the quiet fill; `--on-blue` and `--on-blue-soft`
       are the two inks that sit ON those grounds. ⚠ `--on-blue-soft` IS WHAT DECISION 7f TURNS
       ON: the MW/GW switch keeps D7's quiet fill but takes this ink, because leaving the live
       `--ctl-on-ink:var(--blue-text)` measured 3.53:1 against controlTone's 4.5 floor once
       `--blue-text` moved off the near-black navy. This pairing measures 8.71:1 light,
       10.78:1 dark — see the note at `--ctl-on-ink`. */
    --blue-soft:#AEDBFF;
    --on-blue:#FFFFFF;
    --on-blue-soft:#1b2e6b;
    --on-series:#FFFFFF;
    --on-status:#FFFFFF;

    /* STATUS AS TEXT. `--ok` / `--warn` / `--bad` are FILL values; used as text on paper they
       are a different question, and `--warn` was the one that failed it. One text step each,
       and `--ok-text` is the fill because the fill already clears. */
    --ok-text:var(--ok);
    --warn-text:#8A5A08;

    /* BAND AND TAG WASHES (D14, D23). Grounds for status strips and labels. ⚠ NOT the carbon
       bands: P-04 keeps those on `--ciw*`, which are derived composites of the live ramp and are
       re-derived by `bandWashesAreDerived` rather than stored. Two families, one naming near-miss;
       they must not be confused. */
    --band-ok:#CDEFD8;
    --band-warn:#FDEAB0;
    --band-bad:#FBD9CE;
    --green-soft:#B9F3D3;
    --on-green-soft:#1a4f2e;
    --amber-soft:#FFE28A;
    --tag-blue:#DCE6FF;
    --tag-green:#D9F2E3;
    --tag-amber:#FFE8C2;
    --tag-violet:#F1DCFB;
    --overlay:#00000014;

    /* THE PLACEHOLDER (D14). Ink at 9% over whatever card it sits on, so one token serves both
       themes without a second value to keep in step. */
    --ph:color-mix(in srgb, var(--ink) 9%, transparent);

    /* THE LOGO'S IDENTITY (D13; the tile, rev 207 — Kyle, 3 October 2026). `--logo-blue-mark` is the tile's
       ultramarine, the same in BOTH themes (the favicon's #0000A8); `--logo-tile-edge` is the tile's 2-unit edge —
       its own colour in light (no border), the site's grey line in dark, without which ultramarine is 1.1–1.3:1 on
       the dark card. ⚠ THE RED IS RETIRED FROM THE IDENTITY: `--logo-red` and `--mark-red` are deleted, and
       `--logo-blue` with them (it coloured only the wordmark's underline band, which F does not have). The gas
       colour is data, not branding, and is untouched. */
    --logo-blue-mark:#0000A8;
    --logo-tile-edge:var(--logo-blue-mark);

    /* THE MOCKUPS' SERIES NAMES, ALIASED ONTO THE VALIDATED PALETTE (D1, P-01/P-02).
       These are NOT new colours and must never become any: they exist so a rule copied out of a
       mockup resolves against the series set the site already validated for deuteranopia and
       protanopia. If one of these ever needs a value of its own, that is a palette change and it
       goes through the separation measurement, not through here. */
    --c-solar:var(--solar-line);      --c-solar-ink:var(--solar-line);
    --c-battery:var(--c-storage);     --c-battery-ink:var(--c-storage-ink);
    --c-pumped:var(--c-storage);      --c-pumped-ink:var(--c-storage-ink);
    --c-inter:var(--c-imports);       --c-inter-ink:var(--c-imports-ink);
    /* ---- Chrome. Achromatic on purpose. ----
       This is a data dashboard: the only things on the page that should carry hue are the
       series. A branded chrome colour competes with them for meaning — a reader has to learn
       that the chrome hue means "the tool" and teal means "wind". So chrome is ink, links are ink
       with an underline doing the work colour used to do, and every hue on the page belongs
       to the data. It also means the identity below can change without ever touching the
       validated series palette. ---- */
    /* ---- TWO-LEVEL BLUE, v0.11. The chrome is no longer achromatic, and that is a deliberate
       reversal of a rule this project held from the fork. The rule existed to stop a BRAND hue
       competing with a FUEL hue for meaning, and that risk is real rather than theoretical — so
       the reversal was measured before it was made, and the protection was rebuilt rather than
       dropped. See --mark below: it is the whole reason this is safe.
         · Primary #0000A8 sits ΔE 16.9 from hydro, its nearest series. Comfortably clear.
         · The red accent #C94A4A sits ΔE 14.2 from gas and 11.8 from Other, its nearest series.
         · White on #0000A8 measures 13.4:1, so a reversed header is not a compromise.
       Three blue levels, and they are a HIERARCHY rather than a palette: strong blue is
       structure that matters, light blue is structure that supports, and most things are still
       neither. ---- */
    /* ⚠ rev 155 (D4): #0000A8 -> #175EFE, AND THE THREE-LEVEL HIERARCHY ABOVE IS NOW SPELT WITH
       THE NEW PRIMITIVES. The note above is kept because its ARGUMENT still governs — a chrome
       hue must stay clear of every fuel hue — but its numbers describe the retired navy. The
       replacement was measured the same way: #175EFE sits >=12.04 OKLab x100 from every light
       series and 12.84 from every dark series (the D4 search), against this project's floor of
       10, and carries white at 5.16:1. It is also the specific thing candidate 87 named: the
       navy was "precisely the Elexon palette", and Kyle works at Elexon.
       ⚠ LEVELS 2 AND 3 ARE NO LONGER SEPARATE BLUE-GREYS. `--blue-2` becomes the redesign's
       quiet fill and `--blue-line` becomes the structural edge every border now shares, so the
       "three blue levels" are now one blue plus the line system. Declared ONCE here: both
       resolve through theme-scoped primitives, so the dark block must not redeclare them. */
    --blue:#175EFE;          /* level 1 — panel frames, tabs, primary controls, active states */
    --blue-2:var(--blue-soft);   /* level 2 — fills and hover grounds                          */
    --blue-line:var(--line);     /* level 2, but a BORDER: one edge colour now                 */
    --blue-ink:#00008A;      /* pressed / hover for level 1                                    */
    /* v0.22: MUTED, from #E31B23. The old value was a flag red — fully saturated, and sitting
       as a clean rectangular block against a strong blue it read as broadcast branding rather
       than as a detail. Chroma is down by 29% (OKLab C 0.227 → 0.162) at the same lightness and
       hue, which is the same place a 70% desaturation of the original lands. Measured:
       4.60:1 on white, 4.25:1 on the page, 3.81:1 on the dark surface; ΔE 14.2 from gas, 11.8
       from Other — its nearest series — and 42.3 from the blue it sits beside.
       The brick alternative #B84A3A was tested and rejected: ΔE 11.5 from gas, closer than this
       one, and it drifts toward the Other tan.
       ⚠ The rule this now carries: red is for structural end caps and the hero accent ONLY.
       Not buttons, not text, not borders, not any UI state. The blue does the identity work;
       this is a detail a reader may never consciously notice, which is the intent. */
    --red:#C94A4A;           /* the single restrained accent. One place, one purpose.          */
    /* v2.38 (rev 207): --mark-red, the mark's own flag red (#E31B23 here, #EF3F48 in dark), is deleted — the mark
       is the ultramarine tile now and carries no red. */
    /* #DDE4FF was specified for secondary card borders and measures 1.26:1 against a white
       card — which is not a border, it is a rumour. --blue-line is the next step down the same
       ramp at 1.61:1: still unmistakably the quiet level, but an edge a reader can actually
       find. #DDE4FF is kept for the jobs where 1.26:1 is exactly right — fills and hover
       grounds, where the shape is carried by area rather than by an edge. */
    /* ⚠ rev 155 (C-07): the two accent GROUNDS join the line/fill system; `--accent` and
       `--accent-dark` are untouched and still follow the blue. Declared once — see the dark block. */
    --accent:var(--blue); --accent-dark:var(--blue-ink);
    --accent-tint:var(--blue-soft); --accent-line:var(--line-soft);
    /* Blue has TWO jobs and they need different values, which only shows up in dark mode.
       As a FILL it sits behind white text; as TEXT it sits on the surface itself. In light the
       same #0000A8 does both — 13.4:1 either way. In dark the fill must be deep enough for
       white to read on it and the text must be light enough to read on the surface, and no
       single value does both. --blue-text is that second job. */
    /* ⚠ rev 155 (D4): light now declares its own hex rather than following --blue, so the two
       themes are symmetric and the pairing is readable at a glance. The computed value is
       unchanged from `var(--blue)` — both resolve #175EFE — but the comment above, written when
       "the same #0000A8 does both", no longer describes a navy. Measured as TEXT on the new
       grounds: 5.16:1 on --card, 4.61:1 on --paper, **4.29:1 on --tint** (under AA, the same
       tinted-well failure as --ink-4; recorded, fixed at Phase 2 — register U-09). */
    --blue-text:#175EFE;
    --head-bg:var(--blue);
    /* DATA MARKS ARE NOT CHROME. Every un-series line and point on this page — the price trace,
       the frequency trace, the carbon actual, the band needle — used --accent, so turning the
       chrome blue would have turned them blue too, and put a blue data line in the same panel
       as periwinkle interconnectors. They take --mark instead, which stays ink. The old rule
       said every hue belongs to the data; the new one is narrower and sharper: THE CHROME MAY
       BE BLUE, BUT NOTHING THAT PLOTS A NUMBER MAY WEAR THE CHROME COLOUR. */
    --mark:#111827;
    /* ⚠ v0.57: `--ink-mute` DARKENED FROM #77839A, AND IT IS A CONTRAST FIX, NOT A TASTE ONE.
       A PageSpeed accessibility audit put the page at 87; an axe run against this build named
       the cause exactly — 74 of the 77 contrast failures were this one token. #77839A on the
       panel surface is 3.82:1 and on the page ground 3.53:1, against a WCAG AA floor of 4.5 for
       text under 18.66px. Every muted line on this page is under 18.66px, so every one of them
       failed: the provenance stamps, the axis labels, the metadata rows, the footer.
       ⚠ THE VALUE IS THE MINIMUM THAT CLEARS THE FLOOR ON BOTH GROUNDS — solved rather than
       eyeballed, and 4.54:1 on the page ground is deliberately barely past 4.5 so the token
       stays as quiet as the design intends. The hue and saturation are unchanged; only lightness
       moved. "Quiet" is a job for size, weight and position, and it cannot be bought with
       contrast a reader needs to read the words at all. */
    /* ⚠ rev 155 (C-08): the note above records why #657189 was solved to the minimum that
       clears 4.5 on both grounds, and that solve was against the RETIRED blue-grey grounds. The
       three steps become the four-step warm ramp declared at the top of this block; `--ink-dim`
       and `--ink-mute` are kept as aliases so the ~200 rules that consume them are untouched at
       Phase 1, and are deleted at Phase 5 once no rule asks for them (G-02, G-04). */
    --ink:#12151c; --ink-dim:var(--ink-3); --ink-mute:var(--ink-4);
    /* ⚠ rev 155 (C-07): the four grounds alias onto the warm family. Declared ONCE, here —
       the dark block's copies are deleted, or `html[data-theme="dark"]` (0,1,1) would outrank
       this `:root` (0,1,0) and dark would keep the old palette while light moved. */
    --page:var(--paper); --grey-bg:var(--tint); --grey-line:var(--line-soft); --surface:var(--card);
    /* ⚠ v0.73: the gridline colour is its own token, not `--grey-line`. It is drawn OVER filled
       bars in nine saturated fuel colours as well as over the empty track, so it needs to be
       visible on both without stripping the bars — a translucent ink rather than a solid line. */
    /* ⚠ rev 155 (C-07): the translucent ink becomes the shared soft line. The note above still
       states the requirement — visible over nine saturated fuel fills AND over the empty track —
       and it is re-measured by the existing mix-scale clauses rather than assumed. */
    --mix-grid:var(--line-soft);
    /* CARBON RAMP. Green through yellow to orange, the family NESO uses on its own carbon
       intensity dashboard — so a reader arriving from the official tool reads this the same
       way. Not NESO's exact values: theirs were measured and then improved on. See the note
       on .bandtrack for the numbers. One set serves both themes; it clears every check
       against the light surface and the dark one. */
    /* ⚠ v0.66: CHROMA DOWN 22%, HUE AND LIGHTNESS UNTOUCHED, at the author's request to reduce
       decorative colour across the page. The measurement that justified it is uncomfortable and
       is the reason this happened: the ramp was at mean OKLCH chroma 0.161 while the eight-fuel
       series palette sits at 0.127 — i.e. **the ramp was more saturated than the palette it
       exists to stay out of the way of**, and the note below states the opposite principle
       ("The fuel palette owns SATURATION; this ramp owns LIGHTNESS"). The values had drifted
       from the rule they were written under. Gentle is 0.125, which puts the ramp just under
       the fuels and makes the sentence true.

       ⚠ ONLY CHROMA MOVED. Hue is identical to the step it replaces and lightness is within
       0.002 OKLCH of it, so NESO's family and the ramp's characteristic non-monotone lightness
       shape (it peaks at the yellow) both survive exactly. This is a refinement, not a re-cut.

       ⚠ AND THE COLLISION CURVE IS NOT MONOTONE, WHICH IS WHY 22% AND NOT MORE. The constraint
       that shaped this ramp was distance from the fuel palette. Desaturating walks the ramp
       TOWARDS grey, which passes CLOSE TO Other's orange before clearing it: nearest-fuel ΔE
       goes 10.7 (current) → 8.1 (−22%) → 7.8 (−42%) → 9.5 (−58%). The middle of that range is
       the worst place to stand, not the safest. 8.1 clears the 8.0 target this project set;
       −42% would have looked like the obvious "more of a good thing" and quietly broken it.
       Measured with CIEDE2000, and cross-checked against an independent palette validator.

       Was: --ci1:#4f7a18 --ci2:#93bf34 --ci3:#f7d117 --ci4:#eb9622 --ci5:#dd6414

       ⚠ v0.76: RESTORED TO EXACTLY THAT, at the author's request — "they're lower down on the
       page, so I'm happy for them to be restored to their original brightness". The toning was
       never about these two tiles being wrong in themselves; rev 61 desaturated them because a
       bright ramp high on the page competed with the flow band above it. Rev 65a then moved this
       whole tile row BELOW the two charts, which removed the competition and left the correction
       in place — a fix outliving the thing it fixed.
       ⚠ AND RESTORING IMPROVES THE MEASUREMENT THE TONING WAS WEIGHED AGAINST. The note above
       records nearest-fuel ΔE as 10.7 before the change and 8.1 after: desaturating walked the
       ramp TOWARDS Other's orange. Going back puts 2.6 ΔE of separation from the fuel palette
       back, well clear of the project's 8.0 target. The exact prior values were written down at
       rev 61, so this is a restoration rather than a re-cut — no re-derivation, no drift. */
    /* ⚠ v1.48 (rev 143), CANDIDATE 13 — THE LIGHT GROUND TAKES ITS OWN THREE STEPS.
       MEASURED against the light surface #FFFFFF and the page ground #EDF0F7, on the 3:1 WCAG
       floor for a graphical object:
         --ci1 5.09 / 4.46  ok        --ci4 2.35 / 2.06  FAILED
         --ci2 2.16 / 1.89  FAILED    --ci5 3.55 / 3.12  ok
         --ci3 1.49 / 1.31  FAILED
       Three of five, and the worst of them a yellow at 1.31 — a line a reader could not see.
       ⚠ THE FIVE HAD NO DARK VARIANT: ONE VALUE SERVED TWO GROUNDS, which is what made this
       unfixable in place. On the dark surface the same five run 3.45 to 11.75, so darkening
       them for light would have broken dark. The block below now carries the ORIGINAL values,
       so **dark is byte-for-byte unchanged** and only the light ground moves.
       Hue and saturation are held and lightness alone is reduced, to the first step that clears
       3.05 on BOTH light grounds: #729528 (3.47/3.05), #a18706 (3.51/3.08), #c37812 (3.49/3.06).
       ⚠ WHAT THIS IS AND IS NOT. These five are used in exactly two places today — the carbon
       scale's gradient track and `.meter span` — which are FILLED BARS carrying tick marks and
       labels, not lines. So this was not a live defect a reader was meeting; it is the block
       that stopped candidate 91 (Kyle's band-coloured sparkline), where they WOULD be lines and
       the floor would bite. The visible change is that the light theme's carbon meter reads
       darker through its middle three bands.
       ⚠ AND IT IS NOT ALL OF CANDIDATE 13. Five fuel colours also fail — `--c-gas` at 2.46 on
       the dark ground, `--c-hydro`, `--c-nuclear` and `--c-storage` at ~2.78, and `--c-wind` at
       2.64 on light. Those ARE drawn as chart series. They are left alone deliberately: moving
       five fuel colours changes every chart on the site and is Kyle's to look at, not a change
       to fold into a rev that was about something else. Measured and recorded; not done. */
    --ci1:#4f7a18; --ci2:#729528; --ci3:#a18706; --ci4:#c37812; --ci5:#dd6414;
    /* v0.20: WASHES of the five band colours, for the intensity chart's background zones.
       Not the band colours themselves and not an opacity applied to them: the two surfaces are
       different, so one alpha would give two different results and only one of them would have
       been checked. These are the band colours composited at 36% on the light surface, worked
       out once and written down.
       ⚠ v0.66: RECOMPUTED from the toned ramp at the same 36%, not adjusted by eye. Adjacent
       zones now separate by 6.5–9.2 ΔE (was 4.6–7.5), and --mark keeps 10.8:1 where the trace
       crosses them.  [that was #c2ceb6 #dae7c4 #f9efc6 #f3dbc0 #edccba — superseded, see below]

       ⚠⚠ v0.98 (rev 92): RESTORED — AND THIS WAS A BUG, NOT A PREFERENCE. The author reported
       the intensity panel as washed out. It was: **these were 36% composites of a ramp that
       has not existed since v0.76.** Rev 61 toned the ramp; v0.66 recomputed these from the
       toned values, correctly; then **v0.76 restored --ci1…5 to their original brightness at
       the author's own request and NOBODY RECOMPUTED THE WASHES.** The zones have been derived
       from a superseded source for sixteen releases, so the chart's background and the band
       colours it is supposed to be a wash OF had quietly come apart.
       ⚠ THE PROOF IS THAT THE ARITHMETIC LANDS EXACTLY ON THE OLD VALUES. 36% of the live ramp
       on white reproduces the pre-v0.66 set to ΔE 0.0 on all five bands — this is a derivation
       restored, not a colour chosen. The same is true of dark at 34%.
       ⚠ AND IT IMPROVES THE MEASUREMENT RATHER THAN TRADING AGAINST IT. Adjacent separation
       goes 6.5–9.2 → **6.7–9.8** (CIEDE2000) and --mark holds **10.97:1** across the zones
       against the 10.8 floor. Nothing was given up to get the brightness back, which is what
       you would expect from a fix that restores a derivation rather than overriding one.
       ⚠ THE FORMULA, STATED, BECAUSE THIS FILE HAD TWO. The 6.5–9.2 above is CIEDE2000; the
       same colours measure 7.1–10.3 under CIE76. Rev 92 reproduced both to the decimal and the
       apparent disagreement was a missing label, not an error. Every ΔE in this block is
       CIEDE2000 unless it says otherwise. */
    /* ⚠ v1.48 (rev 143): THREE OF THESE ARE RECOMPUTED, BECAUSE THE RAMP MOVED AND A WASH IS NOT
       A COLOUR — IT IS A DERIVATION. Candidate 13 darkened --ci2, --ci3 and --ci4 for the light
       ground; these washes are those band colours composited at 36% on the surface, so leaving
       them would have left the chart's background zones derived from a ramp that no longer
       exists. That is rev 92's fault exactly — sixteen releases with the background composited
       from a superseded source — and it is why `bandWashesAreDerived` exists.
       ⚠ THE HARNESS CAUGHT IT, ON THE FIRST RUN, WITH A WORST-CHANNEL OFFSET OF 31. I changed the
       ramp and did not think about the washes; the clause recomputes the composite from the LIVE
       ramp rather than trusting the stored hexes, so it failed with the expected values in its
       own output. Recomputed from those, not by eye:
         --ciw2 #d8e8b6 -> #ccd9b2    --ciw3 #fceeab -> #ddd4a5    --ciw4 #f8d9af -> #e9ceaa
       --ciw1 and --ciw5 are unchanged because --ci1 and --ci5 were unchanged. The dark washes are
       untouched for the same reason: the dark ramp did not move, and `darkWashesAreTheLiveRamp-
       Composited` passed on the same run. */
    --ciw1:#c0cfac; --ciw2:#ccd9b2; --ciw3:#ddd4a5; --ciw4:#e9ceaa; --ciw5:#f3c7aa;
    /* ⚠ INTER FIRST, AND THE REST OF THE STACK IS NOT DECORATION. `font-display:swap` means
       every reader sees the fallback for at least one frame, and a reader whose network drops
       the page mid-load sees it for good. So the stack is a face the page actually works in,
       not a token gesture: system-ui resolves to the platform's own UI face (Segoe UI Variable
       on Windows, SF on Apple, Roboto on Android) — the closest metric relative to Inter that
       every reader already has on disk, which keeps the swap from re-flowing the page. */
    /* v2.26 (rev 197): 'Inter fallback' second — Arial reshaped to Inter's metrics (css/fonts.css), so the frame
       before Inter lands is laid out as Inter will be and the swap moves nothing. system-ui follows for a system
       with no Arial (the note above was the reasoning; the live site measured 0.0024 of CLS from the swap). */
    --sans:'Inter','Inter fallback',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
    --mono:ui-monospace,'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
    /* (v1.61, rev 156, D13: `--display` — Hammersmith One for the masthead wordmark, v0.88 — is
       retired with the text wordmark. The name is outlined SVG now; see fonts.css. Nothing
       declares a display face, and the harness asserts that nothing renders in one.) */
    /* ---- v0.18 TYPOGRAPHIC RULE — read this before adding font-family anywhere ----
       Both stacks were correct before this rule was written. What was
       wrong was not the faces but where they were applied: v0.17 set the monospace face on
       status text, timestamps, megawatt figures, percentages, country names and the whole
       temperature chip, which is why the header and panels read as a different typeface from
       the rest of the page — and from the Wiki, which reserves mono for the version label,
       code chips and URLs.
       The rule from v0.18 on:
         MONO  = machine identifiers only — Elexon dataset names (FUELINST, INTOUTHH),
                 BMRS codes (INTNED), endpoint paths and URLs. Marked up as <b class="ds">.
         SANS  = everything else, INCLUDING every number.
       Figures do not need a typewriter face to line up: `font-variant-numeric:tabular-nums`
       gives fixed-width digits in the sans face, which is what the hero tiles already used.
       That is why v0.17's 44px sans hero value sat above a column of mono megawatts. ---- */

    /* ---- Categorical series palette (validated against the six dataviz checks, light) ----
       Worst adjacent pair: ΔE 17.4 deuteranopia / 22.0 normal vision; all eight ≥3:1 on surface.
       THE ARRAY ORDER IS THE CVD-SAFETY MECHANISM — see the SERIES comment before reordering. ---- */
    /* ⚠ v0.96 (rev 90): NUCLEAR, WIND AND OTHER TAKE THE AUTHOR'S PROPOSED HUES. Three of a
       nine-colour set he put forward on 2 Sept; the other six were measured and declined, and
       `Rev90_Colour_Study.html` renders why. What matters here is that the three were tested as
       a SET rather than as a list, because a palette constraint is a property of the whole
       array: worst pair ΔE 9.9 → 12.3 and worst adjacent greyscale 1.58:1 → 1.74:1, so the
       array is measurably SAFER than the one it replaces, not merely different.
       ⚠ His three beat the four I recommended. Adding his Interconnectors dropped the worst
       pair back to 10.3 — Wind/Interconnectors became the new closest pair. A subset can be
       better than the set it came from; the lesson is that neither is knowable by eye.

       ⚠ WIND IS THE BINDING CONSTRAINT AND CANNOT ABSORB ANOTHER LIGHTENING. #0EA5A8 measures
       3.01:1 on white — one hundredth over WCAG 1.4.11's floor for graphical objects, and the
       marks in MOVER_ICONS use it as a 1.7px STROKE, which is a harder test than a filled bar.
       Rendered at true size in both themes and read as legible, so it ships. But the margin was
       bought from somewhere and the alternative is recorded because it is a real trade, not an
       oversight:

         #0EA5A8 (shipped)  3.01:1 on white   worst pair ΔE 12.3
         #00A0A3            3.20:1            worst pair ΔE 10.8   ΔE 1.8 from the shipped value
         #009FA6            3.23:1            worst pair ΔE 10.8   ΔE 3.3

       Darkening wind moves it TOWARD biomass, so contrast margin is paid for in pair
       separation; a search over ±10° of hue and ±4 L* found nothing clearing both. The shipped
       value takes the better half of the trade because pair separation at 9.9 was the light
       array's actual weakness, while 3.20:1 was never comfortable either — wind has been this
       palette's contrast floor since the fork. ⚠ If wind is ever touched again, it goes DARKER
       or not at all, and the pair with biomass is what pays. */
    /* ⚠ v1.04 (rev 98): --c-wind WAS SOLVED TO #2B9DA0 AND THE MOVE WAS THEN REFUSED. Recorded
       because the next person to notice wind at 2.79:1 on --page will re-derive exactly this.
       THE FAULT IS REAL: 3.01:1 on --surface but 2.79:1 on --page, under WCAG 1.4.11's 3:1.
       ⚠ THE COST IS PAID BY COLOUR-BLIND READERS, AND THAT IS WHY IT WAS REFUSED. Any wind
       that clears 3:1 on BOTH grounds at this hue drops the light array's worst dichromat pair
       (biomass/wind) from 12.3 to 10.0 — above the 9.9 floor, but it hands back the whole of
       rev 90's improvement. A search over L and C holding H found 299 values that clear every
       clause at once and EVERY ONE of them is a desaturated grey-teal (#799393 and its
       neighbours), a visible move of 8.1 OKLab — wind stops reading as teal at all.
       ⚠ AND REV 90 ALREADY TOOK THIS TRADE, IN THIS DIRECTION, ON THE RECORD: the note above
       says the value "takes the better half of the trade because pair separation at 9.9 was the
       light array's actual weakness, while 3.20:1 was never comfortable either". Reversing that
       judgement needs new evidence and there is none.
       THE ASYMMETRY THAT DECIDES IT: dichromat separation serves readers who cannot use the
       hue at all and have no other route to it. The 3:1 floor here guards a mark that ALSO
       carries a label, a legend and a table view — three non-colour routes to the same fact.
       Twenty-three hundredths of contrast is not worth 2.3 ΔE of separation for readers with
       no alternative.
       ⚠ SO IT IS A KNOWN, ACCEPTED FAILURE AND THE HARNESS SAYS SO OUT LOUD (see the
       ground-contrast arm of paletteSafety, added this release). An accepted failure that is
       not written down becomes a passing test.
       ⚠ IF THE GROUND EVER CHANGES (candidate 9), THIS IS RE-OPENED — a lighter --page would
       clear wind at no cost to the pair, and that is the cheap fix nobody can take today
       because --ink-mute has four hundredths of headroom against the same ground. */
    --c-nuclear:#6D4ACD; --c-biomass:#4E9A5B; --c-hydro:#10568C; --c-wind:#0EA5A8;
    /* v0.45: the demand side's ground, mixed to sit the same distance from the surface
       as --grey-bg does on the supply side rather than being picked by eye. */
    --teal-pale:#EAF6F5;
    /* ⚠ v0.49: TEAL USED AS TEXT, which the series value cannot do — and this fixes a real
       accessibility defect rather than tidying a preference. The DEMAND SIDE caption and its
       arrow both took --c-wind, which was #279FA0 and measured 3.20:1 on white. That is fine
       for a chart fill, adequate for a graphic, and BELOW THE 4.5:1 WCAG AA FLOOR FOR TEXT —
       and the caption is 10.5px uppercase, which is the least forgiving text on the page.
       Exactly the --blue / --blue-text split, arrived at for exactly the same reason at rev 11:
       a colour chosen to sit in a chart cannot be assumed to carry a label.
       #1B7273 is 5.67:1 on white, ΔE 20.6 from --c-wind (so it does not claim to BE the wind
       series while doing chrome duty) and ΔE 37.9 from hydro, its next nearest.
       ⚠ v0.96 (rev 90): --c-wind became #0EA5A8, 3.01:1 on white, so the ARGUMENT is unchanged
       and stronger — the series value is further below the text floor than before, and the
       separation from this token widened from 17.7 to 20.6. The figures above are restated
       rather than left describing a colour that is no longer there, which is the fault this
       same release retired from the sun mark's prose.
       ⚠ v1.04 (rev 98): --c-wind WAS PROPOSED FOR #2B9DA0 AND HELD. Had it moved, this
       separation would have narrowed 20.6 -> 13.4 and the figures above would have needed
       restating a second time. They did not move, and the near-miss is the note: any future
       --c-wind change must restate them BEFORE shipping, not after.
       ⚠ AND THE CLOSING DELIMITER OF THIS COMMENT MATTERS. An edit at rev 98 replaced a slice
       of this block that ran up to the NEXT DECLARATION, and so ate the comment terminator —
       which put --teal-text INSIDE the comment. The token silently ceased to exist, the demand
       caption fell back to an inherited colour, and `balanceArrows` caught it. A slice that ends
       at the next thing eats whatever sits between the two.
       ⚠ AND THE NOTE ABOVE THEN DID IT AGAIN, IN THE OPPOSITE DIRECTION: the first draft of it
       quoted the terminator literally, which CLOSED THIS COMMENT four lines early and left a
       stray one below. A comment about a delimiter may not contain the delimiter. Both faults
       were found mechanically — the first by the harness, the second by a balance scan — and
       neither was visible by reading. */
    --teal-text:#1B7273;
    /* ⚠ REMOVED AT v0.51 WITH THE FEATURE. The values and their measurements are kept as a
       record, commented out rather than deleted, because the sign-colour question has now been
       decided in both directions once each and the next person to raise it should not have to
       re-derive a red that clears 4.5:1 without colliding with the chrome red or the gas fill.
       If it ever comes back, these are the values and this is why they were those values.

       ⚠ THEY ARE NOT LIVE TOKENS. Nothing references them; do not reference them without also
       restoring the three safeguards listed in the band's markup note.
       --bal-neg is NOT --red (#C94A4A, the chrome device) and NOT --mark-red (#E31B23, the
       logo): the rev 18 rule says red is for structural end caps and the hero accent and
       nothing else, so a figure that goes red needs a red of its own or the rule quietly stops
       meaning anything. #BE2F26 measures 5.79:1 on white and sits ΔE 15.3 from --red and 18.8
       from --mark-red — far enough apart to read as a different thing rather than as the chrome
       device leaking into a number.
       ⚠ THE UNCOMFORTABLE MEASUREMENT, recorded rather than buried: ΔE 10.6 from --c-gas
       (#9B1111). Every red that clears 4.5:1 on white lands within 5–12 ΔE of gas — muting or
       darkening a red moves it TOWARDS the fuel palette, which rev 18 already learned the hard
       way. 10.6 is the best available separation among candidates that clear the contrast floor,
       and the two never appear in the same object: this is a 38px figure in the band, gas is a
       fill in the mix chart.
       --bal-pos #0F7A5A is 5.31:1 on white, ΔE 20.4 from biomass, its nearest series. */
    /* --bal-pos:#0F7A5A; --bal-neg:#BE2F26;  (retired v0.51) */
    --c-storage:#8A3362; --c-imports:#7386D5; --c-gas:#9B1111;   --c-other:#A37A00;

    /* ⚠ v1.51 (rev 146): THE SERIES INKS — EIGHT NAMES, DECLARED IN BOTH THEMES, AND ONLY HALF
       OF THEM ARE NEW VALUES. Kyle: "instead of having the colour dot next to the fuel type
       (e.g. wind, interconnectors etc), is it possible to have the word in the fuel type
       colour?" — and, later, "this could also be applied to the 'Gas is 49.8% of the mix right
       now, nuclear 16.1% and wind 13.0%.' sentence at the top of the Generation details page."

       ⚠ A FILL IS NOT AN INK, WHICH IS THE WHOLE REASON THIS BLOCK EXISTS. `--c-*` are drawn as
       bars and areas and owe WCAG 3:1 as non-text marks. Colouring a WORD with one asks 4.5:1 of
       a value that was never chosen to carry it. Measured on --surface, --page AND --grey-bg —
       three grounds, because these words land in panel bodies, on the page ground and inside
       chips — **eight of the sixteen fills already clear 4.5 on all three and are repeated here
       unchanged; eight needed solving.** Repeating the passing ones rather than aliasing them is
       deliberate: a reader of this sheet should see one ink per series per theme in one place,
       not eight values and eight indirections.

       SOLVED (minimum move from the fill, hue and chroma held, lightness alone reduced/raised):
         light   wind    #0EA5A8 -> #0C787C   5.26 / 4.86 / 4.61
         light   biomass #4E9A5B -> #097E44   5.15 / 4.76 / 4.51
         light   imports #7386D5 -> #4367CB   5.20 / 4.80 / 4.56
         light   other   #A37A00 -> #8E6700   5.13 / 4.75 / 4.50
         dark    gas     #B3241F -> #F94239   4.89 / 5.26 / 4.52
         dark    nuclear #7942DC -> #A664FE   4.90 / 5.27 / 4.52
         dark    hydro   #2069A5 -> #518CBE   4.88 / 5.24 / 4.50
         dark    storage #984C72 -> #C36B96   4.89 / 5.26 / 4.52
       Every ink also clears ΔE 10 from every OTHER ink in its own theme, so two fuel words in one
       sentence can never read as the same series. Checked pairwise, both themes: no collisions.

       ⚠ THERE IS NO SOLAR INK, AND THAT IS THE RULE THIS SHEET ALREADY STATES rather than an
       omission. The note further down says it outright — "Embedded solar: NOT a 9th categorical
       hue" — and solar is encoded by texture, outline and label everywhere on the site: the fuel
       table, the composition strip, the legend and both charts. A ninth ink would quietly reverse
       that. So the solar word keeps `--ink` and keeps its `.ins-mark.is-est` hatch, which is the
       one visible difference in the sentence and carries a real meaning: that figure is modelled,
       not metered.
       ⚠ AND IT HAPPENS TO REMOVE THE ONE COLLISION THE SOLVE FOUND. A solar ink lands at #856800,
       **ΔE 3.4 from the light `other` ink** — the two words would have been indistinguishable in
       running prose. The rule and the measurement agree, which is the comfortable case.

       ⚠ WHAT THIS DOES NOT DO. It does not touch a single `--c-*` fill, so every chart on the
       site is byte-identical. **Candidate 13's open half stays open**: `--c-wind` still measures
       2.79 on the light page ground as a MARK, which is a different failure in a different place
       and is Kyle's to decide. The inks are the answer to the TEXT half only. */
    --c-gas-ink:#9B1111;     --c-nuclear-ink:#6D4ACD; --c-wind-ink:#0C787C;   --c-biomass-ink:#097E44;
    --c-imports-ink:#4367CB; --c-hydro-ink:#10568C;   --c-other-ink:#8E6700;  --c-storage-ink:#8A3362;

    /* ---- Diverging pair for interconnector flow (import / export) ---- */
    /* ⚠ v1.51 (rev 146), KYLE'S OPTION A3: THE EXPORT COLOUR IS NO LONGER A RED, AND
       `--flow-out-ink` IS RETIRED. Read this before the v0.57 note below, which it supersedes.

       KYLE, ON THE DEPLOYED v1.50 DASHBOARD: "The exporting colour looks too much like the gas
       fuel colour … a reddish looks like a liability which exporting sort of is, but it's also
       positive that the system has enough spare generation to export … I still don't like
       exporting being 'red' which I associate with negativity. An orange colour is more neutral
       in meaning, and compliments the interconnector importing colour."

       ⚠ TWO INDEPENDENT COLLISIONS WERE MEASURED, NOT ONE. Light `--flow-out` #8C2E1F sat
       ΔE2000 **4.6** from `--c-gas` #9B1111 — and was BYTE-IDENTICAL to `--bad`. Dark
       `--flow-in` #2FA3A4 sat ΔE **4.7** from `--c-wind` #1EB3B6. Anything under about ΔE 10 is
       the same colour to a reader who is not holding two swatches side by side, and Kyle was
       reading the dashboard, where the gas bar and the export bar sit one panel apart.

       THE VALUES, SOLVED AND MEASURED on --surface / --page / --grey-bg:
         light  --flow-in  #0F6F70  5.95 / 5.50 / 5.22   ΔE 19.5 from wind   — UNCHANGED
         light  --flow-out #A75001  5.54 / 5.12 / 4.86   ΔE 18.2 from gas
         dark   --flow-in  #4D9491  4.97 / 5.34 / 4.59   ΔE 10.2 from wind
       ⚠ THE DARK IMPORT VALUE MOVED ONCE MORE, AND THE HARNESS IS WHY. The first solve returned
       #379391 at 4.79 / 5.15 / **4.42** — it cleared --surface and --page and missed --grey-bg by
       eight hundredths, and the solve had only been given two of the three grounds. Run 1 of
       verify151 failed `bothTokensAreTextSafeOnAllThreeGrounds` on exactly that. #4D9491 is ΔE 2.4
       from it, so nothing about the look changed; the measurement is now true on all three.
         dark   --flow-out #D48E63  6.55 / 7.04 / 6.04   ΔE 29.3 from gas

       ⚠ THE DARK VALUE IS KYLE'S OWN SWATCH, TAKEN UNCHANGED. He sent #D48E63 against the dark
       surface and it already cleared every floor there. It CANNOT serve light: on #FFFFFF it
       measures **2.68:1**, under the 4.5 text floor and under the 3:1 graphical floor as well.
       The light value is the same OKLCH hue (H52) at a darker step, so the two read as one
       colour across the two themes rather than as two different oranges.

       ⚠ WHY THE SIGN NO LONGER NEEDS TO BE RED, in Kyle's own reversal. He first reasoned that
       export goes right-to-left because it is negative and that red therefore fits. He kept the
       premise and reversed the conclusion, and the reversal is the better call: THE SIDE OF ZERO
       ALREADY CARRIES THE POLARITY, and so does the sign on the net figure. A colour saying it a
       third time stops meaning "outbound" and starts meaning "wrong".

       ⚠ AND THERE IS A TECHNICAL ARGUMENT, NOT ONLY A SEMANTIC ONE. Teal–orange is the
       canonical colour-blind-safe diverging pair — the reason ColorBrewer's diverging schemes
       are brown–teal and purple–orange rather than red–green. The two sit ~145° apart and
       separate on BOTH the red–green and the blue–yellow axis. Red–teal, which is what shipped
       from the fork until now, separates on only one of them.

       ⚠ `--flow-out-ink` IS GONE, AND THAT IS THE POINT OF THE MEASUREMENT ABOVE. It existed
       because v0.57 found `--flow-out` at 4.01:1 on the dark surface and one token could not
       answer to both a bar and a label. BOTH A3 VALUES CLEAR 4.5:1 ON ALL THREE GROUNDS IN BOTH
       THEMES, so the six rules that took it collapse back onto `--flow-out`. The token is
       removed rather than left as an alias, and `verify151` asserts it resolves nowhere — a
       retired token kept alive as an alias is a token that comes back.

       ⚠ ONE ADJACENCY TO HOLD, RECORDED RATHER THAN DISCOVERED LATER. In light, #A75001's
       nearest neighbour is `--warn` #98671B at ΔE **11.1**. That clears the 10 floor, but it is
       the same amber that marks an exception in the panel heads — two ambers on one dashboard
       meaning different things. The mitigation is a size and place difference (`--warn` is only
       ever a 6px dot with 11px text in a panel head; export is a large bar in a panel body) and
       it is enough. It is the FIRST thing to re-measure if either token moves again.

       ⚠ THE .22 COMPOSITES WERE MEASURED, BECAUSE NOBODY HAD EVER MEASURED THEM. Two polygon
       fills on /interconnectors draw these colours at `opacity:.22` (the net-flow chart and the
       per-link traces), which is a DIFFERENT COLOUR from the token and had never been checked on
       either palette. Composited over --surface:
         light  current in 1.39:1 / out 1.46:1, the two ΔE 18.8   →   A3 in 1.39 / out 1.37, ΔE 17.1
         dark   current in 1.39:1 / out 1.27:1, the two ΔE 26.8   →   A3 in 1.34 / out 1.45, ΔE 20.1
       A3's WEAKEST composite is 1.34 against the current pair's weakest of 1.27, so the change
       improves the floor in dark and holds it in light, and the two washes stay far apart in
       both. **The opacity stays at .22.** These are grounds under a trace, not graphical objects,
       which is why 1.3–1.5:1 is the design and not a failure — the same class as `.freqband` at
       5.5% ink.

       —— SUPERSEDED, kept because it records why the second token existed for ninety revisions:
       ⚠ v0.57: `--flow-out-ink` IS A SECOND TOKEN, NOT A CHANGE TO THE FIRST.
       In dark, `--flow-out` on the panel surface is 4.01:1 — under the 4.5 floor for the five
       places it colours TEXT (the export figures in the link table, and the EXPORTS FROM GB
       label). The obvious fix, lightening `--flow-out`, would also lighten the export BARS, and
       a fill has no business changing to satisfy a rule about text: WCAG asks 3:1 of a
       non-text mark and the bars clear that comfortably. Worse, moving a series colour is the
       one change this project has a standing rule against, because the palette is ordered for
       colour-blindness safety and a nudged red is a nudged red everywhere.
       So the MARK keeps its colour and the WORDS get their own, lightened only as far as 4.5
       requires. In light they are the same value, because light already passes. */
    --flow-in:#0F6F70; --flow-out:#A75001; --flow-zero:#D9D9D9;

    /* ---- Temperature against seasonal normal (v1.24, rev 118, candidate 43) ----
       ⚠ ALIASES OF THE FLOW PAIR, NOT USES OF IT. The values are identical today and the
       indirection still earns its place: `--flow-*` is named for interconnector direction, and a
       future decision about how an import reads has no business changing how a warm day reads.
       This is the same argument that produced `--flow-out-ink` — one token cannot answer to two
       jobs — and it costs two declarations per theme.
       ⚠ v1.51 (rev 146): THE FUTURE DECISION THIS NOTE ANTICIPATED HAS NOW ARRIVED, AND THE
       INDIRECTION EARNED ITS PLACE EXACTLY AS WRITTEN. `--flow-out` moved from the oxide red
       #8C2E1F to the orange #A75001 (light) / #D48E63 (dark) because Kyle found the export bar
       reading as the gas bar beside it. **The temperature pair did not move with it**, which is
       the whole reason it was declared as literals rather than `var(--flow-*)`: how an import
       reads and how a warm day reads are two questions, and this rev answered only one of them.
       The values below are therefore UNCHANGED and are no longer the same as the flow pair.
       ⚠ AND A FINDING THAT FOLLOWS FROM THAT, RECORDED AND NOT ACTED ON: `--temp-warm` #8C2E1F
       is now the last holder of the value the flow pair just left, which means it is still
       ΔE 4.6 from `--c-gas` and still byte-identical to `--bad`. It sits on /demand's temperature
       tile, a different page from the gas bar, so it is not the collision Kyle reported — but it
       is the same value. **The author has not asked for this and it is not filed as a candidate.**
       ⚠ THE SUPERSEDED NOTE, kept for the measurement: this pair used to alias `--flow-out-ink`
       rather than `--flow-out` because it is text. On the tile surface --flow-out was 4.01:1 in
       dark and failed the 4.5 floor; --flow-out-ink was 4.52:1 and passed. That token is retired
       at v1.51 — see the diverging-pair note above. --temp-cool is 5.95:1 light and 5.75:1 dark.
       ⚠ COLOUR IS NOT THE CARRIER. The figure already reads "+1.6 vs normal" with an explicit
       sign; the colour makes a scannable row out of a line that was already complete. */
    --temp-warm:#8C2E1F; --temp-cool:#0F6F70;

    /* ---- Embedded solar: NOT a 9th categorical hue ----
       Gold cannot be one. At any lightness where it still reads as gold it sits outside the
       categorical band (L 0.845 against a 0.77 ceiling in light mode) and cannot reach 3:1 as
       a fill; in dark mode, whose band is 0.48-0.67, no gold qualifies at all - a search over
       ~1,500 candidates returned zero. That is the documented "9th series" case, and the
       remedy is composite encoding rather than a hue slot. So the band is identified by
       TEXTURE (45 degree hatch) + a solid OUTLINE that does carry contrast (4.9:1 light,
       9.7:1 dark) + a direct label. The pale fill is decorative, and its lightness does useful
       work: it reads as estimated rather than metered, which is exactly what it is. ---- */
    --solar-line:#8A6D0B; --solar-fill:#F7E9B0;
    /* ---- v2.15 (rev 185): --solar-face, THE SOLAR FILL ON /power-stations, AND IT IS A STATED
       EXCEPTION RATHER THAN AN OVERSIGHT. Kyle, 23 September 2026, with the contrast measurement in
       front of him: the swatches, the cards and the capacity track on that page take the squares'
       amber #F5B417 in LIGHT; dark keeps #E3BE4A and gains nothing measurable from a change (amber
       on the dark ground reads 8.95:1 against today's 9.19:1). --solar-line is untouched in both
       themes and is still the one line colour everywhere.
       ⚠ THE FACE IS NOT ASKED TO CARRY CONTRAST, AND MY FIRST OBJECTION MEASURED THE WRONG THING.
       I reported #F5B417 at 1.84:1 against a 3:1 floor and recommended against it; that holds a
       FILL to a floor this sheet's own solar pattern never asks a fill to meet. The note above
       states the remedy for exactly this case — texture, plus a solid OUTLINE that carries the
       contrast, plus a direct label — and every place this token fills on that page already has
       the outline: 1.5px var(--line) on the swatches, 2px on the capacity track, and in light
       var(--line) resolves to the ink. Measured: the fill-vs-outline boundary IMPROVES, 3.72:1 ->
       9.94:1, because today's darker gold sits close to its own near-black edge; outline vs ground
       18.26:1. Nothing to add, one value to change.
       ⚠ AND THE CEILING IS ESTABLISHED, NOT ASSERTED — DO NOT COME BACK WITH A DARKER GOLD. At
       solar's hue no gold clears 3:1 as a fill above about L 0.634 on #F3EEDF or L 0.67 on white;
       two independent searches agree (28,534 candidates swept here returned L 0.644 as the
       brightest passing value). #B18103 at 3.01:1 was declined on its MERITS — it is most of the
       way back to the dull gold this change existed to escape — so a re-proposal has to argue
       against that reason rather than restate the contrast number.
       ⚠ NO HARNESS CLAUSE READS THIS TOKEN OR --c-solar. paletteSafety walks the eight categorical
       hues and solar is absent from that list by design, because it was never a categorical hue;
       the solar clauses name --solar-line and --solar-fill, neither touched. The same exception is
       recorded a second time in tools/_squares_engine.mjs beside the FUELS colour, where solar is
       the one fuel of thirteen that misses 3:1 against the stage (1.59:1 lit, 2.99:1 shaded) and a
       2-unit #141414 outline at 15.9:1 plus the fuel's name in text carry it. ---- */
    --solar-face:#F5B417;
    /* ============================================================================
       ⚠ v0.50: THE HATCH IS DEFINED ONCE, IN TWO SIZES, AND NOWHERE ELSE.
       It was written out as a literal in NINE places — five CSS rules and four JavaScript
       template strings — in two different geometries (3/4.5 for bars, 2/3 for swatches) and,
       in one of them, at the opposite ANGLE (135deg on the glance card, 45deg everywhere
       else). Nobody had noticed the angle, which is what nine copies of a value guarantees.

       Reported as "difficult to resolve on screen", and rendering it at shipping size shows
       exactly why. The bar geometry put 1.5px of ink every 4.5px on a 10px-tall track: at 45°
       that is roughly four strokes across the bar's height, close enough together that they
       average into a muddy gold rather than reading as a texture — and with HARD colour stops
       the stroke edges land differently on each pixel row, which is the shimmer.

       Two changes, both measured against renders at 1× and 2× in both themes:
         - THE PERIOD NEARLY DOUBLES, 4.5px → 8px, with the ink up 1.5px → 1.7px. Fewer, more
           separated strokes. A hatch has to be counted, not just seen.
         - THE EDGES ARE SOFT. A 0.6px ramp either side of each stroke, which is what stops the
           aliasing. Hard stops on a 45° line resample differently on every row; the ramp lets
           the browser antialias, and the shimmer goes.

       TWO SIZES, DELIBERATELY. A 9–10px legend swatch cannot hold an 8px period — it would
       show one diagonal slash and read as a slash. The small variant keeps the same angle and
       the same soft edge at a 5px period, so it reads as the same mark at a smaller size
       rather than as a different one.
       ============================================================================ */
    /* ⚠ v0.51: 135deg, NOT 45deg — AND THE ANGLE IS NOT THE UNIT IT LOOKS LIKE.
       Reported as the hatch running the other way from the 24-hour chart, and it did. Measured
       off the rendered pixels: the chart's stripes run "/" (bottom-left to top-right) and every
       CSS one ran "\". They are mirror images.

       The cause is that `45` means two different things in the two systems, and rev 45 made
       them agree on the NUMBER while leaving the PICTURES opposite:
         - A CSS gradient angle is measured clockwise from "to top" and describes the gradient
           AXIS. The stripes are perpendicular to it, so `45deg` paints "\".
         - An SVG `patternTransform="rotate(45)"` rotates the tile clockwise in a y-down
           coordinate system, and the tile's line is vertical, so it paints "/".
       Two 45s, two diagonals. ⚠ AND THE HARNESS ASSERTED THEY MATCHED, because it compared the
       numbers. A check that compares coordinates in two coordinate systems is not comparing the
       pictures — `solarHatchDirection` measures the rendered stripes now, by sampling the
       painted pixels, which is the only thing that can see this.

       ⚠ WORSE, AND WORTH OWNING: the glance card WAS at 135deg before rev 45, and rev 45
       "fixed" it as the odd one out. It was the only one that agreed with the chart. Nine
       copies of a value made the majority look like the rule, and the majority was wrong. */
    --hatch-est:repeating-linear-gradient(135deg,transparent 0 5.1px,
                var(--solar-line) 5.7px 7.4px,transparent 8px);
    --hatch-est-sm:repeating-linear-gradient(135deg,transparent 0 2.8px,
                   var(--solar-line) 3.2px 4.6px,transparent 5px);
    /* ⚠ v0.98 (rev 92): --night #FAFBFC -> #F7F9FC, AT THE AUTHOR'S REQUEST — AND THE REQUEST
       WAS MADE ON A FIGURE OF MINE THAT WAS ON THE WRONG SCALE, WHICH IS RECORDED HERE SO THE
       NEXT PERSON DOES NOT REPEAT IT. He asked whether night should have a colour at all; I
       reported it at ΔE 1.6 from the surface and called it invisible. That was CIE76. **This
       project measures washes and chrome in OKLab ×100**, where night was 1.25 against day's
       2.81 — a deliberate 2.25:1 asymmetry with a full argument in the chart's own source, not
       an oversight. Three scales are in play in this file and all three were called "ΔE"; see
       the Index §2a convention added at rev 92.
       ⚠ SO THIS IS A TUNING OF A MEASURED ASYMMETRY, NOT A FIX. The argument stands: day is the
       emphasised half, night must not read as "no data here" (rev 27's reason for dropping the
       night wash in the first place), and the pair must separate by HUE as well as lightness.
       All three survive. Night goes 1.25 -> **1.91** from the surface, day is untouched at 2.81,
       so day stays the emphasised half at 1.47:1 rather than 2.25:1. The seam between the two
       washes — which the source note says is what actually matters — IMPROVES, 2.41 -> 2.64.
       Night stays cool at hue 258° against day's 90°, --mark holds 16.82:1 over it, and the
       distance from the solar hatch is unmoved (9.5 -> 9.3 against `--solar-fill`, which is the
       token the harness reads; measuring `--solar-line` instead gives a different number and
       briefly convinced me the old figures were wrong — they were not).
       The author's report is the evidence that 2.25:1 was tuned too far: he looked at the chart
       and asked whether night had a colour. Was: #FAFBFC. */
    --daylight:#FFF9E9; --night:#F7F9FC;
    /* ⚠ rev 155 (C-07): THE FOOTER STOPS HAVING A GROUND OF ITS OWN. #DFE5F2 was the third of
       candidate 87's three same-hue blue-greys; the footer now sits on the page ground and is
       carried by a card at Phase 2 (D18). Declared once — deleted from the dark block. */
    /* rev 165 (Phase 5, G-04): `--foot-bg` DELETED — measured at zero consumers (the footer sits on --paper by name since D18). The other Phase 1 aliases still carry 1–160 consumers each and stay; see the guide v2.00's finding. */

    --head-ink:var(--on-blue);   /* rev 155 (C-07): one ink for everything on the blue */
    /* `--warn` was 4.11:1 on the surface — the amber used by the data-status vocabulary's
       `st-warn` chip, which is the one mark on the page that exists to be noticed. */
    --ok:#0A7A3C; --warn:#98671B; --bad:#8C2E1F;
    --bad-bg:#FBE9E5;
  }
  html[data-theme="dark"]{

    /* ========================================================================================
       REDESIGN PHASE 1 — THE DARK PRIMITIVES (rev 155). ⚠ DARK IS ITS OWN SET OF STEPS AND
       NEVER A FLIP OF LIGHT — the rule this file has held since v0.98, and the source sheet
       carries it too. Only the 27 tokens that actually differ by theme are restated here; the
       aliases (`--page`, `--surface`, `--foot-bg`, `--ink-dim`, `--ink-mute`, `--blue-2`,
       `--blue-line`, `--accent-tint`, `--accent-line`, `--grey-bg`, `--grey-line`, `--mix-grid`,
       `--head-ink`) are declared ONCE on :root and resolve through these, so they are DELETED
       from this block rather than restated. Leaving them would outrank the alias and keep dark
       on the retired palette. ======================================================================================== */
    color-scheme:dark;   /* see the note on the light declaration — this is what makes the
                            canvas, scrollbars and form controls follow the CHOSEN theme */
    --paper:#0F111A;
    --card:#171922;
    --card-2:#1B1E27;
    --tint:#1D202A;       /* rev 156 (7h): was #232733 — dark --ink-4 4.46:1 and --flow-in 4.23:1 on it */
    --ink-2:#CFCCC4;      /* 10.9:1 on card */
    --ink-3:#A5A399;      /* 6.9:1          */
    --ink-4:#8E8D84;      /* 5.25:1 on card, 5.64:1 on paper — dark clears where light is tight */
    /* ⚠ THE DARK EDGE IS OPTION B (#A5A399), NOT THE INK. The #ECE8DD edge measured 14.3:1 and
       GLARED on long pages; this is 6.9:1 on card and 7.4:1 on paper — an edge a reader finds
       without one that shouts. `--shadow-c` follows it for the same reason a black shadow was
       rejected: measured 1.1:1 on dark paper, i.e. invisible. */
    --line:#A5A399;
    --line-mid:#4A4F5C;
    --line-soft:#2A2F3C;
    --shadow-c:var(--line);
    --blue-soft:#16223A;
    --on-blue-soft:#BFD6FF;
    --on-status:#0F111A;
    --warn-text:var(--warn);   /* the dark --warn already clears as text; no second step needed */
    --band-ok:#1A3325;
    --band-warn:#3A3219;
    --band-bad:#3D2320;
    --green-soft:#12291F;
    --on-green-soft:#B5EBCB;
    --amber-soft:#2B2410;
    --tag-blue:#22304F;
    --tag-green:#1D3A2A;
    --tag-amber:#3F3119;
    --tag-violet:#352647;
    --overlay:#FFFFFF1A;
    --logo-tile-edge:var(--line);  /* rev 207: the tile's grey edge in dark (#A5A399); its ultramarine is the light value */
    /* ---- DARK TAKES ITS OWN BLUE, and the reason is a measurement rather than a preference.
       Interconnectors are periwinkle #6f87f9 in dark mode, so EVERY saturated blue tested for
       the chrome landed on top of them — ΔE 1.4 to 10.0 across the whole candidate set. Going
       lighter runs into that series; going toward cyan runs into wind; going violet runs into
       nuclear. The way out is not hue but CHROMA: a deep, low-chroma blue reads unmistakably as
       blue structure while sitting outside the saturated band the series occupy. #2B3A78 is
       ΔE 14.3 from its nearest series (hydro, not interconnectors — the low chroma is what
       moves it), carries white text at 10.6:1, and works as a border at 1.65:1 against the
       surface.
       ⚠ v1.04 (rev 98): 13.9 -> 14.3 because dark --c-hydro moved to #2069A5. The nearest
       series is STILL hydro and the separation IMPROVED, so the argument is unchanged and
       slightly stronger — but the figure is restated rather than left describing a colour that
       is no longer there. ⚠ This claim is one of the seven the design guide re-derives, so a
       stale figure here fails the guide's own method table; that is what caught it. This is why dark is not, and must never be, an automatic flip of light. ---- */
    /* ⚠ rev 155 (D4): #2B3A78 -> #175EFE, AND THE ARGUMENT ABOVE IS SUPERSEDED RATHER THAN
       WRONG. It reasoned that dark needed a deep LOW-CHROMA blue because every saturated blue
       landed on periwinkle interconnectors. D4's search answered the same question with the
       same metric and found a saturated value that clears: #175EFE is 12.84 OKLab x100 from
       every dark series, against the floor of 10 — the earlier candidate #4A66FF was 9.68 from
       imports, which is the failure this note predicted. One blue now serves both themes.
       ⚠ IT IS 3.39:1 ON --card AS A FILL, so white on it is for CHROME, not for small text;
       `--blue-text` is the text job in dark and is unchanged in purpose.
       ⚠ `--blue-2` AND `--blue-line` ARE DELETED FROM THIS BLOCK, not merged: they are declared
       once on :root and resolve through `--blue-soft` and `--line`, which are theme-scoped here.
       A copy left behind would outrank the alias. `--blue-ink` is live-only and stays.
       ⚠ AND `--blue` ITSELF IS NOT RESTATED HERE. D4 gives both themes the same #175EFE, so the
       :root declaration serves dark too and a second copy would be a duplicate that could drift.
       This is the first time in this file's life that one blue has served both themes; the note
       above is kept precisely because it records why that was impossible with the old value. */
    --blue-ink:#3A4A92;
    /* 8.7:1 on the dark surface, and ΔE 13.6 from periwinkle interconnectors — the lightest
       blue that is unmistakably interface rather than data. */
    --blue-text:#9AAEFF;   /* rev 155 (D4): re-stepped for the new blue */
    /* Same hue and chroma as the light value, lifted 0.04 in L: #C94A4A reads 3.81:1 on the
       dark surface and this reads 4.49:1, and a muted red on a near-black ground goes muddy
       before it goes dim. ΔE 12.4 from dark gas, 10.9 from dark Other, 33.8 from the dark blue. */
    /* ⚠ v1.54 (rev 149): #D75755 -> #EC6A67, THE DARK VALUE ONLY, AND IT IS THE DECISION REV 98
       NAMED AND LEFT UNTAKEN. Kyle took it 14 September 2026.
       ⚠ LIGHT --red #C94A4A AND --mark-red ARE BOTH UNTOUCHED, so the brand mark is byte-identical
       in both themes. That is what made this the cheap escape: the identity device does not move.
       ⚠ IT IMPROVES EVERY CONSTRAINT THE HARNESS HOLDS THIS TOKEN TO, measured in OKLab x100 —
       the metric `redIsMuted` and rev 18's 11.5 are both stated in:
         chroma           0.1621 -> 0.1619   (must stay <= 0.20; essentially unchanged)
         contrast surf    4.49   -> 5.69     (and 4.83 -> 6.12 on --page)
         nearest series   gas 12.4 -> other 15.4
         from --blue      33.8   -> 38.5     (floor 30)
         from the carbon ramp  7.0 -> 7.8    (floor 6)
       ⚠ AND IT UNBLOCKS --c-gas, which is the whole reason for touching it: with the red here,
       a gas value exists that clears the 3:1 graphical floor AND stays clear of the red. */
    --red:#EC6A67;
    --mark:#E9EBF4;
    /* rev 155: --accent, --accent-dark, --accent-tint and --accent-line are ALL deleted here.
       Each resolves through a theme-scoped token (--blue, --blue-ink, --blue-soft, --line-soft),
       so the single :root declaration is already correct in dark. */
    /* Panel heads get their own token rather than reusing --accent: inverting them to
       near-white in dark mode is legible but glares. A raised dark bar reads as a header
       against the panel body without shouting. */
    /* rev 155: --head-bg deleted here too — it follows --blue, which now serves both themes. */
    /* Same fix, other direction: #727C93 on the dark surface was 4.19:1. Lightened to the
       minimum that clears 4.5 on both the surface and the page ground. */
    /* ⚠ rev 155 (C-08): #E9EBF4 -> #ECE8DD, the warm ink; 14.3:1 on --card. `--ink-dim` and
       `--ink-mute` are deleted here and aliased once on :root onto --ink-3 / --ink-4. The note
       above records the contrast fix that set #788298 and is kept: the requirement it states —
       clear 4.5 on the surface AND the page ground — is what --ink-4 is measured against here
       (5.25:1 card, 5.64:1 paper), so the rule survives its value. */
    --ink:#ECE8DD;
    /* rev 155 (C-07): all four deleted here — aliased once on :root onto the dark primitives
       at the top of this block, which carry these same two surfaces (#0F111A / #171922). */
    /* rev 155 (C-07): --mix-grid deleted here — aliased once on :root onto --line-soft. */
    /* v0.48: the floor under the balance arrows' fade. See .fb-arrow linearGradient below —
       0 in light, where a hairline running out into white still reads as a line; .3 here,
       where it does not. Dark-mode only, one number, so the two themes cannot drift. */
    --fb-tail:.3;
    /* Unchanged in dark. The green→orange ramp was checked against #1A1A22 as well as #FFFFFF
       and every step clears 3:1 against both — 3.40, 8.02, 11.60, 7.34, 4.86 on dark. A ramp
       that works in both themes without a second set is one fewer thing to keep in step. */
    /* The WASHES do need a second set, for exactly the reason the fuel steps do: the same alpha
       over a near-black surface lands somewhere different. 34% on #171922. Measured: adjacent
       zones separate by 5.0–8.6 ΔE, and --mark keeps 6.0:1 or better across all five.
       ⚠ v0.66: recomputed from the toned ramp at the same 34%. Adjacent zones separated by
       8.1–11.1 ΔE (CIEDE2000) and --mark kept 6.0:1 at its worst step.
       ⚠⚠ v0.98 (rev 92): RESTORED, for the reason set out in full in the light block — v0.76
       put --ci1…5 back to their original brightness and these were never recomputed, so the
       dark zones were 34% of a ramp that no longer exists. 34% of the LIVE ramp on #171922
       lands on the pre-v0.66 set EXACTLY, all five bands, both themes. ⚠ ciw5 is #5a331d and not the
       #5a321d my own arithmetic first produced: Python's round() and the browser's Math.round()
       disagree on a .5 in one channel, and the value that ships is the one the BROWSER computes,
       because the browser is what composites the real thing. The scenario tolerates one channel
       step for exactly this reason and still measures 0 here.
       Adjacent separation 8.1–11.1 → **8.3–11.9**. ⚠ --mark measures **5.99:1** at its worst
       step, which is what it measured before this change too: the "6.0:1 or better" written
       above is a rounding of 5.99 and always was. Recorded rather than left to be rediscovered
       as a regression by whoever next reads the floor as a hard 6.0. */
    --ciw1:#2a3a1f; --ciw2:#415128; --ciw3:#63581e; --ciw4:#5f4422; --ciw5:#5a331d;
    /* ⚠ v1.48 (rev 143), CANDIDATE 13: THE BAND COLOURS NOW HAVE A DARK VARIANT, AND IT IS THE
       VALUE THEY ALWAYS HAD. Until this rev one value served both grounds, which is why the
       three that failed on light could not be fixed without breaking dark. These are the
       originals, restated here so the dark theme is unchanged by the light-ground fix:
       against the dark surface #171922 they run --ci1 3.45, --ci2 8.13, --ci3 11.75,
       --ci4 7.44, --ci5 4.93 — every one clear of the 3:1 floor.
       ⚠ --ci1 AT 3.45 IS THE TIGHT ONE HERE and is left as it is: it clears the floor, and
       moving it would be a change to the one band that never failed anywhere. */
    --ci1:#4f7a18; --ci2:#93bf34; --ci3:#f7d117; --ci4:#eb9622; --ci5:#dd6414;
    /* Dark mode takes its OWN steps from the same hues — not an automatic flip, because a
       colour that clears 3:1 on white rarely clears it on near-black.
       Deliberate trade-off: these steps hold CVD separation at ΔE 15.4 (deuteranopia) /
       19.3 (normal) but leave hydro, storage and gas at 2.6–2.9:1 against the dark surface,
       just under the 3:1 mark. A variant clearing 3:1 outright exists, but only by dropping
       CVD to 9.6 and normal-vision to 15.2 — i.e. right on the floor. Low contrast against
       the background is fully recoverable here (every series carries a direct MW and %
       label, and the table view lists every fuel type); two fills collapsing into each other
       for a colour-blind reader is not. So the CVD margin wins, and the required relief
       channel is shipped rather than assumed. */
    /* ⚠ v0.96 (rev 90): THE DARK TWINS ARE DERIVED AND MEASURED, NEVER FLIPPED — §3's rule.
       Each keeps its light partner's hue (within 3°) and lifts L*, which is the relationship the
       existing dark set already had (+2 to +7 L*, hue held). Solved against the DARK palette's
       own floors on the dark ground, not the light one's.
       ⚠ AND IT EXPOSED SOMETHING THE LIGHT SET HAD HIDDEN: the dark array's worst pair was
       ΔE 6.7 (Nuclear/Hydro) against the light array's 9.9 — the two themes were never equally
       safe, and nothing measured the dark one on its own terms. Replacing three of its members
       takes it to ΔE 16.8 and its worst adjacent greyscale from 1.30:1 to 1.80:1. The remaining
       floor, 2.66:1 on Gas, is untouched and is now the dark set's binding constraint. */
    /* ⚠ v1.04 (rev 98): --c-hydro #0B67A9 -> #2069A5. 2.94:1 on the dark surface, under the
       3:1 graphical floor; now 3.02 surface / 3.25 page. ΔL +0.006 — very nearly invisible,
       which is the point: the fix is a floor clearance, not a restyle. Hue held. */
    --c-nuclear:#7942DC; --c-biomass:#5A9E65; --c-hydro:#2069A5; --c-wind:#1EB3B6;
    --teal-pale:#152A2B;
    /* Dark takes its own values for all three, never a flip of light. #35C8C9 is 8.56:1 on the
       dark surface (the light value would be 2.1:1 there and unreadable) and ΔE 11.9 from dark
       wind. --bal-pos 7.59:1, ΔE 19.0 from dark biomass; --bal-neg 6.79:1, ΔE 14.9 from --red
       and 33.9 from dark gas — the separation from gas is much easier here than in light,
       because a red light enough to read on near-black is nowhere near a saturated fill. */
    --teal-text:#35C8C9;
    /* --bal-pos:#39C08D; --bal-neg:#F2807A;  (retired v0.51 — see the light block) */
    /* ⚠ v1.04 (rev 98): --c-storage #94436D -> #984C72 (2.74/2.94 -> 3.01/3.24, ΔL +0.020).
       ⚠ AND DARK --c-gas IS DELIBERATELY LEFT FAILING AT 2.66/2.86. IT HAS NO SOLUTION WHILE
       --red STAYS PUT: reaching 3:1 needs L +0.03, which lands it ΔE 9.4 from the dark --red,
       under the 11.5 rev 18 examined and rejected. The escape is to move the DARK --red only,
       #D75755 -> #EC6A67 (ΔE 15.3 from the new gas, and it improves the red's own contrast
       4.49 -> 5.69, leaving light --red #C94A4A and therefore the brand mark untouched) —
       that is a decision about the six-place identity device and it has NOT been taken.
       ⚠ SO THE HARNESS RECORDS THIS AS A KNOWN, ACCEPTED FAILURE WITH THIS REASON RATHER THAN
       PASSING IT. An accepted failure that is not written down becomes a passing test. */

    /* ⚠⚠ v1.54 (rev 149): THE EXCEPTION ABOVE IS CLOSED, BY TAKING THE DECISION IT NAMED.
       The rev 98 note was correct in every particular and its escape route was the real one: move
       the DARK --red. Kyle took that decision on 14 September 2026 and `--red` is now #EC6A67, so
       gas can finally clear the graphical floor. **This entry closes candidate 13.**
       ⚠ #B3241F -> #C82823, chosen for MARGIN rather than for the maximum on any one constraint:
         contrast          3.16 on --surface, 3.39 on --page   (3:1 floor, both dark grounds)
         from the new --red  14.5   (floor 10; rev 18's stricter bar is 11.5)
         nearest other series  --c-storage at 12.9
       A window exists from #C32722 to #CF2A24. #C32722 maximises the red separation at 15.4 but
       sits at 3.03 contrast — one rounding step from failing again — so the middle is taken.
       ⚠ HUE AND SATURATION ARE HELD. This is the same red, lighter; it is not a new gas colour.
       ⚠ AND A CORRECTION THAT COST A RUN, KEPT BECAUSE IT IS THE USEFUL PART: earlier in this rev
       I claimed the rev 98 note rested on a wrong number, having measured "12.6" in **ΔE2000**.
       This project's floor is **OKLab x100**. In OKLab the same pair reads 9.8 against a floor of
       10, and `redIsMuted -> clearOfEverySeriesDark` failed in one run. The ΔE2000 implementation
       had been validated 8/8 against Sharma, Wu & Dalal (2005), which proved the arithmetic and
       nothing about whether it was the right question. **VALIDATING THE IMPLEMENTATION OF THE
       WRONG METRIC IS NOT VALIDATION.** Before contradicting a clause, read what it measures.
       ⚠ REV 98 PREDICTED THE NEW GAS WOULD SIT AT ΔE 15.3 FROM #EC6A67. The sweep lands #C32722 at
       15.4. That note was right about everything. */
    --c-storage:#984C72; --c-imports:#6F87F9; --c-gas:#C82823;   --c-other:#B88B0D;
    /* v1.51 (rev 146): the series inks, dark. See the light block for the argument and the
       measurements. Four are the fill repeated (wind, biomass, imports, other already clear 4.5
       on all three dark grounds); four are solved. No solar ink — solar is not a categorical hue. */
    --c-gas-ink:#F94239;     --c-nuclear-ink:#A664FE; --c-wind-ink:#1EB3B6;   --c-biomass-ink:#5A9E65;
    --c-imports-ink:#6F87F9; --c-hydro-ink:#518CBE;   --c-other-ink:#B88B0D;  --c-storage-ink:#C36B96;
    --flow-in:#4D9491; --flow-out:#D48E63; --flow-zero:#383846;
    /* v1.24 (rev 118): the text-safe red, measured 4.52:1 on --surface. ⚠ v1.51 (rev 146): it was
       `--flow-out-ink`'s value and that token is now retired; this literal is its only remaining
       holder, and it is DELIBERATELY left where it is — see the note on --temp-warm above. */
    --temp-warm:#C86458; --temp-cool:#2FA3A4;
    --solar-line:#E3BE4A; --solar-fill:#4A3F1A;
    /* v2.15 (rev 185): dark's face is today's gold, unchanged. NOT MIRRORED, MEASURED — the amber
       gains nothing here (8.95:1 against 9.19:1 on the dark ground) and dark's outline is already
       weak against the current gold (1.41:1), which is not a fault amber would introduce but is
       not one to spread either. See the light block for the whole argument. */
    --solar-face:#E3BE4A;
    /* ⚠ v0.98 (rev 92): --night #1A1D28 -> #1B1F2E, the dark twin of the light change above,
       SOLVED ON THE DARK GROUND rather than flipped. Dark's day wash is 4.62 from its surface
       where light's is 2.81, so matching the light hex would have produced a different
       relationship, not the same one. Night goes 1.77 -> 2.94, giving day:night 1.57:1 against
       light's 1.47:1 — the two themes keep the same asymmetry, which is the property the
       original note set out to hold ("day stays roughly 2.5x the departure in BOTH themes";
       it is now roughly 1.5x in both). Seam 3.26 -> 3.15, --mark 13.77:1, hue 273° and still
       cool. Was: #1A1D28. */
    --daylight:#252325; --night:#1B1F2E;
    /* rev 155 (C-07): --foot-bg deleted here — the footer takes the page ground in both themes. */
    /* rev 155 (C-07): --head-ink deleted here — aliased once on :root onto --on-blue. */
    /* ⚠ rev 152, candidate 8: `--ok` WAS #4EC17E AND IT WAS BELOW THE FLOOR IN THIS THEME ONLY.
       Measured on this project's own metric — OKLab x100, floor 10, NOT dE2000 — the dark Live
       dot sat 9.64 from biomass and 10.14 from wind, i.e. one failing and one inside rounding.
       The LIGHT value clears everything and is untouched.
       #4ECA7A was chosen from 6,848 greens that clear 10 against all eight dark series colours
       AND 4.5:1 on the panel, ranked by how LITTLE they move rather than how far they clear —
       sorting on separation picks the brightest greens in the gamut, 17 OKLab from what the site
       had. It clears BOTH problem colours by a similar margin (biomass 12.21, wind 12.42) rather
       than fixing one and grazing the other, moves 2.81, and lifts panel contrast 7.56 -> 8.22:1.
       ⚠ THE METRIC IS THE POINT: measuring dE2000 once returned a confident 12.6 for a colour
       that was actually at 9.8, and it shipped. `tools/_r152cand8search.mjs` re-runs the search
       using the harness's own `oklab` function, lifted verbatim rather than rewritten. */
    --ok:#4ECA7A; --warn:#D5A94E; --bad:#E0736A;
    --bad-bg:#2B1614;
  }

  /* The measure, named once: `--measure` caps a line's length, which is a property of READING
     and must never be re-derived from the frame's width.
     ⚠ v0.64: `--measure-block` is gone with the centred block it capped. Two nested caps — an
     820px box centred in the frame, holding 74ch paragraphs aligned left inside it — put the
     text at neither the block's centre nor the page's left edge. One cap, one alignment. */
  :root{ --measure:74ch; }

  /* THE RED IS A DEVICE, NOT A DECORATION.
     v0.11 put one red segment at the right end of the header rule. v0.12 keeps it that short and
     applies the SAME device in three more places — always the terminal of a blue structural
     line, always at its right end, always the same proportion. Repetition is what turns a mark
     into a system: four instances of one idea read as intent, where four different red details
     would read as scattering.
       · the header rule            — the page opens
       · the footer rule            — and closes. The two bookend the page.
       · the hero tile's inset bar  — the same device on the one figure the page is for
       · the panel tabs             — the right end of each section header (v0.15)
       · the About dialog's header  — the terminal of its bottom edge (v0.15)
     The masthead trace's final upstroke is the sixth red thing on the page and is NOT one of
     these: it is the mark, and from v0.22 it takes --mark-red rather than --red. The mark is
     identity and has to hold at 16px in a browser tab; the device is chrome and was muted so it
     would stop reading as a second brand colour. Two jobs, two tokens, and the masthead and the
     favicon stay in step with each other rather than with the chrome.
     The total is still about 5% of the accent treatment. --red-tail is the shared length so the
     four cannot drift apart, and the harness measures the share rather than trusting it.

     v0.22: HALVED, 68px to 34px, and the tab's cap from 6px to 4px. The colour change alone was
     not enough. A saturated rectangle immediately beside a strong blue is a blue-and-red block
     whatever the red is, and the block is the cue — so the red is now both quieter and smaller.
     Desktop share falls from 5.1% of the accent treatment to about 2.6%, phone from 9.4% to
     about 5.6%. The cap stays at 10%: it is a ceiling, not a target, and the point of leaving it
     where it is is that the device cannot creep back up to it without the harness saying so. */
  :root{ --red-tail:34px; }

/* The phone value, under the same condition it always had. It sat inline in the dashboard's
   max-width:760px block; the condition moved with it. */
@media (max-width:760px){
  /* 68px of red on a 360px rule would be 19% and would stop being an accent. */
  :root{ --red-tail:20px; }   /* the proportion holds across breakpoints, not the pixel count */
}

  :root{
    --ctl-h:28px;            /* one height for every control in a panel header */
    --ctl-r:8px;             /* one radius — rev 159 (Guide §5): the button and segmented-control radius */
    --ctl-border:var(--line);      /* rev 155 (C-07): one edge colour for every control */
    /* ⚠ v0.52 — THE SELECTED SEGMENT IS NO LONGER A SATURATED SLAB. The contract since v0.31991
       was "active = white text on solid blue", inherited from the masthead's Simplified/Detailed
       control back when that WAS the loudest thing on the page and had to be. It has outlived
       that: on a panel header the same treatment puts `--blue` (#0000A8, the most saturated ink
       in the palette) next to a chart, and the reader's eye goes to the switch. Reported as
       "the content should stand out more than the buttons", which is the correct hierarchy
       stated plainly — a control that says which of two views you are in is not more important
       than the view.
       Level 2 of the same blue instead, with the label in `--blue-text`. That is a pairing the
       page already uses and has already validated — `.legend-key.is-on` and `.driver-key.is-on`
       both fill with `--blue-2` — so this is a control adopting the house treatment rather than
       a new colour being invented for it.
       ⚠ SELECTION IS STILL NOT CARRIED BY COLOUR ALONE. The fill is a filled HALF of a bordered
       track, so its position states the answer; `aria-checked` states it to a screen reader;
       and the two labels still differ in ink. Softening the fill removes weight, not meaning. */
    /* ⚠ rev 155, DECISION 7f (Kyle, 18 September 2026) — AN AMENDMENT TO D7, NOT A REVERSAL.
       D7 says the MW/GW switch stays quiet, and it still does: the fill is `--blue-soft`, never
       the accent, so `controlTone.theActiveFillIsNotTheAccentLight/Dark` still pass. Only the
       INK changes, and it had to.
       ⚠ THE MEASUREMENT THAT FORCED IT. Keeping the live `--ctl-on-ink:var(--blue-text)` through
       the merge drops `controlTone.theLabelStillPassesContrastLight` from **10.61:1 to 3.53:1**,
       under its own 4.5 floor — because `--blue-text` moved off the near-black navy #0000A8 to
       mid-blue #175EFE, NOT because of anything D7 asks for. Proved by elimination: backing the
       `--blue-2` alias out entirely and keeping #DDE4FF still measures 4.08:1, so the fill is
       not the cause.
       `--on-blue-soft` exists in the source sheet for exactly this pairing — it is the ink that
       sits on `--blue-soft` — and the live indirection simply predates it. Measured on the
       rendered control in both themes, with the harness's own WCAG function:
         light  #1b2e6b on #AEDBFF   **8.71:1**      (was 3.53:1 under the unamended merge)
         dark   #BFD6FF on #16223A   **10.78:1**
       ⚠ THE COST, PER A-8: `controlTone.theActiveLabelIsBlueTextLight/Dark` are repointed from
       `--blue-text` to `--on-blue-soft`, citing D7. No scenario is deleted. Seen and chosen from
       `Rev155_ControlTone_Mock.html`, which renders all four options on the real control.
       ⚠ THE NOTE ABOVE STANDS AND IS WHY THIS IS AN AMENDMENT: "the content should stand out
       more than the buttons" is still the rule being followed. A quieter fill with a legible
       label is that rule; a quieter fill with an illegible one is a different failure. */
    /* ⚠ rev 169 (v2.00), task 41 — Kyle, 20 September 2026: the selected segment takes the PROTOTYPE'S fill, the
       paper tint with the page ink on it, instead of the soft blue. The mechanics are untouched (one button, one
       slider, `data-flip`). Same reasoning as D7 and 7f — a preference set once should be the quietest thing in
       the masthead — carried one step further: no blue at all on a control that is not the primary action.
       Measured with the harness's WCAG function: light #141414 on #F3F0E6 17.4:1, dark #EEE9D8 on #1D202A 14.2:1.
       `controlTone` repointed from level two of the blue to the tint (A-8: named for what it asserts). */
    --ctl-on-bg:var(--tint);
    --ctl-on-ink:var(--ink);
  }
  @media (pointer:coarse){ :root{ --ctl-h:44px; } }

  /* ⚠ `--blue-pale` WAS NEVER DEFINED, IN EITHER THEME. Two rules ask for it — the CSV button's
     hover and the stack legend's selected key — and an invalid `var()` computes to `unset`,
     which for `background` means `transparent`. So neither had the hover ground it was written
     to have, silently, for as long as the token has been referenced. (The same trap the harness
     hit at rev 46 from the other side: an invalid var() cannot be detected by looking at the
     rendered value, because the rendered value is a legal one.)
     Defined here as the tint the palette already carries for exactly this job, so both themes
     get it from a value that has been through the contrast work rather than a new guess. */
  :root{ --blue-pale:var(--accent-tint); }
