/* CLIXHOUSE design system — neutral token defaults + OKLCH palette generator
   ─────────────────────────────────────────────────────────────────
   The framework ships a complete set of neutral semantic tokens
   so any site renders acceptably from the first paint, before a
   single brand value is defined. Sites override what differs;
   everything left undefined inherits a sensible default from here.

   The palette generator is the load-bearing trick: a site defines
   THREE primitives (--paper, --ink, --accent), and the framework
   derives ~12 semantic tokens — hover state, soft fill, contrast
   text, border ramp, surface ramp — via OKLCH relative color syntax
   and color-mix(). One source flows through the whole system.

   Imported via: @import url("/static/design/tokens-defaults.css") layer(tokens);

   Browser support (verified web-features, 2026):
     oklch() color           — Baseline widely available (Chrome 111 / Safari 15.4 / Firefox 113)
     oklch(from ...) relative — Baseline NEWLY available 2024-09-16 (Chrome 125 / Safari 18 /
                                Firefox 128); widely available 2027-03-16. The generator's
                                relative-color derivations are @supports-gated below for this.
     color-mix(in oklch, ...) — Baseline widely available (Chrome 111 / Safari 16.2 / Firefox 113)
     contrast-color()         — Baseline newly available 2026; @supports-gated below.
     light-dark()             — Baseline newly available 2024; widely available 2026-11-13.
*/

@layer tokens {
  :root {
    /* color-scheme lets light-dark() resolve per the user's OS preference.
       This is the SINGLE dark-mode mechanism for the framework default
       path — there is no @media (prefers-color-scheme) block (enforced by
       lint-dark-single-source.sh). dark.css layers the manual [data-theme]
       toggle + .invert section override on top of this. */
    color-scheme: light dark;

    /* ── PRIMITIVES — three colors define the whole brand ───────── */
    /* Sites override these three (and optionally any derived token).
       Each is a light-dark() pair so the default path is dark-mode-aware
       from first paint; every derived token re-evaluates because it
       references --paper / --ink / --accent, not literal colors. */
    --paper:  light-dark(oklch(0.98 0.005 90), oklch(0.16 0.010 250));  /* near-white / deep charcoal */
    --ink:    light-dark(oklch(0.20 0.010 60), oklch(0.95 0.005 90));   /* near-black / near-white */
    --accent: light-dark(oklch(0.55 0.15 250), oklch(0.70 0.15 250));   /* mid blue / brighter for dark bg */
    --accent-resolved: var(--accent);  /* gamut-safe accent: sRGB-clamped below, full chroma on P3 (B4) */

    /* ── SURFACES — derived from --paper + --ink ─────────────────── */
    --surface-0:      var(--paper);
    --surface-1:      color-mix(in oklch, var(--paper) 98%, var(--ink));
    --surface-2:      color-mix(in oklch, var(--paper) 95%, var(--ink));
    --surface-sunken: color-mix(in oklch, var(--paper) 96%, var(--ink));

    /* ── TEXT — graded ink mixed with paper ──────────────────────── */
    --text-strong:    var(--ink);
    --text-body:      color-mix(in oklch, var(--ink) 88%, var(--paper));
    --text-muted:     color-mix(in oklch, var(--ink) 60%, var(--paper));
    --text-faint:     color-mix(in oklch, var(--ink) 40%, var(--paper));
    /* Auto-contrast text on accent: binary step — near-white on dark
       accents, near-black on light ones. Linear `1 - l` produces mid-grey
       for mid-lightness accents (an l=0.52 blue → l=0.48 grey text gives
       ~3:1 contrast, failing WCAG AA). Multiplying a small (T - l)
       delta by 999 forces channel clamping at the threshold T so the
       result is always ~0 or ~1, never an unreadable middle.

       Threshold 0.623 is Lea Verou's published WCAG floor
       (lea.verou.me/blog/2024/contrast-color/) — every accent with
       l ≥ 0.623 passes WCAG 4.5:1 with #000 text; every accent below
       passes with #fff text. Her APCA variant uses 0.7. We pick the
       WCAG floor so corporate sites pass automated audits.

       The values here are widely-supported FALLBACKS (color-mix / a safe
       default). The precise relative-color derivations override them in
       the @supports (oklch(from ...)) block after :root, and native
       contrast-color() supersedes the contrast text where available —
       tiered so every browser gets a correct ramp, never a broken one. */
    --text-on-accent: white;

    /* ── ACCENT VARIANTS — color-mix fallbacks (precise oklch(from) below) ── */
    --accent-hover:   color-mix(in oklch, var(--accent), black 10%);
    --accent-active:  color-mix(in oklch, var(--accent), black 18%);
    --accent-soft:    color-mix(in oklch, var(--accent) 12%, var(--paper));
    --accent-strong:  color-mix(in oklch, var(--accent), black 14%);

    /* ── TONE PRIMITIVES — first-class status colors ─────────────── */
    /* Sites override these like --accent. Defaults are restrained — a
       success that won't shout, a danger that won't panic. Each tone
       generates -soft / -strong variants via the same recipe as accent,
       so banners, badges, field-error pills, and dropcaps can pick a
       tone and inherit the whole ramp. */
    --success:        oklch(0.52 0.13 150);
    --success-soft:   color-mix(in oklch, var(--success) 12%, var(--paper));
    --success-strong: color-mix(in oklch, var(--success), black 12%);

    --warning:        oklch(0.68 0.15 75);
    --warning-soft:   color-mix(in oklch, var(--warning) 14%, var(--paper));
    --warning-strong: color-mix(in oklch, var(--warning), black 12%);

    --danger:         oklch(0.55 0.18 25);
    --danger-soft:    color-mix(in oklch, var(--danger) 12%, var(--paper));
    --danger-strong:  color-mix(in oklch, var(--danger), black 12%);

    /* --error is an alias for --danger — pick by context. Form-field
       errors read better as "error"; banners read better as "danger". */
    --error:          var(--danger);

    /* ── BORDERS — ink mixed with paper, two weights ─────────────── */
    --border-subtle:  color-mix(in oklch, var(--ink) 10%, var(--paper));
    --border-strong:  color-mix(in oklch, var(--ink) 25%, var(--paper));

    /* ── FOCUS — Soueidan pattern, default to ink + paper ────────── */
    --focus-ring:     var(--ink);
    --focus-halo:     var(--paper);

    /* ── TYPOGRAPHY — system font stacks (sites override) ────────── */
    --font-display:   ui-sans-serif, system-ui, -apple-system, "Segoe UI",
                      Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-body:      ui-sans-serif, system-ui, -apple-system, "Segoe UI",
                      Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono:      ui-monospace, SFMono-Regular, "SF Mono", Menlo,
                      Consolas, "Liberation Mono", monospace;

    /* ── TYPE SCALE — Utopia shape, rem-based clamp (OddBird-safe) ─ */
    /* Generated via utopia.fyi/type/calculator with:
         viewport min/max:    320 / 1240
         base font min/max:    16 /   20
         ratio min/max:      1.20 / 1.25
       Regenerating requires ALL four pairs — viewport + ratio alone
       produces a different scale (the maxes match the published
       calculator; the mins reflect min-base 16). */
    --step--2:        clamp(0.69rem, 0.66rem + 0.16vw, 0.79rem);
    --step--1:        clamp(0.83rem, 0.78rem + 0.27vw, 0.99rem);
    --step-0:         clamp(1.00rem, 0.92rem + 0.39vw, 1.25rem);
    --step-1:         clamp(1.20rem, 1.07rem + 0.65vw, 1.56rem);
    --step-2:         clamp(1.44rem, 1.25rem + 0.99vw, 1.95rem);
    --step-3:         clamp(1.73rem, 1.45rem + 1.44vw, 2.44rem);
    --step-4:         clamp(2.07rem, 1.66rem + 2.04vw, 3.05rem);
    --step-5:         clamp(2.49rem, 1.91rem + 2.84vw, 3.81rem);
    --step-6:         clamp(2.99rem, 2.18rem + 3.91vw, 4.77rem);

    /* ── SPACE SCALE — Utopia shape, rem-based clamp ─────────────── */
    --space-3xs:      clamp(0.25rem, 0.23rem + 0.08vw, 0.31rem);
    --space-2xs:      clamp(0.50rem, 0.45rem + 0.16vw, 0.62rem);
    --space-xs:       clamp(0.75rem, 0.68rem + 0.24vw, 0.94rem);
    --space-s:        clamp(1.00rem, 0.92rem + 0.39vw, 1.25rem);
    --space-m:        clamp(1.50rem, 1.37rem + 0.65vw, 1.88rem);
    --space-l:        clamp(2.00rem, 1.84rem + 0.78vw, 2.50rem);
    --space-xl:       clamp(3.00rem, 2.76rem + 1.18vw, 3.75rem);
    --space-2xl:      clamp(4.50rem, 4.14rem + 1.76vw, 5.62rem);
    --space-3xl:      clamp(6.00rem, 5.51rem + 2.35vw, 7.50rem);

    /* ── MEASURE — readable line length cap ──────────────────────── */
    --measure:        65ch;

    /* ── RADIUS — four steps ─────────────────────────────────────── */
    --radius-sm:      0.25rem;
    --radius-md:      0.5rem;
    --radius-lg:      1rem;
    --radius-full:    9999px;

    /* ── MOTION — durations, easings, travel, stagger ────────────── */
    /* These are the SINGLE source the motion layer (motion.css) and the
       data-motion direction presets (below) read from. Sites tune feel by
       overriding these tokens, never by rewriting per-primitive rules.

       linear() easing support (verified 2026): Baseline — Chrome 113 /
       Safari 17.2 / Firefox 112. The cubic-bezier values here are the
       widely-supported FALLBACKS; the @supports block after :root upgrades
       the springs to true-overshoot linear() curves where available, so
       every engine gets a correct curve, never a broken one. */
    --motion-fast:    150ms;
    --motion-base:    250ms;
    --motion-slow:    500ms;

    --motion-ease:    cubic-bezier(0.4, 0, 0.2, 1);   /* standard in-out */
    --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); /* decisive ease-out */
    --motion-spring:    cubic-bezier(0.34, 1.56, 0.64, 1); /* fallback (linear() upgrade below) */
    --ease-spring:      cubic-bezier(0.34, 1.56, 0.64, 1); /* fallback */
    --ease-spring-soft: cubic-bezier(0.34, 1.30, 0.64, 1); /* fallback */

    /* Default reveal travel distance — direction presets scale this. */
    --motion-travel:  1rem;

    /* view() timeline window a scroll-reveal plays across (animation-range).
       "entry 0% cover 30%" = start as the element enters, finish once 30%
       covered. Direction presets widen/tighten this. */
    --reveal-range:   entry 0% cover 30%;

    /* ── STAGGER — sequential-reveal choreography (no JS) ─────────── */
    /* Set --stagger-index per item (inline style or :nth-child). The
       per-item delay is computed AT THE ELEMENT in motion.css via
       calc(var(--stagger-index) * var(--stagger-step)) — NOT pre-derived
       here. A derived `--stagger-delay: calc(... index ...)` at :root would
       resolve once against :root's index (0) and children would inherit
       that frozen 0s — the custom-property-calc trap. Compute where the
       index lives. --stagger-index is @property-typed <number>, so an
       invalid override falls back to 0 instead of voiding the calc. */
    --stagger-step:   60ms;
    --stagger-index:  0;

    /* ── MOBILE VIEWPORT — chrome-aware heights ──────────────────── */
    /* svh = smallest viewport (browser chrome shown) — the safe default
       for full-height heroes (no overflow on first paint). lvh = largest
       (chrome hidden) for immersive fills. dvh tracks live as chrome
       animates — never transition/animate a dvh height (reflow jitter).
       Baseline widely available: Chrome 108 / Safari 15.4 / Firefox 101. */
    --vh-small:    100svh;
    --vh-large:    100lvh;
    --vh-dynamic:  100dvh;

    /* ── SAFE AREA — notch / home-indicator insets ───────────────── */
    /* Resolve to 0 when absent (and on browsers without env()), so layout
       stays valid everywhere. Requires <meta viewport-fit=cover> on edge-
       to-edge surfaces for the insets to be non-zero. */
    --safe-top:    env(safe-area-inset-top, 0px);
    --safe-right:  env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left:   env(safe-area-inset-left, 0px);

    /* ── ELEVATION — token-driven shadow ramp (six steps) ────────── */
    /* Replaces the one-off shadow literals that drifted across
       capabilities.css / showcase.css. Static black-alpha so it renders
       identically in every engine and in both color schemes. */
    --shadow-1:  0 1px 2px -1px rgb(0 0 0 / 0.08);
    --shadow-2:  0 2px 4px -2px rgb(0 0 0 / 0.10), 0 1px 2px -1px rgb(0 0 0 / 0.07);
    --shadow-3:  0 4px 12px -2px rgb(0 0 0 / 0.12), 0 2px 4px -2px rgb(0 0 0 / 0.08);
    --shadow-4:  0 8px 24px -4px rgb(0 0 0 / 0.14);
    --shadow-5:  0 16px 40px -8px rgb(0 0 0 / 0.18);
    --shadow-6:  0 24px 64px -12px rgb(0 0 0 / 0.22);

    /* ── CONTAINER WIDTHS ────────────────────────────────────────── */
    --container-narrow:  40rem;
    --container-default: 72rem;
    --container-wide:    90rem;
  }

  /* ── SPRING EASINGS — linear() upgrade (progressive) ───────────── */
  /* cubic-bezier can only approximate a spring (single control arc); a
     real spring overshoots and settles, which needs many sample points —
     exactly what linear() interpolates. These curves rise past 1.0 (the
     overshoot) then settle, giving the "alive" feel of Framer Motion /
     Motion springs in pure CSS. Gated so non-supporting engines keep the
     cubic-bezier fallback above. Stops are generator output (joshwcomeau /
     motion.dev); --ease-spring overshoots ~12%, -soft ~4%. */
  @supports (transition-timing-function: linear(0, 1)) {
    :root {
      --ease-spring: linear(
        0, 0.224, 0.665, 0.939, 1.075, 1.120, 1.115,
        1.083, 1.046, 1.018, 1.002, 0.997, 1);
      --motion-spring: var(--ease-spring);
      --ease-spring-soft: linear(
        0, 0.26, 0.69, 0.91, 1.01, 1.04, 1.04, 1.02, 1.005, 1);
    }
  }

  /* ── PRECISE RELATIVE-COLOR DERIVATIONS (progressive) ──────────── */
  /* Override the widely-supported color-mix fallbacks above with exact
     OKLCH relative-color math where supported. Baseline newly available
     2024-09-16; widely available 2027-03-16 — hence the @supports gate
     (a browser without it keeps the color-mix ramp, never a broken one). */
  @supports (color: oklch(from red l c h)) {
    :root {
      /* gamut-safe accent: clamp chroma into sRGB by default; the P3
         media query below unlocks full chroma on wide-gamut displays. */
      --accent-resolved: oklch(from var(--accent) l min(c, 0.18) h);

      /* binary auto-contrast text: clamp forces l to 0 or 1 at the 0.623
         WCAG floor — never the unreadable mid-grey the old `* 999` hack
         produced for accents within ~0.001 of the threshold. */
      --text-on-accent: oklch(from var(--accent) clamp(0, (0.623 - l) * infinity, 1) 0 0);

      --accent-hover:   oklch(from var(--accent) calc(l - 0.06) c h);
      --accent-active:  oklch(from var(--accent) calc(l - 0.12) c h);
      --accent-strong:  oklch(from var(--accent) calc(l - 0.10) calc(c * 1.1) h);
      --success-strong: oklch(from var(--success) calc(l - 0.10) calc(c * 1.1) h);
      --warning-strong: oklch(from var(--warning) calc(l - 0.10) calc(c * 1.1) h);
      --danger-strong:  oklch(from var(--danger)  calc(l - 0.10) calc(c * 1.1) h);
    }
  }

  /* ── NATIVE CONTRAST-COLOR (progressive) ───────────────────────── */
  /* contrast-color() picks black/white for guaranteed contrast against
     the given color — supersedes the lightness-clamp heuristic where
     available (Baseline newly available 2026). */
  @supports (color: contrast-color(red)) {
    :root {
      --text-on-accent:  contrast-color(var(--accent));
      --text-on-success: contrast-color(var(--success));
      --text-on-danger:  contrast-color(var(--danger));
    }
  }

  /* ── WIDE-GAMUT — unlock full accent chroma on P3 displays ─────── */
  /* On sRGB, --accent-resolved is chroma-clamped (above) so vivid brand
     accents don't gamut-map unpredictably; on Display-P3 hardware the
     full chroma is safe. --accent-resolved is the gamut-safe token for
     fills that want it; --accent stays the brand input (unchanged, so no
     live site is silently desaturated). */
  @media (color-gamut: p3) {
    :root {
      --accent-resolved: var(--accent);
    }
  }

  /* ── MOTION DIRECTION PRESETS — one palette, many characters ──── */
  /* The SAME motion.css primitives express different personalities by
     swapping ONE attribute — data-motion — on <html> or any section.
     Presets override ONLY the shared motion tokens (duration / easing /
     travel / stagger / reveal-range), never per-primitive rules, so a
     whole site or a single section re-skins its motion feel in one line.

     There are no "rules," only directions: the default (no attribute) is
     restrained luxury — calm, slow, soft eases, small travel. Kinetic is
     quick and decisive. Expressive is bold, springy, longer choreography.
     Author the feel by choosing a direction, then tuning tokens per brand.

       <html data-motion="kinetic">           ← whole site
       <section data-motion="expressive">     ← one section, louder

     Equal specificity to :root (0,1,0); source order after :root lets a
     preset on <html> win, while a preset on a section overrides inherited
     tokens for that subtree. */

  [data-motion="kinetic"] {
    --motion-fast:   100ms;
    --motion-base:   180ms;
    --motion-slow:   320ms;
    --motion-ease:   var(--ease-out-quint);
    --motion-travel: 1.5rem;
    --stagger-step:  45ms;
    --reveal-range:  entry 0% cover 22%;
  }

  [data-motion="expressive"] {
    --motion-fast:   180ms;
    --motion-base:   320ms;
    --motion-slow:   680ms;
    --motion-ease:   var(--ease-spring-soft);
    --motion-travel: 2.5rem;
    --stagger-step:  90ms;
    --reveal-range:  entry 0% cover 45%;
  }

  [data-motion="restrained"] {
    /* explicit opt-in to the calm default — e.g. a restful section nested
       inside a kinetic page; mirrors :root so it overrides an ancestor. */
    --motion-fast:   150ms;
    --motion-base:   250ms;
    --motion-slow:   500ms;
    --motion-ease:   cubic-bezier(0.4, 0, 0.2, 1);
    --motion-travel: 1rem;
    --stagger-step:  60ms;
    --reveal-range:  entry 0% cover 30%;
  }
}
