/* Researchgen — design tokens.
   Design system: design-system/researchgen/MASTER.md
   Frozen values: docs/FRONTEND-CONTRACT.md §9 (--viz-*) and §10 (theme mechanism).

   Layering: theme.css (tokens) -> base.css (primitives) -> app.css / dash.css (surfaces).
   Nothing below this file may reference a --l-* / --d-* primitive: components only ever
   read a semantic alias, so re-theming is a change to ONE block and cannot drift.

   Fonts: system stack ONLY, no @font-face, no external stylesheet. On a Mac/iPhone
   -apple-system resolves to SF and nothing is ever downloaded; keeping CSP at
   `default-src 'self'` with no second origin is worth more than one typeface. */

/* ── primitives, declared exactly once ───────────────────────────────────────
   Light and dark values live side by side here. The two switch blocks further
   down contain ONLY short `--bg: var(--d-bg)` remaps, so there is no value
   duplication anywhere in the file to drift out of sync. */
:root {
  /* light */
  --l-bg: #ffffff;
  --l-bg-alt: #f5f5f7;
  --l-bg-elev: #ffffff;
  --l-text: #1d1d1f;
  --l-text-2: #6e6e73;
  --l-text-3: #86868b;
  --l-accent: #0066cc;
  --l-accent-hover: #0077ed;
  /* White ink on the light accent measures 5.56:1 — AA. */
  --l-accent-ink: #ffffff;
  --l-tint-pct: 10%;
  --l-border: rgba(0, 0, 0, .1);
  --l-border-strong: rgba(0, 0, 0, .22);
  --l-nav-bg: rgba(255, 255, 255, .72);
  --l-nav-bg-solid: #ffffff;
  --l-scrim: rgba(0, 0, 0, .4);
  --l-danger: #d70015;
  /* Light mode needs the DARK amber: #ffd60a on white is ~1.3:1. */
  --l-warn: #a05a00;
  --l-success: #1d7a3f;
  --l-code-bg: #f5f5f7;
  --l-shadow-sm: 0 1px 2px rgba(0, 0, 0, .06);
  --l-shadow-md: 0 4px 16px rgba(0, 0, 0, .08);
  --l-shadow-lg: 0 12px 40px rgba(0, 0, 0, .16);

  /* dark */
  --d-bg: #000000;
  --d-bg-alt: #1d1d1f;
  --d-bg-elev: #2c2c2e;
  --d-text: #f5f5f7;
  --d-text-2: #86868b;
  --d-text-3: #6e6e73;
  --d-accent: #2997ff;
  --d-accent-hover: #47a9ff;
  /* THE most important token in the build: #ffffff on #2997ff is 3.05:1 and FAILS
     AA, while #00121f is ~6.6:1. Any rule that hard-codes color:#fff on a filled
     accent control ships a dark-mode accessibility regression. */
  --d-accent-ink: #00121f;
  --d-tint-pct: 16%;
  --d-border: rgba(255, 255, 255, .14);
  --d-border-strong: rgba(255, 255, 255, .28);
  --d-nav-bg: rgba(29, 29, 31, .72);
  --d-nav-bg-solid: #1d1d1f;
  --d-scrim: rgba(0, 0, 0, .6);
  --d-danger: #ff453a;
  --d-warn: #ffd60a;
  --d-success: #30d158;
  --d-code-bg: #1c1c1e;
  /* Raised alphas: a .06 shadow over #000 reads as nothing at all. */
  --d-shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
  --d-shadow-md: 0 4px 16px rgba(0, 0, 0, .4);
  --d-shadow-lg: 0 12px 40px rgba(0, 0, 0, .6);
}

/* ── semantic aliases — the ONLY colour names a component may use ─────────── */
:root {
  color-scheme: light dark;

  --bg: var(--l-bg);
  --bg-alt: var(--l-bg-alt);
  --bg-elev: var(--l-bg-elev);
  --text: var(--l-text);
  --text-2: var(--l-text-2);
  --text-3: var(--l-text-3);
  --accent: var(--l-accent);
  --accent-hover: var(--l-accent-hover);
  --accent-ink: var(--l-accent-ink);
  --tint-pct: var(--l-tint-pct);
  --border: var(--l-border);
  --border-strong: var(--l-border-strong);
  --nav-bg: var(--l-nav-bg);
  --nav-bg-solid: var(--l-nav-bg-solid);
  --scrim: var(--l-scrim);
  --danger: var(--l-danger);
  --warn: var(--l-warn);
  --success: var(--l-success);
  --code-bg: var(--l-code-bg);
  --shadow-sm: var(--l-shadow-sm);
  --shadow-md: var(--l-shadow-md);
  --shadow-lg: var(--l-shadow-lg);

  /* Derived from --accent, so only the percentage is themed. */
  --accent-tint: color-mix(in srgb, var(--accent) var(--tint-pct), transparent);
  --focus-ring: 0 0 0 4px color-mix(in srgb, var(--accent) 35%, transparent);

  /* ── type ──────────────────────────────────────────────────────────────────
     TWO scales, on purpose, and this is the one tension the redesign resolves:
     17px system prose for everything a human wrote or reads as prose, 13px mono
     for machine output. Applying 17px to .trace-row doubles a long run's height
     and makes the console unusable. Do not flatten these into one scale. */
  --t-12: 12px;
  --t-14: 14px;
  --t-17: 17px;
  --t-21: 21px;
  --t-28: 28px;
  --t-40: 40px;
  /* ONE step outside design-frontend §A.4's seven, and only because
     a2-design-dashboard §B.1 freezes the hero figure at "48px, 40px under
     420px". It is used by exactly one rule (figure.viz[data-hero] .viz-value in
     base.css); prose and UI text still ride the §A.4 ramp, so the ramp is not
     widened, it is annotated. Without it the 419px step was a no-op and the
     hero shipped at 40px everywhere. */
  --t-48: 48px;
  --t-56: 56px;
  --tm-11: 11px;
  --tm-13: 13px;
  /* Optical tracking only pays off at display sizes. */
  --tr-tight: -0.02em;
  --lh-body: 1.47;
  --lh-tight: 1.2;
  --lh-display: 1.07;

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
    system-ui, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
    "Liberation Mono", monospace;

  /* ── space ── marketing sections use 64-96, console internals 8-24. */
  --sp-2: 2px;
  --sp-4: 4px;
  --sp-8: 8px;
  --sp-12: 12px;
  --sp-16: 16px;
  --sp-24: 24px;
  --sp-32: 32px;
  --sp-48: 48px;
  --sp-64: 64px;
  --sp-80: 80px;
  --sp-96: 96px;
  /* Legacy aliases: app.css is PORTED rule-by-rule from the v8 sheet, so the old
     names must keep resolving until the last rule has moved over. */
  --s1: var(--sp-4);
  --s2: var(--sp-8);
  --s3: var(--sp-12);
  --s4: var(--sp-16);
  --s5: var(--sp-24);
  --s6: var(--sp-32);

  /* ── radius ── */
  --r-8: 8px;
  --r-12: 12px;
  --r-18: 18px;
  --r-pill: 980px;
  --r-circle: 50%;
  --radius: var(--r-8);

  /* ── motion ── */
  --dur-fast: 150ms;
  --dur: 200ms;
  --dur-slow: 300ms;
  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);

  /* ── layout ── */
  --nav-h: 48px;
  --sidebar-w: 300px;
  --panel-w: 360px;
  --read-w: 760px;
  --marketing-w: 980px;
  --card-w: 420px;
  --banner-h: 56px;

  /* ── dataviz ── FRONTEND-CONTRACT §9.2 freezes every hex below; ui-check.mjs
     string-equals them per theme. --viz-accent and --viz-cat-1 are BOTH blue and
     must never share a figure: as one 9-slot run the palette validator FAILS at
     delta-E 5.0 (deutan). accent = single-series / emphasis / ramp anchor;
     cat-1..8 = per-analyst identity only, 9th analyst folds into --viz-dim. */
  --viz-surface: #ffffff;
  --viz-plane: #f5f5f7;
  --viz-ink: #1d1d1f;
  --viz-ink-2: #6e6e73;
  --viz-ink-muted: #86868b;
  --viz-grid: rgba(0, 0, 0, .08);
  --viz-axis: rgba(0, 0, 0, .18);

  --viz-accent: #0066cc;

  --viz-cat-1: #2a78d6;
  --viz-cat-2: #eb6834;
  /* cat-3/4/5 are sub-3:1 on white (2.82 / 2.17 / 2.69). A figure that paints
     them MUST carry data-relief="labels"; ui-check.mjs fails the build if not. */
  --viz-cat-3: #1baf7a;
  --viz-cat-4: #eda100;
  --viz-cat-5: #e87ba4;
  --viz-cat-6: #008300;
  --viz-cat-7: #4a3aa7;
  --viz-cat-8: #e34948;

  --viz-dim: #c7c7cc;

  /* One validated five-step blue ramp per theme serves both roles: --viz-seq-*
     ascends by magnitude, --viz-ord-* by rank. Light: more = DARKER. */
  --viz-seq-1: #86b6ef;
  --viz-seq-2: #5598e7;
  --viz-seq-3: #2a78d6;
  --viz-seq-4: #1c5cab;
  --viz-seq-5: #104281;
  --viz-ord-1: var(--viz-seq-1);
  --viz-ord-2: var(--viz-seq-2);
  --viz-ord-3: var(--viz-seq-3);
  --viz-ord-4: var(--viz-seq-4);
  --viz-ord-5: var(--viz-seq-5);

  --viz-div-pos: #0066cc;
  --viz-div-mid: #d2d2d7;
  --viz-div-neg: #d03b3b;

  /* Status is FIXED, never themed: the same red must mean the same thing in both
     appearances. Always paired with an icon AND a word — never colour alone. */
  --viz-status-good: #0ca30c;
  --viz-status-warn: #fab219;
  --viz-status-serious: #ec835a;
  --viz-status-critical: #d03b3b;

  --viz-fill-ink: #ffffff;

  /* Reserved chart heights: plot + axis band + table-view summary row, applied
     BEFORE JS runs. This is what makes the zero-layout-shift gate pass and the
     direct fix for "fixed height clips the axis band -> nested scroll". */
  --viz-h-hero: 132px;
  --viz-h-tile: 104px;
  --viz-h-spark: 32px;
  --viz-h-line: 204px;
  --viz-h-stack: 316px;
  --viz-h-bullet: 22px;
  --viz-h-dumbbell: 24px;
  --viz-h-bars: 28px;
  --viz-h-heat: 112px;
}

/* ── the dark remap ──────────────────────────────────────────────────────────
   Declared twice, deliberately, and the two blocks are byte-identical:

     (a) inside the media query for the `auto` preference, guarded by
         :not([data-theme="light"]) so an explicit LIGHT choice wins over a dark
         OS. With `auto` the page is correct before any JS runs and even with JS
         disabled, because the media query is evaluated at parse time.
     (b) on [data-theme="dark"] for the explicit choice, which theme-init.js
         sets pre-paint.

   A CSS nesting trick or :where() list could collapse them into one, but the
   duplication is 40 short `var()` remaps with no literal values in it — there is
   nothing here that can drift, and both selectors stay greppable. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: var(--d-bg);
    --bg-alt: var(--d-bg-alt);
    --bg-elev: var(--d-bg-elev);
    --text: var(--d-text);
    --text-2: var(--d-text-2);
    --text-3: var(--d-text-3);
    --accent: var(--d-accent);
    --accent-hover: var(--d-accent-hover);
    --accent-ink: var(--d-accent-ink);
    --tint-pct: var(--d-tint-pct);
    --border: var(--d-border);
    --border-strong: var(--d-border-strong);
    --nav-bg: var(--d-nav-bg);
    --nav-bg-solid: var(--d-nav-bg-solid);
    --scrim: var(--d-scrim);
    --danger: var(--d-danger);
    --warn: var(--d-warn);
    --success: var(--d-success);
    --code-bg: var(--d-code-bg);
    --shadow-sm: var(--d-shadow-sm);
    --shadow-md: var(--d-shadow-md);
    --shadow-lg: var(--d-shadow-lg);

    --viz-surface: #1d1d1f;
    --viz-plane: #000000;
    --viz-ink: #f5f5f7;
    --viz-ink-2: #86868b;
    --viz-ink-muted: #86868b;
    --viz-grid: rgba(255, 255, 255, .10);
    --viz-axis: rgba(255, 255, 255, .22);
    --viz-accent: #2997ff;
    --viz-cat-1: #3987e5;
    --viz-cat-2: #d95926;
    --viz-cat-3: #199e70;
    --viz-cat-4: #c98500;
    --viz-cat-5: #d55181;
    --viz-cat-6: #008300;
    --viz-cat-7: #9085e9;
    --viz-cat-8: #e66767;
    --viz-dim: #48484a;
    /* Dark: more = LIGHTER. On a #1d1d1f card the visible end of a blue ramp is
       the light end, so seq-1 is the near-surface step. Direction is chosen per
       theme, never flipped at runtime; both were validated against #1d1d1f. */
    --viz-seq-1: #184f95;
    --viz-seq-2: #256abf;
    --viz-seq-3: #3987e5;
    --viz-seq-4: #86b6ef;
    --viz-seq-5: #cde2fb;
    --viz-div-pos: #2997ff;
    --viz-div-mid: #3a3a3c;
    --viz-div-neg: #e66767;
    /* Reuses --d-accent-ink for the same measured reason as the button token. */
    --viz-fill-ink: #00121f;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--d-bg);
  --bg-alt: var(--d-bg-alt);
  --bg-elev: var(--d-bg-elev);
  --text: var(--d-text);
  --text-2: var(--d-text-2);
  --text-3: var(--d-text-3);
  --accent: var(--d-accent);
  --accent-hover: var(--d-accent-hover);
  --accent-ink: var(--d-accent-ink);
  --tint-pct: var(--d-tint-pct);
  --border: var(--d-border);
  --border-strong: var(--d-border-strong);
  --nav-bg: var(--d-nav-bg);
  --nav-bg-solid: var(--d-nav-bg-solid);
  --scrim: var(--d-scrim);
  --danger: var(--d-danger);
  --warn: var(--d-warn);
  --success: var(--d-success);
  --code-bg: var(--d-code-bg);
  --shadow-sm: var(--d-shadow-sm);
  --shadow-md: var(--d-shadow-md);
  --shadow-lg: var(--d-shadow-lg);

  --viz-surface: #1d1d1f;
  --viz-plane: #000000;
  --viz-ink: #f5f5f7;
  --viz-ink-2: #86868b;
  --viz-ink-muted: #86868b;
  --viz-grid: rgba(255, 255, 255, .10);
  --viz-axis: rgba(255, 255, 255, .22);
  --viz-accent: #2997ff;
  --viz-cat-1: #3987e5;
  --viz-cat-2: #d95926;
  --viz-cat-3: #199e70;
  --viz-cat-4: #c98500;
  --viz-cat-5: #d55181;
  --viz-cat-6: #008300;
  --viz-cat-7: #9085e9;
  --viz-cat-8: #e66767;
  --viz-dim: #48484a;
  --viz-seq-1: #184f95;
  --viz-seq-2: #256abf;
  --viz-seq-3: #3987e5;
  --viz-seq-4: #86b6ef;
  --viz-seq-5: #cde2fb;
  --viz-div-pos: #2997ff;
  --viz-div-mid: #3a3a3c;
  --viz-div-neg: #e66767;
  --viz-fill-ink: #00121f;
}

/* An explicit LIGHT choice must also pin color-scheme, or form controls and
   scrollbars stay dark on a dark OS while the page is light. */
:root[data-theme="light"] {
  color-scheme: light;
}

/* ── frosted-surface fallbacks — mandatory, not polish ───────────────────────
   backdrop-filter on a full-width sticky nav plus a blurred dialog backdrop is
   GPU-expensive and janky on older Macs and Android, and AA contrast over the
   frosted bar is only GUARANTEED in the opaque fallback. Both escape hatches
   swap --nav-bg for the solid value; base.css applies the blur only inside the
   @supports branch, so there is one place that decides.

   THE SELECTOR LIST IS THE FIX, and it is not cosmetic. The dark remaps live on
   `:root:not([data-theme="light"])` and `:root[data-theme="dark"]` — specificity
   (0,2,0). A plain `:root` here is (0,1,0) and LOSES to them no matter how late
   it sits in the file, so the fallbacks were dead in dark mode: a translucent,
   unblurred sticky bar with the page scrolling through it. Verified in headless
   Chrome before and after. The two selectors below union to every possible root,
   each at (0,2,0), and come last, so they win in light, dark and auto-dark. Any
   future dark scope must be added here too. */
@media (prefers-reduced-transparency: reduce) {
  :root:not([data-theme="light"]),
  :root[data-theme="light"] {
    --nav-bg: var(--nav-bg-solid);
  }
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root:not([data-theme="light"]),
  :root[data-theme="light"] {
    --nav-bg: var(--nav-bg-solid);
  }
}

/* ── reduced motion — one kill switch, at the token level ────────────────────
   Zeroing the duration tokens disables every transition in every downstream
   sheet without those sheets needing their own media query. The belt-and-braces
   universal rule below catches anything that hard-coded a duration anyway.
   The dashboard has NO draw-in animation at any time (charts paint in one pass),
   so `getAnimations().length === 0` must hold here. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-fast: 0.01ms;
    --dur: 0.01ms;
    --dur-slow: 0.01ms;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
