/* ============================================================
   NODI — COLOR TOKENS
   Base ramps (Green/Neutral/Yellow/Red/Blue) plus the semantic
   aliases that map each ramp to a UI role (surface, text, border, action).
   ============================================================ */
:root {
  /* ---- Primaries ---- */
  --nodi-node-green: #2fc278;      /* Node Green — primary brand */
  --nodi-carbon-black: #0d0d0d;    /* Carbon Black */
  --nodi-pure-canvas: #fbfbfb;     /* Pure Canvas */

  /* ---- Green ramp (Neural) ---- */
  --green-50: #edfff5;
  --green-100: #cbfae2;   /* Origin Pulse */
  --green-200: #9aefc8;
  --green-300: #68e2ab;
  --green-400: #45d98e;
  --green-500: #38d787;   /* Neural Flow */
  --green-600: #2fc278;   /* Node Green */
  --green-700: #20a363;
  --green-800: #147848;
  --green-900: #0b5630;   /* Deep Node */
  --green-950: #062e1c;

  /* ---- Neutral ramp ---- */
  --neutral-0:   #ffffff;
  --neutral-50:  #fbfbfb;
  --neutral-100: #f0f0f0;
  --neutral-200: #e2e2e2;   /* Ghost Layer */
  --neutral-300: #c8c8c8;
  --neutral-400: #aeaeae;   /* Neutral Hub */
  --neutral-500: #8c8c8c;
  /* This step has a job in this system: it is the secondary text grey on light, where both
     --text-muted and --text-faint point at it, so it owes 4.5:1 on every light surface.
     At #6e6e6e it cleared card (5.10) and canvas (4.93) and landed on 4.47 over
     --surface-sunken — under the bar, on the one surface nobody measured. Nine components
     pair muted text with a sunken track or inset, so that was not an edge case.
     #686868 clears all three: 5.57 card · 5.38 canvas · 4.89 sunken, a margin matching what
     this system already accepted for --text-brand on the same surface (4.84). Three semantic
     tokens read this step and nothing else does, so moving it moves nothing unrelated. */
  --neutral-600: #686868;
  --neutral-700: #515151;   /* Core Protocol */
  --neutral-800: #3a3a3a;
  --neutral-900: #1a1a1a;
  --neutral-950: #0d0d0d;   /* Carbon Black */

  /* ---- Accent ramps (semantic 100/500/900) ---- */
  --yellow-100: #f7ecb1;   /* Quantum Spark */
  --yellow-500: #ffe555;   /* Pathfinder */
  /* The 900 step of every ramp is the one dark enough to set text in. This one was not: at
     #b07912 it measured 3.76:1 on white while its siblings sat at 8.79 (green), 11.31 (red)
     and 7.38 (blue) — a mid amber wearing the darkest step's name. Every place it was used
     as a foreground failed the minimum: 3.15:1 on the yellow badge, 2.97:1 as the yellow
     avatar's initial. Deepened into its siblings' range: 8.11:1 on the badge, 7.65:1 on the
     avatar. It reads brown rather than amber now, which is what a real 900 looks like. */
  --yellow-900: #5c3f06;   /* Heuristic Glow */

  --red-100: #ffd6d6;      /* Empathy Node */
  --red-500: #ff7979;      /* Human Loop */
  --red-900: #7b0303;      /* Deadlock */

  --blue-100: #c6e2f3;     /* Open Channel */
  --blue-500: #75caff;     /* Active Sync */
  --blue-900: #005a8d;     /* Trust Link */

  /* ============================================================
     SEMANTIC ALIASES
     ============================================================ */
  /* Surfaces */
  --surface-canvas:   var(--neutral-50);    /* page background (light) */
  --surface-card:     var(--neutral-0);     /* card / panel */
  --surface-sunken:   var(--neutral-100);   /* insets, toggle track */
  --surface-inverse:  var(--neutral-950);   /* dark sections / footer */
  --surface-inverse-2: #141414;             /* dark elevated panel */

  /* Text */
  --text-strong:    var(--neutral-950);
  --text-body:      var(--neutral-700);
  --text-muted:     var(--neutral-600);
  /* --text-faint is informational copy (hints, captions, fig labels) and must clear
     4.5:1 like any other readable text; on light it lands on the same step as
     --text-muted because nothing lighter clears the bar on card, canvas OR sunken.
     Measure a text token against all three: this comment used to say "white or canvas"
     and that is exactly the surface the step was failing on. Disabled controls are
     exempt from that ratio and use --text-disabled. */
  --text-faint:     var(--neutral-600);
  --text-disabled:  var(--neutral-500);
  --text-on-dark:   var(--neutral-50);
  --text-on-dark-muted: var(--neutral-400);
  /* Brand green SET AS TEXT, which is not the same green as brand green filling a shape.
     Node Green measures 2.31:1 on white, and this token is what colours every link on the
     site, the eyebrows, and the auth screens' legal copy — all of it small text owing
     4.5:1. Green-800 clears it on all three light surfaces (5.51 white / 5.33 canvas /
     4.84 sunken) and is already the green this system reaches for on light: it is
     --action-primary and --focus-ring. Node Green stays the brand green for fills, where
     nothing has to be read through it. Dark keeps green-400, which measures 10.71:1. */
  --text-brand:     var(--green-800);
  --text-on-brand:  #ffffff;

  /* Borders / hairlines */
  --border-subtle:  var(--neutral-300);   /* 0.5px grid hairlines */
  --border-default: var(--neutral-200);
  --border-strong:  var(--neutral-950);
  --border-on-dark: rgba(255,255,255,0.12);

  /* Brand action — hover/press BRIGHTEN, never darken, and swap the label to Carbon Black:
     a brighter fill with a dark label carries MORE contrast than the resting pair, and it is
     the same pairing dark mode already uses at rest. Light hover and dark rest converge.
     Contrast climbs at every step: rest 5.6:1 · hover 8.4:1 · press 10.4:1. */
  --action-primary:        var(--green-800);
  --action-primary-hover:  var(--nodi-node-green);
  --action-primary-active: var(--green-500);
  --text-on-action:        var(--text-on-brand);
  --text-on-action-hover:  var(--nodi-carbon-black);
  --text-on-action-active: var(--nodi-carbon-black);

  /* Focus ring — the outline every control shows on keyboard focus. A focus indicator has to
     clear 3:1 against the surface behind it, and no single green clears it on both themes:
     Node Green measures 2.31:1 on white. Green-800 on light gives 5.51:1, and dark flips to
     Node Green for 8.44:1 on Carbon Black. Applied once, in styles.css. */
  --focus-ring: var(--green-800);

  /* Feedback — success is fed from the brand green primitives (see badge-success-bg/fg,
     gradient-avatar-success): the one status color that intentionally doubles as brand emphasis */
  --success: var(--text-brand);
  --warning: var(--yellow-900);
  --danger:  var(--red-900);
  --info:    var(--blue-900);

  /* ComparisonTable's recommended-column panel, and the row band under the pointer. These
     lived in the website's own sheet while the table that used them lived there too; they
     moved here with the component, because a component whose colours are defined in its
     consumer only works in that one consumer. */
  --cmp-panel-bg:     #f5fcf8;
  --cmp-panel-ring:   rgba(47, 194, 120, 0.28);
  --row-hover:        rgba(13, 13, 13, 0.025);
  /* THE INDEX GRID'S TWO SURFACES, and they are a PAIR: the cover panel a cell draws behind
     its artwork, and the fill the whole cell takes under the pointer or when its title has
     keyboard focus. The rule is that the cover is always one REAL step above the cell fill,
     so a cell sinks under the cursor and its picture stays the brightest thing in it. The
     cell used to take --surface-sunken, the same surface as the cover: at hover the panel
     dissolved into the cell and the card lit up around its own image instead of receding
     under it.
     TWO NAMES RATHER THAN ONE, because the neutral ramp does not keep its order across
     themes and picking by hex would have flipped the pair. On light, sunken (#f0f0f0) sits
     BELOW card (#ffffff); on dark, sunken (#1f1f1f) sits ABOVE card (#141414). So each theme
     assigns the two roles to whichever step actually holds them, and the relationship — cover
     lighter, cell darker — comes out the same in both.
     Measured on the cell fill, because that is the one a paragraph is read on:
     light #f0f0f0 · body 6.96:1 · muted/faint 4.90:1 · brand 4.84:1
     dark  #141414 · body 11.01:1 · muted 8.31:1 · faint 5.48:1 · brand 10.16:1 */
  --index-cover-bg:   var(--surface-card);
  --index-cell-hover: var(--surface-sunken);
  /* Flattens a full-colour mark to the theme's ink: none on light, inverted on dark. Used by
     wordmarks that ship as single-colour artwork. */
  --mark-filter:      none;

  /* Theme-aware effects. They live in this file, not in effects.css, because they flip with
     the theme and effects.css has no theme blocks. The values here are the light defaults and
     the fallback for an element with no theme set; the [data-theme] blocks below override
     them. Same file is what makes that work: :root and [data-theme="dark"] carry equal
     specificity, so the tie is settled by source order, and only a declaration further down
     THIS file is guaranteed to come later. */

  /* Shadows: layered cool-navy tint (rgba 0,22,62). */
  --shadow-card:  0 1px 2px rgba(0,22,62,0.04), 0 8px 24px rgba(0,22,62,0.06);
  --shadow-panel: 0 2px 4px rgba(0,22,62,0.04), 0 12px 32px rgba(0,22,62,0.10);
  --shadow-pop:   0 8px 40px rgba(0,22,62,0.18);
  --shadow-soft:  0 4px 16px rgba(174,174,174,0.36);
  /* ComparisonTable's recommended column. It sits here with the others rather than only in
     the theme blocks: --cmp-panel-bg, --cmp-panel-ring and --row-hover all declare a :root
     default, and a component whose fill and ring resolve while its shadow does not renders
     half-drawn on any element with no theme set. */
  --cmp-panel-shadow: 0 12px 28px rgba(0,22,62,0.06), 0 2px 6px rgba(0,22,62,0.04);
  /* Menu / dropdown panel lift: shallower than --shadow-panel, because these float a few px
     off a hairline bar, so the shadow reads as proximity, not elevation. */
  --nav-lift:     0 1px 2px rgba(0,22,62,0.05), 0 10px 28px rgba(0,22,62,0.11);
  /* PromptRow hover fill: lighter than --surface-sunken, because these rows sit inside a card
     and a full sunken fill would read as a selected state rather than a hover. */
  --prompt-hover-bg: rgba(0,22,62,0.03);

  /* Badge soft/outline colors: light defaults (100 fill / 900 text) */
  --badge-green-bg: var(--green-100);   --badge-green-fg: var(--green-800);
  --badge-success-bg: var(--green-100); --badge-success-fg: var(--green-800);
  --badge-yellow-bg: var(--yellow-100); --badge-yellow-fg: var(--yellow-900);
  --badge-red-bg: var(--red-100);       --badge-red-fg: var(--red-900);
  --badge-blue-bg: var(--blue-100);     --badge-blue-fg: var(--blue-900);
}

/* ============================================================
   LIGHT THEME (explicit) — restores light semantics even when an
   ancestor (e.g. the host) has set data-theme="dark". Use
   data-theme="light" / .theme-light to force a light island.
   ============================================================ */
[data-theme="light"], .theme-light {
  /* Badge soft/outline colors — light defaults */
  --badge-green-bg: var(--green-100);   --badge-green-fg: var(--green-800);
  --badge-success-bg: var(--green-100); --badge-success-fg: var(--green-800);
  --badge-yellow-bg: var(--yellow-100); --badge-yellow-fg: var(--yellow-900);
  --badge-red-bg: var(--red-100);       --badge-red-fg: var(--red-900);
  --badge-blue-bg: var(--blue-100);     --badge-blue-fg: var(--blue-900);

  --surface-canvas:   var(--neutral-50);
  --surface-card:     var(--neutral-0);
  --surface-sunken:   var(--neutral-100);
  --surface-inverse:  var(--neutral-950);
  --surface-inverse-2: #141414;

  --text-strong:    var(--neutral-950);
  --text-body:      var(--neutral-700);
  --text-muted:     var(--neutral-600);
  --text-faint:     var(--neutral-600);
  --text-disabled:  var(--neutral-500);
  --text-on-dark:   var(--neutral-50);
  --text-on-dark-muted: var(--neutral-400);
  --text-brand:     var(--green-800);   /* see the :root note — brand green as TEXT, not as fill */
  --text-on-brand:  #ffffff;

  --border-subtle:  var(--neutral-300);
  --border-default: var(--neutral-200);
  --border-strong:  var(--neutral-950);
  --border-on-dark: rgba(255,255,255,0.12);

  --action-primary:        var(--green-800);
  --action-primary-hover:  var(--nodi-node-green);
  --action-primary-active: var(--green-500);
  --text-on-action:        var(--text-on-brand);
  --text-on-action-hover:  var(--nodi-carbon-black);
  --text-on-action-active: var(--nodi-carbon-black);

  --focus-ring: var(--green-800);

  --success: var(--text-brand);
  --warning: var(--yellow-900);
  --danger:  var(--red-900);
  --info:    var(--blue-900);

  --shadow-card:  0 1px 2px rgba(0,22,62,0.04), 0 8px 24px rgba(0,22,62,0.06);
  --shadow-panel: 0 2px 4px rgba(0,22,62,0.04), 0 12px 32px rgba(0,22,62,0.10);
  --shadow-pop:   0 8px 40px rgba(0,22,62,0.18);
  --shadow-soft:  0 4px 16px rgba(174,174,174,0.36);
  --nav-lift:     0 1px 2px rgba(0,22,62,0.05), 0 10px 28px rgba(0,22,62,0.11);
  --prompt-hover-bg: rgba(0,22,62,0.03);

  --cmp-panel-bg:     #f5fcf8;
  --cmp-panel-ring:   rgba(47, 194, 120, 0.28);
  --cmp-panel-shadow: 0 12px 28px rgba(0,22,62,0.06), 0 2px 6px rgba(0,22,62,0.04);
  --row-hover:        rgba(13, 13, 13, 0.025);
  /* THE INDEX GRID'S TWO SURFACES, and they are a PAIR: the cover panel a cell draws behind
     its artwork, and the fill the whole cell takes under the pointer or when its title has
     keyboard focus. The rule is that the cover is always one REAL step above the cell fill,
     so a cell sinks under the cursor and its picture stays the brightest thing in it. The
     cell used to take --surface-sunken, the same surface as the cover: at hover the panel
     dissolved into the cell and the card lit up around its own image instead of receding
     under it.
     TWO NAMES RATHER THAN ONE, because the neutral ramp does not keep its order across
     themes and picking by hex would have flipped the pair. On light, sunken (#f0f0f0) sits
     BELOW card (#ffffff); on dark, sunken (#1f1f1f) sits ABOVE card (#141414). So each theme
     assigns the two roles to whichever step actually holds them, and the relationship — cover
     lighter, cell darker — comes out the same in both.
     Measured on the cell fill, because that is the one a paragraph is read on:
     light #f0f0f0 · body 6.96:1 · muted/faint 4.90:1 · brand 4.84:1
     dark  #141414 · body 11.01:1 · muted 8.31:1 · faint 5.48:1 · brand 10.16:1 */
  --index-cover-bg:   var(--surface-card);
  --index-cell-hover: var(--surface-sunken);
  --mark-filter:      none;
  color-scheme: light;
}

/* ============================================================
   DARK THEME — set data-theme="dark" (or class .theme-dark) on any
   wrapper. Only the SEMANTIC aliases flip; the base ramps are shared.
   ============================================================ */
[data-theme="dark"], .theme-dark {
  /* Badge soft/outline colors — translucent 500 tint + 500 text (100 fill glares on black) */
  --badge-green-bg: rgba(47,194,120,0.15);   --badge-green-fg: var(--green-400);
  --badge-success-bg: rgba(47,194,120,0.15); --badge-success-fg: var(--green-400);
  --badge-yellow-bg: rgba(255,229,85,0.15);  --badge-yellow-fg: var(--yellow-500);
  --badge-red-bg: rgba(255,121,121,0.15);    --badge-red-fg: var(--red-500);
  --badge-blue-bg: rgba(117,202,255,0.15);   --badge-blue-fg: var(--blue-500);

  /* Surfaces */
  --surface-canvas:   var(--neutral-950);   /* Carbon Black page */
  --surface-card:     #141414;              /* elevated panel */
  --surface-sunken:   #1f1f1f;              /* insets, toggle track */
  --surface-inverse:  var(--neutral-50);    /* light sections (inverted) */
  --surface-inverse-2: #141414;              /* dark elevated panel — fixed, doesn't invert with theme (it's already the dark tone) */

  /* Text */
  --text-strong:    var(--neutral-0);
  --text-body:      var(--neutral-300);
  --text-muted:     var(--neutral-400);
  /* On carbon the 500 step already clears 4.5:1 on all three dark surfaces (5.78 canvas ·
     5.48 card · 4.90 sunken), so the third emphasis level survives here even though light
     has to collapse it into --text-muted. */
  --text-faint:     var(--neutral-500);
  /* Follows --neutral-600 down from 3.81:1 to 3.59:1 on carbon. Deliberate: a disabled
     control is exempt from the text minimum (it is telling you it is not for you right
     now), and keeping the step honest for the readable tokens on light is worth more than
     holding the disabled tone where it was. */
  --text-disabled:  var(--neutral-600);
  --text-on-dark:   var(--neutral-50);
  --text-on-dark-muted: var(--neutral-400);
  --text-brand:     var(--green-400);
  --text-on-brand:  var(--nodi-carbon-black);

  /* Borders / hairlines */
  --border-subtle:  rgba(255,255,255,0.10);
  --border-default: rgba(255,255,255,0.16);
  --border-strong:  var(--neutral-50);
  --border-on-dark: rgba(255,255,255,0.12);

  /* Brand action — green stays; hover brightens instead of darkens */
  --action-primary:        var(--nodi-node-green);
  --action-primary-hover:  var(--green-500);
  --action-primary-active: var(--green-400);
  --text-on-action:        var(--text-on-brand);
  --text-on-action-hover:  var(--nodi-carbon-black);
  --text-on-action-active: var(--nodi-carbon-black);

  /* Node Green rather than green-800 here: on Carbon Black the darker step drops to 3.53:1,
     barely over the bar, while Node Green gives 8.44:1. */
  --focus-ring: var(--nodi-node-green);

  /* Feedback — lighter stops read better on dark */
  --success: var(--text-brand);
  --warning: var(--yellow-500);
  --danger:  var(--red-500);
  --info:    var(--blue-500);

  /* Card shadow — cool navy is invisible on black; use a soft black */
  --shadow-card:  0 1px 2px rgba(0,0,0,0.4), 0 8px 24px rgba(0,0,0,0.45);
  --shadow-panel: 0 2px 4px rgba(0,0,0,0.4), 0 12px 32px rgba(0,0,0,0.5);
  --shadow-pop:   0 8px 40px rgba(0,0,0,0.6);
  --shadow-soft:  0 4px 16px rgba(0,0,0,0.5);
  --nav-lift:     0 1px 2px rgba(0,0,0,0.45), 0 10px 28px rgba(0,0,0,0.5);
  --prompt-hover-bg: rgba(255,255,255,0.05);

  /* The recommended-column panel goes from a tinted white card to a lit green wash: on black
     a near-white panel would outshine the table it sits inside. */
  --cmp-panel-bg:     rgba(47, 194, 120, 0.08);
  --cmp-panel-ring:   rgba(47, 194, 120, 0.34);
  --cmp-panel-shadow: 0 12px 28px rgba(0,0,0,0.5), 0 2px 6px rgba(0,0,0,0.4);
  --row-hover:        rgba(255, 255, 255, 0.04);
  /* The pair swaps steps here; see the long note in :root. Sunken is the LIGHTER of the two
     on black, so it is the cover, and card is the cell. The relationship is unchanged: the
     cover is a real step above the fill the cell takes under the pointer. */
  --index-cover-bg:   var(--surface-sunken);
  --index-cell-hover: var(--surface-card);
  --mark-filter:      brightness(0) invert(1);
  color-scheme: dark;
}
