/*
 * tokens.css — Dazzle design system as CSS custom properties.
 *
 * Every length, colour, type-size, radius, shadow, and motion value
 * used in component CSS MUST resolve to a token defined here.
 * Literal values (px, rem, em, hex, hsl numerics) are permitted only
 * inside this file. Stylelint enforces this rule.
 *
 * UK English throughout: --colour-*, not --color-*.
 *
 * ─────────────────────────────────────────────────────────────────
 *
 * Portions of this file derived from Open Props by Adam Argyle (MIT).
 * Source: https://github.com/argyleink/open-props
 * Values have been renamed to Dazzle's vocabulary and selectively
 * adopted. Open Props is NOT a runtime dependency — values are
 * vendored in source.
 */

:root {
  /* ─────────────────────────────── spacing ─────────────────────────
   * 4px base. Geometric-ish progression: each step roughly 1.5x.
   * Use semantic names; fall back to scale when truly arbitrary.
   * ───────────────────────────────────────────────────────────────── */
  --space-xs: 0.25rem;   /* 4px */
  --space-sm: 0.5rem;    /* 8px */
  --space-md: 0.75rem;   /* 12px */
  --space-lg: 1rem;      /* 16px */
  --space-xl: 1.5rem;    /* 24px */
  --space-2xl: 2rem;     /* 32px */
  --space-3xl: 3rem;     /* 48px */

  /* ─────────────────────────── fluid spacing ───────────────────────
   * For responsive padding/gap that adapts smoothly. Lifted formulae
   * pattern from Open Props (props.sizes.css). */
  --space-fluid-sm: clamp(0.5rem, 1vw, 0.75rem);
  --space-fluid-md: clamp(0.75rem, 1.5vw, 1rem);
  --space-fluid-lg: clamp(1rem, 2.5vw, 1.5rem);

  /* ────────────────────────────── type scale ───────────────────────
   * Fluid via clamp(). Lifted from Open Props (props.fonts.css)
   * fluid-font-size block, renamed to Dazzle's vocabulary. The
   * clamp expressions are non-trivial to derive correctly.
   * ───────────────────────────────────────────────────────────────── */
  --text-xs: clamp(0.75rem, 0.7rem + 0.2vw, 0.875rem);
  --text-sm: clamp(0.875rem, 0.8rem + 0.25vw, 1rem);
  --text-base: clamp(1rem, 0.9rem + 0.3vw, 1.125rem);
  --text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  --text-xl: clamp(1.375rem, 1.2rem + 0.7vw, 1.75rem);
  --text-2xl: clamp(1.75rem, 1.5rem + 1vw, 2.25rem);
  --text-3xl: clamp(2.25rem, 1.9rem + 1.5vw, 3rem);
  --text-display: clamp(3rem, 2.4rem + 2.5vw, 4.5rem);

  /* ─────────────────────────── heading scale (#983) ────────────────
   * Two scales for two contexts. The app shell is dense and
   * compact (h1 ~18px); the marketing site is editorial and
   * expansive (h1 ~3.5rem). Pre-#983 every renderer hard-coded its
   * own font-size, so promoting `.dz-table-title` from `--text-base`
   * to `--text-lg` was a one-off tweak instead of a tokenised
   * decision.
   *
   * The marketing tokens are also defined in `design-system.css`
   * (canonical home for site/marketing variables). Mirroring them
   * here keeps app-shell + marketing token discovery in one file
   * for adopters reading tokens.css to understand the system.
   */
  --dz-heading-app-page-title: var(--text-lg);
  --dz-heading-app-section-title: var(--text-base);
  --dz-heading-app-subsection-title: var(--text-sm);

  --dz-heading-marketing-hero: var(--dz-font-size-hero-headline, 3.5rem);
  --dz-heading-marketing-cta: var(--dz-font-size-cta-headline, 3rem);
  --dz-heading-marketing-section: var(--dz-font-size-section-headline, 2.25rem);

  /* ─────────────────────────── font families ─────────────────────
   * `--font-system` is the OS-default sans stack; `--font-mono` is
   * used by tabular numerics in chart summaries (bar_track etc.). */
  --font-system: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono",
               Consolas, "Liberation Mono", monospace;

  /* ─────────────────────────── line height ─────────────────────────
   * Unitless; multiplies the font-size of the same element. */
  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-loose: 1.8;

  /* ─────────────────────────── font weight ─────────────────────────
   * Variable-font friendly; only the weights we actually use. */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  /* ─────────────────────────── radii ───────────────────────────────
   * Use semantic names. Full = pill. */
  /* Focus ring (HaTchi-MaXchi TASTE-1): accent at 40% alpha, 2px, offset
     2px — one ring vocabulary for every interactive element. */
  --focus-ring-color: color-mix(in oklab, var(--colour-brand) 40%, transparent);
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;

  --radius-sm: 0.25rem;  /* 4px — small inputs, badges */
  --radius-md: 0.375rem; /* 6px — cards, buttons */
  --radius-lg: 0.75rem;  /* 12px — large cards, modals */
  --radius-full: 9999px;

  /* ─────────────────────────── shadows ─────────────────────────────
   * Layered for depth. Sources: Open Props shadow blocks adjusted
   * for Dazzle's visual identity (lower contrast, warmer ambient). */
  --shadow-sm:
    0 1px 2px hsl(0 0% 0% / 0.04),
    0 1px 3px hsl(0 0% 0% / 0.06);
  --shadow-md:
    0 2px 4px hsl(0 0% 0% / 0.04),
    0 4px 8px hsl(0 0% 0% / 0.08);
  --shadow-lg:
    0 4px 8px hsl(0 0% 0% / 0.04),
    0 12px 24px hsl(0 0% 0% / 0.12);

  /* ─────────────────────────── motion ──────────────────────────────
   * Easings lifted from Open Props (props.easings.css). The
   * cubic-bezier values for spring-like easings are well-tuned. */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.5, -0.5, 0.5, 1.5);

  /* Spring-easing ladder, Open Props convention. Progressively
   * more bounce — pick the smallest one that conveys the affordance.
   * #960 layer 1. */
  --ease-spring-1: cubic-bezier(0.5, 0.75, 0.75, 1.25);
  --ease-spring-2: cubic-bezier(0.5, 1, 0.75, 1.25);
  --ease-spring-3: cubic-bezier(0.5, 1.25, 0.75, 1.25);
  --ease-spring-4: cubic-bezier(0.5, 1.5, 0.75, 1.25);
  --ease-spring-5: cubic-bezier(0.5, 1.75, 0.75, 1.25);

  /* Elastic easings — for dismiss/snap-back motion. */
  --ease-elastic-1: cubic-bezier(0.5, 0.75, 0.75, 1.25);
  --ease-elastic-2: cubic-bezier(0.5, 1, 0.75, 1.25);
  --ease-elastic-3: cubic-bezier(0.5, 1.25, 0.75, 1.25);

  --duration-fast: 80ms;
  --duration-base: 150ms;
  --duration-slow: 300ms;

  /* Named animation shorthands, Open Props convention. Each combines
   * keyframe + duration + easing in one var so templates can write
   * `animation: var(--animation-fade-in)` directly. The keyframes
   * themselves are defined below the :root block. #960 layer 1. */
  --animation-fade-in: dz-fade-in var(--duration-base) var(--ease-out);
  --animation-fade-out: dz-fade-out var(--duration-base) var(--ease-in);
  --animation-scale-up: dz-scale-up var(--duration-base) var(--ease-spring-2);
  --animation-scale-down: dz-scale-down var(--duration-base) var(--ease-in);
  --animation-slide-in-up: dz-slide-in-up var(--duration-base) var(--ease-out);
  --animation-slide-in-down: dz-slide-in-down var(--duration-base) var(--ease-out);
  --animation-slide-in-left: dz-slide-in-left var(--duration-base) var(--ease-out);
  --animation-slide-in-right: dz-slide-in-right var(--duration-base) var(--ease-out);
  --animation-slide-out-up: dz-slide-out-up var(--duration-base) var(--ease-in);
  --animation-slide-out-down: dz-slide-out-down var(--duration-base) var(--ease-in);
  --animation-slide-out-left: dz-slide-out-left var(--duration-base) var(--ease-in);
  --animation-slide-out-right: dz-slide-out-right var(--duration-base) var(--ease-in);
  --animation-shake-x: dz-shake-x 0.4s var(--ease-out);
  --animation-pulse: dz-pulse 2s var(--ease-in-out) infinite;

  /* ─────────────────────────── widths ──────────────────────────────
   * Common content widths. */
  --width-prose: 65ch;
  --width-content: 56rem;
  --width-page: 80rem;

  /* App-shell content measure (``data-dz-measure`` on ``.dz-app-main``).
   * pick-a-measure.md — product readable form/detail; wide intermediate;
   * app soft ultrawide for list/board scan. */
  --dz-content-measure: 48rem;        /* product */
  --dz-content-measure-wide: 56rem;   /* wide (= --width-content) */
  --dz-content-measure-app: 96rem;    /* app soft cap */

  /* ─────────────────────────── touch targets ───────────────────────
   * Minimum hit-area on touch devices. Apple HIG calls for 44×44pt;
   * Material recommends 48×48dp. We use 44px as a good middle —
   * matches Apple, hits Material's 80% threshold, doesn't visually
   * inflate dense desktop UIs.
   *
   * The min-size is enforced under `@media (pointer: coarse)` only
   * (see `components/touch-targets.css`) so desktop pixel-perfect
   * sizing is preserved while touch users get a comfortable target.
   *
   * #958 cycle 1.
   */
  --dz-touch-target-min: 44px;

  /* ─────────────────────────── colour ramps ────────────────────────
   * Raw OKLCH ramps lifted from Open Props
   * (props.colors-oklch.css), renamed to Dazzle's vocabulary. Use
   * semantic tokens (--colour-*) below for almost all styling;
   * raw ramps only when no semantic token fits.
   * ───────────────────────────────────────────────────────────────── */
  --neutral-50: oklch(98.5% 0.002 247.84);
  --neutral-100: oklch(96.7% 0.003 264.54);
  --neutral-200: oklch(92.8% 0.006 264.53);
  --neutral-300: oklch(86.9% 0.012 252.89);
  --neutral-400: oklch(70.6% 0.022 261.32);
  --neutral-500: oklch(55.1% 0.027 264.36);
  --neutral-600: oklch(44.6% 0.030 256.80);
  --neutral-700: oklch(37.3% 0.034 259.73);
  --neutral-800: oklch(27.8% 0.030 256.85);
  --neutral-900: oklch(21.0% 0.034 264.66);
  --neutral-950: oklch(13.0% 0.028 261.69);

  --brand-50: oklch(97.0% 0.014 254.60);
  --brand-100: oklch(93.2% 0.032 255.59);
  --brand-200: oklch(88.2% 0.059 254.13);
  --brand-300: oklch(80.9% 0.105 251.81);
  --brand-400: oklch(70.7% 0.165 254.62);
  --brand-500: oklch(62.3% 0.214 259.81);
  --brand-600: oklch(54.6% 0.245 262.88);
  --brand-700: oklch(48.8% 0.243 264.05);
  --brand-800: oklch(42.4% 0.199 265.64);
  --brand-900: oklch(37.9% 0.146 265.52);
  --brand-950: oklch(28.2% 0.091 267.94);

  --success-500: oklch(64.8% 0.150 142.50);
  --success-600: oklch(53.0% 0.140 142.50); /* 4.5:1 on the soft wash AND white (axe-verified) */
  --warning-500: oklch(76.9% 0.188 70.08);
  --warning-600: oklch(52.0% 0.150 70.08); /* 4.5:1 on the soft wash AND white (axe-verified) */
  --danger-500: oklch(63.7% 0.208 25.33);
  --danger-600: oklch(51.0% 0.195 25.33); /* 4.5:1 as text on wash/white; fills carry white text */

  /* ─────────────────────────── colour: semantic ────────────────────
   * Semantic tokens that components reference. Project themes
   * override THESE, not the raw ramps. light-dark() handles
   * scheme switching when supported; @media fallback below.
   * ───────────────────────────────────────────────────────────────── */
  --colour-bg: light-dark(var(--neutral-50), var(--neutral-950));
  --colour-surface: light-dark(white, var(--neutral-900));
  --colour-surface-muted: light-dark(var(--neutral-100), var(--neutral-800));
  --colour-text: light-dark(var(--neutral-900), var(--neutral-50));
  --colour-text-muted: light-dark(var(--neutral-600), var(--neutral-400));
  --colour-border: light-dark(var(--neutral-200), var(--neutral-700));
  --colour-brand: var(--brand-600);
  /* Brand as TEXT (badges/avatars/tags on washes): scheme-aware, unlike
     --colour-brand which stays the vivid FILL accent in both schemes. */
  --colour-brand-text: light-dark(var(--brand-600), var(--brand-300));
  --colour-brand-contrast: white;  /* foreground on filled brand fills (CTAs) */
  --colour-accent: var(--brand-500);
  /* Tone TEXT semantics are scheme-aware: the -600 steps carry 4.5:1 on
   light washes/white; dark mode needs LIGHT tone text on dark washes.
   Solid fills that carry white text must use the fixed ramp steps
   (--success-600 etc.), never these flippable text tokens. */
  --colour-success: light-dark(var(--success-600), oklch(74% 0.13 142.50));
  --colour-warning: light-dark(var(--warning-600), oklch(80% 0.13 70.08));
  --colour-danger: light-dark(var(--danger-600), oklch(75% 0.15 25.33));
  /* Info: cyan-leaning so chart series stay distinguishable from
   * --colour-brand (hue ~263). Scheme-stable, like the tone hues. */
  --colour-info: oklch(60% 0.13 220);

  /* Tone tints (background washes for surfaces) — semantic tokens
   * the dz-tones replacements consume. Authors don't hand-craft
   * `hsl(var(--success) / 0.08)` strings any more. */
  --colour-success-soft: light-dark(oklch(from var(--colour-success) 96.5% 0.04 h), oklch(from var(--colour-success) 28% 0.06 h));
  --colour-warning-soft: light-dark(oklch(from var(--colour-warning) 96.5% 0.04 h), oklch(from var(--colour-warning) 28% 0.06 h));
  --colour-danger-soft: light-dark(oklch(from var(--colour-danger) 96.5% 0.04 h), oklch(from var(--colour-danger) 28% 0.06 h));
  --colour-brand-soft: light-dark(oklch(from var(--colour-brand) 96.5% 0.04 h), oklch(from var(--colour-brand) 28% 0.06 h));

  /* Default colour-scheme advertisement — lets browser-chrome
   * (scrollbars, form controls) pick appropriate variants. */
  color-scheme: light dark;
}

/* #938 — bind `data-theme="dark"` (the JS toggle's output) to the
 * actual `color-scheme` property so `light-dark()` flips when the
 * user toggles. Without this, the toggle would set `data-theme` but
 * `light-dark()` would still resolve based on `prefers-color-scheme`,
 * leaving framework components stuck on the light variant of
 * `--colour-bg` / `--colour-text` / `--colour-border` even though
 * the topbar swapped its sun/moon icon. The marketing-site `site.js`
 * sets `style.colorScheme` imperatively for the same reason; this
 * CSS rule covers the in-app shell whose Alpine controller only
 * sets `data-theme`. */
[data-theme="dark"] {
  color-scheme: dark;
}
[data-theme="light"] {
  color-scheme: light;
}

/* Fallback for browsers without light-dark() support. Modern
 * Chrome / Firefox / Safari all support it as of 2024 but the
 * @supports gate keeps the door open for outliers. */
@media (prefers-color-scheme: dark) {
  @supports not (color: light-dark(white, black)) {
    :root {
      --colour-bg: var(--neutral-950);
      --colour-surface: var(--neutral-900);
      --colour-surface-muted: var(--neutral-800);
      --colour-text: var(--neutral-50);
      --colour-text-muted: var(--neutral-400);
      --colour-border: var(--neutral-700);
      --colour-success: oklch(74% 0.13 142.50);
      --colour-warning: oklch(80% 0.13 70.08);
      --colour-danger: oklch(75% 0.15 25.33);
      --colour-brand-text: var(--brand-300);
      --colour-success-soft: oklch(from var(--colour-success) 28% 0.06 h);
      --colour-warning-soft: oklch(from var(--colour-warning) 28% 0.06 h);
      --colour-danger-soft: oklch(from var(--colour-danger) 28% 0.06 h);
      --colour-brand-soft: oklch(from var(--colour-brand) 28% 0.06 h);
    }
  }
}

/* ─────────────────────────── animation keyframes ────────────────────
 * Backing keyframes for the `--animation-*` named tokens above.
 * Prefixed `dz-` so they don't collide with project-defined
 * keyframes. Open Props convention; #960 layer 1.
 *
 * Honoured by `prefers-reduced-motion: reduce` further down. */
@keyframes dz-fade-in {
  from { opacity: 0; }
}
@keyframes dz-fade-out {
  to { opacity: 0; }
}
@keyframes dz-scale-up {
  from { transform: scale(0.92); opacity: 0; }
}
@keyframes dz-scale-down {
  to { transform: scale(0.92); opacity: 0; }
}
@keyframes dz-slide-in-up {
  from { transform: translateY(8%); opacity: 0; }
}
@keyframes dz-slide-in-down {
  from { transform: translateY(-8%); opacity: 0; }
}
@keyframes dz-slide-in-left {
  from { transform: translateX(8%); opacity: 0; }
}
@keyframes dz-slide-in-right {
  from { transform: translateX(-8%); opacity: 0; }
}
@keyframes dz-slide-out-up {
  to { transform: translateY(-8%); opacity: 0; }
}
@keyframes dz-slide-out-down {
  to { transform: translateY(8%); opacity: 0; }
}
@keyframes dz-slide-out-left {
  to { transform: translateX(-8%); opacity: 0; }
}
@keyframes dz-slide-out-right {
  to { transform: translateX(8%); opacity: 0; }
}
@keyframes dz-shake-x {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-4px); }
  40%, 80% { transform: translateX(4px); }
}
@keyframes dz-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* Reduced-motion guard: collapse all dz-* animations to a near-instant
 * fade so users who request reduced motion still get state changes
 * without parallax/translate/scale. Operates on the keyframe name
 * (`animation-name`) so component-local @keyframes definitions
 * (dashboard, etc.) aren't affected by this rule. */
@media (prefers-reduced-motion: reduce) {
  [style*="animation"], [class*="dz-anim-"], .dz-toast {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
