/* =========================================================================
   RIDGELINE TOKEN CONTRACT — v1
   Phase A signed off 28 July 2026 · landed in Phase B, 28 July 2026

   One naming scheme, one scale, three themes. Drop this in ahead of each
   repo's own stylesheet; every surface then draws from the same well even
   though their stylesheets stay separate.

   WHY --rl-* : namespacing makes the find-and-replace unambiguous in all
   three repos and cannot collide with Tailwind's generated variables or
   Next's --font-* custom properties.

   WHY NUMBERED LAYERS : "card" and "surface" are metaphors that run out at
   the third level. Numbers do not. Neither Signal's --card/--line nor
   Radar's --surface/--border wins, so neither team lives with the other's
   word — which was the brief's instruction.

   WHY --rl-rule : a typographic term, for a firm whose one structural
   device is where a rule starts and stops.

   ⚠ THREE VALUES MOVE FROM THE FROZEN PALETTE. All three are AA failures.
   Approved 28 July 2026 because the brief made accessibility a hard gate.
   Marked below. Nothing else in the palette changes.
   ========================================================================= */

:root {

  /* --- Ground → raised --------------------------------------------------
     Chart plot areas sit on bg-0, NOT bg-1. In dark that is what lifts the
     series coal #D9603F from 4.34:1 on bg-1 to 4.97:1 on bg-0 — both clear
     the 3:1 graphical floor, so this is headroom rather than a rescue.

     PROVENANCE OF THE 2.99 → 3.43 IN THE PHASE A SHEET, because it caused
     one wrong correction already and should not cause a second. Those are
     the LIGHT coal #B5482E measured against the two DARK grounds — 2.99 on
     bg-1, 3.43 on bg-0. The arithmetic was right; the label was misleading,
     because it said "series coal" inside a dark-theme comment where the
     lifted #D9603F is what actually renders. Both readings support the same
     rule, which is why the rule stands either way: unlifted coal only
     clears 3:1 on bg-0, and lifted coal gains a further 0.63 there.
     (Flagged item 01 is the separate problem that digest-map.ts re-declares
     its own colours in TypeScript and can see none of this.) */
  --rl-bg-0: #F4F6FA;   /* page ground                                      */
  --rl-bg-1: #FFFFFF;   /* raised surface: cards, modals, sticky header     */
  --rl-bg-2: #FBFCFE;   /* tonal band — third term in the section rhythm    */
  --rl-bg-3: #EEF2F8;   /* inset: chips, code, table zebra, input wells     */

  /* --- Ink -------------------------------------------------------------- */
  --rl-fg-1: #0E1B2E;   /* 17.29:1 on bg-1 · 15.98:1 on bg-0                */
  --rl-fg-2: #3B5067;   /*  8.30:1 on bg-1 ·  7.67:1 on bg-0                */
  --rl-fg-3: #5C6F86;   /*  5.16:1 on bg-1 ·  4.77:1 on bg-0                */
  /* ⚠ EXCEPTION 1 — fg-3 was #6F8298, which measures 3.95:1 on white and
     3.65:1 on paper. It is the colour of every provenance line on all three
     surfaces. Smallest delta that clears 4.5:1 on the harder ground. */

  /* --- Rules. One weight, 1px, everywhere. ------------------------------ */
  --rl-rule:   #E3E8F0;
  --rl-rule-2: #EDF1F6; /* chart gridlines; outboard half of a section rule */
  --rl-rule-w: 1px;

  /* --- Accent -----------------------------------------------------------
     THE RULE: cobalt when it is a colour, cobalt-dark when it is readable.
     #3D6BFF is 4.43:1 as text on white — under AA — and 4.43:1 beneath a
     white button label, which at 16px/600 is not "large text". */
  --rl-accent:      #3D6BFF;  /* fills, focus rings, decorative accents     */
  --rl-accent-ink:  #2F57DB;  /* links, and any fill carrying white. 5.99:1 */
  --rl-accent-soft: #EAF0FD;  /* selected states, active tab wells          */
  --rl-focus:       #3D6BFF;  /* 4.09:1 on bg-0 — a graphical object, ≥3:1  */
  /* ⚠ EXCEPTION 3 — the link/label accent. No new colour, only a role:
     cobalt-dark #2F57DB already existed on the site as --cobalt-dark. */

  /* --- Accent FILLS. The pair, not a per-component decision. -------------
     THE RULE INVERTS BETWEEN THEMES, which is the half Phase A never said.
     --rl-accent-ink is a LINK colour: in dark it is #6E93FF, chosen so it
     clears AA against a dark ground (6.35:1 on bg-0). White on that same
     colour as a FILL measures 2.88:1 and fails badly. So the fill and its
     label swap roles across the flip, and both halves live here so no
     component has to remember:
        light   fill #2F57DB · label #FFFFFF   → 5.99:1
        dark    fill #6E93FF · label #0B1524   → 6.35:1
     Every accent-filled control on all three surfaces draws from this pair.
     A white label surviving a theme flip onto an accent fill is THE failure
     mode — verification assertion 17 exists to catch exactly that. */
  --rl-accent-fill:     var(--rl-accent-ink);
  --rl-accent-fill-ink: #FFFFFF;

  /* --- Semantic ---------------------------------------------------------
     Deliberately NOT the same values as the series palette below. They are
     different meanings that happened to be wearing one colour in light. */
  --rl-pos: #1A7A50;    /* 5.33:1 on bg-1 · 4.92:1 on bg-0                  */
  --rl-neg: #C0492F;    /* 4.96:1 on bg-1 · 4.58:1 on bg-0                  */
  /* ⚠ EXCEPTION 2 — pos was #1F8A5B, which measures 4.33:1 on white while
     its rust counterpart measures 4.96:1. As it stands, in a market
     briefing a loss is legible and a gain is not. This is the LIGHT theme
     only; Signal's dark values were already correct and are adopted below. */

  /* --- Series — CVD-validated. DO NOT RESTYLE. -------------------------- */
  --rl-series-coal:  #B5482E;
  --rl-series-gas:   #D98A1E;
  --rl-series-batt:  #3D6BFF;
  --rl-series-renew: #1F8A5B;
  /* Signal defines a LIFTED set for dark (below) and that is correct — it
     holds the same separation against a darker ground rather than changing
     the data's meaning. Both sets are Signal's; neither is invented here.

     ⚠ THE TRAP. --rl-series-renew and --rl-pos used to share a hex. Since
     Exception 2 they DIVERGE in light (#1F8A5B vs #1A7A50) and converge in
     dark (both #3BB07E). The light divergence is deliberate: one is a data
     series (renewables), one is a semantic (a gain). They were never the
     same meaning, only the same colour by accident. NEVER re-merge them,
     and do not "fix" the near-duplicate. */

  /* --- Type: two tracks --------------------------------------------------
     A single ratio cannot hold 17px marketing body and 15px product body at
     once. Text steps at 1.125 (body copy needs fine gradations); display
     steps at 1.25 (headings need telling apart at distance). Both existing
     bodies land on the text track exactly one step apart. */
  --rl-display: 'Space Grotesk', 'Sora', ui-sans-serif, system-ui, sans-serif;
  --rl-body:    'Sora', ui-sans-serif, system-ui, -apple-system, sans-serif;

  --rl-t-micro: 12px;   --rl-t-micro-lh: 1.45;  /* provenance, axis, legend */
  --rl-t-label: 13.5px; --rl-t-label-lh: 1.4;   /* table/form/tab labels    */
  --rl-t-dense: 15px;   --rl-t-dense-lh: 1.5;   /* product body             */
  --rl-t-body:  17px;   --rl-t-body-lh:  1.65;  /* site body                */
  --rl-t-lede:  19px;   --rl-t-lede-lh:  1.6;   /* hero support, deks       */

  --rl-d-4: 21px;  --rl-d-4-lh: 1.25;   /* h4, module title                 */
  --rl-d-3: 26px;  --rl-d-3-lh: 1.2;    /* h3                               */
  --rl-d-2: 33px;  --rl-d-2-lh: 1.12;   /* h2 — was up to 64px              */
  --rl-d-1: 41px;  --rl-d-1-lh: 1.08;   /* h1                               */
  --rl-d-0: 51px;  --rl-d-0-lh: 1.05;   /* site hero ONLY, ≥1200px          */

  --rl-track-display: -0.02em;
  --rl-track-kicker:   0.14em;
  --rl-measure: 70ch;   /* prose max-width. 65–75 characters.               */

  /* --- Space. 4px base, geometric above 16. Ten steps, no ad-hoc values. */
  --rl-sp-1: 4px;   --rl-sp-2: 8px;   --rl-sp-3: 12px;  --rl-sp-4: 16px;
  --rl-sp-5: 24px;  --rl-sp-6: 32px;  --rl-sp-7: 48px;  --rl-sp-8: 64px;
  --rl-sp-9: 96px;  --rl-sp-10: 128px;

  --rl-step: 24px;  /* the one contour increment. Every hierarchy level
                       starts one of these to the right of its parent. */

  /* Three section heights, replacing the uniform .section/.section-alt
     alternation. "Major" is used twice on the homepage and nowhere else. */
  --rl-section-tight:    64px;
  --rl-section-standard: 96px;
  --rl-section-major:   128px;

  /* --- Radius. 4px is the ceiling. ------------------------------------- */
  --rl-r-0: 0;      /* chart frames                                        */
  --rl-r-1: 2px;    /* chips, inputs, badges                               */
  --rl-r-2: 4px;    /* cards, buttons                                      */

  /* --- Shadow. Two steps. -----------------------------------------------
     THESE VALUES are light-only: the tint is navy (rgba(14,27,46,…)) and it
     is wrong on a near-black ground. That is what "light only" means here —
     do not reuse THIS PAIR in dark. It does NOT mean dark has no elevation.
     The dark block redefines both with a black tint; see there. Read as
     "no elevation in dark" once already, on 28 Jul 2026, and reversed. */
  --rl-shadow-1: 0 1px 2px rgba(14,27,46,.06);
  --rl-shadow-2: 0 1px 2px rgba(14,27,46,.05), 0 8px 24px rgba(14,27,46,.06);

  /* --- Motion. The whole allowance. ------------------------------------ */
  --rl-dur:  180ms;
  --rl-ease: cubic-bezier(.2,.6,.2,1);
}

/* =========================================================================
   DARK — one theme, not two.

   The two products disagreed about ONE thing: altitude. Both independently
   chose the same elevation step from ground to raised surface (Signal 1.146,
   Radar 1.145). Radar's altitude wins — series colours separate better on
   it, and it keeps brand navy a foreground value instead of promoting it to
   a background. Signal's chip and grid are re-derived down to it so its own
   judgement about those steps survives.

   Selector covers both mounting points: Signal themes <html>, Radar scopes
   to the .radar subtree. That scoping is deliberate and stays.
   ========================================================================= */

html[data-theme="dark"],
.radar[data-theme="dark"] {
  --rl-bg-0: #0B1524;   /* Radar    · adopted                              */
  --rl-bg-1: #132238;   /* Radar    · preserves the 1.145 step             */
  --rl-bg-2: #0F1D30;   /* Radar    · uncontested; Signal gains a band     */
  --rl-bg-3: #1C2E47;   /* Signal   · its --chip, adopted as-is            */

  --rl-fg-1: #F4F6FA;   /* Signal   · it is Paper. 16.92:1 on bg-0         */
  --rl-fg-2: #A9B7C8;   /* Signal   ·  8.98:1 — a real step below fg-1     */
  --rl-fg-3: #8598B0;   /* Radar    ·  5.42:1 on bg-1. Signal's was 4.89.  */

  --rl-rule:   #243650; /* Radar    · referenced in the digest print reset */
  --rl-rule-2: #17283E; /* derived  · holds Signal's 1.073 gridline step   */

  --rl-accent:      #6E93FF;  /* Signal · nearer brand cobalt. 5.54:1      */
  --rl-accent-ink:  #6E93FF;
  --rl-accent-soft: #1B2C47;  /* Radar                                     */
  --rl-focus:       #6E93FF;  /* 6.35:1 on bg-0                            */

  /* The fill pair, inverted. See the light block for why this is a pair. */
  --rl-accent-fill:     var(--rl-accent);   /* #6E93FF                     */
  --rl-accent-fill-ink: var(--rl-bg-0);     /* #0B1524 · 6.35:1            */

  /* ⚠ bg-3 and accent-soft are 1/255 apart in dark (#1C2E47 vs #1B2C47) —
     a neutral inset and an accent wash that the LIGHT theme keeps clearly
     apart (#EEF2F8 vs #EAF0FD) have collapsed onto one value. Rather than
     invent a bluer dark wash, carry selection in dark with an accent BORDER
     and leave the fill neutral. One more non-colour carrier, no new hex.
     See .rl-selected at the foot of this file. */

  --rl-pos: #3BB07E;    /* Signal · 5.86:1 on bg-1. Already correct.       */
  --rl-neg: #E06B4C;    /* Signal · 4.85:1 on bg-1. Already correct.       */
  /* Signal solved dark semantics; Radar never did. Radar's signal.red and
     signal.green are Tailwind theme entries with no dark variant, so inside
     .radar[data-theme=dark] they render at the light values — red #C0492F
     at 3.22:1 and green #1F8A5B at 3.69:1 on the surface. (The Phase A
     sheet printed that pair the other way round; measured, it is the RED
     that is worse.) Adopting Signal's pair closes both, and needs no new
     colour from anyone. */

  /* Series — Signal's lifted dark set, adopted wholesale. All four clear
     the 3:1 floor on bg-1: coal 4.34, gas 7.99, batt 5.54, renew 5.86. */
  --rl-series-coal:  #D9603F;
  --rl-series-gas:   #E6AE42;
  --rl-series-batt:  #6E93FF;
  --rl-series-renew: #3BB07E;

  /* Signal's dark shadow, kept. Tuned for a near-black ground — a BLACK tint
     (rgba(0,0,0,.3)/.35), not the light theme's navy one — and it works.

     ⚠ DO NOT SET THESE TO none. Tried on 28 Jul 2026 on a reading of "shadow,
     two steps, LIGHT ONLY" in the light block, and reversed the same day. With
     no shadow, a dark card is separated from its ground by a 1.145:1 luminance
     step and NOTHING ELSE — and dark is the theme where depth does the most
     work in this product. If the shadow reads as a smudge, the answer is a
     tuned pair with the reasoning attached, not deletion. */
  --rl-shadow-1: 0 1px 2px rgba(0,0,0,.3);
  --rl-shadow-2: 0 1px 2px rgba(0,0,0,.3), 0 10px 30px rgba(0,0,0,.35);
}

/* =========================================================================
   SHARED BEHAVIOUR — three rules the estate should hold in common.
   Radar already has all three; Signal and the site adopt them rather than
   each writing a fourth version.
   ========================================================================= */

*:focus-visible {
  outline: 2px solid var(--rl-focus);
  outline-offset: 2px;
}

/* Anywhere a figure appears: readouts, tables, timestamps, axis ticks,
   window counts, prices, the WEM strip. Stops columns dancing on refresh. */
.rl-num, td[data-num], .readout, .prov time, .big, .seg {
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   THE CONTOUR — the section rule.

   The drawn stepped rule from §2b was tested and rejected: invisible at one
   hairline weight, a pun at any weight that reads, and repeated a dozen
   times a page. What survives is the step expressed in TONE — the rule runs
   full-bleed but reaches --rl-rule only across the measure.

   The structural device is --rl-step: hierarchy encoded in position, which
   survives greyscale and a screen reader.
   ========================================================================= */

/* The gradient itself, named once. Two carriers need it and they must not be
   allowed to drift into two slightly different rules:
     .rl-rule       a standalone element, where a background is the whole job
     .rl-ruled-*    an existing border-top/bottom on a section or a list row,
                    where replacing the border with an element would mean
                    touching markup that has no other reason to change.
   border-image with a slice of 1 paints a 1px border from the same gradient,
   so both carriers are the same line by construction. */
:root {
  --rl-rule-grad: linear-gradient(90deg,
    var(--rl-rule-2) 0 var(--rl-step),
    var(--rl-rule)   var(--rl-step) calc(100% - var(--rl-step)),
    var(--rl-rule-2) calc(100% - var(--rl-step)) 100%);
}

.rl-rule {
  height: var(--rl-rule-w);
  border: 0;
  margin: 0;
  background: var(--rl-rule-grad);
}

/* border-image cancels border-radius, which is why these are only ever put on
   a single edge of a square-cornered block — a section boundary or a list row. */
.rl-ruled-top {
  border-top: var(--rl-rule-w) solid var(--rl-rule);
  border-image: var(--rl-rule-grad) 1;
}
.rl-ruled-bottom {
  border-bottom: var(--rl-rule-w) solid var(--rl-rule);
  border-image: var(--rl-rule-grad) 1;
}

.rl-kicker {
  font: 600 11px/1.4 var(--rl-body);
  letter-spacing: var(--rl-track-kicker);
  text-transform: uppercase;
  color: var(--rl-fg-3);
}

/* Content starts one step right of its section head. */
.rl-stepped { padding-left: var(--rl-step); }

/* Selection, carried without a fill colour. In LIGHT the accent wash is a
   clear step away from the neutral inset, so it is used; in DARK the two
   collapsed onto one value (see the note above), so the border does the
   work and the fill stays neutral. */
.rl-selected {
  background: var(--rl-accent-soft);
  border: var(--rl-rule-w) solid var(--rl-accent);
}
html[data-theme="dark"] .rl-selected,
.radar[data-theme="dark"] .rl-selected {
  background: var(--rl-bg-3);
  border-color: var(--rl-accent);
}

/* =========================================================================
   MIGRATION ALIASES

   Keep these while Phase B lands, so a repo can adopt the contract without
   a single big-bang find-and-replace. Delete the block per repo once its
   own token names are gone.

   ⚠ SIGNAL CANNOT DELETE THIS BLOCK. app.js (~258 KB, off limits by the
   brief) reads --accent --batt --card --coal --gas --grid --ink --ink2
   --ink3 --line --neg --pos --renew --rust --slate --sw off the computed
   style to draw every chart; chart-hover.js reads --card --ink --ink3 --sw
   and disclaimers.js reads --accent --gas --ink2 --pos. Deleting the
   aliases would blank the charts. They are the compatibility layer, not
   debt, until app.js is in scope. Verification assertion 02 therefore
   cannot pass on Signal; see the Phase B report.
   ========================================================================= */

:root {
  /* Signal's names — note it already carries five duplicate pairs of its
     own (--muted/--ink3, --hair/--line, --green/--pos, --rust/--neg,
     --navy/--ink, --slate/--ink2, --paper/--bg, --cobalt/--accent). All of
     them collapse here onto one role, which is most of the tidy-up. */
  --bg:     var(--rl-bg-0);
  --paper:  var(--rl-bg-0);
  --card:   var(--rl-bg-1);
  --ink:    var(--rl-fg-1);
  --navy:   var(--rl-fg-1);
  --ink2:   var(--rl-fg-2);
  --slate:  var(--rl-fg-2);
  --ink3:   var(--rl-fg-3);
  --muted:  var(--rl-fg-3);
  --line:   var(--rl-rule);
  --hair:   var(--rl-rule);
  --grid:   var(--rl-rule-2);
  --chip:   var(--rl-bg-3);
  --accent: var(--rl-accent);
  --cobalt: var(--rl-accent);
  --pos:    var(--rl-pos);
  --green:  var(--rl-pos);
  --neg:    var(--rl-neg);
  --rust:   var(--rl-neg);
  --coal:   var(--rl-series-coal);
  --gas:    var(--rl-series-gas);
  --batt:   var(--rl-series-batt);
  --renew:  var(--rl-series-renew);
  --shadow: var(--rl-shadow-2);

  /* Radar's names. NOTE: Radar must re-declare these ON .radar, not here —
     a custom property whose value is var(...) is substituted on the element
     that declares it, so an alias sitting on :root freezes at the light
     value and never follows a subtree theme. See globals.css. */
  --surface:     var(--rl-bg-1);
  --surface2:    var(--rl-bg-2);
  --border:      var(--rl-rule);
  --accent-soft: var(--rl-accent-soft);

  /* The site's names */
  --band2:       var(--rl-bg-2);
  --white:       var(--rl-bg-1);
  --slate2:      var(--rl-fg-3);
  --cobalt-dark: var(--rl-accent-ink);
  --line-dark:   rgba(255, 255, 255, 0.12);
  --maxw:        1120px;
  --pad:         var(--rl-sp-5);
  --display:     var(--rl-display);
  --body:        var(--rl-body);
}

/* ORPHANS — not aliased, deliberately.
   --red #E5484D  site only, no counterpart in either product, a third red
                  in a two-red system. Flagged item 06: grep usages, then
                  retire in favour of --rl-neg. NOT DONE — still flagged.
   --navy-deep    one name, two values: #0a1320 on the site, #15263C in
                  Radar. Neither is used for anything the contract needs.
                  Pick one or drop both; do not keep two. Each repo still
                  declares its own; NOT DONE — still flagged. */
