/* ============================================================
   DH SYSTEM v2 — TOKENS
   Layer 1 (Brand Pack) + Layer 2 (Surface System) + @theme glue
   ============================================================
   ARCHITECTURE
   ------------
   This file defines the TOKEN ARCHITECTURE that sits ABOVE
   Tailwind v4. Every Tailwind utility class generated from this
   file's @theme block reads from a DH token, never the other way.

   READ DIRECTION (cascade):
   Brand Pack JSON → :root vars → @theme block → utilities + components

   FILE ORDER (load order matters):
   1. Tailwind v4 (via CDN <script> or compiled <link>)
   2. THIS FILE (tokens-v2.css)
   3. components-v2.css (Layer 3)
   4. Page-specific overrides (Layer 4)

   ============================================================
   PRODUCTION NOTE: when compiling with Vite/CLI, this file's
   parent entry CSS should start with `@import "tailwindcss";`.
   With the browser CDN (prototype), the script tag handles setup
   itself, so no @import is needed here.
   ============================================================ */

/* ============================================================
   LAYER 0.5 — @PROPERTY REGISTRATIONS (Round 13, 2026-05-15)
   ============================================================
   Type the brand-color tokens as <color> so Chrome triggers full
   style recalc when they change at runtime via JS.

   Without these, Chrome caches the result of `oklch(from var(...))`
   formulas the first time it computes them, and never re-evaluates
   when the source untyped custom property changes via inline style.
   This is the documented Chrome relative-color cache quirk.

   Registering the source variables as typed colors makes Chrome
   treat changes the same way it treats normal property changes,
   which invalidates downstream consumers including relative-color
   formulas in /system/tokens.css.

   Scoped to brand-pack-editor for Round 13. If this fixes the bug,
   graduate to /system/tokens.css in a follow-up.

   Source: loader.js applyPack() sets --brand-X + --surface-X.
   /system/ derives --color-X-fill / -hover via oklch(from var(--color-X) ...).
   ============================================================ */

@property --brand-primary {
  syntax: '<color>';
  inherits: true;
  initial-value: #888888;
}
@property --brand-secondary {
  syntax: '<color>';
  inherits: true;
  initial-value: #888888;
}
@property --brand-accent {
  syntax: '<color>';
  inherits: true;
  initial-value: #A0A0A0;
}
@property --brand-deep {
  syntax: '<color>';
  inherits: true;
  initial-value: #2A2A2A;
}

@property --color-primary {
  syntax: '<color>';
  inherits: true;
  initial-value: #888888;
}
@property --color-secondary {
  syntax: '<color>';
  inherits: true;
  initial-value: #888888;
}
@property --color-accent {
  syntax: '<color>';
  inherits: true;
  initial-value: #A0A0A0;
}
@property --color-deep {
  syntax: '<color>';
  inherits: true;
  initial-value: #2A2A2A;
}
@property --color-pop {
  syntax: '<color>';
  inherits: true;
  initial-value: #888888;
}

@property --surface-paper {
  syntax: '<color>';
  inherits: true;
  initial-value: #FAFAFA;
}
@property --surface-ink {
  syntax: '<color>';
  inherits: true;
  initial-value: #1A1A1A;
}
@property --surface-tone {
  syntax: '<color>';
  inherits: true;
  initial-value: #FFFFFF;
}

/* ============================================================
   LAYER 1 — BRAND PACK SURFACE (ground truth)
   ============================================================
   These are the values the brand pack JSON loader writes to.
   The loader sets these on :root from /brand/examples/*.json.
   Defaults below show the DH brand pack structure.
   ============================================================ */

:root {
  /* Brand color slots — defaults match dh-magenta.json so the FIRST PAINT
     of any page already renders the default DH brand correctly, without
     waiting for the async brand-pack JSON fetch. The loader still runs and
     re-applies the same values on resolve, but no FOUC flash for the
     default brand. Non-default brands (?brand=X) will still see a brief
     swap, but that's a smaller subset of traffic. */
  --brand-primary: #A5205B;
  --brand-secondary: #EB5E55;
  --brand-accent: #FF8A6F;
  --brand-deep: #45123B;

  /* Surface palette slots — match dh-magenta.json */
  /* Round 15.10: surfaces auto-derive subtle tint from --color-primary.
     Neutral primary -> pure neutral surface (no brown bias).
     Tinted primary -> subtle brand-flavored surface.
     Loader.js still overrides --surface-ink / -paper / -tone when brand JSON
     explicitly sets ink / paper / tone fields. */
  --surface-ink:   oklch(from var(--color-primary, #888888) 0.10 calc(c * 0.25) h);
  --surface-paper: oklch(from var(--color-primary, #888888) 0.97 calc(c * 0.15) h);
  --surface-tone:  oklch(from var(--color-primary, #888888) 0.99 calc(c * 0.08) h);

  /* Surface FOREGROUND on ink — defaults to a light gray. Brand packs can
     override (Linenfield warm cream, Calvary pale gold, etc.) by setting
     "colors.ink-text" in their JSON. Loader writes it to this token. */
  --surface-ink-text: #ededed;

  /* Status slots */
  --signal-success: #3AD08A;
  --signal-warning: #F59E0B;
  --signal-error: #DC2626;

  /* Round 15.7: Text-on-X tokens REMOVED from editor overlay.
     /system/tokens.css defines --text-on-primary / -secondary / -accent
     using a chroma-aware auto-flip formula that always matches the actual
     rendered fill luminance. Hardcoding them here was overriding the
     /system/ logic and causing the hero CTA to render dark text on bright
     fills (and other mismatches between hero + rest of page). Letting
     /system/ win guarantees the editor preview matches every other
     consumer page exactly.
     --text-on-deep stays because /system/ does not auto-derive it. */
  --text-on-deep: #FFFFFF;

  /* Typography slots — defaults match dh-magenta.json so first paint uses
     the right faces. The <link> in <head> preloads Fraunces + Inter so
     they're available immediately. Loader still writes these on brand
     swap. */
  --font-display: "Fraunces", "Poppins", Georgia, serif;
  --font-body: "Inter", "Poppins", system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
}

/* ============================================================
   LAYER 2 — SURFACE SYSTEM
   ============================================================
   Derived tokens. Computed from Layer 1 brand pack values.
   Adds -text, -fill, -tint, -soft, -hover, -active variants.
   Adds the universal surface palette that components read.
   ============================================================ */

:root {
  /* ---------- BRAND COLOR FAMILIES ---------- */

  /* Brand color families.
     -text and -fill variants are SCHEME-AWARE and live in the
     LIGHT/DARK surface groups below. Everything here is invariant. */

  /* Primary family — only the SOURCE alias. /system/ derives all other
     variants (-tint, -soft, -text, -fill, -hover, -active, -deep) via
     OKLCH luminance-targeted formulas that flip cleanly between light
     + dark schemes. */
  --color-primary: var(--brand-primary);
  /* --color-primary-tint/-soft DELETED Round 15.42 (2026-05-16).
     The color-mix(srgb 14%/45%, white) formulas only worked in light
     mode. /system/'s OKLCH version flips between
     oklch(0.97, c*0.5, h) light and oklch(0.18, c*0.6, h) dark.
     Editor was shadowing the dark-scheme variant. */
  /* --color-primary-hover/-active DELETED 2026-05-14 (Andrew Round 9).
     /system/ provides v2.7.x fixed-delta formulas (oklch from -fill calc(l +/- delta) c h)
     with consistent perceptual shifts across brand range. */
  /* --color-primary-deep DELETED — /system/ provides clamped formula. */

  /* Secondary family — source alias only. */
  --color-secondary: var(--brand-secondary);

  /* Accent family — source alias only. */
  --color-accent: var(--brand-accent);

  /* Deep family — source alias only. */
  --color-deep: var(--brand-deep);

  /* ============================================================
     RADIX-STYLE 12-STEP ACCENT SCALE (oklch-derived from brand-primary)
     ============================================================
     Each step has a SPECIFIC UI ROLE. Components reference steps by
     role, not formulas. Hand-tuned curve approximating Radix Themes.

       1  Page bg                  7  UI border
       2  Subtle bg                8  UI border hover
       3  UI element bg            9  Solid bg (the brand)
       4  UI element bg hover     10  Solid hover
       5  UI element bg active    11  Low-contrast text
       6  Subtle border           12  High-contrast text
     ============================================================ */
  --accent-1:  oklch(from var(--brand-primary) 0.985 calc(c * 0.04) h);
  --accent-2:  oklch(from var(--brand-primary) 0.965 calc(c * 0.10) h);
  --accent-3:  oklch(from var(--brand-primary) 0.935 calc(c * 0.20) h);
  --accent-4:  oklch(from var(--brand-primary) 0.895 calc(c * 0.35) h);
  --accent-5:  oklch(from var(--brand-primary) 0.845 calc(c * 0.55) h);
  --accent-6:  oklch(from var(--brand-primary) 0.78  calc(c * 0.75) h);
  --accent-7:  oklch(from var(--brand-primary) 0.70  calc(c * 0.90) h);
  --accent-8:  oklch(from var(--brand-primary) 0.62  c h);
  --accent-9:  var(--brand-primary);
  --accent-10: oklch(from var(--brand-primary) calc(l - 0.04) c h);
  --accent-11: oklch(from var(--brand-primary) clamp(0.38, calc(l - 0.05), 0.48) calc(c * 1.5) h);
  --accent-12: oklch(from var(--brand-primary) clamp(0.18, calc(l - 0.30), 0.28) calc(c * 1.2) h);

  /* Alpha versions of the accent scale for overlays + tints */
  --accent-a1:  color-mix(in oklch, var(--brand-primary) 3%,  transparent);
  --accent-a2:  color-mix(in oklch, var(--brand-primary) 6%,  transparent);
  --accent-a3:  color-mix(in oklch, var(--brand-primary) 12%, transparent);
  --accent-a4:  color-mix(in oklch, var(--brand-primary) 18%, transparent);
  --accent-a5:  color-mix(in oklch, var(--brand-primary) 25%, transparent);
  --accent-a6:  color-mix(in oklch, var(--brand-primary) 35%, transparent);
  --accent-a7:  color-mix(in oklch, var(--brand-primary) 50%, transparent);
  --accent-a8:  color-mix(in oklch, var(--brand-primary) 70%, transparent);

  /* 12-step GRAY scale, subtly tinted with accent hue.
     Brand-tinted grays make the whole UI harmonize with the accent.
     This is why every Radix theme feels intentional. */
  --gray-1:  oklch(from var(--brand-primary) 0.988 0.003 h);
  --gray-2:  oklch(from var(--brand-primary) 0.97  0.005 h);
  --gray-3:  oklch(from var(--brand-primary) 0.945 0.007 h);
  --gray-4:  oklch(from var(--brand-primary) 0.92  0.008 h);
  --gray-5:  oklch(from var(--brand-primary) 0.88  0.010 h);
  --gray-6:  oklch(from var(--brand-primary) 0.83  0.012 h);
  --gray-7:  oklch(from var(--brand-primary) 0.74  0.013 h);
  --gray-8:  oklch(from var(--brand-primary) 0.60  0.013 h);
  --gray-9:  oklch(from var(--brand-primary) 0.48  0.012 h);
  --gray-10: oklch(from var(--brand-primary) 0.42  0.011 h);
  --gray-11: oklch(from var(--brand-primary) 0.34  0.010 h);
  --gray-12: oklch(from var(--brand-primary) 0.18  0.008 h);

  /* ROLE-BASED SEMANTIC TOKENS — components reference these by intent */
  --color-app-bg:               var(--gray-1);
  --color-subtle-bg:            var(--gray-2);
  --color-element-bg:           var(--gray-3);
  --color-element-bg-hover:     var(--gray-4);
  --color-element-bg-active:    var(--gray-5);
  --color-subtle-border:        var(--gray-6);
  --color-element-border:       var(--gray-7);
  --color-element-border-hover: var(--gray-8);
  --color-solid-bg:             var(--accent-9);
  --color-solid-bg-hover:       var(--accent-10);
  --color-text-low:             var(--gray-11);
  --color-text-high:            var(--gray-12);

  --color-accent-bg:            var(--accent-3);
  --color-accent-bg-hover:      var(--accent-4);
  --color-accent-bg-active:     var(--accent-5);
  --color-accent-border:        var(--accent-7);
  --color-accent-text-low:      var(--accent-11);
  --color-accent-text-high:     var(--accent-12);

  /* ---------- DEFAULT SURFACE (paper) ----------
     Use --paper / --ink / --tone (short tokens, no @property registration)
     for --surface-bg / -text / -card-bg consumers. The long --surface-paper
     names ARE @property-registered. In Chrome, an inline change to a
     @property-registered source doesn't always invalidate downstream var()
     consumers if they live inside non-!important :root rules. /system/
     dark mode survives because its !important rule re-declares
     --surface-bg via var(--ink) on every cascade pass. Mirror that pattern
     in light mode by chaining off the short --paper/--ink/--tone tokens
     (loader writes BOTH long + short on every brand apply). */
  --surface-bg: var(--paper);
  --surface-text: var(--ink);
  --surface-card-bg: var(--tone);
  --surface-eyebrow: var(--color-primary-text);

  /* r462c-batch · Surface effect opacity — default 1 on light surfaces.
     Overridden on dark/branded surfaces below so the aurora gradient
     doesn't muddy already-saturated bg colors. */
  --surface-effect-opacity: 1;

  /* r462c-batch · Surface card shadow — default subtle 1px drop on
     light surfaces. Overridden per surface below so nested cards
     always feel lifted from their parent. */
  --surface-card-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent), 0 2px 8px color-mix(in srgb, var(--ink) 4%, transparent);

  /* ---------- SURFACE-AWARE TEXT TIERS ----------
     All three tiers derive from the canonical pair (--surface-text +
     --surface-bg). They auto-adapt anywhere in the cascade because
     CSS resolves var() at the use site.

     We mix in OKLCH space, not alpha. Alpha mixing on colored brand
     surfaces (e.g. magenta primary) creates pastel washes that
     visually merge with the bg. OKLCH mixing produces tonal shifts
     that always read as text against the surface.

     Use --surface-text for headlines + body copy. Use -secondary
     for subordinate body and captions. Use --surface-muted for
     decorative or helper text only. Match Carbon's text-primary /
     text-secondary / text-helper tiering. */
  /* Secondary + muted text use ALPHA mix toward transparent so the
     surface-text color dominates regardless of how saturated the bg is.
     OKLCH mix toward saturated dark surfaces produced muddy mid-tones
     that read dim. Alpha keeps text readable on every surface. */
  --surface-text-secondary: color-mix(in srgb, var(--surface-text) 92%, transparent);
  --surface-muted:          color-mix(in srgb, var(--surface-text) 72%, transparent);
  /* r362 — srgb mix. oklch was producing hue=none in dark mode where
     surface-text + surface-bg are both near-neutral, which Chrome renders
     as warm (~0deg). Subtle but visible warm tint on every card border. */
  --surface-border:         color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);

  /* ---------- TYPOGRAPHY SCALE ---------- */
  --t-base: 1rem;
  --t-ratio: 1.25;
  --t-step--2: calc(var(--t-base) / var(--t-ratio) / var(--t-ratio));
  --t-step--1: calc(var(--t-base) / var(--t-ratio));
  --t-step-0: var(--t-base);
  --t-step-1: calc(var(--t-base) * var(--t-ratio));
  --t-step-2: calc(var(--t-step-1) * var(--t-ratio));
  --t-step-3: calc(var(--t-step-2) * var(--t-ratio));
  --t-step-4: calc(var(--t-step-3) * var(--t-ratio));
  --t-step-5: calc(var(--t-step-4) * var(--t-ratio));
  --t-step-6: calc(var(--t-step-5) * var(--t-ratio));
  --t-step-7: calc(var(--t-step-6) * var(--t-ratio));

  --t-caption: var(--t-step--2);
  --t-small: var(--t-step--1);
  --t-body: var(--t-step-0);
  --t-lead: var(--t-step-1);
  --t-title: var(--t-step-2);
  --t-display-sm: var(--t-step-3);
  --t-display-md: var(--t-step-5);
  --t-display-lg: var(--t-step-6);
  --t-display-xl: var(--t-step-7);

  --lh-tight: 1.1;
  --lh-snug: 1.25;
  --lh-body: 1.6;

  --tr-tight: -0.01em;
  --tr-eyebrow: 0.12em;

  --fw-light: 300;
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semi: 600;
  --fw-bold: 700;
  --fw-black: 800;

  /* Brand-clamped display weight ceiling. Headings + brand marks should
     use var(--fw-display-max) instead of --fw-black so the loader can
     clamp them to the heaviest weight the active display font actually
     ships. Calvary tops out at 700, Cormorant at 700, Fraunces ships
     900. Without this clamp, browsers synthesize a faux-bold which
     looks awful. Loader sets this from max(brand.fonts.displayWeights). */
  --fw-display-max: 800;

  /* ---------- SPACING ---------- */
  --space-base: 4px;
  --space-multiplier: 1;
  --space-unit: calc(var(--space-base) * var(--space-multiplier));
  --space-2xs: calc(var(--space-unit) * 1);
  --space-xs: calc(var(--space-unit) * 2);
  --space-sm: calc(var(--space-unit) * 3);
  --space-md: calc(var(--space-unit) * 4);
  --space-lg: calc(var(--space-unit) * 6);
  --space-xl: calc(var(--space-unit) * 8);
  --space-2xl: calc(var(--space-unit) * 12);
  --space-3xl: calc(var(--space-unit) * 16);
  --space-4xl: calc(var(--space-unit) * 24);

  /* ---------- RADIUS ---------- */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-pill: 999px;

  /* ---------- BORDERS ---------- */
  --border-thin: 1px;
  --border-med: 2px;
  --border-thick: 3px;

  /* ---------- MOTION (Hallmark interaction system, 2026-05-19) ---------- */
  /* Brand-mood scale = single lever that drives every duration. Changing
     [data-motion] on root rewrites the scale, which cascades through the
     calc() tokens below to every transition in the system. Zero work
     per consumer to respect the brand's motion personality. */
  --motion-scale: 1;

  /* Canonical duration ladder — three buckets named by job.
     Use --dur-micro for button presses, color shifts, instant feedback.
     Use --dur-short for hover lifts, tooltips, menus, single fades.
     Use --dur-long  for modals, drawers, accordions, page reveals.
     Each one internally applies the brand-mood scale. */
  --dur-micro: calc(120ms * var(--motion-scale));
  --dur-short: calc(220ms * var(--motion-scale));
  --dur-long:  calc(420ms * var(--motion-scale));

  /* Easings — three curves cover ~90% of UI motion.
     ease-out for entering (decelerate into place).
     ease-in  for leaving (accelerate away).
     ease-in-out for symmetric state toggles. */
  --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:       cubic-bezier(0.7, 0, 0.84, 0);
  --ease-in-out:   cubic-bezier(0.65, 0, 0.35, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);

  /* Back-compat aliases — existing consumers using these still work
     and automatically pick up the scale via the canonical tokens. */
  --motion-fast:   var(--dur-micro);
  --motion-base:   var(--dur-short);
  --motion-medium: var(--dur-long);
  --motion-slow:   calc(680ms * var(--motion-scale));
  --transition-base: var(--dur-short) var(--ease-out);

  /* Exit pattern: 75% of enter duration with reversed ease.
     Use these for "is-closing" / "is-leaving" state classes. */
  --dur-micro-exit: calc(var(--dur-micro) * 0.75);
  --dur-short-exit: calc(var(--dur-short) * 0.75);
  --dur-long-exit:  calc(var(--dur-long)  * 0.75);

  /* ---------- SHADOWS ---------- */
  --elev-distance: 1;
  --elev-spread: 1;
  --elev-intensity: 1;
  --shadow-xs: 0 1px 2px rgba(10, 6, 8, calc(0.06 * var(--elev-intensity)));
  --shadow-sm: 0 2px 8px rgba(10, 6, 8, calc(0.08 * var(--elev-intensity)));
  --shadow-md: 0 8px 24px rgba(10, 6, 8, calc(0.10 * var(--elev-intensity)));
  --shadow-lg: 0 20px 48px rgba(10, 6, 8, calc(0.14 * var(--elev-intensity)));

  /* ============================================================
     LAYOUT — SITE STRUCTURE (Layer 1)
     ============================================================
     Container widths, padding, gaps, breakpoints, z-index. Templates
     consume these via canonical shell classes in components-v2.css.
     Templates do NOT redefine widths or hardcode spacing.

     Spec: 📘 Blog Platform Specs → Site Structure — Spec (Layer 1)
     ============================================================ */

  /* Container widths — 4-tier shell system. Outer locked at 1440px
     for the cinematic nav + page width per Andrew's call. Site Structure
     Audit had 1280 as the alt; we went wider. */
  --shell-outer-max:   1440px;  /* Nav, footer, full-page hero */
  --shell-content-max: 1200px;  /* Section bodies, multi-col grids */
  --shell-reading-max:  720px;  /* Article prose, single-column read */
  --shell-card-max:     880px;  /* Single-card moments, offer blocks */

  /* Legacy alias kept for back-compat. */
  --shell-max: var(--shell-content-max);

  /* Responsive shell-pad — value below gets bumped at tablet/desktop
     breakpoints further down in this file. */
  --shell-pad:         24px;
  --shell-pad-mobile:  20px;
  --shell-pad-tablet:  32px;
  --shell-pad-desktop: 48px;

  /* Section + block vertical rhythm. */
  --gap-section:       96px;
  --gap-section-tight: 64px;
  --gap-block:         48px;
  --gap-row:           24px;

  /* Breakpoints — canonical 4. Templates write media queries against
     these via raw px (CSS media queries don't read CSS vars). Listed
     here as tokens for JS consumers + future container-query refs. */
  --bp-mobile:   480px;
  --bp-tablet:   768px;
  --bp-desktop: 1024px;
  --bp-wide:    1280px;

  /* Z-index stack — no more ad-hoc stacking decisions. */
  --z-base:       0;
  --z-raised:    10;
  --z-sticky:   100;
  --z-overlay: 1000;
  --z-modal:   1100;
  --z-toast:   1200;
  --z-tooltip: 1300;

  /* ---------- COMPONENT TOKENS ---------- */
  --btn-radius: var(--radius-md);
  --btn-padding-y: 12px;
  --btn-padding-x: 20px;
  --btn-border-width: var(--border-med);
  --btn-weight: var(--fw-semi);
  --card-radius: var(--radius-lg);
  --card-padding: var(--space-lg);
  --pill-radius: var(--radius-md);
  --pill-padding-y: 6px;
  --pill-padding-x: 14px;

  /* Focus ring uses --accent-7 from the 12-step scale (mid-bright accent).
     Auto-tuned per surface because step 7 already adapts to scheme + brand.
     Surface-specific overrides below for ink/deep where higher contrast wins. */
  --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent-7) 50%, transparent);

  color-scheme: light;
}

/* ============================================================
   LAYER 2 — SURFACE VARIANTS (data-surface attribute)
   ============================================================
   Drop a [data-surface="X"] on any container. Children inherit
   the rewired surface tokens via the cascade. Components react
   automatically with zero changes to their own CSS.
   ============================================================ */

/* W5a: each surface block declares the FULL 24-token contract
   (the constitution's LAW: no surface-dependent token inherits
   through a surface boundary). Derivations anchor to the block's
   own bg/text so nesting re-resolves correctly. Brand-foreground
   tokens (--color-X-text, --color-X-fill) come from LIGHT/DARK groups. */

[data-surface="paper"] {
  --surface-bg: var(--paper);
  --surface-text: var(--ink);
  --surface-card-bg: var(--tone);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--ink);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="tone"] {
  --surface-bg: var(--tone);
  --surface-text: var(--ink);
  --surface-card-bg: var(--paper);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--ink);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="ink"] {
  --surface-bg: var(--ink);
  --surface-text: var(--surface-ink-text);
  /* Use the same gray-3 formula as :root[data-scheme="dark"] so an ink
     island (e.g., Dark Preview hero on a light page) renders IDENTICALLY
     to how the page renders when the global scheme is set to dark. The
     gray-X scales are re-derived for ink surfaces in the dark text block
     below, so gray-3 here resolves to the dark-context value. */
  --surface-card-bg: var(--gray-3);
  /* Tighter border on ink surfaces. The :root default formula (oklch mix
     78% bg + 22% text) reads too bright on an ink island in a light page.
     Translucent surface-text at 14% alpha matches the canonical look in
     full dark scheme and stays brand-flex because surface-text is the
     source (Linenfield warm cream, Calvary pale gold, etc.). */
  --surface-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: #ffffff;
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="deep"] {
  --surface-bg: var(--brand-deep);
  --surface-text: #ffffff;
  /* Match dark-scheme lift formula for visual consistency across the system. */
  --surface-card-bg: var(--gray-3);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: #ffffff;
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="primary"] {
  --surface-bg: var(--color-primary);
  --surface-text: var(--text-on-primary);
  --surface-card-bg: color-mix(in srgb, var(--color-primary) 88%, white);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--surface-text);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="secondary"] {
  --surface-bg: var(--color-secondary);
  --surface-text: var(--text-on-secondary);
  --surface-card-bg: color-mix(in srgb, var(--color-secondary) 88%, white);
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--surface-text);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="tint"] {
  --surface-bg: var(--color-primary-tint);
  --surface-text: var(--surface-ink);
  --surface-card-bg: white;
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--ink);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

[data-surface="soft"] {
  --surface-bg: var(--color-primary-soft);
  --surface-text: var(--surface-ink);
  --surface-card-bg: white;
  /* ---- W5a (2026-06-06): full 24-token surface contract. The block
     declared only bg/text/card-bg; the other 21 contract tokens either
     resolved through :root (capturing ROOT scope, wrong inside nested
     surfaces) or did not exist (11 bare var() consumers were silently
     invalid). Every token now anchors to THIS block's own bg/text, so
     it re-resolves correctly at every surface boundary. Gate: SC1. ---- */
  --surface-text-strong: var(--ink);
  --surface-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --surface-text-subtle: color-mix(in srgb, var(--surface-text) 55%, transparent);
  --surface-text-disabled: color-mix(in srgb, var(--surface-text) 38%, transparent);
  --surface-card-text: var(--surface-text);
  --surface-card-border: color-mix(in srgb, var(--surface-text) 14%, transparent);
  --surface-block-bg: color-mix(in srgb, var(--surface-text) 6%, transparent);
  --surface-block-text: var(--surface-text);
  --surface-block-border: color-mix(in srgb, var(--surface-text) 12%, transparent);
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 78%);
  --surface-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
  --interactive-bg: color-mix(in srgb, var(--surface-text) 7%, transparent);
  --interactive-bg-hover: color-mix(in srgb, var(--surface-text) 11%, transparent);
  --interactive-bg-active: color-mix(in srgb, var(--surface-text) 15%, transparent);
  --interactive-bg-selected: color-mix(in srgb, var(--color-primary) 14%, transparent);
  --interactive-bg-disabled: color-mix(in srgb, var(--surface-text) 4%, transparent);
  --interactive-text: var(--surface-text);
  --interactive-text-muted: color-mix(in srgb, var(--surface-text) 60%, transparent);
  --interactive-text-disabled: color-mix(in srgb, var(--surface-text) 35%, transparent);
  --interactive-border: color-mix(in srgb, var(--surface-text) 22%, transparent);
  --interactive-ring: color-mix(in srgb, var(--color-primary-text, var(--color-primary)) 55%, transparent);
}

/* Per-surface FOCUS RING tuning. Dark surfaces get a brighter
   accent-step ring so it pops against dark backgrounds. */
[data-surface="ink"],
[data-surface="deep"],
[data-surface="primary"],
[data-surface="secondary"] {
  --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent-9) 65%, transparent);
}

/* ============================================================
   LAYER 2 — SCHEME-AWARE BRAND FOREGROUND (the smart cascade)
   ============================================================
   Two surface modes drive how brand-color FOREGROUND tokens
   compute via oklch lightness-clamp:

     LIGHT MODE  → clamp(0.35, l, 0.5) calc(c * 1.2)
     DARK MODE   → clamp(0.7, l, 0.88) min(c, 0.18)

   Surface variants self-classify into one of those modes.
   Cross-restoration handles mixed cases (dark scheme page +
   light surface section). Specificity does the routing.

   --surface-muted, --surface-border, --surface-eyebrow all
   AUTO-DERIVE from --surface-text and --color-primary-text via the
   :root formulas. They self-adapt anywhere in the cascade.
   No literal rgba overrides. Ever.
   ============================================================ */

/* ---------- LIGHT SURFACE GROUP ----------
   Default behavior. Plus paper/tone/tint/soft surfaces.
   Brand text clamps DOWN to dark side (0.35-0.5 lightness).
   Brand fill stays at raw brand color (already saturated). */
:root,
[data-surface="paper"],
[data-surface="tone"],
[data-surface="tint"],
[data-surface="soft"] {
  --color-primary-text: oklch(from var(--brand-primary)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);
  --color-secondary-text: oklch(from var(--brand-secondary)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);
  --color-accent-text: oklch(from var(--brand-accent)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);

  /* --color-X-fill DELETED 2026-05-14 (Andrew Round 9). /system/ provides
     these with v2.7.x lightness clamps (min(l, 0.7) on light, max(l, 0.45)
     on dark) that handle very-light + very-dark primaries. Local raw defs
     were shadowing those clamps. */
}

/* ---------- DARK SURFACE GROUP ----------
   Dark scheme default. Plus ink/deep/primary/secondary surfaces.
   Brand text clamps UP to light side (0.7-0.88 lightness).
   Brand fill clamps to mid-light (0.55-0.7) for BG legibility.
   Also redefines the 12-step accent + gray scales so "dark island"
   sections inside a light page (e.g. ink hero in light editor) get
   the dark scale tokens for correct brand-foreground rendering. */
:root[data-scheme="dark"],
[data-surface="ink"],
[data-surface="deep"],
[data-surface="primary"],
[data-surface="secondary"] {
  --color-primary-text: oklch(from var(--brand-primary)
    clamp(0.7, l, 0.88) min(c, 0.18) h);
  --color-secondary-text: oklch(from var(--brand-secondary)
    clamp(0.7, l, 0.88) min(c, 0.18) h);
  --color-accent-text: oklch(from var(--brand-accent)
    clamp(0.7, l, 0.88) min(c, 0.18) h);

  /* --color-X-fill DELETED 2026-05-14 (Andrew Round 9). /system/ provides
     scoped versions for [data-surface=ink/dark/brand]. Local was shadowing
     with a more complex but stale formula. */

  /* 12-step accent scale — dark formula */
  --accent-1:  oklch(from var(--brand-primary) 0.155 calc(c * 0.20) h);
  --accent-2:  oklch(from var(--brand-primary) 0.195 calc(c * 0.30) h);
  --accent-3:  oklch(from var(--brand-primary) 0.245 calc(c * 0.50) h);
  --accent-4:  oklch(from var(--brand-primary) 0.295 calc(c * 0.70) h);
  --accent-5:  oklch(from var(--brand-primary) 0.355 calc(c * 0.85) h);
  --accent-6:  oklch(from var(--brand-primary) 0.42 c h);
  --accent-7:  oklch(from var(--brand-primary) 0.50 c h);
  --accent-8:  oklch(from var(--brand-primary) 0.58 c h);
  --accent-9:  var(--brand-primary);
  --accent-10: oklch(from var(--brand-primary) calc(l + 0.04) c h);
  --accent-11: oklch(from var(--brand-primary) clamp(0.72, calc(l + 0.20), 0.85) calc(c * 1.5) h);
  --accent-12: oklch(from var(--brand-primary) clamp(0.92, calc(l + 0.40), 0.97) c h);

  /* 12-step gray scale — dark formula with brand-tint floor (perceptible) */
  --gray-1:  oklch(from var(--brand-primary) 0.135 calc(c * 0.15) h);
  --gray-2:  oklch(from var(--brand-primary) 0.175 calc(c * 0.18) h);
  --gray-3:  oklch(from var(--brand-primary) 0.215 calc(c * 0.20) h);
  --gray-4:  oklch(from var(--brand-primary) 0.255 calc(c * 0.20) h);
  --gray-5:  oklch(from var(--brand-primary) 0.295 calc(c * 0.20) h);
  --gray-6:  oklch(from var(--brand-primary) 0.345 calc(c * 0.18) h);
  --gray-7:  oklch(from var(--brand-primary) 0.42 calc(c * 0.16) h);
  --gray-8:  oklch(from var(--brand-primary) 0.55 calc(c * 0.12) h);
  --gray-9:  oklch(from var(--brand-primary) 0.62 calc(c * 0.10) h);
  --gray-10: oklch(from var(--brand-primary) 0.66 calc(c * 0.08) h);
  --gray-11: oklch(from var(--brand-primary) 0.78 calc(c * 0.05) h);
  --gray-12: oklch(from var(--brand-primary) 0.95 calc(c * 0.03) h);
}

/* ============================================================
   LAYER 2 — DENSITY VARIANTS (data-density attribute)
   ============================================================
   Compact = tighter UIs (dashboards, data-dense apps)
   Comfortable = default (marketing pages, blog)
   Spacious = breathing room (luxury, editorial, premium)

   Adjusts --space-multiplier which feeds every spacing token.
   Components don't need any density-specific code. Cascade
   does the work via --space-unit recomputation.
   ============================================================ */
:root[data-density="compact"]      { --space-multiplier: 0.85; }
:root[data-density="comfortable"]  { --space-multiplier: 1; }
:root[data-density="spacious"]     { --space-multiplier: 1.15; }
:root[data-density="editorial"]    { --space-multiplier: 1.25; }

/* ============================================================
   RESPONSIVE SHELL-PAD — bumps inner edge padding at wider breakpoints.
   Mobile keeps 20px (tight, edge-to-edge). Tablet 32px. Desktop 48px.
   ============================================================ */
@media (min-width: 768px)  { :root { --shell-pad: var(--shell-pad-tablet); } }
@media (min-width: 1024px) { :root { --shell-pad: var(--shell-pad-desktop); } }

/* ============================================================
   LAYER 2 — MOTION PERSONALITY PRESETS (data-motion attribute)
   ============================================================
   Each brand pack picks its motion personality. Transitions,
   easings, and micro-interaction speeds adapt to brand vibe.

     subtle    Snappy, near-imperceptible. Pro/SaaS dashboards.
     calm      Default. Standard ease-out, balanced timing.
     playful   Bouncy springs, longer durations. Creative brands.
     energetic Quick + crisp + slight overshoot. Action-driven.
   ============================================================ */
/* Brand-mood motion is a TWO-LAYER system so brand-mood AND curated style
   can both influence pacing without one overriding the other.
     --motion-scale-base  ← the data-motion dial (subtle/calm/playful/energetic)
     --motion-scale-style ← the data-curated-style multiplier (editorial slows
                            everything down, bold tightens it up, etc)
     --motion-scale       ← computed product, what every duration token uses
   This lets a "playful + editorial" pack feel slower + more considered while
   "energetic + bold" packs feel tight + snappy, without either dial
   silently winning. Bouncy easings removed per Hallmark slop-test gate 3
   (overshoot easings banned on UI elements; reserve for drag-release). */
:root {
  --motion-scale-base: 1;
  --motion-scale-style: 1;
  --motion-scale: calc(var(--motion-scale-base) * var(--motion-scale-style));
}
:root[data-motion="subtle"]    { --motion-scale-base: 0.55; }
:root[data-motion="calm"]      { --motion-scale-base: 1; }
:root[data-motion="playful"]   { --motion-scale-base: 1.45; }
:root[data-motion="energetic"] { --motion-scale-base: 0.75; }

/* Curated style → motion multiplier. Each style has a personality the
   motion should reinforce. Editorial is slow + composed. Bold is snappy +
   confident. Playful is breathy + felt. Minimal is efficient. Luxury is
   considered. Wellness is calm. Sharp is decisive. Modern is balanced. */
:root[data-curated-style="editorial"]     { --motion-scale-style: 1.15; }
:root[data-curated-style="minimal"]       { --motion-scale-style: 0.70; }
:root[data-curated-style="playful"]       { --motion-scale-style: 1.25; }
:root[data-curated-style="sharp"]         { --motion-scale-style: 0.65; }
:root[data-curated-style="bold"]          { --motion-scale-style: 0.75; }
:root[data-curated-style="modern"]        { --motion-scale-style: 0.90; }
:root[data-curated-style="sophisticated"] { --motion-scale-style: 1.10; }
:root[data-curated-style="luxury"]        { --motion-scale-style: 1.20; }
:root[data-curated-style="wellness"]      { --motion-scale-style: 1.15; }

/* Reduced-motion = full kill switch. Scale → 0 zeroes every duration
   through the cascade. Animations fall back to a fixed 0ms via the
   override below. Hallmark-canonical first-class accessibility state. */
@media (prefers-reduced-motion: reduce) {
  :root { --motion-scale: 0; }
  *, *::before, *::after {
    animation-duration: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0ms !important;
  }
}

/* ============================================================
   LAYER 2 — VIEW TRANSITIONS (View Transitions API)
   ============================================================
   When DH.applyBrand() is called inside startViewTransition(),
   the browser captures pre + post snapshots and crossfades
   between them. Below tunes the transition timing per motion
   personality.
   ============================================================ */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--motion-medium, 320ms);
  animation-timing-function: var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}

::view-transition-group(root) {
  animation-duration: var(--motion-medium, 320ms);
}

/* ---------- SCHEME=DARK BASE SURFACES + 12-STEP SCALE FLIP ----------
   Page-level dark scheme switches the default surface to ink.
   Also flips the 12-step accent + gray scales to their dark counterparts.
   In dark mode, step 1 is the DARKEST bg, step 12 is the LIGHTEST text. */
:root[data-scheme="dark"] {
  color-scheme: dark;

  /* Surface defaults — card-bg now uses the 12-step gray-3 for proper lift
     above the page bg. Old color-mix(ink 86%, white) was too close to the
     page bg, so cards visually merged with the surface.
     Uses --ink (short, no @property) so inline brand swaps propagate
     identically to the light mode path. */
  --surface-bg: var(--ink);
  --surface-text: var(--surface-ink-text);
  --surface-card-bg: var(--gray-3);
  /* r362 — Stronger border in dark mode for visible component edges.
     srgb mix — oklch produced hue=none → warm tint on every card border. */
  --surface-border: color-mix(in srgb, var(--surface-text), var(--surface-bg) 60%);

  /* r462c-batch · Dark scheme aurora opacity cap (the ink page bg
     already carries depth; full aurora on top reads as noise). */
  --surface-effect-opacity: 0.55;

  /* r462c-batch · Dark scheme card shadow — stronger drop with
     brand-tinted halo so cards lift above the dark page. */
  --surface-card-shadow: 0 2px 6px color-mix(in srgb, #000 35%, transparent), 0 8px 24px color-mix(in srgb, var(--color-primary) 14%, transparent);

  /* DARK 12-step accent scale */
  --accent-1:  oklch(from var(--brand-primary) 0.155 calc(c * 0.20) h);
  --accent-2:  oklch(from var(--brand-primary) 0.195 calc(c * 0.30) h);
  --accent-3:  oklch(from var(--brand-primary) 0.245 calc(c * 0.50) h);
  --accent-4:  oklch(from var(--brand-primary) 0.295 calc(c * 0.70) h);
  --accent-5:  oklch(from var(--brand-primary) 0.355 calc(c * 0.85) h);
  --accent-6:  oklch(from var(--brand-primary) 0.42  c h);
  --accent-7:  oklch(from var(--brand-primary) 0.50  c h);
  --accent-8:  oklch(from var(--brand-primary) 0.58  c h);
  --accent-9:  var(--brand-primary);
  --accent-10: oklch(from var(--brand-primary) calc(l + 0.04) c h);
  --accent-11: oklch(from var(--brand-primary) clamp(0.72, calc(l + 0.20), 0.85) calc(c * 1.5) h);
  --accent-12: oklch(from var(--brand-primary) clamp(0.92, calc(l + 0.40), 0.97) c h);

  /* DARK 12-step gray scale — chroma floor lifted from 0.005-0.014
     to 0.020-0.032 so the brand hue is perceptible in dark surfaces.
     Below ~0.020 chroma at these lightness levels, dark colors visually
     collapse to "the void" and lose all brand identity. */
  --gray-1:  oklch(from var(--brand-primary) 0.135 calc(c * 0.15) h);
  --gray-2:  oklch(from var(--brand-primary) 0.175 calc(c * 0.18) h);
  --gray-3:  oklch(from var(--brand-primary) 0.215 calc(c * 0.20) h);
  --gray-4:  oklch(from var(--brand-primary) 0.255 calc(c * 0.20) h);
  --gray-5:  oklch(from var(--brand-primary) 0.295 calc(c * 0.20) h);
  --gray-6:  oklch(from var(--brand-primary) 0.345 calc(c * 0.18) h);
  --gray-7:  oklch(from var(--brand-primary) 0.42  calc(c * 0.16) h);
  --gray-8:  oklch(from var(--brand-primary) 0.55  calc(c * 0.12) h);
  --gray-9:  oklch(from var(--brand-primary) 0.62  calc(c * 0.10) h);
  --gray-10: oklch(from var(--brand-primary) 0.66  calc(c * 0.08) h);
  --gray-11: oklch(from var(--brand-primary) 0.78  calc(c * 0.05) h);
  --gray-12: oklch(from var(--brand-primary) 0.95  calc(c * 0.03) h);
}

/* ============================================================
   SURFACE-AWARE BRAND FILLS (r114, 2026-05-17)
   ============================================================
   Ported from r1566 + /system/ Round 15.41 surface-aware fill
   pattern. /system/'s base clamp(0.20, l, 0.92) light + clamp(0.30, l,
   0.92) dark is too permissive for the editor preview — a dark olive
   primary (L≈0.25) on dark mode page (L≈0.10) lifts only to 0.30 and
   still reads as nearly invisible. A pale yellow primary (L≈0.85)
   on light mode page (L≈0.97) passes through unchanged and disappears
   against paper. Tighter clamps below guarantee BRAND IDENTITY stays
   intact (hue + chroma preserved) while LIGHTNESS lands in the
   readable band against whichever surface is active.

   The architectural principle: brand pack stores the raw input. The
   -fill family is the BG-SAFE rendered variant. Every preview surface
   that uses the brand as a fill (button, chip, dot, hero overlay,
   pill, badge, callout) must consume -fill, not raw -primary. Raw
   -primary stays for swatches + reference where the un-adjusted color
   matters.
   ============================================================ */

/* Light scheme — cap L ceiling at 0.75 so a near-white primary
   darkens enough to read on paper without crushing pastels into
   dusty mauve. /system/'s text-on-primary auto-flip handles the
   white-vs-dark text contrast independently, so we can give light
   primaries more breathing room. Raise the floor from /system/'s
   0.20 to keep it identical for darker primaries (no regression
   for normal-range brands). */
:root:not([data-scheme="dark"]) {
  --color-primary-fill:   oklch(from var(--color-primary)   clamp(0.20, l, 0.82) c h);
  --color-secondary-fill: oklch(from var(--color-secondary) clamp(0.20, l, 0.82) c h);
  --color-accent-fill:    oklch(from var(--color-accent)    clamp(0.20, l, 0.82) c h);
  --color-deep-fill:      oklch(from var(--color-deep)      clamp(0.15, l, 0.55) c h);
}

/* Dark scheme — lift L floor from /system/'s 0.30 to 0.45 so very
   dark primaries (Calvary stone, dark olive, near-black) become
   visible against the ink page bg. Cap at 0.85 so very-light
   primaries don't go full-white against the dark bg, which would
   clash with white text-on-primary overlays. */
:root[data-scheme="dark"] {
  --color-primary-fill:   oklch(from var(--color-primary)   clamp(0.45, l, 0.85) c h);
  --color-secondary-fill: oklch(from var(--color-secondary) clamp(0.45, l, 0.85) c h);
  --color-accent-fill:    oklch(from var(--color-accent)    clamp(0.45, l, 0.85) c h);
  --color-deep-fill:      oklch(from var(--color-deep)      clamp(0.40, l, 0.78) c h);
}

/* ============================================================
   TEXT-ON-PRIMARY auto-flip threshold (r135, 2026-05-18)
   ============================================================
   /system/ uses a 0.65 L threshold for choosing white vs dark text on
   brand fills. That switches WAY too early. A purple at L=0.66, a pink
   at L=0.65, and a mid-blue at L=0.67 all land just above 0.65 and get
   dark text, which reads worse than white for those hues.

   Push the threshold to 0.72 so cusp brand colors get WHITE text.
   Only very light primaries (pastels above L=0.72) flip to dark text.
   The actual WCAG contrast on these mid-range colors is still fine
   with white text because the chroma + hue do most of the perceptual
   contrast work.
   ============================================================ */
:root {
  --text-on-primary-auto:   oklch(from var(--color-primary)   clamp(0.05, calc((0.72 - l) * 1000), 0.95) 0 0);
  --text-on-secondary-auto: oklch(from var(--color-secondary) clamp(0.05, calc((0.72 - l) * 1000), 0.95) 0 0);
  --text-on-accent-auto:    oklch(from var(--color-accent)    clamp(0.05, calc((0.72 - l) * 1000), 0.95) 0 0);
}

/* ---------- CROSS-RESTORATION ----------
   Page is in dark scheme but THIS section uses a light surface.
   Specificity (0,3,0) beats both single-attr selectors (0,2,0)
   so light-mode formulas win locally. Also restores the 12-step
   light scale so brand-foreground tokens read correctly inside
   light-island sections (e.g. paper hero inside a dark page). */
:root[data-scheme="dark"] [data-surface="paper"],
:root[data-scheme="dark"] [data-surface="tone"],
:root[data-scheme="dark"] [data-surface="tint"],
:root[data-scheme="dark"] [data-surface="soft"] {
  --surface-text: var(--surface-ink);

  --color-primary-text: oklch(from var(--brand-primary)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);
  --color-secondary-text: oklch(from var(--brand-secondary)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);
  --color-accent-text: oklch(from var(--brand-accent)
    clamp(0.35, l, 0.5) calc(c * 1.2) h);

  /* --color-X-fill DELETED 2026-05-14 (Andrew Round 9). /system/ provides
     these with v2.7.x lightness clamps (min(l, 0.7) on light, max(l, 0.45)
     on dark) that handle very-light + very-dark primaries. Local raw defs
     were shadowing those clamps. */

  /* 12-step accent scale — restore light formula */
  --accent-1:  oklch(from var(--brand-primary) 0.985 calc(c * 0.04) h);
  --accent-2:  oklch(from var(--brand-primary) 0.965 calc(c * 0.10) h);
  --accent-3:  oklch(from var(--brand-primary) 0.935 calc(c * 0.20) h);
  --accent-4:  oklch(from var(--brand-primary) 0.895 calc(c * 0.35) h);
  --accent-5:  oklch(from var(--brand-primary) 0.845 calc(c * 0.55) h);
  --accent-6:  oklch(from var(--brand-primary) 0.78  calc(c * 0.75) h);
  --accent-7:  oklch(from var(--brand-primary) 0.70  calc(c * 0.90) h);
  --accent-8:  oklch(from var(--brand-primary) 0.62  c h);
  --accent-9:  var(--brand-primary);
  --accent-10: oklch(from var(--brand-primary) calc(l - 0.04) c h);
  --accent-11: oklch(from var(--brand-primary) clamp(0.38, calc(l - 0.05), 0.48) calc(c * 1.5) h);
  --accent-12: oklch(from var(--brand-primary) clamp(0.18, calc(l - 0.30), 0.28) calc(c * 1.2) h);

  /* 12-step gray scale — restore light formula */
  --gray-1:  oklch(from var(--brand-primary) 0.988 0.003 h);
  --gray-2:  oklch(from var(--brand-primary) 0.97  0.005 h);
  --gray-3:  oklch(from var(--brand-primary) 0.945 0.007 h);
  --gray-4:  oklch(from var(--brand-primary) 0.92  0.008 h);
  --gray-5:  oklch(from var(--brand-primary) 0.88  0.010 h);
  --gray-6:  oklch(from var(--brand-primary) 0.83  0.012 h);
  --gray-7:  oklch(from var(--brand-primary) 0.74  0.013 h);
  --gray-8:  oklch(from var(--brand-primary) 0.60  0.013 h);
  --gray-9:  oklch(from var(--brand-primary) 0.48  0.012 h);
  --gray-10: oklch(from var(--brand-primary) 0.42  0.011 h);
  --gray-11: oklch(from var(--brand-primary) 0.34  0.010 h);
  --gray-12: oklch(from var(--brand-primary) 0.18  0.008 h);
}

/* r462c-batch · per-surface aurora opacity cap.
   ink/deep/primary surfaces are already saturated — aurora gets
   dampened to 0.55 so it adds atmosphere without muddying content.
   Paper/tone/tint stay at the inherited light-scheme 1.0 value. */
:root [data-surface="deep"],
:root [data-surface="ink"],
:root [data-surface="primary"] {
  --surface-effect-opacity: 0.55;
}

/* r462c-batch · per-surface card shadow — stronger on dark surfaces
   so nested cards visibly lift from the parent. */
:root [data-surface="deep"] {
  --surface-card-shadow: 0 3px 8px color-mix(in srgb, #000 38%, transparent), 0 10px 28px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
:root [data-surface="ink"] {
  --surface-card-shadow: 0 4px 10px color-mix(in srgb, #000 50%, transparent), 0 14px 32px color-mix(in srgb, var(--color-primary) 22%, transparent);
}
:root [data-surface="primary"] {
  --surface-card-shadow: 0 3px 8px color-mix(in srgb, #000 30%, transparent), 0 12px 30px color-mix(in srgb, var(--color-ink) 30%, transparent);
}

/* Restore the bg + card-bg per-surface in dark scheme */
:root[data-scheme="dark"] [data-surface="paper"] {
  --surface-bg: var(--paper);
  --surface-card-bg: var(--tone);
}
:root[data-scheme="dark"] [data-surface="tone"] {
  --surface-bg: var(--tone);
  --surface-card-bg: var(--paper);
}
:root[data-scheme="dark"] [data-surface="tint"] {
  --surface-bg: var(--color-primary-tint);
  --surface-card-bg: white;
}
:root[data-scheme="dark"] [data-surface="soft"] {
  --surface-bg: var(--color-primary-soft);
  --surface-card-bg: white;
}

/* ============================================================
   DESIGN SYSTEM — PREMIUM MENU (r304, 2026-05-20)
   ============================================================
   Custom click-to-open dropdown menu that gives the SAME premium
   styling on both the trigger AND the open panel (something native
   <select> can't do — the OS picker takes over on open).
   Modeled exactly on the Download menu pattern in index.html.

   Usage (HTML):
     <div class="dh-menu" data-dh-menu>
       <button type="button" class="dh-menu__trigger" data-dh-menu-trigger>
         <span class="dh-menu__label">CATEGORY</span>
         <span class="dh-menu__value">Show all</span>
         <svg class="dh-menu__caret" viewBox="0 0 12 8" aria-hidden="true">
           <path d="M1 1.5L6 6.5L11 1.5" fill="none" stroke="currentColor"
                 stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
         </svg>
       </button>
       <div class="dh-menu__panel" role="menu" aria-hidden="true">
         <button type="button" class="dh-menu__item is-active" data-dh-menu-value="">Show all</button>
         <button type="button" class="dh-menu__item" data-dh-menu-value="brand-identity">Brand Identity</button>
         ...
       </div>
     </div>

   Open/close + outside-click + ESC are handled by wireDhMenuDelegation()
   in JS. Items dispatch a `dh-menu:select` CustomEvent with the value.
   ============================================================ */
.dh-menu {
  position: relative;
  display: inline-block;
  width: 100%;
}
.dh-menu__trigger {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  background: var(--surface-card-bg);
  /* True neutral border — chrome, not brand expression. */
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: var(--btn-radius, 10px);
  color: var(--surface-text);
  font-family: var(--font-body, system-ui, sans-serif);
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
              0 1px 3px rgba(0, 0, 0, 0.04);
  transition: border-color var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              box-shadow var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
:root[data-scheme="dark"] .dh-menu__trigger {
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4),
              0 1px 3px rgba(0, 0, 0, 0.3);
}
.dh-menu__trigger:hover {
  border-color: rgba(0, 0, 0, 0.22);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06),
              0 4px 12px -2px rgba(0, 0, 0, 0.08);
}
:root[data-scheme="dark"] .dh-menu__trigger:hover {
  border-color: rgba(255, 255, 255, 0.28);
}
.dh-menu.is-open .dh-menu__trigger {
  border-color: rgba(0, 0, 0, 0.22);
  box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.10);
}
.dh-menu__trigger:focus-visible {
  outline: 2px solid var(--color-primary-fill, var(--color-primary));
  outline-offset: 2px;
}
.dh-menu__label {
  font-family: ui-monospace, "JetBrains Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--surface-text) 60%, transparent);
  flex-shrink: 0;
}
.dh-menu__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dh-menu__caret {
  width: 12px;
  height: 8px;
  flex-shrink: 0;
  color: color-mix(in srgb, var(--surface-text) 55%, transparent);
  transition: transform var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              color var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
.dh-menu__trigger:hover .dh-menu__caret {
  color: color-mix(in srgb, var(--surface-text) 80%, transparent);
}
.dh-menu.is-open .dh-menu__caret {
  transform: rotate(180deg);
  color: var(--color-primary-fill, var(--color-primary));
}

/* The panel — popover below the trigger. Card surface, real drop shadow,
   smooth open/close transition. Matches Download menu panel exactly. */
.dh-menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  min-width: 100%;
  max-height: 60vh;
  overflow-y: auto;
  padding: 8px;
  background: var(--surface-card-bg);
  border: 1px solid color-mix(in srgb, var(--surface-text, #1a1a1a) 12%, transparent);
  border-radius: var(--card-radius, 14px); /* menus wear the card dial */
  box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.22),
              0 4px 12px -2px rgba(0, 0, 0, 0.08);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              transform var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              visibility var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
  z-index: 200;
}
:root[data-scheme="dark"] .dh-menu__panel {
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.6),
              0 4px 12px -2px rgba(0, 0, 0, 0.4);
}
.dh-menu.is-open .dh-menu__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.dh-menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: 8px;
  text-align: left;
  cursor: pointer;
  color: var(--surface-text);
  font-family: var(--font-body, system-ui, sans-serif);
  font-size: 0.92rem;
  font-weight: 500;
  transition: background var(--dur-micro, 120ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
.dh-menu__item + .dh-menu__item {
  margin-top: 1px;
}
.dh-menu__item:hover,
.dh-menu__item:focus-visible {
  background: color-mix(in srgb, var(--surface-text) 6%, transparent);
  outline: none;
}
.dh-menu__item.is-active {
  background: color-mix(in oklch, var(--color-primary) 10%, transparent);
  color: var(--color-primary-text, var(--color-primary));
  font-weight: 600;
}
.dh-menu__item.is-active:hover {
  background: color-mix(in oklch, var(--color-primary) 16%, transparent);
}
.dh-menu__item-check {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  margin-left: auto;
  opacity: 0;
  color: var(--color-primary-fill, var(--color-primary));
}
.dh-menu__item.is-active .dh-menu__item-check {
  opacity: 1;
}

/* ============================================================
   DESIGN SYSTEM — PREMIUM SELECT (r298, 2026-05-20)
   ============================================================
   Reusable wrapper that elevates any native <select> to match the
   Download menu aesthetic. Wrap any select in .dh-menu-select-wrap with
   the select itself wearing .dh-menu-select. Custom caret SVG sits in
   the wrapper as an absolutely-positioned sibling. Strip native
   chrome with appearance:none.

   Usage:
     <label class="dh-menu-select-wrap">
       <span class="dh-menu-select-label">Label</span>     // optional
       <select class="dh-menu-select">...</select>
       <svg class="dh-menu-select-caret" viewBox="0 0 12 8" aria-hidden="true">
         <path d="M1 1.5L6 6.5L11 1.5" fill="none" stroke="currentColor"
               stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
       </svg>
     </label>

   Works in chrome (root scope) + canvas (.prev-v3 scope) because all
   tokens it references (--surface-card-bg, --surface-text, --color-primary,
   --dur-short, --btn-radius) cascade through both contexts.
   ============================================================ */
.dh-menu-select-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  background: var(--surface-card-bg);
  /* True neutral border (NOT brand-tinted) so dropdowns feel like
     consistent UI chrome across every brand pack. Width always 1px
     regardless of brand pack borderWidth dial. */
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: var(--btn-radius, 10px);
  color: var(--surface-text);
  cursor: pointer;
  /* Two-layer ambient shadow — short hairline catch + soft drop. Gives
     premium "button-like" elevation without brand-tinting. Matches the
     Download menu trigger pattern (visual weight from shadow, not color). */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
              0 1px 3px rgba(0, 0, 0, 0.04);
  transition: border-color var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              box-shadow var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              background var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
/* Dark scheme: white alphas so the hairline + lift read against ink. */
:root[data-scheme="dark"] .dh-menu-select-wrap {
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4),
              0 1px 3px rgba(0, 0, 0, 0.3);
}
.dh-menu-select-wrap:hover {
  border-color: rgba(0, 0, 0, 0.22);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06),
              0 4px 12px -2px rgba(0, 0, 0, 0.08);
}
:root[data-scheme="dark"] .dh-menu-select-wrap:hover {
  border-color: rgba(255, 255, 255, 0.28);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5),
              0 4px 12px -2px rgba(0, 0, 0, 0.4);
}
/* IMPORTANT — no :focus / :focus-within styling on the wrapper.
   Chrome treats native <select> mouse clicks as :focus-visible (because
   the picker opening counts as "obvious focus intent"), AND the select
   retains focus after the picker closes. Any focus rule on the wrapper
   pins a styled state on screen until the user clicks elsewhere, which
   reads as "stuck open / buggy."
   Mirrors the Download menu trigger pattern: no focus chrome on the
   trigger, all visual weight lives on hover. Keyboard-only users get a
   small ring on the inner select via .dh-menu-select:focus-visible below. */
.dh-menu-select:focus-visible {
  outline: 2px solid var(--color-primary-fill, var(--color-primary));
  outline-offset: 4px;
  border-radius: 4px;
}

/* Optional eyebrow label that sits left of the select trigger.
   Matches the typography rhythm of the Download menu small text. */
.dh-menu-select-label {
  font-family: ui-monospace, "JetBrains Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--surface-text) 60%, transparent);
  flex-shrink: 0;
  pointer-events: none;
}

/* The actual native select — chrome stripped, brand typography applied.
   font-size 0.95rem keeps iOS from auto-zooming on focus (their floor is 16px;
   0.95rem at default 16px = 15.2px which is close enough they usually skip
   the zoom, and we prioritize visual rhythm over the edge case). */
.dh-menu-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background: transparent;
  border: 0;
  outline: 0;
  flex: 1 1 auto;
  min-width: 0;
  padding: 11px 28px 11px 0;
  font-family: var(--font-body, system-ui, sans-serif);
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--surface-text);
  cursor: pointer;
  line-height: 1.2;
  /* Remove the default focus halo since the wrapper handles focus-within. */
  outline: none;
}
.dh-menu-select::-ms-expand { display: none; }

/* Custom caret — positioned over the right edge of the wrapper.
   Rotates 180° when the select is open via :focus-within (heuristic;
   native select doesn't expose open state, but focus is close enough for
   the visual cue). */
.dh-menu-select-caret {
  position: absolute;
  right: 14px;
  top: 50%;
  width: 12px;
  height: 8px;
  transform: translateY(-50%);
  color: color-mix(in srgb, var(--surface-text) 55%, transparent);
  pointer-events: none;
  transition: transform var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1)),
              color var(--dur-short, 220ms) var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
}
/* Caret stays pointing down by default. Color tints toward brand on
   hover for a soft affordance. The 180deg rotation was misleading on
   native selects — they don't actually stay open while focused, so the
   caret would lie. Keep it static. */
.dh-menu-select-wrap:hover .dh-menu-select-caret {
  color: color-mix(in oklch, var(--color-primary) 70%, var(--surface-text));
}

/* ============================================================
   LAYER 6 — ACTION COMPONENT SAFETY TOKENS (r459d, 2026-05-25)
   ============================================================
   THE RULE: Brand color is INPUT. Action token is OUTPUT.

   Brand --color-primary-fill can be beige, gold, dark, near-paper,
   anything. The action token contract clamps it into a safe range
   so buttons always read as intentional interactive controls,
   regardless of which brand they come from.

   ROUTING:
   - Buttons, CTAs, clickable chips, focus rings, selected states
       → consume --action-primary-* and --action-secondary-*
   - Background art, hero atmosphere, tiles, decorative gradients
       → keep consuming raw brand vars

   FIXES: CTA-SAFETY-01 (r459c P1 finding).
   Warm/pale brand primaries were blending into hero gradient/glow.
   Hero CTA + brand-kit CTA + gradient buttons all affected.

   IMPLEMENTATION NOTE: CSS-safe clamps only. No JS APCA generation
   in this round. A future token compiler round can replace the
   lightness clamp with APCA-resolved foregrounds if needed.
   ============================================================ */

/* r462c-fix6h: @property declarations REMOVED.
   Chrome's registered-property substitution locks the value to
   initial-value when the var() chain has any momentarily-undefined
   link (e.g. before the brand pack JSON loads --text-on-primary).
   On primary surface, --action-primary-bg was supposed to flip to
   --surface-text (white) per fix6. The chain --surface-text →
   --text-on-primary fails its initial substitution check and the
   property gets locked to initial-value #A5205B (magenta), causing
   getComputedStyle to report magenta even after the JS pack defines
   the missing var. Visually the paint pipeline may still show the
   correct color, but the audit (which uses getComputedStyle) reads
   the locked value.

   Fix: leave these tokens UNREGISTERED. They become plain CSS custom
   properties with lazy var() substitution. Chrome computes them
   correctly at runtime once all vars are defined. The transitions
   on the .btn rule's `background` shorthand still work because
   transitions apply to the shorthand property, not the custom prop.

   What we lose by removing @property:
   - syntax type checking (not needed; values are always colors)
   - initial-value fallback (we want lazy resolution anyway)
   - transitions ON the custom property (not used anywhere) */

:root {
  /* Primary action contract (r462b-fix3).
     --action-primary-bg now consumes --color-primary-fill directly.
     The previous tight clamp(0.36, l, 0.62) double-clamped on top of
     primary-fill's own clamp(0.20, l, 0.82), crushing bright neon/lime
     primaries to a muted 0.62 lightness. That made the hero CTA look
     dim against the lower buttons that consume --color-primary-fill
     directly (.prev-btn--primary, .prev-header__cta, etc).
     Brand vividness is now preserved. CTA-SAFETY-01 (warm/pale primaries
     blending into hero glow) is still handled by:
       - --action-primary-text via --text-on-primary-auto (auto contrast)
       - --action-primary-shadow surface-aware 1px outer ring on
         ink/deep/primary surfaces (the override below at the
         [data-surface=...] selector list).
     Pack-level --color-primary-fill is already softly clamped to keep
     near-paper / near-ink primaries inside a safe range, so no extra
     belt-and-suspenders is needed here. */
  --action-primary-bg: var(--color-primary-fill, var(--color-primary, #888));

  --action-primary-text:
    var(--text-on-primary-auto, var(--text-on-primary, #fff));

  --action-primary-border:
    color-mix(in srgb, var(--action-primary-text) 22%, transparent);

  --action-primary-shadow:
    0 14px 30px color-mix(in srgb, var(--color-ink, #111) 18%, transparent);

  --action-primary-hover-bg:
    oklch(from var(--action-primary-bg) calc(l - 0.04) c h);

  /* Secondary action contract.
     Ghost-style by default. Border-driven, no fill. Stays readable
     on any surface because it consumes --surface-text directly. */
  --action-secondary-bg: transparent;
  --action-secondary-text: var(--surface-text, var(--color-ink, #111));
  --action-secondary-border:
    color-mix(in srgb, var(--surface-text, var(--color-ink, #111)) 28%, transparent);
  --action-secondary-hover-bg:
    color-mix(in srgb, var(--surface-text, var(--color-ink, #111)) 8%, transparent);
}

/* Surface-aware overrides.
   Branded / dark surfaces need stronger edge contrast. The button
   already sits on a deep bg, so the inherited 22%-text border can
   be too subtle. Paper-tinted border + extra ring inside the shadow
   stack gives the button a visible silhouette against any dark hero. */
[data-surface="ink"],
[data-surface="deep"],
[data-surface="primary"] {
  --action-primary-border:
    color-mix(in srgb, var(--color-paper, #fff) 32%, transparent);

  --action-primary-shadow:
    0 18px 42px color-mix(in srgb, var(--color-ink, #111) 42%, transparent),
    0 0 0 1px color-mix(in srgb, var(--color-paper, #fff) 12%, transparent);

  --action-secondary-text: var(--color-paper, #fff);
  --action-secondary-border:
    color-mix(in srgb, var(--color-paper, #fff) 32%, transparent);
  --action-secondary-hover-bg:
    color-mix(in srgb, var(--color-paper, #fff) 10%, transparent);
}

/* Dark scheme: same logic, with paper-source flipped.
   Already inherited from the surface tokens, but action-secondary-text
   needs an explicit declaration at the scheme level so it doesn't
   leak the light-mode --surface-text into a dark page. */
:root[data-scheme="dark"] {
  --action-secondary-text: var(--surface-text);
  --action-secondary-border:
    color-mix(in srgb, var(--surface-text) 32%, transparent);
  --action-secondary-hover-bg:
    color-mix(in srgb, var(--surface-text) 10%, transparent);
}

/* r462c-fix6c proof marker */
:root {
  --bikg-token-version: "r462c-fix6h-unregister-property";
}

/* ============================================================
   r462c-fix6 — Primary action contract flip (kept from prior round)
   ============================================================
   On [data-surface="primary"], default --action-primary-bg resolves
   to --color-primary-fill which equals --surface-bg. Action token
   buttons disappear into the section.

   Fix: flip --action-primary-bg/text/border/hover to consume
   --surface-text and --surface-bg so the button auto-inverts.
   Auto-tracks all 6 brand packs.

   The legacy --color-primary-fill is NOT touched here. The lab now
   tests the canonical action contract. Legacy consumers (.btn-gradient,
   hero CTA) are tested separately so we can see fill-path drift.
   ============================================================ */
[data-surface="primary"] {
  --action-primary-bg: var(--surface-text);
  --action-primary-text: var(--surface-bg);
  --action-primary-hover-bg:
    color-mix(in srgb, var(--surface-text) 88%, var(--surface-bg));
  --action-primary-border:
    color-mix(in srgb, var(--surface-text) 35%, transparent);
}

/* ============================================================
   END tokens-v2.css — Layers 1 + 2 + 6 (action tokens).
   ZERO Tailwind dependency in this file.
   ============================================================
   Tailwind bridge moved to: tailwind-bridge-v2.css
   Components live in:        components-v2.css
   ============================================================ */
