/* graphrAI design tokens — Monochrome Glassmorphism
 *
 * Two tiers, and the distinction matters:
 *
 *   Tier 1 — primitives (--zinc-*, --white, --black). Raw values. Identical in
 *            both themes. Nothing outside this file may reference them except
 *            the Tier 2 block below; component CSS that reaches for --zinc-700
 *            directly has hardcoded a theme and will break the other one.
 *   Tier 2 — semantic tokens (--color-*, --glass-*, --shadow-*). These carry
 *            intent ("surface", "border", "text-muted") and are the ONLY colour
 *            tokens component CSS is allowed to use.
 *
 * Theme resolution, in precedence order:
 *   1. :root[data-theme="light"|"dark"]  — explicit user choice, set by
 *      theme-boot.js before first paint and by theme.js on change.
 *   2. @media (prefers-color-scheme) on :root:not([data-theme]) — OS preference,
 *      used when the user has picked "System" (or never chose).
 * Dark is the shipped default, so the bare :root block below is the dark theme.
 *
 * Palette rule: zero vibrant colour in the chrome. Surfaces, text, borders and
 * primary actions are strictly neutral. Two deliberate exceptions, both because
 * removing the hue would cost information rather than taste:
 *   - --color-focus-ring stays amber. A neutral focus ring on a neutral UI is
 *     invisible; this is the one place chroma is doing accessibility work.
 *   - status/agent hues survive at ~0.06 chroma — muted enough to read as
 *     monochrome at a glance, distinct enough to decode a node's state.
 *
 * Two borders, and picking the wrong one is an accessibility bug rather than a
 * visual one: --color-border is a decorative edge (cards, dividers, panel
 * seams) and sits around 1.1:1 on purpose; --color-border-interactive is the
 * boundary of something the user can operate (input, select, textarea, outlined
 * button) and clears the 3:1 WCAG 1.4.11 floor.
 */

/* ---------------------------------------------------------------------------
 * Tier 1 — primitives
 * Tailwind's zinc ramp, in oklch. Theme-independent: these never change.
 * ------------------------------------------------------------------------- */
:root {
  --white: oklch(1 0 0);
  --black: oklch(0 0 0);

  --zinc-50:  oklch(0.985 0 0);
  --zinc-100: oklch(0.967 0.001 286);
  --zinc-200: oklch(0.920 0.004 286);
  --zinc-300: oklch(0.871 0.006 286);
  --zinc-400: oklch(0.705 0.015 286);
  --zinc-500: oklch(0.552 0.016 286);
  --zinc-600: oklch(0.442 0.017 286);
  --zinc-700: oklch(0.370 0.013 286);
  --zinc-800: oklch(0.274 0.006 286);
  --zinc-900: oklch(0.210 0.006 286);
  --zinc-950: oklch(0.141 0.005 286);

  /* The only chromatic primitives. Amber, kept solely for focus rings.
     Two values because a focus ring must clear 3:1 against the surface it lands
     on (WCAG 2.4.11, non-text contrast): the bright amber measures 6.11:1 on
     dark surfaces but only 1.64:1 on light ones, which would have made keyboard
     focus invisible in light mode — the exact failure the amber ring exists to
     prevent. The darker value measures 3.55:1 at its worst on light. */
  --amber-focus: oklch(0.78 0.14 55);
  --amber-focus-dark: oklch(0.58 0.15 55);
}

/* ---------------------------------------------------------------------------
 * Tier 2 — semantic tokens, DARK theme (the default)
 * ------------------------------------------------------------------------- */
:root,
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Backgrounds — a deep charcoal-black base, layered surfaces above it. */
  --color-bg: var(--zinc-950);
  --color-surface: var(--zinc-900);
  --color-surface-2: var(--zinc-800);
  --color-surface-3: oklch(0.32 0.006 286);
  /* Hover wash and "raised" fill. Previously inlined as rgba(255,255,255,.04)
     fallbacks in components.css / i18n.css; now first-class tokens. */
  --color-surface-hover: oklch(1 0 0 / 0.06);
  --color-surface-raised: oklch(1 0 0 / 0.04);

  --color-border: oklch(0.274 0.006 286 / 0.60);   /* zinc-800/60 */
  --color-border-strong: var(--zinc-700);
  --color-border-subtle: oklch(0.274 0.006 286 / 0.35);
  /* The boundary of a control the user can operate. --color-border is tuned for
     dividers and card edges and composites to 1.10:1 against --color-surface —
     fine for decoration, but WCAG 1.4.11 wants 3:1 for anything that identifies
     a component. An input whose only edge is a 1.10:1 line is not discoverable.
     Use this on inputs, selects, textareas and outlined buttons; keep the softer
     --color-border for dividers, cards and other purely decorative edges. */
  --color-border-interactive: oklch(0.59 0.013 286);

  /* Text */
  --color-text: var(--zinc-50);
  --color-text-2: var(--zinc-300);
  --color-text-secondary: var(--color-text-2); /* alias — widely used name */
  /* muted and faint step outside the zinc ramp deliberately. zinc-400 (0.705)
     and zinc-500 (0.552) measure 4.07:1 and 2.9:1 on --color-surface-3, failing
     AA on the densest surface either is used on. These two values are the
     shallowest ramp that clears 4.5:1 everywhere (6.33:1 and 4.57:1 worst-case)
     while staying >=0.09 L apart, so the hierarchy is still readable as three
     distinct greys rather than two that look identical. */
  --color-text-muted: oklch(0.78 0.013 286);
  --color-text-faint: oklch(0.69 0.014 286);
  --color-text-inverse: var(--zinc-950);

  /* Primary action — monochrome. On dark, "primary" is the lightest surface:
     a near-white fill with near-black text, mirroring how the amber button
     used to invert its label. */
  --color-primary: var(--zinc-50);
  --color-primary-hover: var(--white);
  --color-primary-text: var(--zinc-950);
  --color-primary-light: oklch(1 0 0 / 0.10);
  --color-primary-soft: oklch(1 0 0 / 0.06);

  /* Focus ring — the deliberate chromatic exception. See header. */
  --color-focus-ring: var(--amber-focus);

  /* Text selection. Left undefined, this is the browser's default blue — the
     single loudest patch of chroma in an otherwise achromatic UI, and the one
     colour a monochrome palette cannot afford to leave to the user agent.
     Solid rather than an alpha wash so the paired foreground below is a
     guarantee rather than a function of whatever surface is underneath. */
  --color-selection: oklch(0.42 0.012 286);
  --color-selection-text: var(--zinc-50);

  /* Glass — frosted surfaces. --glass-bg is the everyday panel; --glass-strong
     is for surfaces that float over content (popovers, composer, modals) and
     needs more opacity to stay legible. */
  --glass-bg: oklch(0.210 0.006 286 / 0.40);        /* zinc-900/40 */
  --glass-bg-strong: oklch(0.210 0.006 286 / 0.72); /* zinc-900/72 */
  --glass-bg-subtle: oklch(0.210 0.006 286 / 0.24);
  --glass-border: oklch(0.274 0.006 286 / 0.60);    /* zinc-800/60 */
  --glass-border-strong: oklch(0.370 0.013 286 / 0.70);
  --glass-highlight: oklch(1 0 0 / 0.06);           /* top inner edge light */
  --color-scrim: oklch(0 0 0 / 0.60);               /* modal backdrop */

  /* Semantic status — hue retained, chroma dropped to ~0.06 so the DAG reads
     as monochrome until you look at a node. */
  --color-user: oklch(0.72 0.055 235);
  --color-user-soft: oklch(0.30 0.035 235);
  --color-success: oklch(0.74 0.060 155);
  --color-success-soft: oklch(0.30 0.035 155);
  --color-warning: oklch(0.80 0.065 85);
  --color-warning-soft: oklch(0.31 0.038 85);
  --color-error: oklch(0.70 0.075 25);
  --color-error-soft: oklch(0.30 0.045 25);
  --color-info: oklch(0.72 0.055 235);
  --color-info-soft: oklch(0.30 0.035 235);

  /* Text drawn ON a --color-*-soft fill (status badges, callouts). The plain
     --color-success/warning/error are tuned for solid fills and borders and
     measure too low against their own soft variant, so badges need their own
     foreground. Each pair below is verified >=4.5:1. */
  --color-on-success-soft: oklch(0.76 0.055 155);
  --color-on-warning-soft: oklch(0.80 0.060 85);
  --color-on-error-soft: oklch(0.74 0.070 25);
  --color-on-info-soft: oklch(0.74 0.055 240);
  /* Neutral badge text on --color-surface-3. */
  --color-on-neutral-soft: var(--zinc-400);

  /* "I brought you here" — the transient highlight both directions of
     cross-view navigation flash on their target (a graph node, a timeline row).
     Deliberately not --color-primary: this marks a location, not a selection,
     and the two can be on screen at once. Review §4.1. */
  --color-locate: oklch(0.72 0.055 235);

  /* Bubble colours. The user bubble is a raised neutral surface rather than a
     coloured fill — the monochrome equivalent of the old blue bubble. */
  --color-user-bubble: var(--zinc-200);
  --color-user-bubble-text: var(--zinc-950);
  --color-agent-bubble: var(--glass-bg);
  --color-agent-bubble-text: var(--color-text);
  --color-agent-bubble-border: var(--glass-border);

  /* Node status */
  --color-node-current: var(--color-text);
  --color-node-complete: var(--color-success);
  --color-node-pending: var(--color-warning);
  --color-node-error: var(--color-error);
  --color-node-deprecated: var(--color-text-faint);
  --color-node-sibling: var(--color-text-muted);

  /* Agent avatar colours — the app's primary information channel, not chrome:
     a panel or debate puts six-plus of these on screen and colour is what tells
     them apart. Sized against a categorical-palette chroma budget rather than
     the 0.058 chrome budget, on a constant-lightness ring (L=0.76) so all eight
     read as one set, hues spaced 45 degrees apart. Chroma is 0.129 rather than a
     round 0.130 because hue 200 clips the sRGB red channel at 0.130.

     Every entry clears AA as text on all four surface tokens (worst 5.58:1,
     up from the previous palette's 3.42:1) and carries --color-text-inverse as
     an avatar label (worst 8.74:1). Min mutual oklab distance is 0.0987 — 1.7x
     the previous palette's 0.057, which sat right against the 0.055 test floor
     with no headroom. See docs/ideas/palette-review-2026-08.md for the solve. */
  --agent-color-0: oklch(0.76 0.129 20);    /* rose   */
  --agent-color-1: oklch(0.76 0.129 65);    /* amber  */
  --agent-color-2: oklch(0.76 0.129 110);   /* citron */
  --agent-color-3: oklch(0.76 0.129 155);   /* jade   */
  --agent-color-4: oklch(0.76 0.129 200);   /* cyan   */
  --agent-color-5: oklch(0.76 0.129 245);   /* azure  */
  --agent-color-6: oklch(0.76 0.129 290);   /* violet */
  --agent-color-7: oklch(0.76 0.129 335);   /* magenta*/

  /* Shadows — soft and diffused. Depth comes from blur, never from a hard edge. */
  --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.30);
  --shadow-sm: 0 2px 8px -2px oklch(0 0 0 / 0.40);
  --shadow-md: 0 8px 24px -10px oklch(0 0 0 / 0.45);
  --shadow-lg: 0 24px 60px -20px oklch(0 0 0 / 0.60), 0 8px 24px -10px oklch(0 0 0 / 0.40);
  /* Glass panels pair an outer diffusion with a 1px inner highlight along the
     top edge — the detail that sells frosted glass rather than "translucent
     rectangle". */
  --shadow-glass: 0 8px 32px -12px oklch(0 0 0 / 0.50),
                  inset 0 1px 0 0 var(--glass-highlight);
}

/* ---------------------------------------------------------------------------
 * Tier 2 — semantic tokens, LIGHT theme
 * Declared once in a custom property block, applied by both selectors below so
 * the explicit choice and the OS preference can never drift apart.
 * ------------------------------------------------------------------------- */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;

    --color-bg: var(--white);
    --color-surface: var(--zinc-50);
    --color-surface-2: var(--zinc-100);
    --color-surface-3: var(--zinc-200);
    --color-surface-hover: oklch(0 0 0 / 0.04);
    --color-surface-raised: oklch(0 0 0 / 0.03);

    --color-border: oklch(0.920 0.004 286 / 0.50);  /* zinc-200/50 */
    --color-border-strong: var(--zinc-300);
    --color-border-subtle: oklch(0.920 0.004 286 / 0.30);
    --color-border-interactive: oklch(0.60 0.016 286);

    --color-text: var(--zinc-900);
    --color-text-2: var(--zinc-700);
    --color-text-muted: var(--zinc-600);
    /* zinc-500 (0.552) measures ~4.2:1 on --color-surface-3, failing AA. 0.50
       clears 4.5:1 on every light surface token. */
    --color-text-faint: oklch(0.50 0.016 286);
    --color-text-inverse: var(--zinc-50);

    --color-primary: var(--zinc-900);
    --color-primary-hover: var(--zinc-950);
    --color-primary-text: var(--zinc-50);
    --color-primary-light: oklch(0 0 0 / 0.06);
    --color-primary-soft: oklch(0 0 0 / 0.04);

    --color-focus-ring: var(--amber-focus-dark);

    --color-selection: oklch(0.84 0.012 286);
    --color-selection-text: var(--zinc-900);

    --glass-bg: oklch(0.985 0 0 / 0.80);        /* zinc-50/80 */
    --glass-bg-strong: oklch(1 0 0 / 0.85);
    --glass-bg-subtle: oklch(0.985 0 0 / 0.55);
    --glass-border: oklch(0.920 0.004 286 / 0.50);
    --glass-border-strong: oklch(0.871 0.006 286 / 0.70);
    --glass-highlight: oklch(1 0 0 / 0.70);
    --color-scrim: oklch(0.141 0.005 286 / 0.35);

    --color-user: oklch(0.50 0.055 240);
    --color-user-soft: oklch(0.94 0.020 240);
    --color-success: oklch(0.46 0.060 155);
    --color-success-soft: oklch(0.94 0.022 155);
    /* 0.58 measured 4.31:1 on --color-bg and 3.40:1 on --color-surface-3 — safe
       as a fill or border, but it is also used as a label and icon colour (e.g.
       the paused pause/resume button), where AA wants 4.5:1. 0.51 clears it on
       every light surface. The other three status hues already did. */
    --color-warning: oklch(0.51 0.065 80);
    --color-warning-soft: oklch(0.95 0.026 85);
    --color-error: oklch(0.52 0.080 25);
    --color-error-soft: oklch(0.95 0.022 25);
    --color-info: oklch(0.50 0.055 240);
    --color-info-soft: oklch(0.94 0.020 240);

    --color-on-success-soft: oklch(0.44 0.060 155);
    --color-on-warning-soft: oklch(0.45 0.060 80);
    --color-on-error-soft: oklch(0.48 0.075 25);
    --color-on-info-soft: oklch(0.46 0.055 240);
    --color-on-neutral-soft: var(--zinc-600);

    --color-locate: oklch(0.50 0.055 240);

    --color-user-bubble: var(--zinc-900);
    --color-user-bubble-text: var(--zinc-50);
    --color-agent-bubble: var(--glass-bg);
    --color-agent-bubble-text: var(--color-text);
    --color-agent-bubble-border: var(--glass-border);

    --color-node-current: var(--color-text);
    --color-node-deprecated: var(--color-text-faint);

    /* Light is structurally tighter than dark: avatars print
       --color-text-inverse (near white), which needs a dark enough fill to
       carry it, while the same fill also has to work as body text on a near
       white page — both push L down to 0.50, the sRGB gamut waist, where
       0.085 is the most chroma every hue can hold. Min mutual oklab distance
       0.0651, worst text contrast 4.54:1, worst avatar label 5.51:1. */
    --agent-color-0: oklch(0.50 0.085 20);
    --agent-color-1: oklch(0.50 0.085 65);
    --agent-color-2: oklch(0.50 0.085 110);
    --agent-color-3: oklch(0.50 0.085 155);
    --agent-color-4: oklch(0.50 0.085 200);
    --agent-color-5: oklch(0.50 0.085 245);
    --agent-color-6: oklch(0.50 0.085 290);
    --agent-color-7: oklch(0.50 0.085 335);

    /* Light-theme shadows are far weaker — on white, a 0.4 alpha shadow reads
       as dirt. Depth here comes from many soft layers at low opacity. */
    --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.04);
    --shadow-sm: 0 2px 8px -3px oklch(0 0 0 / 0.08);
    --shadow-md: 0 8px 24px -12px oklch(0 0 0 / 0.10);
    --shadow-lg: 0 24px 60px -24px oklch(0 0 0 / 0.16), 0 8px 24px -12px oklch(0 0 0 / 0.08);
    --shadow-glass: 0 8px 32px -16px oklch(0 0 0 / 0.12),
                    inset 0 1px 0 0 var(--glass-highlight);
  }
}

/* Explicit light choice. Duplicates the block above rather than @extend-ing it
   (CSS has no such mechanism) — the parity test in
   tests/interfaces/web/test_design_tokens.py enforces that the two stay
   identical, so edit both or the suite fails. */
:root[data-theme="light"] {
  color-scheme: light;

  --color-bg: var(--white);
  --color-surface: var(--zinc-50);
  --color-surface-2: var(--zinc-100);
  --color-surface-3: var(--zinc-200);
  --color-surface-hover: oklch(0 0 0 / 0.04);
  --color-surface-raised: oklch(0 0 0 / 0.03);

  --color-border: oklch(0.920 0.004 286 / 0.50);
  --color-border-strong: var(--zinc-300);
  --color-border-subtle: oklch(0.920 0.004 286 / 0.30);
  --color-border-interactive: oklch(0.60 0.016 286);

  --color-text: var(--zinc-900);
  --color-text-2: var(--zinc-700);
  --color-text-muted: var(--zinc-600);
  --color-text-faint: oklch(0.50 0.016 286);
  --color-text-inverse: var(--zinc-50);

  --color-primary: var(--zinc-900);
  --color-primary-hover: var(--zinc-950);
  --color-primary-text: var(--zinc-50);
  --color-primary-light: oklch(0 0 0 / 0.06);
  --color-primary-soft: oklch(0 0 0 / 0.04);

  --color-focus-ring: var(--amber-focus-dark);

  --color-selection: oklch(0.84 0.012 286);
  --color-selection-text: var(--zinc-900);

  --glass-bg: oklch(0.985 0 0 / 0.80);
  --glass-bg-strong: oklch(1 0 0 / 0.85);
  --glass-bg-subtle: oklch(0.985 0 0 / 0.55);
  --glass-border: oklch(0.920 0.004 286 / 0.50);
  --glass-border-strong: oklch(0.871 0.006 286 / 0.70);
  --glass-highlight: oklch(1 0 0 / 0.70);
  --color-scrim: oklch(0.141 0.005 286 / 0.35);

  --color-user: oklch(0.50 0.055 240);
  --color-user-soft: oklch(0.94 0.020 240);
  --color-success: oklch(0.46 0.060 155);
  --color-success-soft: oklch(0.94 0.022 155);
  --color-warning: oklch(0.51 0.065 80);
  --color-warning-soft: oklch(0.95 0.026 85);
  --color-error: oklch(0.52 0.080 25);
  --color-error-soft: oklch(0.95 0.022 25);
  --color-info: oklch(0.50 0.055 240);
  --color-info-soft: oklch(0.94 0.020 240);

  --color-on-success-soft: oklch(0.44 0.060 155);
  --color-on-warning-soft: oklch(0.45 0.060 80);
  --color-on-error-soft: oklch(0.48 0.075 25);
  --color-on-info-soft: oklch(0.46 0.055 240);
  --color-on-neutral-soft: var(--zinc-600);

  --color-locate: oklch(0.50 0.055 240);

  --color-user-bubble: var(--zinc-900);
  --color-user-bubble-text: var(--zinc-50);
  --color-agent-bubble: var(--glass-bg);
  --color-agent-bubble-text: var(--color-text);
  --color-agent-bubble-border: var(--glass-border);

  --color-node-current: var(--color-text);
  --color-node-deprecated: var(--color-text-faint);

  /* Light is structurally tighter than dark: avatars print
     --color-text-inverse (near white), which needs a dark enough fill to
     carry it, while the same fill also has to work as body text on a near
     white page — both push L down to 0.50, the sRGB gamut waist, where
     0.085 is the most chroma every hue can hold. Min mutual oklab distance
     0.0651, worst text contrast 4.54:1, worst avatar label 5.51:1. */
  --agent-color-0: oklch(0.50 0.085 20);
  --agent-color-1: oklch(0.50 0.085 65);
  --agent-color-2: oklch(0.50 0.085 110);
  --agent-color-3: oklch(0.50 0.085 155);
  --agent-color-4: oklch(0.50 0.085 200);
  --agent-color-5: oklch(0.50 0.085 245);
  --agent-color-6: oklch(0.50 0.085 290);
  --agent-color-7: oklch(0.50 0.085 335);

  --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.04);
  --shadow-sm: 0 2px 8px -3px oklch(0 0 0 / 0.08);
  --shadow-md: 0 8px 24px -12px oklch(0 0 0 / 0.10);
  --shadow-lg: 0 24px 60px -24px oklch(0 0 0 / 0.16), 0 8px 24px -12px oklch(0 0 0 / 0.08);
  --shadow-glass: 0 8px 32px -16px oklch(0 0 0 / 0.12),
                  inset 0 1px 0 0 var(--glass-highlight);
}

/* ---------------------------------------------------------------------------
 * Theme-independent tokens
 * ------------------------------------------------------------------------- */
:root {
  /* Glass blur strengths. Named for intent, not pixel count, so the whole app's
     frost depth can be retuned from one place. */
  --blur-sm: 8px;
  --blur-md: 16px;
  --blur-lg: 24px;
  --glass-filter: blur(var(--blur-md)) saturate(1.4);
  --glass-filter-sm: blur(var(--blur-sm)) saturate(1.2);
  --glass-filter-lg: blur(var(--blur-lg)) saturate(1.6);

  /* Typography */
  --font-sans: "IBM Plex Sans", "Vazirmatn", "Inter", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, monospace;
  --font-size-xs: 11px;
  --font-size-sm: 12.5px;
  --font-size-base: 14px;
  --font-size-md: 15px;
  --font-size-lg: 16px;

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 10px;
  --radius-2xl: 14px;
  --radius-full: 9999px;

  /* Spacing */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;

  /* Section gap token (2026 SaaS standard spacing) */
  --section-gap: 96px;

  /* Layout */
  --panel-left-width: 260px;
  --panel-right-width: 300px;
  --top-bar-height: 48px;
  --composer-max-height: 180px;

  /* Motion */
  --transition-fast: 0.15s ease;
  --transition-base: 0.2s ease;
}

/* Tablet breakpoint */
@media (max-width: 1200px) {
  :root {
    --section-gap: 80px;
  }
}

/* Mobile breakpoint */
@media (max-width: 767px) {
  :root {
    --section-gap: 64px;
    --panel-left-width: 280px;
    --panel-right-width: 100%;
    --composer-max-height: 120px;
  }
}
