/* ============================================================================
   THE TOKEN FILE. This is the ONLY place in web/ where a colour literal or a
   font size literal may appear. tests/test_design_tokens.py fails when one
   appears anywhere else, which is what stops the drift returning.

   BEFORE (measured 2026-08-04, reproduced by scripts/design_audit.py):
     99 distinct hex colours across web/, 36 of them inside index.html
     22 distinct font-size declarations
     195 inline style="..." attributes
     69 direct .style. assignments in JavaScript
     no stylesheet at all; one 415 line <style> block inside a 54 KB index.html

   THE COLOUR BUDGET, and how it is counted:
     15 chrome, accent and status colours   (the brief's "at most 15")
     13 data ramp steps, used ONLY for data (the brief's "semantic ramps")
     28 literals in this file, and none anywhere else.

   BRAND. Sampled from web/Logo-twinlink.eu_.png by scripts/design_audit.py:
   the logo is deep navy #002858 to #003070, a brand blue near #0060b8 and a
   leaf green near #78b028. The chrome is built from that navy, the accent from
   that blue and the second brand mark from that green. Nothing is invented.

   COLOUR VISION. Every data ramp is validated, not eyeballed, by
   scripts/design_ramps.py, which uses the dataviz skill's own six checks
   (OKLab deltaE under Machado protan and deutan simulation, lightness
   monotonicity, WCAG contrast against the panel surface). Results are written
   to data/cache/ui_shots/ramp_validation.json and asserted by
   tests/test_design_tokens.py, so a ramp cannot be edited into an unreadable
   state without a test going red.

   No em dashes.
   ========================================================================= */

:root {
  /* ---------------------------------------------------------------- colour
     1. CHROME. Neutral, derived from the logo navy. 8 values. */
  --c-canvas:      #071228;   /* behind everything, and the 3D scene void */
  --c-surface:     #0c1b33;   /* panel body, the surface every ramp is checked against */
  --c-surface-2:   #12253f;   /* raised block inside a panel */
  --c-line:        #2a3f5e;   /* a border you are meant to see */
  --c-line-soft:   #1b2c47;   /* a border that only separates */
  --c-ink:         #edf3fa;   /* primary text        15.4:1 on --c-surface */
  --c-ink-2:       #b3c2d4;   /* secondary text       9.5:1 */
  --c-ink-3:       #8196ac;   /* small print          5.7:1, still AA */

  /* 2. ACCENT. One accent, three steps, plus the second brand mark. 4 values. */
  --c-accent:      #3f92e0;   /* the accent                5.2:1 */
  --c-accent-deep: #155a9e;   /* the accent as a filled background */
  --c-accent-ink:  #bcdcf3;   /* text or icon ON the accent tint */
  --c-leaf:        #8db829;   /* TwinLink green, brand mark only, never data */

  /* 3. STATUS. Reserved. Never used as a data series. 3 values. */
  --c-good:        #7ed492;
  --c-warn:        #f0b03c;
  --c-bad:         #ff7a70;

  /* ------------------------------------------------------------- data ramps
     ONLY for data. Never for chrome.

     SEQUENTIAL, five classes, one hue (amber), light end to dark end.
     Validated ordinal: monotone lightness, every adjacent gap >= 0.06 OKLCH L,
     hue spread 11 degrees, light end clears the surface. Used for every
     "how much" layer, with the legend words carrying the direction. */
  --d-seq-1:       #8c5d32;   /* lowest  */
  --d-seq-2:       #af7033;
  --d-seq-3:       #d48434;
  --d-seq-4:       #fca34d;
  --d-seq-5:       #ffcd8d;   /* highest */

  /* TWO NAMED RAMPS FOR THE SATELLITE LAYERS, because one shared ramp was wrong for
     both of them. --d-seq above is amber, brown to cream, and GREENNESS WAS BEING
     DRAWN IN BROWNS: an NDVI map where more vegetation is a paler brown teaches the
     reader the opposite of what the number says. Every other "how much" layer keeps
     --d-seq; these two do not.

     GREENNESS. Pale to deep green, low NDVI to high, so the colour and the quantity
     agree. Strictly decreasing relative luminance, minimum adjacent gap 0.109 OKLCH L
     against the ramp floor of 0.06, worst adjacent pair 10.2 deltaE under protanopia
     and deuteranopia, ends 47.5 apart under deuteranopia.

     TEMPERATURE. Green to brown to red, cool to hot.

     WHY THE LUMINANCE CONSTRAINT EXISTS, and it is not decoration. Green and red both
     read as a brownish yellow to a deuteranope, and the midpoint of this ramp really is
     brown, so for those readers HUE CARRIES NO ORDER HERE AT ALL: the two ends and the
     middle collapse toward the same colour. What survives that collapse is lightness.
     So the five steps are strictly monotonic in relative luminance, 0.6712, 0.4327,
     0.2642, 0.1496, 0.0866, and the ramp still reads as an ordered scale when the hues
     go. tests/test_satellite_ramps.py recomputes those five numbers from these hex
     values and fails if the order breaks, so the constraint cannot be edited away by
     changing a colour here.

     Minimum adjacent gap 0.081 OKLCH L, worst adjacent pair 7.9 deltaE under simulated
     colour vision deficiency, ends 38.2 apart under deuteranopia.

     THE DARK END OF EACH RAMP SITS AT ABOUT 2.2 CONTRAST AGAINST --c-surface, which is
     lower than --d-seq manages, and that is accepted rather than overlooked. A ramp that
     has to span light to dark to stay readable under colour vision deficiency cannot also
     hold every step far from a dark surface. These cells are drawn on the MAP over aerial
     imagery, not on the panel, and the legend swatch carries a border and its class name
     in words. Lifting the dark end to fix the contrast was measured and rejected: it
     collapsed the top adjacent gap to 0.036 OKLCH L and 3.1 deltaE, which breaks the
     thing the ramp exists for.

     Derived with scripts/design_ramps.py's own OKLCH and Machado helpers. */
  --d-green-1:     #aaf599;   /* least green, low NDVI  */
  --d-green-2:     #71d66d;
  --d-green-3:     #57ab60;
  --d-green-4:     #3e834d;
  --d-green-5:     #225d36;   /* most green, high NDVI  */

  --d-therm-1:     #8deaa2;   /* coolest */
  --d-therm-2:     #a6b948;
  --d-therm-3:     #b88300;
  --d-therm-4:     #b74a00;
  --d-therm-5:     #ab0000;   /* hottest */

  /* ENERGY LABEL A to G. A regulated seven class scale, so the green to red
     hue order is kept rather than invented away. It is made READABLE under
     colour vision deficiency by being MONOTONE IN LIGHTNESS, so A to G still
     reads as an ordered scale when the hues collapse: A against G measures
     deltaE 32.6 under deuteranopia, against 12.2 for the ramp this replaces.
     Adjacent pairs measure 5.5 under deuteranopia, which is below the ramp
     floor of 6, and is legal here ONLY because the letter is printed beside
     every swatch and inside every label chip, so identity is never colour
     alone. Derived by scripts/design_ramps.py. */
  --d-label-a:     #69fa88;
  --d-label-b:     #a4da4a;
  --d-label-c:     #bdb940;
  --d-label-d:     #c39c35;
  --d-label-e:     #c47f2a;
  --d-label-f:     #c65c1d;
  --d-label-g:     #c7282b;

  /* CATEGORICAL, four slots, fixed order, never cycled. These paint the RETROFIT
     FAMILY a building is assigned: electric heat, insulation, heat network,
     solar. It is four and not six because six measure colours could not be made
     colour-blind separable at all (the best six slot search reached deltaE 1.7
     under deuteranopia, which is invisible), and because a map a citizen reads
     cannot carry six identities. The six catalogue measures collapse into these
     four families on the MAP; the building panel still prints the exact measure
     in words, so nothing is lost. Worst pair measures deltaE 10.0 under protan
     and deutan and 18.1 under normal vision. */
  --d-cat-1:       #4a86e8;   /* electric heat: heat pump, all electric */
  --d-cat-2:       #a9b52e;   /* insulation */
  --d-cat-3:       #e07ab0;   /* heat network */
  --d-cat-4:       #c96a12;   /* solar, with or without a battery */

  /* DECORATION. Not chrome and not data.
     M18 REMOVED WALK MODE, WHICH IS WHAT THIS TONE WAS ADDED FOR, AND THE TOKEN
     STAYS BECAUSE IT HAS A SECOND CONSUMER. web/trees.js derives the tree TRUNK
     colour from it, and a trunk is a visual reconstruction of a real thing rather
     than invented detail. Removing the token would have taken the trunks with it.
     The eight jittered facade tones in web/visual.js are gone. */
  --dec-brick:     #c99a7e;

  /* NO DATA. One neutral, and it must never be mistaken for a ramp step. The
     other "no answer" states reuse chrome deliberately, so that "we did not
     look", "we looked and found nothing" and "we cannot tell" never look like
     a value on a scale:
       never assessed        --c-surface-2
       assessed, none found  --c-line
       cannot tell           --d-none
       outside the area      --c-line-soft  */
  --d-none:        #8b98a6;
  /* ROUND 10 PHASE 1 (2026-09-04). Three more "no value" states, each its own hue and
     none of them a ramp step, for the same reason as above:
       too small to assess   --d-small   the footprint is under the detector's own floor
       panels, no capacity   --d-nocap   panels traced but no kWp could be computed
       not a building        --d-gone    excluded by its live BAG status (list empty for now) */
  --d-small:       #2f5c5a;
  --d-nocap:       #b8a0e0;
  --d-gone:        #5a3d6e;
  /* could not assess  --d-noassess  Round 10 Phase 2: the footprint is wider than PDOK serves
     at the detector's resolution, so no detector read it; a state, not a verdict */
  --d-noassess:    #6b5a3d;

  /* ------------------------------------------------------------------ alpha
     Tints are derived from the tokens above with color-mix, so no new colour
     literal is introduced by a translucent surface. */
  --c-glass:       color-mix(in srgb, var(--c-surface) 88%, transparent);
  --c-glass-2:     color-mix(in srgb, var(--c-surface-2) 92%, transparent);
  --c-accent-tint: color-mix(in srgb, var(--c-accent) 14%, transparent);
  --c-accent-tint-strong: color-mix(in srgb, var(--c-accent) 24%, transparent);
  --c-good-tint:   color-mix(in srgb, var(--c-good) 13%, transparent);
  --c-warn-tint:   color-mix(in srgb, var(--c-warn) 13%, transparent);
  --c-bad-tint:    color-mix(in srgb, var(--c-bad) 13%, transparent);
  --c-leaf-tint:   color-mix(in srgb, var(--c-leaf) 15%, transparent);
  --c-hover:       color-mix(in srgb, var(--c-ink) 7%, transparent);
  --c-sunken:      color-mix(in srgb, var(--c-canvas) 55%, transparent);
  --c-scrim:       color-mix(in srgb, var(--c-canvas) 78%, transparent);

  /* ------------------------------------------------------------------- type
     ONE base size and ONE ratio. Every size in the interface is a step on this
     scale, which is how 22 sizes became 7. Presentation mode moves --fs-base
     and the whole interface scales with it. */
  --font-head: "Space Grotesk", "IBM Plex Sans", "Segoe UI", sans-serif;
  --font-body: "IBM Plex Sans", "Segoe UI", Arial, sans-serif;

  --fs-base: 13px;
  --fs-1: calc(var(--fs-base) / 1.32);   /* 9.8px   micro, legend caption   */
  --fs-2: calc(var(--fs-base) / 1.15);   /* 11.3px  small print, caveats    */
  --fs-3: var(--fs-base);                /* 13px    body                    */
  --fs-4: calc(var(--fs-base) * 1.16);   /* 15.1px  lead, card title        */
  --fs-5: calc(var(--fs-base) * 1.46);   /* 19px    stat value              */
  --fs-6: calc(var(--fs-base) * 1.92);   /* 25px    hero number             */
  --fs-7: calc(var(--fs-base) * 2.46);   /* 32px    presentation hero       */

  --lh-tight: 1.25;
  --lh-snug: 1.4;
  --lh-normal: 1.55;
  --lh-loose: 1.7;

  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  --track-caps: 0.05em;      /* letter spacing for uppercase eyebrows */
  --track-head: 0.005em;

  /* ---------------------------------------------------------------- spacing */
  --sp-1: 4px;
  --sp-2: 6px;
  --sp-3: 8px;
  --sp-4: 12px;
  --sp-5: 16px;
  --sp-6: 24px;
  --sp-7: 32px;

  /* ----------------------------------------------------------------- radius */
  --r-1: 6px;
  --r-2: 9px;
  --r-3: 12px;
  --r-4: 16px;
  --r-pill: 999px;

  /* -------------------------------------------------------------- elevation */
  --e-1: 0 2px 8px color-mix(in srgb, var(--c-canvas) 45%, transparent);
  --e-2: 0 10px 34px color-mix(in srgb, var(--c-canvas) 55%, transparent);
  --e-3: 0 20px 60px color-mix(in srgb, var(--c-canvas) 70%, transparent);
  --blur: 16px;

  /* ----------------------------------------------------------------- motion */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --t-fast: 120ms var(--ease);
  --t-base: 200ms var(--ease);
  --t-slow: 320ms var(--ease);

  /* ------------------------------------------------------------ icon sizes
     Icons were the single largest source of inline styles: 60 of the 195
     were width:Npx;height:Npx on a lucide glyph. */
  --ic-1: 12px;
  --ic-2: 14px;
  --ic-3: 16px;
  --ic-4: 18px;
  --ic-5: 22px;

  /* --------------------------------------------------------------- geometry */
  --topbar-h: 54px;
  --below: calc(var(--topbar-h) + var(--sp-4));
  --panel-w: 340px;
  --bpanel-w: 384px;
  --rail-w: 66px;
  /* how tall the bottom left column may grow before its boxes start scrolling */
  --dock-h: 60vh;
  /* a dock in that column never shrinks below this, so a long message cannot squeeze
     it down to its own title bar */
  --dock-min-h: 180px;
  /* and the message never takes more than this, so the two bounds cannot both be
     satisfied only by the column overflowing */
  --notice-max-h: 24vh;
}

/* ==========================================================================
   PRESENTATION MODE. This gets projected in meetings and read from the back of
   a room, so it is not a separate stylesheet: it moves the base size, widens
   the panels, and swaps in the higher contrast surfaces. Everything else
   follows from the tokens, which is the point of having them.

   THE SELECTOR IS :root, NOT body, AND THAT IS NOT COSMETIC. A custom property
   is substituted at computed-value time on the element that DECLARES it, so
   --fs-1, which is calc(var(--fs-base) / 1.32) declared on :root, resolves
   var(--fs-base) against :root. Overriding --fs-base further down the tree
   therefore does nothing to the scale. The first version of this file put the
   override on body.presenting: the panel widened and the surfaces darkened, and
   THE TYPE DID NOT GROW AT ALL. Measured at 1920 by 1080, the smallest rendered
   text stayed at 9.85px in both modes. Caught by measuring, not by looking.
   ========================================================================== */
:root.presenting {
  --fs-base: 18px;
  --c-surface: #050d1c;
  --c-surface-2: #0d1a2f;
  --c-line: #47638c;
  --c-line-soft: #2b405f;
  --c-ink-2: #cfdaea;
  --c-ink-3: #a5b7cb;
  --c-glass: color-mix(in srgb, var(--c-surface) 96%, transparent);
  --c-glass-2: color-mix(in srgb, var(--c-surface-2) 98%, transparent);
  --panel-w: 470px;
  --bpanel-w: 500px;
  --topbar-h: 64px;
  --ic-1: 15px;
  --ic-2: 18px;
  --ic-3: 21px;
  --ic-4: 24px;
  --ic-5: 30px;
  --blur: 6px;
}
