/* ════════════════════════════════════════════════════════════════════
   Delayer design tokens — THE single source of truth for every color.

   Canonical file — lives at ui/_shared/. Mirrored into ui/frontend/src/_shared/ and
   ui/system-portal/src/_shared/ by scripts/ci/sync_shared.sh (CI gates
   drift). Both SPAs <link> it BEFORE their own stylesheet, so either can
   override a token by re-declaring it at matching specificity.

   ── Iris palette (2026-07-20 rebrand: navy+gold → indigo-slate + violet) ─
     Chrome:   violet-tinted slate surfaces (hue ~235°, low chroma)
     Accent:   violet — links, focus, active nav, selection
       dark  #a78bfa   light #6d28d9
     CTA:      violet fill (primary buttons) — the brand anchor
       dark  #7c5cf0   light #6d28d9
   The pre-2026-07 brand was navy #001d3d/#003566 + gold #ffc300, retired
   because the dark accent gold sat ΔE ~5 from --warn — the brand color was
   the caution color. Violet is a single interaction hue in BOTH themes, so
   the brand no longer flips when the user toggles light/dark.

   ── Why the surfaces are not one raw brand hue ───────────────────────
   sRGB luminance weights are R 21% · G 72% · B 7%. A pure-blue navy ramp
   steps the channel carrying 7% of perceived lightness, so it looks like a
   big hex jump but barely moves L* — the cards stop lifting off the page.
   So: surfaces hold a violet-slate HUE at low saturation and let all three
   channels carry the ramp, giving real luminance steps. In light the ramp
   is lavender-tinted (no pure white) — the page, cards and chrome all carry
   a faint violet cast so a white card never reads as a jarring blank sheet.

   ── Invariants (a change that breaks one of these is a bug) ──────────
   The AUTHORITATIVE list is the check_contrast.py docstring (I1–I10),
   enforced per theme on every PR. The load-bearing ones here:
   I1  Every adjacent surface step is >= 4.0 dL*   (panels visibly lift)
   I2  --border is >= 5.0 dL* from panel/-2/-3     (edges survive selection)
   I3  --text / --muted / --dim all clear WCAG 4.5:1 on EVERY surface
   I4  Never put --text on a saturated fill — use --on-fill
   I6  --accent clears 3:1 on every surface (the focus ring)
   I7/I8/I10 keep the severity ramp legible + separated
   I5  Status colors encode compliance semantics, not brand. Don't
       retune them for taste: a "pass" that reads amber is a bug.
   Severity is ONE ramp (--sev-*). --sev-* is the INK (4.5:1); --sev-*-line
   is the chart STROKE (3:1) and nothing else. Low was cyan; it is now a
   quiet SLATE so attention ramps monotonically critical>high>medium>low
   (cyan out-shouted yellow "medium" and collided with the blue --info).

   scripts/ci/check_contrast.py enforces all of this on every PR.

     :root                     -> dark tokens (default)
     :root[data-theme="light"] -> light tokens
     @media (prefers-color-scheme: light), no data-theme -> light

   The data-theme attribute (stamped by ui/_shared/theme.js) always beats
   the media query, in both directions. The @media block below is NOT
   validated by the gate — keep it BYTE-IDENTICAL to the light block by hand.
   ════════════════════════════════════════════════════════════════════ */

:root {
  /* Raw brand ramp — documentation + marketing only. Zero UI consumers
     (--accent / --cta hold literal hex, since the contrast gate cannot
     resolve var()). Kept accurate to the Iris story. */
  --brand-ink:       #0a0c12;
  --brand-slate:     #14161f;
  --brand-indigo:    #4f46e5;
  --brand-violet:    #a78bfa;
  --brand-violet-hi: #c4b5fd;

  /* Surfaces — violet-slate hue, desaturated so luminance can actually step */
  --bg:      #0a0c12;   /* page                              */
  --panel:   #14161f;   /* cards, drawers, menus             */
  --panel-2: #1c1f2b;   /* nested / hover                    */
  --panel-3: #262a38;   /* active / selected                 */
  --overlay: rgba(5, 6, 11, 0.72);

  /* Lines — I2: strictly separated from panel/-2/-3 */
  --border:     #39404f;
  --border-mid: #505874;  /* emphasis */

  /* Type — I3: all three clear 4.5:1 on every surface */
  --text:  #e8eaf0;
  --muted: #a6acba;
  --dim:   #8792a3;   /* the floor; do not darken */

  /* Washes — translucent overlays that must work in BOTH themes.
     A raw rgba(255,255,255,.05) is invisible on a white panel: that is
     how every hover state and every progress-bar gutter silently died in
     light mode. These flip polarity with the theme.
       --lift    hover / raised row
       --lift-2  stronger hover
       --track   progress-bar gutter (must stay visible at 0% fill)
       --inset   recessed block: <code> in a banner, expanded audit JSON */
  --lift:   rgba(255, 255, 255, 0.045);
  --lift-2: rgba(255, 255, 255, 0.075);
  --track:  rgba(255, 255, 255, 0.10);
  --inset:  rgba(0, 0, 0, 0.28);
  --shadow: rgba(0, 0, 0, 0.5);

  /* Accent — links, focus, active nav, selection (violet) */
  --accent:     #a78bfa;
  --accent-hi:  #c4b5fd;
  --accent-fg:  #0a0c12;                  /* text ON an accent fill */
  --accent-a04: rgba(167, 139, 250, 0.06);
  --accent-a10: rgba(167, 139, 250, 0.12);
  --accent-a16: rgba(167, 139, 250, 0.18);
  --accent-a35: rgba(167, 139, 250, 0.38);

  /* Primary CTA — violet fill in BOTH themes (the brand anchor).
     Dark uses a deeper violet so WHITE text clears AA (I4). */
  --cta:    #6d4fd8;
  --cta-hi: #7c5cf0;
  --cta-fg: #ffffff;

  /* I4: text placed ON any saturated fill (accent/status chip, avatar).
     Dark-theme fills are LIGHT (violet, mint, salmon) -> near-black text.
     Light-theme fills are DARK (violet, forest, maroon) -> white text. */
  --on-fill: #0a0c12;

  /* Status — I5. Exact pre-rebrand dark values (semantic, not brand). */
  --ok:        #6ee7a8;
  --ok-2:      #22c55e;
  --ok-bg:     rgba(110, 231, 168, 0.16);
  --ok-border: rgba(110, 231, 168, 0.35);

  --danger:        #ff7a7a;
  --danger-2:      #ff9a9a;
  --danger-3:      #ef4444;
  --danger-bg:     rgba(255, 122, 122, 0.18);
  --danger-border: rgba(255, 122, 122, 0.38);

  --warn:        #f6c76a;
  --warn-2:      #ffaa55;
  --warn-3:      #f59e0b;
  --warn-bg:     rgba(246, 199, 106, 0.16);
  --warn-border: rgba(246, 199, 106, 0.35);

  --info:        #6aaaff;
  --info-bg:     rgba(106, 170, 255, 0.14);
  --info-border: rgba(106, 170, 255, 0.32);

  --neutral:        #94a3b8;
  --neutral-bg:     rgba(148, 163, 184, 0.15);
  --neutral-border: rgba(148, 163, 184, 0.25);

  /* ── Severity ────────────────────────────────────────────────────────
     ONE ramp for critical/high/medium/low, everywhere: filter chips, table
     chips, the severity sparkline chart, finding cards, CVE cards, DAM rule
     chips, home stat cards, the DSPM sensitivity pill.

     Baseline = red / orange / yellow, with LOW retuned from cyan to a quiet
     SLATE. Cyan out-shouted yellow "medium" (priority inversion) and sat in
     the same hue family as both --info and the new violet accent; slate is
     recessive, so severity now ramps monotonically. The slate chip bg is
     pushed toward blue and offset in lightness so it clears I10's ΔE>=8
     from the violet-slate surfaces (a violet-family bg would collapse in). */
  --sev-critical:        #f26868;
  --sev-critical-bg:     #261316;
  --sev-critical-border: rgba(242, 104, 104, 0.38);

  --sev-high:            #f97316;
  --sev-high-bg:         #1a1109;
  --sev-high-border:     rgba(249, 115, 22, 0.38);

  --sev-medium:          #eab308;
  --sev-medium-bg:       #17130a;
  --sev-medium-border:   rgba(234, 179, 8, 0.38);

  --sev-low:             #9db0c9;
  --sev-low-bg:          #0e2036;
  --sev-low-border:      rgba(157, 176, 201, 0.38);

  /* Chart STROKES. WCAG holds text to 4.5:1 but a graphical object to only
     3:1 (1.4.11) — so a single value can't serve both without dragging the
     chart line down to text-darkness. --sev-* is the INK (chips, labels,
     counts, stat values); --sev-*-line is the STROKE (sparkline polylines +
     hover dots) and nothing else. Same hue, so they read as one color. */
  --sev-critical-line:   #ef4444;
  --sev-high-line:       #f97316;
  --sev-medium-line:     #eab308;
  --sev-low-line:        #9db0c9;

  /* ── Categorical (--cat-*) ───────────────────────────────────────────
     IDENTITY hues for the inventory donuts (db-family, provider). Not a
     ramp and NOT severity: these encode "which kind of thing", so they
     carry no ordering and must never be reused for status.

     3:1 graphical fills (WCAG 1.4.11), like --sev-*-line — not 4.5:1 ink.
     Light gets its own darker steps: the dark swatches are tuned for a
     near-black panel and a bright yellow/lime/cyan is invisible on the
     near-white lavender one (#eab308 measured 1.86:1 there).

     ── HONEST LIMIT: this set is NOT colorblind-safe, and cannot be ────
     12 categorical slots exceed what hue alone can separate (the ceiling
     is ~8). A maximin search over a 3206-colour in-gamut pool could not
     find 12 that clear the dataviz validator's floors, so the validator
     still reports `ok:false` on this palette — as it did on the one this
     replaces. What changed is that the CATASTROPHIC pairs are gone:

       dark  worst CVD    0.3 -> 4.2  (was warehouse/ledger: two violets
                                       that were literally identical to a
                                       protanope; now nosql/document)
       dark  worst normal 5.1 -> 8.3  (now timeseries/widecolumn)
       light worst CVD          5.8 · worst normal 9.8

     Identity here is NOT colour-alone: every donut slice has a legend row
     printing label + count + percentage beside the dot, which is the
     secondary encoding that makes the residual overlap acceptable.

     Residual close pairs, documented rather than papered over:
       dark   nosql/document      protan 4.2   (green vs yellow)
              cache/timeseries    tritan 4.1
              timeseries/widecolumn normal 8.3 (pink vs rose)
       light  cache/document      deutan 5.8
              cache/timeseries    tritan 1.8
              graph/tables        normal 9.8   (two teals)
     Do not "fix" one of these by retuning a neighbour without re-running
     the validator over all 12 in BOTH themes — every one of these pairs
     was created by a well-meant single-slot edit. */
  --cat-relational: #4f8cff;
  --cat-nosql:      #22c55e;
  --cat-cache:      #f97316;
  --cat-warehouse:  #d946ef;   /* was #a855f7: dE 0.3 from ledger under
                                  protanopia, and collided with --accent */
  --cat-document:   #eab308;
  --cat-graph:      #22d3ee;
  --cat-timeseries: #ec4899;
  --cat-ledger:     #b45309;   /* was #8b5cf6 (the other half of that pair).
                                  NOT a grey — a stone grey here lands
                                  dE 4.4 from --cat-other. */
  --cat-widecolumn: #f43f5e;
  --cat-search:     #a3e635;   /* NEW — opensearch used to fall through to
                                  the grey fallback and read as "Other" */
  --cat-tables:     #0d9488;   /* was #06b6d4: a second cyan beside graph */

  /* The residual bucket is deliberately the neutral grey, so it recedes
     behind every real category. var() resolves at USE time against the
     active theme's --neutral, so this is declared once and follows the
     light/dark toggle on its own — do not duplicate it into the light
     blocks. */
  --cat-other:      var(--neutral);
}

/* ── Light theme ─────────────────────────────────────────────────────── */
:root[data-theme="light"] {
  /* Lavender-tinted ramp — NO pure white. --panel (near-white lavender) is
     the peak; the page and nested surfaces sit below it. --panel-2 is a
     hover *inside* a card, so its neighbour is --panel, never --bg; the two
     may land near each other. The whole ramp carries a faint violet cast so
     the app reads as one tinted world rather than white boxes on grey. */
  --bg:      #e8eaf5;
  --panel:   #f9f9fd;
  --panel-2: #e9eaf4;
  --panel-3: #dbdded;
  --overlay: rgba(18, 16, 30, 0.45);

  --border:     #c2c6de;
  --border-mid: #a6abc9;

  --text:  #171923;
  --muted: #4a5068;
  --dim:   #515b73;   /* darkened for the tinted (non-white) panel-3; still
                         lighter than --muted so the two ink tiers separate */

  --lift:   rgba(109, 40, 217, 0.05);
  --lift-2: rgba(109, 40, 217, 0.08);
  --track:  rgba(109, 40, 217, 0.12);
  --inset:  rgba(109, 40, 217, 0.06);
  --shadow: rgba(30, 20, 60, 0.16);

  --accent:     #6d28d9;   /* violet — dark enough to be ink on lavender */
  --accent-hi:  #5b21b6;
  --accent-fg:  #ffffff;
  --accent-a04: rgba(109, 40, 217, 0.05);
  --accent-a10: rgba(109, 40, 217, 0.10);
  --accent-a16: rgba(109, 40, 217, 0.14);
  --accent-a35: rgba(109, 40, 217, 0.32);

  --cta:    #6d28d9;       /* brand anchor: white on #6d28d9 is ~7:1 */
  --cta-hi: #5b21b6;
  --cta-fg: #ffffff;

  --on-fill: #ffffff;      /* light-theme fills are dark -> white text */

  --ok:        #067647;
  --ok-2:      #059142;
  --ok-bg:     rgba(6, 118, 71, 0.10);
  --ok-border: rgba(6, 118, 71, 0.28);

  --danger:        #b42318;
  --danger-2:      #d64a41;
  --danger-3:      #b42318;
  --danger-bg:     rgba(180, 35, 24, 0.09);
  --danger-border: rgba(180, 35, 24, 0.26);

  --warn:        #b54708;
  --warn-2:      #c2620c;
  --warn-3:      #b54708;
  --warn-bg:     rgba(181, 71, 8, 0.10);
  --warn-border: rgba(181, 71, 8, 0.26);

  --info:        #175cd3;
  --info-bg:     rgba(23, 92, 211, 0.09);
  --info-border: rgba(23, 92, 211, 0.26);

  --neutral:        #52708f;
  --neutral-bg:     rgba(82, 112, 143, 0.10);
  --neutral-border: rgba(82, 112, 143, 0.24);

  /* Severity — same warm hues as dark, darkened to be legible ink on the
     tinted (darker-than-white) panel-3. Low is a blue-slate; its chip bg is
     pushed toward blue so it clears I10's ΔE>=8 from the lavender surfaces. */
  --sev-critical:        #bd1414;
  --sev-critical-bg:     #fce8e8;
  --sev-critical-border: rgba(189, 20, 20, 0.30);

  --sev-high:            #96450f;
  --sev-high-bg:         #fbede3;
  --sev-high-border:     rgba(150, 69, 15, 0.30);

  --sev-medium:          #74590d;
  --sev-medium-bg:       #f5f0e2;
  --sev-medium-border:   rgba(116, 89, 13, 0.30);

  --sev-low:             #4c5f7e;
  --sev-low-bg:          #c8dcff;
  --sev-low-border:      rgba(76, 95, 126, 0.30);

  /* Strokes only need 3:1, so they stay vivid where the ink cannot. High +
     medium are darkened vs the old white-panel values because --panel is now
     a near-white lavender (L* 98, not 100) — a mid orange/olive tipped under
     3:1 against it. */
  --sev-critical-line:   #ef4444;
  --sev-high-line:       #d1600f;
  --sev-medium-line:     #9a7a0c;
  --sev-low-line:        #64748b;

  /* Categorical — same hue families as dark (a category must not change
     identity when the user toggles the theme: every step stays within 12
     degrees of its dark counterpart's OKLCH hue), re-lit for the near-white
     lavender panel. Every step clears 3:1 there.
     NOT a mechanical darkening: flattening all 12 onto one lightness plane
     collapsed graph/other to protan dE 0.2. Separation needs lightness
     SPREAD, so these run L* 0.40–0.62 rather than sitting at one level.
     --cat-other is inherited (it is var(--neutral), which is themed). */
  --cat-relational: #0557c0;
  --cat-nosql:      #316631;
  --cat-cache:      #ab3513;
  --cat-warehouse:  #9c17a5;
  --cat-document:   #9e6800;
  --cat-graph:      #2994ae;
  --cat-timeseries: #aa0065;
  --cat-ledger:     #821e00;
  --cat-widecolumn: #e12870;
  --cat-search:     #799238;
  --cat-tables:     #1d876e;
}

/* OS preference — applies only until the user makes an explicit choice.
   Kept byte-identical to the block above; if you edit one, edit both.
   (The contrast gate does NOT validate this block — a drift here ships
   silently to un-toggled OS-light users.) */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg: #e8eaf5;        --panel: #f9f9fd;      --panel-2: #e9eaf4;
    --panel-3: #dbdded;   --overlay: rgba(18, 16, 30, 0.45);
    --border: #c2c6de;    --border-mid: #a6abc9;
    --text: #171923;      --muted: #4a5068;      --dim: #515b73;
    --lift: rgba(109, 40, 217, 0.05);
    --lift-2: rgba(109, 40, 217, 0.08);
    --track: rgba(109, 40, 217, 0.12);
    --inset: rgba(109, 40, 217, 0.06);
    --shadow: rgba(30, 20, 60, 0.16);
    --accent: #6d28d9;    --accent-hi: #5b21b6;  --accent-fg: #ffffff;
    --on-fill: #ffffff;
    --accent-a04: rgba(109, 40, 217, 0.05);
    --accent-a10: rgba(109, 40, 217, 0.10);
    --accent-a16: rgba(109, 40, 217, 0.14);
    --accent-a35: rgba(109, 40, 217, 0.32);
    --cta: #6d28d9;       --cta-hi: #5b21b6;     --cta-fg: #ffffff;
    --ok: #067647;        --ok-2: #059142;
    --ok-bg: rgba(6, 118, 71, 0.10);       --ok-border: rgba(6, 118, 71, 0.28);
    --danger: #b42318;    --danger-2: #d64a41;   --danger-3: #b42318;
    --danger-bg: rgba(180, 35, 24, 0.09);  --danger-border: rgba(180, 35, 24, 0.26);
    --warn: #b54708;      --warn-2: #c2620c;     --warn-3: #b54708;
    --warn-bg: rgba(181, 71, 8, 0.10);     --warn-border: rgba(181, 71, 8, 0.26);
    --info: #175cd3;
    --info-bg: rgba(23, 92, 211, 0.09);    --info-border: rgba(23, 92, 211, 0.26);
    --neutral: #52708f;
    --neutral-bg: rgba(82, 112, 143, 0.10);
    --neutral-border: rgba(82, 112, 143, 0.24);
    --sev-critical: #bd1414;
    --sev-critical-bg: #fce8e8;
    --sev-critical-border: rgba(189, 20, 20, 0.30);
    --sev-high: #96450f;
    --sev-high-bg: #fbede3;
    --sev-high-border: rgba(150, 69, 15, 0.30);
    --sev-medium: #74590d;
    --sev-medium-bg: #f5f0e2;
    --sev-medium-border: rgba(116, 89, 13, 0.30);
    --sev-low: #4c5f7e;
    --sev-low-bg: #c8dcff;
    --sev-low-border: rgba(76, 95, 126, 0.30);
    --sev-critical-line: #ef4444;
    --sev-high-line: #d1600f;
    --sev-medium-line: #9a7a0c;
    --sev-low-line: #64748b;
    --cat-relational: #0557c0;
    --cat-nosql: #316631;
    --cat-cache: #ab3513;
    --cat-warehouse: #9c17a5;
    --cat-document: #9e6800;
    --cat-graph: #2994ae;
    --cat-timeseries: #aa0065;
    --cat-ledger: #821e00;
    --cat-widecolumn: #e12870;
    --cat-search: #799238;
    --cat-tables: #1d876e;
  }
}
