/* ============================================================
   Catalation / Synapse — theme tokens
   THE single source of colour truth for the whole site.

   Every colour the site paints is declared here and nowhere else.
   `tools/lint.mjs` enforces that: a hex, rgb() or hsl() literal anywhere
   else under public/ is a lint error. The one documented exception is the
   `theme-color` meta tag in index.html, because a meta attribute cannot read
   a custom property.

   ---------- Green (web-site#7, superseding the red of #6) ----------

   One anchor hue. Everything chromatic on the page is the same green at a
   different depth, and the scale below is the whole of it:

     --green-50   a very light green tint — the inset panel and card fills
     --green-100  the next tint up — chips, badges, icon tiles
     --green-500  the bright accent — rules, gradient stops, decorative washes
     --green-700  deep forest green — the anchor: links, labels, icons, the CTA
     --green-900  dark pine — depth: hover states and the far end of every
                  gradient

   The numbers name *depth of emphasis*, not absolute lightness, because the
   dark theme re-strikes every step (see the block at the foot of this file):
   on a near-black page the quiet steps go darker and the loud steps have to
   gain luminance rather than lose it. What is constant in both themes is the
   ordering — 50 and 100 recede, 500 is the bright accent, 700 carries text,
   900 is the deep end — and the hue, which sits at 120° throughout. The red
   the previous pass shipped, and the blue/green ramp before it, are gone: no
   token here is anything but this one green, a near-neutral or a verdict.

   Light is the default. `index.html` ships `data-theme="light"` and the dark
   theme is opt-in through the toggle.

   ---------- Why these particular values ----------

   `--green-700` and `--green-900` clear WCAG AA 4.5:1 as *body text* on all
   four surfaces the site paints them on — the page, a card, the inset panel
   and a chip — in both themes. That is what lets one token carry a label, a
   2px rule, an icon stroke and a card border without a second "is this one
   safe for text?" question.

   `--green-500` is held to 1.4.11's 3:1 for meaningful non-text instead,
   which is the bar it actually has to clear: it paints rules, gradient stops
   and decorative washes and is never set as copy. It measures 3.70:1 at worst
   in the light theme and 8.48:1 in the dark one, so it has room to spare — a
   pure `#00FF00` would not (it is 1.4:1 on white, nowhere near either bar),
   which is exactly why the bright end of the scale is a darkened leaf green
   rather than a primary green.

   Green sits at the most luminous end of sRGB, so every step that carries
   copy is struck darker than the equivalent red would have been: the anchor
   is a forest green, not the mid green a "brand green" usually names.

   `npm run contrast` prints the measured table; /styleguide publishes every
   token in it with the ratio and the bar it is held to.
   ============================================================ */

:root {
  /* ---------- The green scale (light theme) ----------
     Five steps on one hue. Exactly 120° throughout — red and blue are held
     equal in every step, so "the same green at another depth" is true by
     construction rather than by tuning, and the 8° the test allows is never
     spent. */
  --green-50:  #EBF7EB; /* very light green tint */
  --green-100: #D3ECD3; /* chip / badge fill     */
  --green-500: #178717; /* bright accent         */
  --green-700: #0E620E; /* deep forest — the anchor */
  --green-900: #094109; /* dark pine             */

  /* ---------- The three named roles ----------
     So a component can ask for "the brand green" without knowing it is step
     700, and so re-tuning a step moves everything that depends on it. */
  --primary: var(--green-700); /* text-weight accent */
  --accent:  var(--green-500); /* bright, non-text   */
  --deep:    var(--green-900); /* depth              */

  /* The green in force for the current subtree. Every eyebrow, rule, marker,
     icon stroke and card border reads it. The page sets it once here and only
     three places re-point it: the primary button (so its hover glow follows
     the button), the CEO node in the org chart and the "Human" role badge —
     each of which takes `--deep` so the pair it belongs to is told apart by
     depth as well as by the label it already carries. */
  --tone: var(--primary);

  /* The primary call to action is one fixed shade for the whole page, so the
     click target stays learnable. `--cta` is that shade; `--cta-2` is the
     same green at depth and exists only to give the fill a gradient, which is
     the "buttons get depth" half of the brief. Nothing else may paint a
     button. */
  --cta:   var(--primary);
  --cta-2: var(--deep);

  /* The solid fill behind a chip, badge or icon tile. A single anchor hue
     means this is a constant, so it is a token rather than a per-tone
     `color-mix()`. */
  --fill: var(--green-100);

  /* ---------- Wash strengths ----------
     The two places a green is painted as a translucent gradient rather than
     as a solid. Backgrounds stay light and near-neutral, so a section-sized
     wash is capped at 4%; the hero band — the one place the brief asks for a
     gradient you can actually see — is allowed 10%. Both are measured against
     the copy that sits on them in test/theme.test.js. */
  --tint-wash: 4%;
  --tint-hero: 10%;

  /* ---------- Neutrals (light theme) ----------
     A near-white page with a light green undertone, near-black body copy,
     green-cast graphite for secondary text. Every one of them leans a few
     points toward the anchor so the page reads as part of the palette rather
     than as grey with green dropped on top, and every one is capped by chroma
     in the tests so the lean stays a tint. */
  --bg:            #F0F8F0;
  --surface:       #FFFFFF;
  --surface-2:     var(--green-50);
  --text-strong:   #0A140A;
  --text:          #162316;
  --body:          #233323;
  --muted:         #405640;
  --border:        #CDE3CD;
  /* The visible edge: card borders, the timeline connector, the org-chart
     links and the build-mode outlines. Held above 1.4.11's 3:1 on the page,
     on a card, on the panel and on a chip, in both themes. */
  --border-strong: #238623;
  --border-soft:   #E2EFE2;
  --on-accent:     #FFFFFF;

  /* Shadows carry the same green cast as the page, so a lifted card does not
     drop a neutral grey smudge onto a tinted background. */
  --shadow-sm: 0 1px 2px rgba(18, 58, 24, 0.05), 0 2px 8px rgba(18, 58, 24, 0.05);
  --shadow-md: 0 4px 16px rgba(18, 58, 24, 0.07), 0 12px 40px rgba(18, 58, 24, 0.08);
  --shadow-lg: 0 12px 32px rgba(18, 58, 24, 0.11), 0 24px 70px rgba(18, 58, 24, 0.13);
  --grid-line: rgba(40, 112, 46, 0.06);

  /* ---------- Verdict bands ----------
     Semantic, and deliberately outside the brand scale: Fund green, Watch
     amber, Pass red, so a band means the same thing wherever it lands. The
     three are untouched in meaning by this pass — what changed is which of
     them the brand puts under pressure.

     Under a red brand that was Pass. Under a green one it is Fund, and it is
     resolved the same three ways at once rather than by hue alone:

       · shape — every verdict is paired with an icon (a check, a bar-and-dot,
         a cross) masked into `.verdict-dot` by style.css, so the three are
         separable with no colour vision at all;
       · label — the band's name and its score range are in the markup, as
         they already were;
       · shade — Fund is an emerald at 148°, 28° off every step of the brand
         scale and 1.49:1 (light) / 1.37:1 (dark) away from `--primary` in
         contrast, so it does not read as "just the brand colour" even side by
         side with it — including for a viewer who cannot separate the two
         greens at all. The dark-theme Fund is struck lighter than the red
         pass left it for exactly that reason; its hue and its meaning are
         unchanged.

     All three clear 4.5:1 on a card, which is stricter than the 3:1 a dot or
     a 3px rule needs, and is what lets the same token set the styleguide's AA
     column as text. */
  --verdict-fund:  #0E7F43;
  --verdict-watch: #745900;
  --verdict-pass:  #C05010;

  /* Form errors take the Pass scarlet, not the brand green: an error must not
     look like an accent — and with a green brand it must not look like a
     success, either. */
  --danger: var(--verdict-pass);

  /* ---------- Terminal ----------
     The code block is a dark surface in *both* themes, so most of these do
     not flip with the theme. The syntax roles keep three separate hues
     because they are telling three kinds of token apart, and they are drawn
     from hues the page already owns — the brand green, the Watch amber and
     the Fund emerald. The two greens are the pair that could collide, so they
     are separated by lightness as well as by angle: keywords take the bright
     brand green, strings the deeper emerald 35° off it. Each clears 4.5:1 on
     the code background. */
  --code-bg:     #0A120A;
  --code-text:   #CCDBCC;
  --code-dim:    #8AA48A;
  --code-border: rgba(255, 255, 255, 0.08);
  --term-key:    #7FE07F; /* keywords  — brand green  */
  --term-fn:     #ECB851; /* functions — amber        */
  --term-str:    #35C98C; /* strings   — Fund emerald */
}

/* ============================================================
   Dark theme — opt-in through the toggle

   The same hue, re-struck for a near-black page: the two quiet steps go
   darker and the three loud ones gain luminance, because on a dark surface a
   green has to get lighter to clear AA rather than darker. The ordering, the
   roles and the hue angles are the light theme's, so the palette is
   recognisably the same green in both.

   The deep end has less room here: `--green-900` stops at a mid green rather
   than at a pine, because it is also the far end of the button fill and the
   near-black label on that fill still has to clear 4.5:1.
   ============================================================ */

[data-theme="dark"] {
  --green-50:  #0D150D;
  --green-100: #112211;
  --green-500: #60D060;
  --green-700: #35BB35;
  --green-900: #2FA32F;

  --bg:            #070F07;
  --surface:       #101A10;
  --text-strong:   #EEF7EE;
  --text:          #DAE5DA;
  --body:          #B4C7B4;
  --muted:         #9DB39D;
  --border:        #2B3F2B;
  --border-strong: #0F8F0F;
  --border-soft:   #1F2E1F;
  /* A button label on a light fill has to be near-black here, not white. */
  --on-accent:     #030F03;

  --shadow-sm: 0 1px 2px rgba(2, 8, 3, 0.45);
  --shadow-md: 0 8px 30px rgba(2, 8, 3, 0.5);
  --shadow-lg: 0 20px 60px rgba(2, 8, 3, 0.6);
  --grid-line: rgba(140, 240, 150, 0.05);

  /* Re-struck light enough to clear 4.5:1 on a dark card, at the same three
     hues, so a verdict is the same colour idea in both themes — and Fund
     keeps its distance in shade from the brand green here too, which is what
     its extra lightness buys. */
  --verdict-fund:  #4FD68F;
  --verdict-watch: #BE9717;
  --verdict-pass:  #E25C0E;

  --code-bg: #050A05;
}

/* ============================================================
   A note on which derived tokens are safe here

   `--primary: var(--green-700)` and `--tone: var(--primary)` look like the
   kind of derivation that silently does not work, so it is worth saying why
   they do.

   A custom property's `var()` references are substituted at the point the
   property is *used* on an element, against that element's computed values.
   Every token referenced above — the five steps, the neutrals — is declared
   on the root element and nowhere else, including the dark overrides: `:root`
   and `[data-theme="dark"]` both match <html>, so a token declared on `:root`
   resolves against whichever theme is in force.

   What is *not* safe is deriving from `--tone`, which the button and two
   diagram nodes reassign on themselves. A `color-mix()` of `var(--tone)`
   declared here would be mixed from the root's value and every element on the
   page would get the same result no matter what it set. So style.css writes
   each `color-mix()` out at the use site, where `var(--tone)` resolves
   against the element actually being painted. Only the *strengths*
   (`--tint-wash`, `--tint-hero`) are tokens, because a plain percentage has
   no `var()` to mis-resolve.
   ============================================================ */
