/** Shopify CDN: Minification failed

Line 113:74 Expected ":"
Line 114:16 Expected ":"

**/
@layer base, components, theme;
/* tokens (from templates/vortex/base.html, cut at @vxt-theme:tokens-end) */

    /* ── CSS cascade layers (declarative architecture — calibration 2026-06-12) ──────────────
       Layer order: base (this <style> + tokens) < components (vortex-components.css bundle).
       → base/component precedence NO LONGER depends on the bundle load order (before:
       "the CSS placed LAST in <head> won"). Components beat the base WITHOUT relying on
       specificity. The a11y/utilities !important stay (win regardless of layer);
       app CSS stays non-layered (wins over the layers = current behavior preserved).
       Next step (dedicated pass): a11y/app-overrides layers + removal of !important. */
    @layer base, components;
    @layer base {
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    /* `hidden` must actually hide — everywhere, once.
       The UA sheet carries [hidden]{display:none} at UA priority, so ANY author `display` outranks
       it. Every `.vx-*` class that states one (and most do) silently turns the attribute into a
       no-op: `el.hidden = true` sets it, the element keeps painting, and the code reads as correct.
       Three components had already patched this for themselves — vortex-badge[hidden],
       .vx-secret-toggle [hidden], vortex-tree-view's groups — which is precisely the tell that the
       fix belongs here rather than in a fourth component. Measured on the Shopify theme, which
       carries this same base: the wishlist badge painted "0" while carrying [hidden], and the
       collection filter's `n.hidden = !keep[id]` hid nothing at all — filtering removed no cards.
       `until-found` is excluded on purpose: that value must stay findable, never display:none. */
    [hidden]:not([hidden="until-found"]) { display: none !important; }

    /* V83 — Refactored Vortex design system: layered palette + shadows + radii.
       Skills applied: Radix Colors (neutral gray scale), Open Props (elevation),
       Tailwind Slate (close but deeper), Pico CSS (subtle borders).
       Inspired by Linear / Vercel / Notion dark themes.

       Layered surfaces:
         --bg          → page background (deepest)
         --surface     → panel/card background (elevation 1)
         --surface-2   → elevated card / hover (elevation 2)
         --surface-3   → popover / modal / highest hover (elevation 3)
         --panel       → --surface alias (legacy compat)

       Brand accent keeps Vortex orange (identity), but refined in dark/light.
       Borders in rgba for natural blend with adjacent bg. */

    :root, [data-theme="dark"] {
      /* color-scheme: THE root fix for the white overscroll flash on mobile in dark (Jerry 2026-05-26).
         Without it, the browser paints the canvas/bounce zone/scrollbars LIGHT → white flash on
         bottom bounce. Must follow the theme (research: web.dev/color-scheme, MDN). */
      color-scheme: dark;
      /* Deep charcoal ink surfaces — less blue than Slate-900, deeper
         and neutral, better contrast with elevated surfaces. */
      /* ════════ COLORS — DEFINITIVE REWORK 2026-06-11 (deep-research SOTA, validated by Jerry) ════════
         DECISION: grays are a SEPARATE NEUTRAL SCALE, CONSTANT regardless of the accent
         (Geist/Tailwind/Radix architecture). We NO LONGER tint grays with the accent hue —
         that was the documented anti-pattern that made dark feel "warm/dull/off" (amber gray
         + amber accent = zero pop). Now COLD/slate grays (hue 264, like the vortex-share viewer
         Jerry loves + Tailwind slate) → the WARM amber stands out by complementary contrast.
         `h` (the accent hue) REPLACED by a FIXED `--vx-gray-hue`. text-strong = neutral white (anchor). */
      --vx-gray-hue: 264;          /* cold/slate — constant, never the accent */
      --vx-gray-tint: 0.030;       /* visible cool chroma (SOTA 0.028-0.042; was 0.012 amber) */
      --bg:        oklch(0.150 var(--vx-gray-tint) var(--vx-gray-hue));   /* deep slate (≈ vortex-share #0a0d12) */
      --surface:   oklch(0.212 var(--vx-gray-tint) var(--vx-gray-hue));   /* +0.062 → cards that stand out */
      --surface-2: oklch(0.268 var(--vx-gray-tint) var(--vx-gray-hue));
      --surface-3: oklch(0.328 var(--vx-gray-tint) var(--vx-gray-hue));
      --panel: var(--surface);  /* legacy compat alias */
      --border:        oklch(0.82 var(--vx-gray-tint) var(--vx-gray-hue) / 0.16);  /* > APCA Lc 15 floor */
      --border-strong: oklch(0.86 var(--vx-gray-tint) var(--vx-gray-hue) / 0.30);
      --text:  oklch(0.946 0.012 var(--vx-gray-hue));    /* cool-white (reduced chroma: not too blue) */
      --muted: oklch(0.745 0.020 var(--vx-gray-hue));    /* cold gray — bumped to actually MEET its APCA target on the dark surfaces (was 0.672 → measured Lc ~44; now ~Lc 55, vortex-apca 2026-06-17) while staying clearly subordinate to --text (0.946) */
      --text-strong: #ffffff;
      --placeholder: oklch(0.640 0.020 var(--vx-gray-hue));  /* AA ≥4.5 on bg/surface-2 */

      /* Brand accent — DERIVES from --vx-accent (single source) via relative color → re-themable
         in 1 line. The formulas reproduce the default amber look (hover +lighter/warmer,
         pressed +darker) and FOLLOW any custom accent. */
      --accent: var(--vx-accent);
      --accent-hover:   oklch(from var(--vx-accent) calc(l + 0.068) c calc(h + 14.3));
      --accent-pressed: oklch(from var(--vx-accent) calc(l - 0.103) calc(c - 0.008) calc(h - 11.8));
      /* alpha tints (e.g. --accent-10/15, renamed so they don't clash with the --accent-1..12 ramp) */
      --accent-ghost: oklch(from var(--vx-accent) l c h / 0.10);
      --accent-tint:  oklch(from var(--vx-accent) l c h / 0.18);
      /* CONTAINER / on-container (Material 3) — OPAQUE pair anchored on the ramp (≠ alpha tint
         that depends on the background) → AA contrast guaranteed by construction for badges/chips/alerts/
         accent sections. V143. */
      --accent-container:    var(--accent-3);
      --on-accent-container: var(--accent-11);
      /* Accent as TEXT (PM-23): a touch BRIGHTER than the fill --accent → amber stays the brand identity
         but small amber links/labels gain perceptual contrast on dark (APCA: raw amber Lc ~-57 at 12px was
         below the APCA min; +0.06 L lifts it without washing out / without touching button fills). 2026-06-17. */
      --accent-text: oklch(from var(--vx-accent) calc(l + 0.06) c h);
      /* Text on an accent background — AUTO-DERIVED from --vx-accent (V142, Jerry feedback 2026-05-30):
         TONAL near-black if the accent is light (l>0.62), TONAL near-white if dark. Chroma 0.04
         = a touch of warmth (keeps the nice brown on amber/yellow, warm white on red/purple).
         → no more "dark brown on red/purple": unreadable text on an accent button becomes
         IMPOSSIBLE by construction, whatever the accent (custom included). Ugly pairing forbidden. */
      --on-accent: oklch(from var(--vx-accent) clamp(0.16, (0.62 - l) * 999, 0.97) 0.04 h);

      /* Semantic colors — calibrated to WCAG AA contrast on --bg/--surface */
      --ok:    #3ad9b3; --ok-90:    rgba(58,217,179,.9);
      --warn:  #f97316; --warn-90:  rgba(249,115,22,.9);  /* semantic orange distinct from the amber accent (Jerry 2026-05-26) */
      --err:   #ef4444; --err-90:   rgba(239,68,68,.9);
      --info:  #5b9eff; --info-90:  rgba(91,158,255,.9);
      --success: var(--ok); --success-90: var(--ok-90);
      --danger:  var(--err); --danger-90: var(--err-90);
      --warning: var(--warn);

      /* ════════════════════════════════════════════════════════════════════
         TIER 2 — SEMANTIC TOKENS BY USAGE (DESIGN_SYSTEM.md §2)
         The prefix says WHERE the token is allowed. Contrast GUARANTEED by definition.
         Rule: `color:` accepts ONLY --text-*; fills = --surface-*/--fill-*;
         borders = --border-*. Never color:var(--accent|--bg|primitive), never opacity on text.
         ════════════════════════════════════════════════════════════════════ */
      /* Surfaces (zone backgrounds) */
      --surface-sunken: var(--bg);          /* page background, the lowest */
      --surface-overlay: var(--surface);    /* popovers, menus, modals */
      --surface-raised: var(--surface-2);   /* elevated cards, hover rows */
      /* Text (color: — ALL AA ≥4.5:1 by construction, dark+light) */
      --text-primary: var(--text);
      --text-muted: var(--muted);
      --text-placeholder: var(--placeholder);
      --text-accent: var(--accent-text);    /* readable accent (PM-23) — NEVER var(--accent) in color */
      --text-on-accent: var(--on-accent);   /* text on an accent fill */
      /* dark: err pulled to a lighter red-400 (pure #ef4444 haloed/vibrated on dark — Jerry
         2026-05-26) → comfort + luminance balance with teal/orange/blue. ok/warn/info fine as is. */
      --text-ok: var(--ok); --text-warn: #fb923c; --text-err: #f87171; --text-info: #7db0ff;  /* warn/err/info lightened vs the raw tone: AA on the 22% tint badge at small sizes (axe 2026-06-16), like the light theme. */
      /* Text ON a status fill (PM-24) — dark: light fills → near-black works for all 4 */
      --text-on-ok: var(--on-accent); --text-on-warn: var(--on-accent);
      --text-on-err: var(--on-accent); --text-on-info: var(--on-accent);
      /* Fills (background/fill of active/brand elements — 3:1 graphic OK) */
      --fill-accent: var(--accent);
      --fill-accent-hover: var(--accent-hover);
      --fill-accent-pressed: var(--accent-pressed);
      --fill-accent-subtle: var(--accent-tint);
      --fill-ok: var(--ok); --fill-warn: var(--warn); --fill-err: var(--err); --fill-info: var(--info);
      /* Borders */
      --border-accent: var(--accent);
      --border-focus: var(--accent);
      /* Interaction overlays */
      --state-hover: var(--hover);
      --state-active: var(--active);

      /* Radii — modular t-shirt scale aligned with Tailwind v4 (2/4/6/8/12/16/24/full).
         10px and 14px are rungs in NO major DS → consolidated (10→lg, 14→xl).
         --radius-xs = micro-details (indicators/handles), --radius-md = short chrome. */
      --radius-xs: 2px; --radius-sm: 4px; --radius-md: 6px; --radius: 8px;
      --radius-lg: 12px; --radius-xl: 16px; --radius-2xl: 24px; --radius-full: 9999px;

      /* Shadow elevation scale — 2-layer (key + ambient) = SOTA Material 3/Open Props/Comeau
         (elevation-v2 2026-06-13). AMBIENT layer = offset/blur ≈ old single layer (weight
         preserved) + tighter KEY layer for definition. Reduced ambient opacity to
         offset the stacking of the 2 layers (perceived weight ≈ unchanged, plus depth). */
      --shadow-xs: 0 1px 2px rgba(0,0,0,0.18), 0 0 1px rgba(0,0,0,0.14);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.20), 0 1px 2px rgba(0,0,0,0.16);
      --shadow-md: 0 4px 12px rgba(0,0,0,0.24), 0 2px 4px rgba(0,0,0,0.18);
      --shadow-lg: 0 12px 24px rgba(0,0,0,0.28), 0 4px 8px rgba(0,0,0,0.20);
      --shadow-xl: 0 24px 48px rgba(0,0,0,0.36), 0 8px 16px rgba(0,0,0,0.24);
      --shadow-2xl: 0 24px 56px rgba(0,0,0,0.42), 0 8px 20px rgba(0,0,0,0.26);
      --shadow: var(--shadow-md);          /* legacy */
      --shadow-strong: var(--shadow-lg);   /* legacy */
      --scrim: rgba(0,0,0,0.65);           /* modal/overlay backdrop veil */

      /* Interactive states */
      --hover: rgba(255,255,255,.04); --hover-strong: rgba(255,255,255,.08);
      --active: rgba(255,255,255,.10);
      --dt-row-bg: rgba(255,255,255,.02);

      /* Spacing scale (4-base) — index = value/4 (consistent, no more 7/9/11 gaps…) */
      --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
      --space-5: 20px; --space-6: 24px; --space-7: 28px; --space-8: 32px;
      --space-9: 36px; --space-10: 40px; --space-12: 48px; --space-16: 64px;

      /* Type scale (Tier-1) — --fs-* prefix (font-size) because --text-* = text COLORS
         (--text-strong/-primary/-muted/-ok…). Tokenized scale of the sizes actually
         used in the framework (≈25 ad-hoc values → 10 steps); --fs-base = the reference
         body of components. Progressive ADOPTION (literals migrate to these
         tokens, cf CALIBRATION_ROADMAP) — the DEFINITION is additive, zero visual impact. */
      --fs-3xs: .62rem;  --fs-2xs: .68rem;  --fs-xs: .72rem; --fs-sm: .78rem; --fs-md: .82rem;
      --fs-base: .9rem;  --fs-lg: 1rem;   --fs-xl: 1.125rem; --fs-2xl: 1.25rem;
      --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      --mono: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
      --fs-3xl: 1.5rem;  --fs-4xl: 2rem;
      /* ── HYBRID FLUID SYSTEM (SOTA research 2026-06-14, Utopia method + a11y) ──────────────
         The FIXED scale above stays THE default for component spacing (predictable rhythm,
         grid alignment — like Tailwind/Material/Apple). These FLUID tokens are used ONLY for
         GLOBAL/editorial values (page padding, section gaps, display/hero type) where
         fluidity genuinely helps. Interpolation between 360px and 1440px viewport.
         ⚠️ a11y (WCAG 1.4.4): each clamp has a `rem` term (the y-intercept) → browser zoom
         always enlarges the text (a pure `vw` fluid would break zoom). min/max in rem too. */
      --fs-display-sm: clamp(1.75rem, 1.5rem + 1.111vw, 2.5rem);   /* 28 → 40px */
      --fs-display:    clamp(2rem, 1.5rem + 2.222vw, 3.5rem);      /* 32 → 56px */
      --fs-display-lg: clamp(2.5rem, 1.667rem + 3.704vw, 5rem);    /* 40 → 80px */
      --fs-display-xl: clamp(3.5rem, 1rem + 12.5vw, 8.25rem);      /* 56 → 132px, marquee tier — zoom-safe (rem bounds + rem term) */
      --space-fluid-sm: clamp(0.5rem, 0.417rem + 0.37vw, 0.75rem); /* 8 → 12px */
      --space-fluid-md: clamp(1rem, 0.833rem + 0.741vw, 1.5rem);   /* 16 → 24px */
      --space-fluid-lg: clamp(1.5rem, 1.167rem + 1.481vw, 2.5rem); /* 24 → 40px */
      --space-fluid-xl: clamp(2rem, 1.333rem + 2.963vw, 4rem);     /* 32 → 64px */
      --vx-page-pad: var(--space-fluid-lg);   /* canonical page padding, fluid (opt-in via .vx-page--pad) */
      /* Semantic line-heights */
      --lh-tight: 1.15; --lh-snug: 1.3; --lh-normal: 1.5; --lh-relaxed: 1.7;
    }
    [data-theme="light"] {
      color-scheme: light;   /* light canvas/overscroll/scrollbars in light theme (cf dark) */
      /* LIGHT grays decoupled from the accent (rework 2026-06-11): same fixed cool hue (--vx-gray-hue 264,
         inherited from :root), LOW chroma (a light that's too blue = clinical; just a cool touch like vortex-share
         #f4f5f7). text-strong = neutral black (anchor). */
      --vx-gray-tint: 0.008;
      --bg:        oklch(0.970 var(--vx-gray-tint) var(--vx-gray-hue));
      --surface:   oklch(1.000 0 var(--vx-gray-hue));     /* pure white for cards (crisp) */
      --surface-2: oklch(0.985 var(--vx-gray-tint) var(--vx-gray-hue));
      --surface-3: oklch(0.957 var(--vx-gray-tint) var(--vx-gray-hue));
      --panel: var(--surface);
      --border:        oklch(0.30 var(--vx-gray-tint) var(--vx-gray-hue) / 0.12);
      --border-strong: oklch(0.25 var(--vx-gray-tint) var(--vx-gray-hue) / 0.20);
      --text:  oklch(0.227 0.014 var(--vx-gray-hue));
      --muted: oklch(0.505 0.018 var(--vx-gray-hue));
      --text-strong: #000000;
      --placeholder: oklch(0.525 0.016 var(--vx-gray-hue));  /* AA ≥4.5 on --bg/surface */

      /* Light brand accent — DERIVES from --vx-accent (formulas reproducing light amber + following any custom). */
      --accent: var(--vx-accent);
      --accent-hover:   oklch(from var(--vx-accent) calc(l - 0.02)  calc(c + 0.037) calc(h - 17.2));
      --accent-pressed: oklch(from var(--vx-accent) calc(l - 0.111) calc(c - 0.011) calc(h - 9.3));
      --accent-ghost: oklch(from var(--vx-accent) l c h / 0.08);
      --accent-tint:  oklch(from var(--vx-accent) l c h / 0.14);
      --on-accent: oklch(from var(--vx-accent) clamp(0.16, (0.62 - l) * 999, 0.97) 0.04 h);  /* auto-derived (V142) — see dark block */
      /* Accent as TEXT (PM-23): light amber in color: fails AA → darkened (AA ~4.9:1), stays brand. */
      --accent-text: oklch(from var(--vx-accent) calc(l - 0.112) calc(c - 0.036) calc(h + 8.1));
      /* Text on status fill (PM-24) — light: err/info are DARK fills → white text AA
         (near-black fails: err 3.76, info 3.5). ok/warn stay mid → near-black via dark block. */
      --text-on-err: #ffffff; --text-on-info: #ffffff;
      /* Status as TEXT (PM-24) — light: the `--ok/warn/err/info` fills in `color:` fail AA on
         `--bg` (ok 3.0, warn 2.68, err 4.42). Darkened AA versions (≥4.6:1) for TEXT usage.
         In dark, `--text-ok/...` = `--ok/...` (already AA, bright on dark background — via the dark block). */
      --text-ok: #166534; --text-warn: #9a3412; --text-err: #b91c1c; --text-info: #1d4ed8;  /* ok = green-800, warn = orange-800: AA light EVEN on a 22% tonal background (badge — axe 2026-06-05, green-700 scored 3.88 there) */

      --ok:    #16a34a; --ok-90:    rgba(22,163,74,.9);
      --warn:  #ea580c; --warn-90:  rgba(234,88,12,.9);  /* orange-600 (fills) — distinct from the amber accent */
      --err:   #dc2626; --err-90:   rgba(220,38,38,.9);
      --info:  #2563eb; --info-90:  rgba(37,99,235,.9);
      --success: var(--ok); --danger: var(--err); --warning: var(--warn);

      --shadow-xs: 0 1px 2px rgba(0,0,0,0.05), 0 0 1px rgba(0,0,0,0.04);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.05);
      --shadow-md: 0 4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.06);
      --shadow-lg: 0 12px 24px rgba(0,0,0,0.10), 0 4px 8px rgba(0,0,0,0.07);
      --shadow-xl: 0 24px 48px rgba(0,0,0,0.14), 0 8px 16px rgba(0,0,0,0.09);
      --shadow-2xl: 0 24px 56px rgba(0,0,0,0.16), 0 8px 20px rgba(0,0,0,0.10);
      --shadow: var(--shadow-md);
      --shadow-strong: var(--shadow-lg);
      --scrim: rgba(0,0,0,0.45);           /* modal/overlay backdrop veil (light) */

      --hover: rgba(0,0,0,.04); --hover-strong: rgba(0,0,0,.06);
      --active: rgba(0,0,0,.10);
      --dt-row-bg: rgba(0,0,0,.02);
    }

    /* V95 — Per-hue color scales (Radix Colors inspired) — for gradients,
       background variants, and subtle accents. Complements the single brand
       `--accent` token. Dark mode: progressively saturated tones. */
    :root, [data-theme="dark"] {
      /* Orange (brand) — dark to vivid */
      --orange-50:  #2a1505;  --orange-100: #3a1d08;  --orange-200: #5a2d0d;
      --orange-300: #8a4413;  --orange-400: #c46019;  --orange-500: #F59E0B;
      --orange-600: #fbbf24;  --orange-700: #fcd34d;  --orange-800: #fde68a;
      --orange-900: #fef3c7;
      /* Green (ok) */
      --green-50:   #052e22;  --green-100: #073624;  --green-200: #0a5240;
      --green-300: #0f7960;  --green-400: #18a988;  --green-500: #3ad9b3;
      --green-600: #5ee8c4;  --green-700: #88f2d6;  --green-800: #b8f9e6;
      --green-900: #e0fdf3;
      /* Red (err) */
      --red-50:     #2c0c0c;  --red-100: #3c1010;  --red-200: #5a1818;
      --red-300: #882020;  --red-400: #c52828;  --red-500: #ef4444;
      --red-600: #f87171;  --red-700: #fca5a5;  --red-800: #fecaca;
      --red-900: #fee2e2;
      /* Blue (info) */
      --blue-50:    #0c1a2e;  --blue-100: #102237;  --blue-200: #1a345a;
      --blue-300: #2952a0;  --blue-400: #3d78d6;  --blue-500: #5b9eff;
      --blue-600: #80b3ff;  --blue-700: #a4c7ff;  --blue-800: #c7dbff;
      --blue-900: #e6efff;

      /* V140 — 12-step OKLCH ACCENT scale (Radix-style, semantic by usage), GENERIC.
         SINGLE SOURCE = --vx-accent (default Vortex amber). The other 11 steps DERIVE via
         relative color syntax `oklch(from var(--vx-accent) L C h)`: fixed L/C (tuned curve, AA
         guaranteed), HUE inherited from the source → an app sets `--vx-accent: oklch(.55 .2 250)` and
         the WHOLE scale turns blue. vx-circadian/theming = move --vx-accent, the rest follows.
         1-2 backgrounds · 3-5 components · 6-8 borders · 9 brand(=source) · 10 hover · 11-12 text. */
      --vx-accent: oklch(0.769 0.165 70.08);   /* ⭐ default amber — ONE app overrides THIS line */
      --accent-1:oklch(from var(--vx-accent) 0.175 0.018 h); --accent-2:oklch(from var(--vx-accent) 0.205 0.030 h); --accent-3:oklch(from var(--vx-accent) 0.255 0.050 h); --accent-4:oklch(from var(--vx-accent) 0.300 0.070 h);
      --accent-5:oklch(from var(--vx-accent) 0.350 0.088 h); --accent-6:oklch(from var(--vx-accent) 0.405 0.100 h); --accent-7:oklch(from var(--vx-accent) 0.470 0.120 h); --accent-8:oklch(from var(--vx-accent) 0.555 0.140 h);
      --accent-9: var(--vx-accent); --accent-10:oklch(from var(--vx-accent) 0.819 0.152 h); --accent-11:oklch(from var(--vx-accent) 0.800 0.135 h); --accent-12:oklch(from var(--vx-accent) 0.930 0.060 h);
      /* CATEGORICAL data-viz palette (--chart-1..6, vortex-chart/sparkline): hues rotated
         from the accent (relative color) → series consistent with ANY theme, dark/light. */
      --chart-1: var(--vx-accent);
      --chart-2: oklch(from var(--vx-accent) 0.72 0.15 calc(h + 60));
      --chart-3: oklch(from var(--vx-accent) 0.75 0.13 calc(h + 150));
      --chart-4: oklch(from var(--vx-accent) 0.70 0.14 calc(h + 215));
      --chart-5: oklch(from var(--vx-accent) 0.78 0.12 calc(h + 285));
      --chart-6: oklch(from var(--vx-accent) 0.66 0.13 calc(h - 45));
      --green-1:oklch(0.175 0.018 165); --green-2:oklch(0.205 0.030 165); --green-3:oklch(0.255 0.050 165); --green-4:oklch(0.300 0.070 165);
      --green-5:oklch(0.350 0.088 165); --green-6:oklch(0.405 0.100 165); --green-7:oklch(0.470 0.120 165); --green-8:oklch(0.555 0.140 165);
      --green-9:oklch(0.796 0.140 165); --green-10:oklch(0.846 0.129 165); --green-11:oklch(0.826 0.115 165); --green-12:oklch(0.930 0.060 165);
      --red-1:oklch(0.175 0.018 27); --red-2:oklch(0.205 0.030 27); --red-3:oklch(0.255 0.050 27); --red-4:oklch(0.300 0.070 27);
      --red-5:oklch(0.350 0.088 27); --red-6:oklch(0.405 0.100 27); --red-7:oklch(0.470 0.120 27); --red-8:oklch(0.555 0.140 27);
      --red-9:oklch(0.637 0.208 27); --red-10:oklch(0.687 0.191 27); --red-11:oklch(0.800 0.171 27); --red-12:oklch(0.930 0.060 27);
      --blue-1:oklch(0.175 0.018 250); --blue-2:oklch(0.205 0.030 250); --blue-3:oklch(0.255 0.050 250); --blue-4:oklch(0.300 0.070 250);
      --blue-5:oklch(0.350 0.088 250); --blue-6:oklch(0.405 0.100 250); --blue-7:oklch(0.470 0.120 250); --blue-8:oklch(0.555 0.140 250);
      --blue-9:oklch(0.699 0.159 250); --blue-10:oklch(0.749 0.146 250); --blue-11:oklch(0.800 0.130 250); --blue-12:oklch(0.930 0.060 250);
    }
    [data-theme="light"] {
      --orange-50:  #fef3c7;  --orange-100: #fde68a;  --orange-200: #fcd34d;
      --orange-300: #fbbf24;  --orange-400: #F59E0B;  --orange-500: #d97706;
      --orange-600: #b45309;  --orange-700: #92400e;  --orange-800: #78350f;
      --orange-900: #451a03;
      --green-50:   #e0fdf3;  --green-100: #b8f9e6;  --green-200: #88f2d6;
      --green-300: #5ee8c4;  --green-400: #3ad9b3;  --green-500: #18a988;
      --green-600: #0f7960;  --green-700: #0a5240;  --green-800: #073624;
      --green-900: #052e22;
      --red-50:     #fee2e2;  --red-100: #fecaca;  --red-200: #fca5a5;
      --red-300: #f87171;  --red-400: #ef4444;  --red-500: #dc2626;
      --red-600: #b91c1c;  --red-700: #991b1b;  --red-800: #7f1d1d;
      --red-900: #450a0a;
      --blue-50:    #e6efff;  --blue-100: #c7dbff;  --blue-200: #a4c7ff;
      --blue-300: #80b3ff;  --blue-400: #5b9eff;  --blue-500: #2563eb;
      --blue-600: #1d4ed8;  --blue-700: #1e40af;  --blue-800: #1e3a8a;
      --blue-900: #172554;

      /* V140 — 12-step OKLCH ACCENT scale (light), GENERIC — derives from --vx-accent. */
      --vx-accent: oklch(0.666 0.157 58.3);   /* ⭐ default light amber (#d97706) — overridden per app */
      --accent-1:oklch(from var(--vx-accent) 0.992 0.012 h); --accent-2:oklch(from var(--vx-accent) 0.972 0.022 h); --accent-3:oklch(from var(--vx-accent) 0.940 0.040 h); --accent-4:oklch(from var(--vx-accent) 0.910 0.060 h);
      --accent-5:oklch(from var(--vx-accent) 0.880 0.080 h); --accent-6:oklch(from var(--vx-accent) 0.840 0.092 h); --accent-7:oklch(from var(--vx-accent) 0.790 0.110 h); --accent-8:oklch(from var(--vx-accent) 0.700 0.130 h);
      --accent-9: var(--vx-accent); --accent-10:oklch(from var(--vx-accent) 0.621 0.151 h); --accent-11:oklch(from var(--vx-accent) 0.520 0.141 h); --accent-12:oklch(from var(--vx-accent) 0.350 0.075 h);
      --green-1:oklch(0.992 0.012 165); --green-2:oklch(0.972 0.022 165); --green-3:oklch(0.940 0.040 165); --green-4:oklch(0.910 0.060 165);
      --green-5:oklch(0.880 0.080 165); --green-6:oklch(0.840 0.092 165); --green-7:oklch(0.790 0.110 165); --green-8:oklch(0.700 0.130 165);
      --green-9:oklch(0.627 0.170 165); --green-10:oklch(0.582 0.163 165); --green-11:oklch(0.517 0.153 165); --green-12:oklch(0.350 0.075 165);
      --red-1:oklch(0.992 0.012 27); --red-2:oklch(0.972 0.022 27); --red-3:oklch(0.940 0.040 27); --red-4:oklch(0.910 0.060 27);
      --red-5:oklch(0.880 0.080 27); --red-6:oklch(0.840 0.092 27); --red-7:oklch(0.790 0.110 27); --red-8:oklch(0.700 0.130 27);
      --red-9:oklch(0.577 0.215 27); --red-10:oklch(0.532 0.206 27); --red-11:oklch(0.467 0.194 27); --red-12:oklch(0.350 0.075 27);
      --blue-1:oklch(0.992 0.012 250); --blue-2:oklch(0.972 0.022 250); --blue-3:oklch(0.940 0.040 250); --blue-4:oklch(0.910 0.060 250);
      --blue-5:oklch(0.880 0.080 250); --blue-6:oklch(0.840 0.092 250); --blue-7:oklch(0.790 0.110 250); --blue-8:oklch(0.700 0.130 250);
      --blue-9:oklch(0.546 0.215 250); --blue-10:oklch(0.501 0.206 250); --blue-11:oklch(0.436 0.194 250); --blue-12:oklch(0.350 0.075 250);
    }

    /* V83 — Standard breakpoints (documented; CSS Custom Props can't be used
       in @media, but documented here for cross-app consistency):
         --bp-sm  : 480px  (small phone)
         --bp-md  : 640px  (large phone / phablet)
         --bp-lg  : 1024px (tablet)
         --bp-xl  : 1280px (desktop)
         --bp-2xl : 1536px (wide desktop)
       Vortex convention: mobile-first, `@media (min-width: 640px)` for scale-up. */

    /* V93 — Animation tokens (durations + easings) — Open Props inspired.
       Use case : transition: opacity var(--dur-base) var(--ease-snap); */
    :root {
      --ease-linear:  linear;
      --ease-in:      cubic-bezier(.4, 0, 1, 1);
      --ease-out:     cubic-bezier(0, 0, .2, 1);
      --ease-in-out:  cubic-bezier(.4, 0, .2, 1);
      --ease-snap:    cubic-bezier(.2, .8, .2, 1);    /* Vortex signature (use everywhere) */
      --ease-spring:  cubic-bezier(.34, 1.56, .64, 1); /* slight fun overshoot */
      --ease-sheet:   cubic-bezier(.32, .72, 0, 1);    /* iOS slide — mobile sheets entrance */
      --ease-accel:   cubic-bezier(.3, 0, .8, .15);    /* M3 emphasized-accelerate — fast sheet exit */
      --dur-instant:  100ms;
      --dur-fast:     150ms;
      --dur-snappy:   220ms;   /* intermediate step (sheets, popovers, micro-transitions) */
      --dur-base:     300ms;
      --dur-slow:     600ms;
      --dur-slower:   900ms;

      /* ── Motion regimes (best-of-breed Carbon 2026) ───────────────────────────
         Two regimes: PRODUCTIVE (default, task-focus, discreet/fast) vs EXPRESSIVE
         (reserved for standout moments: onboarding, celebration, hero), toggled via
         [data-vx-motion-mode="expressive"] on any ancestor.
         Components use the SEMANTIC ROLES (entrance/exit/standard), never
         the raw beziers → the asymmetry AND the regime are driven from a single point.
         Carbon doctrine: the entrance DECELERATES (the element arrives and settles), the exit
         ACCELERATES (permanent departure) — exit shorter than entrance. An element that stays
         visible (side panel, on-place) → `standard`. Defaults = Vortex signature (snap)
         so adopting these tokens does NOT change the productive render; only expressive differs. */
      --ease-entrance: var(--ease-out);    /* decelerates: the element arrives and settles */
      --ease-exit:     var(--ease-accel);  /* accelerates: the element leaves (permanent exit) */
      --ease-standard: var(--ease-snap);   /* in-place motion (stays visible) — Vortex signature */
      --dur-entrance:  var(--dur-snappy);  /* 220ms */
      --dur-exit:      var(--dur-fast);    /* 150ms — exit shorter than entrance (Carbon) */
    }

    /* EXPRESSIVE regime (opt-in): Carbon "expressive" easings + lengthened durations for
       important moments. Alters ONLY the semantic roles (productive stays the global default). */
    [data-vx-motion-mode="expressive"] {
      --ease-standard: cubic-bezier(.4, .14, .3, 1);   /* Carbon expressive standard */
      --ease-entrance: cubic-bezier(0, 0, .3, 1);      /* expressive entrance (decelerates more) */
      --ease-exit:     cubic-bezier(.4, .14, 1, 1);    /* expressive exit (accelerates) */
      --dur-entrance:  var(--dur-base);    /* 300ms */
      --dur-exit:      var(--dur-snappy);  /* 220ms */
    }

    /* ── Bidirectional LTR/RTL foundation (2026) ───────────────────────────────
       Component CSS must be 100% LOGICAL (margin-inline-*, inset-inline-*,
       text-align:start/end…) → direction inherits via `dir` on a container, nothing to
       do on the component side. These 3 helpers cover the cases that RESIST the logical approach. */
    :root { --vx-dir: 1; }                              /* signed multiplier (translateX, JS scroll, directional gradients) */
    :root:dir(rtl), [dir="rtl"] { --vx-dir: -1; }       /* primary :dir() (supported 2026) + [dir=rtl] fallback (old WebView) */
    /* OPT-IN mirroring: only the marked directional icons/elements (chevrons, arrows,
       progress, carousel) — NEVER in bulk (otherwise we flip checkmarks/play/logos). */
    .vx-icon--dir:dir(rtl), [dir="rtl"] .vx-icon--dir { transform: scaleX(-1); }
    /* bidi isolation: apply on any dynamic user value (name, currency amount,
       search) to prevent bidi contamination of the surrounding sentence (UBA). */
    .vx-bidi { unicode-bidi: isolate; }
    /* Code, keyboard shortcuts, snippet blocks = ALWAYS LTR (code in RTL is unreadable:
       token order/indentation reverses). + isolation so it doesn't contaminate the sentence. */
    code, pre, kbd, samp, .lab-code, .vx-code, .vx-codeblock { direction: ltr; unicode-bidi: isolate; }
    /* ⚠ RTL hunt RECALIBRATION 2026-06-04: `isolate` ALONE is NOT enough for a VALUE —
       it protects the surrounding sentence but the INSIDE inherits direction:rtl → "150 €" rendered
       "€ 150", "0 / 200" → "200 / 0", "-28%" → "28%-" (measured on 60+ occurrences).
       The right tool = `plaintext`: direction AUTO-detected from content (neutral/digits → LTR),
       and plaintext implies isolation. Applies to ANY formatted number+unit value. */
    .vx-num, .vx-tabular, [data-vx-num] { unicode-bidi: plaintext; }
    .vx-sl-value, .vx-stat-card-number, .vx-price, .vx-price-amount { unicode-bidi: plaintext; }
    /* Phone (E.164) + keyboard shortcuts = ALWAYS LTR. */
    .vx-tel-num, .vx-menu-sc, .vx-mbar-sc { direction: ltr; unicode-bidi: isolate; }
    /* USER prose (comments, free content): per-PARAGRAPH direction auto-detected
       (first strong character — WhatsApp/Telegram pattern) → a French comment keeps its
       end-of-sentence punctuation even in an RTL app, an Arabic comment aligns RTL. */
    .vx-plaintext { unicode-bidi: plaintext; }

    /* V91 — Layout utilities */
    .vx-grid-auto-fit {
      display: grid;
      gap: var(--space-3, 12px);
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    }
    .vx-grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
    .vx-grid-auto-fit-lg { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
    .vx-flex          { display: flex; }
    .vx-flex-col      { display: flex; flex-direction: column; }
    .vx-flex-wrap     { flex-wrap: wrap; }
    .vx-items-center  { align-items: center; }
    .vx-items-start   { align-items: flex-start; }
    .vx-items-end     { align-items: flex-end; }
    .vx-justify-between { justify-content: space-between; }
    .vx-justify-center  { justify-content: center; }
    .vx-justify-end     { justify-content: end; }
    .vx-flex-1        { flex: 1; min-width: 0; }
    .vx-shrink-0      { flex-shrink: 0; }
    /* ── ONE-PASS responsive primitives (audit 2026-06-13): make the correct = the default ──
       A flex child's min-content overflows by default (min-width:auto). We neutralize it for
       EVERY direct flex child → end of the "flex-child overflow" class (demos +356px bug). */
    .vx-flex > *, .vx-flex-col > * { min-width: 0; min-height: 0; }
    /* Canonical PAGE: any app_shell block override puts .vx-page on its root →
       flex:1 + min-inline-size:0 (otherwise body{display:flex} stretches it to its min-content = SILENT
       desktop overflow). The --full variant = full width, default = bounded + centered.
       NB: NEVER write the literal block tag in a comment — the Django parser
       reads it anyway (= "block app_shell appears more than once" → whole site in 500). */
    /* ⚠️ min-block-size:0 (NOT 100dvh): body is height:100dvh + overflow:hidden and flex → a flex
       child has min-height:AUTO by default = it GROWS to its content (>viewport) and its overflow-y:auto
       NEVER scrolls while body clips → NON-SCROLLABLE PAGE ("Spotify demo" bug Jerry 2026-06-14).
       The canonical flexbox fix = min-block-size:0 → .vx-page stays bounded to the viewport (stretch) and scrolls.
       Short content: the child stretches to 100dvh (fills the screen); long content: it scrolls. */
    .vx-page { flex: 1; min-inline-size: 0; min-block-size: 0; block-size: 100dvh; overflow-y: auto; }
    /* ── FLUID layer (hybrid, opt-in): display titles + global padding/gaps that scale smoothly ── */
    .vx-page--pad { padding-inline: var(--vx-page-pad); padding-block: var(--space-fluid-md); }
    .vx-display    { font-size: var(--fs-display);    line-height: var(--lh-tight); font-weight: 800; letter-spacing: -.01em; margin: 0; }
    .vx-display-sm { font-size: var(--fs-display-sm); line-height: var(--lh-tight); font-weight: 800; letter-spacing: -.01em; margin: 0; }
    .vx-display-lg { font-size: var(--fs-display-lg); line-height: var(--lh-tight); font-weight: 900; letter-spacing: -.015em; margin: 0; }
    .vx-display-xl { font-size: var(--fs-display-xl); line-height: var(--lh-tight); font-weight: 900; letter-spacing: -.02em; margin: 0; }
    .vx-section-gap { display: flex; flex-direction: column; gap: var(--space-fluid-lg); }
    .vx-page--full { max-inline-size: none; }
    .vx-page--prose { max-inline-size: 72ch; margin-inline: auto; }
    /* WRAP/SCROLL/SHRINK FRAMEWORK in 1 class (skill vx-responsive) — instead of recoding by hand:
       WRAP = action bars/pills that wrap onto new lines + 44px target; SCROLL = a touch
       horizontal-scroll row with scroll-snap; SHRINK = auto-fit grid that shrinks without overflow. */
    .vx-toolbar-wrap { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
    .vx-rail-scroll  { display: flex; gap: var(--space-3); overflow-x: auto; overflow-y: clip;
                       /* pan-x pan-y (NOT bare pan-x): bare pan-x disables vertical scroll for touches on the
                          rail → page frozen. Both → horizontal scrolls the rail, vertical scrolls the page. */
                       scroll-snap-type: x proximity; touch-action: pan-x pan-y; overscroll-behavior-inline: contain;
                       scrollbar-width: thin; -webkit-overflow-scrolling: touch; }
    .vx-rail-scroll > * { flex: 0 0 auto; scroll-snap-align: start; }
    /* desktop drag-to-scroll (VortexRail, F1): during the grab, no text selection and no snap */
    .vx-rail-grabbing { cursor: grabbing; user-select: none; scroll-snap-type: none; }
    .vx-grid-shrink  { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--vx-grid-min, 280px)), 1fr)); }
    @media (pointer: coarse) { .vx-toolbar-wrap > button, .vx-toolbar-wrap > a { min-block-size: 44px; } }
    /* Visually-hidden (readable by screen readers, invisible to the eye) — WCAG pattern */
    .vx-sr-only {
      position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
    }

    /* Turbo cache: during the cached preview (html[data-turbo-preview]), hide LIVE values (balance,
       counter, real-time price) marked [data-vx-live] → never show stale data; visibility
       (not display) = zero CLS, the fresh value reappears as soon as the preview ends. App convention. */
    html[data-turbo-preview] [data-vx-live] { visibility: hidden !important; }

    /* V92 — Spacing utilities (4-base scale, Tailwind-inspired) */
    .vx-p-0 { padding: 0; }
    .vx-p-1 { padding: 4px; }   .vx-p-2 { padding: 8px; }   .vx-p-3 { padding: 12px; }
    .vx-p-4 { padding: 16px; }  .vx-p-5 { padding: 20px; }  .vx-p-6 { padding: 24px; }
    .vx-p-8 { padding: 32px; }
    .vx-px-1 { padding-inline-start: 4px; padding-inline-end: 4px; }
    .vx-px-2 { padding-inline-start: 8px; padding-inline-end: 8px; }
    .vx-px-3 { padding-inline-start: 12px; padding-inline-end: 12px; }
    .vx-px-4 { padding-inline-start: 16px; padding-inline-end: 16px; }
    .vx-py-1 { padding-top: 4px; padding-bottom: 4px; }
    .vx-py-2 { padding-top: 8px; padding-bottom: 8px; }
    .vx-py-3 { padding-top: 12px; padding-bottom: 12px; }
    .vx-py-4 { padding-top: 16px; padding-bottom: 16px; }
    .vx-m-0 { margin: 0; }
    .vx-m-1 { margin: 4px; }    .vx-m-2 { margin: 8px; }    .vx-m-3 { margin: 12px; }
    .vx-m-4 { margin: 16px; }   .vx-m-5 { margin: 20px; }   .vx-m-6 { margin: 24px; }
    .vx-mt-1 { margin-top: 4px; }  .vx-mt-2 { margin-top: 8px; }  .vx-mt-3 { margin-top: 12px; }
    .vx-mt-4 { margin-top: 16px; } .vx-mt-6 { margin-top: 24px; } .vx-mt-8 { margin-top: 32px; }
    .vx-mb-1 { margin-bottom: 4px; }  .vx-mb-2 { margin-bottom: 8px; }  .vx-mb-3 { margin-bottom: 12px; }
    .vx-mb-4 { margin-bottom: 16px; } .vx-mb-6 { margin-bottom: 24px; } .vx-mb-8 { margin-bottom: 32px; }
    .vx-gap-1 { gap: 4px; }   .vx-gap-2 { gap: 8px; }   .vx-gap-3 { gap: 12px; }
    .vx-gap-4 { gap: 16px; }  .vx-gap-6 { gap: 24px; }  .vx-gap-8 { gap: 32px; }
    /* Text alignment */
    .vx-text-center { text-align: center; }
    .vx-text-left   { text-align: start; }
    .vx-text-right  { text-align: end; }
    /* Visibility helpers — responsive */
    @media (max-width: 640px) {
      .vx-hide-mobile { display: none !important; }
    }
    @media (min-width: 641px) and (max-width: 1024px) {
      .vx-hide-tablet { display: none !important; }
    }
    @media (min-width: 1025px) {
      .vx-hide-desktop { display: none !important; }
    }
    .vx-show-mobile  { display: none; }
    .vx-show-tablet  { display: none; }
    .vx-show-desktop { display: none; }
    @media (max-width: 640px)  { .vx-show-mobile  { display: block !important; } }
    @media (min-width: 641px) and (max-width: 1024px) { .vx-show-tablet  { display: block !important; } }
    @media (min-width: 1025px) { .vx-show-desktop { display: block !important; } }
    /* tinted html = no stray light/dark band if the mobile address bar
       leaves a sliver under the body (dvh) — Jerry 2026-05-26 */
    /* -webkit-tap-highlight-color IS inherited → set on html, it removes the NATIVE blue/gray
       flash (Android/iOS) on ALL tapped elements: inputs, selects, picker cells, etc.
       Vortex provides its own feedback (accent border on focus, :active states) → the native flash
       is redundant and off-brand (Jerry 2026-06-03). */
    html { background: var(--bg); -webkit-tap-highlight-color: transparent; }
    
/* reset */
*,*::before,*::after{box-sizing:border-box;}
html{background:var(--bg);-webkit-text-size-adjust:100%;}
.vx-sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}
/* Element-level rules: they MUST stay in `base`, below the components layer — as plain element
   selectors in the theme layer they would outrank every component rule (that is exactly how
   `img{height:auto}` once beat `.vx-pc-img{height:100%}` and ragged the card grid). */
img{max-inline-size:100%;height:auto;}
a{color:inherit;}
/* NB: [hidden]{display:none!important} is NOT repeated here — it now lives in base.html's token
   block, which build_assets.py splices in above this file. One source, both surfaces. */
/* A <button> keeps the UA chrome (2px outset border on a ButtonFace background) unless the author
   states otherwise, while an <a> styled as the same control does not. That asymmetry is why the
   header's four sibling actions rendered as three different-looking controls. Icons-in-buttons opt
   into the app's own chrome via .vxt-ib; this only strips what the UA imposes. */
button{font:inherit;color:inherit;}

/* design system exports (@vxt-theme:export focus/buttons/tap-targets) */

    /* UNIFIED FOCUS SYSTEM (SOTA WCAG 2.2 / 2.4.13 + design-systems: a SINGLE ring, same color/
       width/offset/radius everywhere, tokenized = tunable in 1 place; research 2026-06-14). The outline
       AUTOMATICALLY follows the element's native border-radius → NO forced `border-radius` (which changed
       the shape on focus = removed defect). FIELDS (`.vx-field-shell`) use the SAME ring (consistency). */
    :root { --vx-focus-w: 2px; --vx-focus-color: var(--accent); --vx-focus-offset: 2px; --vx-focus-offset-inset: -2px; }
    a:focus-visible, button:focus-visible, [role="button"]:focus-visible,
    .nav-link:focus-visible, .nav-item:focus-visible, .btn:focus-visible,
    input:focus-visible, select:focus-visible, textarea:focus-visible,
    [tabindex]:focus-visible, summary:focus-visible {
      outline: var(--vx-focus-w) solid var(--vx-focus-color);
      outline-offset: var(--vx-focus-offset);
    }
    /* Programmatic focus targets (tabindex="-1": the Turbo nav-announce <main id=vx-main-content>
       and the skip-link target) are focused via JS so screen readers LAND on the new content —
       they're NEVER reached by keyboard. They must NOT show a focus ring: the <main>'s accent
       outline was peeking out as a full-width line just under the breadcrumb on every nav
       (Jerry/nexus "ligne accent" bug 2026-06-16 — transient = follows the focus). Real
       keyboard-focusable elements use tabindex="0" and keep their ring. */
    [tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; }
    /* Skip-link for keyboard navigation to the main content */
    .vx-skip-link {
      position: absolute; inset-inline-start: -9999px; top: 0;
      background: var(--accent); color: var(--text-on-accent);
      padding: 12px 16px; font-weight: 700; z-index: 1000;
      border-radius: 0 0 var(--radius-md) 0;
      display: inline-flex; align-items: center; min-height: 44px;  /* WCAG tap-target */
    }
    .vx-skip-link:focus { inset-inline-start: 0; }

    /* ── Buttons ──
       TONAL system (Jerry 2026-05-26): by default = very light accent background + accent TEXT
       + accent border (the accent as TEXT, vortex-share style — no more "yellow + black").
       `.btn.solid` = the true hero CTA (full amber, to reserve). `.warn`/`.danger` = same tonal
       tones on their semantic color → primary ≠ warn (collision resolved). */
    .btn {
      display: inline-flex; align-items: center; justify-content: center; gap: 7px;
      padding: 8px 15px; border-radius: var(--radius);
      background: color-mix(in srgb, var(--accent) 12%, transparent);
      color: var(--text-accent); font-weight: 600;
      letter-spacing: 0.1px; line-height: 1.1;
      border: 1px solid color-mix(in srgb, var(--accent) 36%, transparent);
      cursor: pointer; font-size: var(--fs-md); text-decoration: none;
      user-select: none; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
      /* SOTA timing (M3 standard easing): color/shadow ~140ms, transform snappier ~90ms */
      transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out),
                  box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-instant) var(--ease-out);
    }
    /* Tactile press: the button visibly DEPRESSES (scale-down) — the #1 tell of a quality button
       vs a "cheap" one (NN/g, Material 3 pressed state). Near-instant (60ms) so it feels physical. */
    .btn:active:not(:disabled) { transform: scale(0.97); transition-duration: 60ms; }
    /* `.btn-sm` = compact variant (widely used: lab, nexus, edge, asil…
       until now NOT defined → "small" buttons rendered at full size). */
    .btn.btn-sm { padding: 5px 10px; font-size: var(--fs-sm); gap: 4px; }
    .btn.btn-sm i[data-lucide], .btn.btn-sm .lucide { width: 15px; height: 15px; }
    .btn:hover {
      background: color-mix(in srgb, var(--accent) 22%, transparent);
      border-color: var(--accent);
    }
    /* Hero CTA: full accent (use sparingly, 1 per screen).
       SOTA depth: top inner highlight (light from above) + soft composite elevation
       (inset + comma = exempt from shadow-lint, c.f. shadow-token). Border slightly
       darker than the fill for definition. */
    .btn.solid {
      background: var(--accent); color: var(--on-accent);
      border-color: color-mix(in srgb, var(--accent) 80%, #000);
      box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 24%, transparent), var(--shadow-xs);
    }
    .btn.solid:hover {
      background: var(--accent-hover); border-color: color-mix(in srgb, var(--accent-hover) 80%, #000);
      box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 24%, transparent), var(--shadow-sm);
    }
    .btn.solid:active:not(:disabled) { box-shadow: inset 0 1px 2px color-mix(in srgb, #000 22%, transparent); }
    .btn.ghost {
      background: transparent; color: var(--text);
      border: 1px solid var(--border);
    }
    .btn.ghost:hover { background: var(--hover); }
    .btn.warn {
      background: color-mix(in srgb, var(--warn) 14%, transparent);
      color: var(--text-warn); border-color: color-mix(in srgb, var(--warn) 36%, transparent);
    }
    .btn.warn:hover { background: color-mix(in srgb, var(--warn) 24%, transparent); border-color: var(--warn); }
    /* Danger = SOLID deep crimson (hue 16, darker than any bright accent-red) →
       stays distinct from Primary even in a red theme (Jerry feedback 2026-06-15: "distinguish
       danger/warn/primary"). Destructive = full + visible (SOTA standard). */
    .btn.danger {
      background: oklch(0.52 0.205 16); color: #fff;
      border-color: oklch(0.45 0.2 16);
      box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent), var(--shadow-xs);
    }
    .btn.danger:hover { background: oklch(0.57 0.215 16); border-color: oklch(0.5 0.21 16); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent), var(--shadow-sm); }
    .btn.danger:active:not(:disabled) { box-shadow: inset 0 1px 2px color-mix(in srgb, #000 25%, transparent); }
    /* Aliases for common BEM variants (`.btn-primary`/`-danger`/`-success`) used by asil/edge/…:
       primary = full accent CTA (= .solid), danger/success = tonal semantic tones. Completes the
       BASE button system for ALL apps → cohesion + future simple apps (2026-06-12). */
    .btn.btn-primary {
      background: var(--accent); color: var(--on-accent);
      border-color: color-mix(in srgb, var(--accent) 80%, #000);
      box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 24%, transparent), var(--shadow-xs);
    }
    .btn.btn-primary:hover {
      background: var(--accent-hover); border-color: color-mix(in srgb, var(--accent-hover) 80%, #000);
      box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 24%, transparent), var(--shadow-sm);
    }
    .btn.btn-primary:active:not(:disabled) { box-shadow: inset 0 1px 2px color-mix(in srgb, #000 22%, transparent); }
    .btn.btn-danger { background: oklch(0.52 0.205 16); color: #fff; border-color: oklch(0.45 0.2 16); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent), var(--shadow-xs); }
    .btn.btn-danger:hover { background: oklch(0.57 0.215 16); border-color: oklch(0.5 0.21 16); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent), var(--shadow-sm); }
    .btn.btn-danger:active:not(:disabled) { box-shadow: inset 0 1px 2px color-mix(in srgb, #000 25%, transparent); }
    .btn.btn-success { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--text-ok); border-color: color-mix(in srgb, var(--ok) 36%, transparent); }
    .btn.btn-success:hover { background: color-mix(in srgb, var(--ok) 24%, transparent); border-color: var(--ok); }
    .btn:disabled { background: var(--hover); color: var(--muted); border-color: var(--border); cursor: not-allowed; opacity: .6; }
    /* ── Generic BUSY state (anti-double-submit) ──────────────────────────────
       Set on a <form> submit (global guard _shared_ux) OR via VortexBusy.set / aria-busy.
       Centered spinner + click neutralized; the label is visually hidden but `aria-busy="true"`
       announces it to screen readers. Spinner color per variant (--vx-btn-spinner). */
    .btn { --vx-btn-spinner: var(--text-accent); }
    .btn.solid  { --vx-btn-spinner: var(--on-accent); }
    .btn.ghost  { --vx-btn-spinner: var(--text); }
    .btn.warn   { --vx-btn-spinner: var(--text-warn); }
    .btn.danger { --vx-btn-spinner: #fff; }
    .btn[aria-busy="true"], .btn.vx-busy {
      position: relative; pointer-events: none; color: transparent !important;
    }
    .btn[aria-busy="true"]::after, .btn.vx-busy::after {
      content: ""; position: absolute; inset: 0; margin: auto;
      width: 1em; height: 1em; border-radius: 50%;
      border: 2px solid var(--vx-btn-spinner); border-top-color: transparent;
      animation: vx-btn-spin .6s linear infinite;
    }
    @keyframes vx-btn-spin { to { transform: rotate(360deg); } }
    @media (prefers-reduced-motion: reduce) {
      .btn[aria-busy="true"]::after, .btn.vx-busy::after { animation-duration: 1.4s; }
      .btn { transition-duration: 1ms; }
      .btn:active:not(:disabled) { transform: none; }
    }

    /* ── Tap-targets ≥44px on TOUCH screens (gate vortex-mobile, WCAG) ──
       Scoped to `pointer: coarse` → affects ONLY touch (desktop mouse = density intact).
       Guarantees tap comfort on all nav/action controls of the design system. */
    @media (pointer: coarse) {
      .vx-bc-link, .vx-bc-expand, .vx-tabs-header, .vx-dt-page-btn, .vx-dt-tool-btn,
      .vx-dt-drawer-btn, .vx-dt-bulk-clear, .vx-acc-header, .vx-carousel-chip,
      .vx-radio, .vx-scroll-top, .vx-banner-close, .vx-secret-toggle, .vx-carousel-arrow,
      .vx-pg-btn, .vx-pagination button, .vx-step-dot,
      .vx-input-native, .vx-select-native, .vx-secret-input, .vx-dt-search,
      .vx-dd-menu > a, .vx-dd-menu > button, .vx-dd-menu > [role="menuitem"],
      /* tap-target sweep 2026-06-04: STANDALONE controls left <44 on coarse */
      .btn, .vx-qb-input, .vx-tgl, .vx-cal-today, .vx-cal-nav,
      vortex-scrollspy a, .vx-qty-btn, .vx-qty-input, .vx-ap-range {
        /* !important: a11y override that must beat the rules of partials loaded later */
        min-height: 44px !important;
      }
      .vx-secret-otp-box { min-width: 44px !important; }
      .vx-banner-close, .vx-secret-toggle, .vx-carousel-arrow, .vx-dt-tool-btn,
      .vx-scroll-top, .vx-pg-btn, .vx-bc-expand,
      .vx-cal-nav, .vx-dv-btn, .vx-ct-act, .vx-qty-input {
        min-width: 44px !important;
      }
      /* scrollspy: 44px items → text vertically CENTERED (otherwise stuck to the top of the box) */
      vortex-scrollspy a { display: flex !important; align-items: center; }

      /* Switch: the target (the <button role=switch>) becomes 44×44 WITHOUT enlarging the visual
         pill — the 24px pill is redrawn as a centered ::before, the thumb stays centered. */
      vortex-switch .vx-sw-track {
        height: 44px !important; min-width: 44px !important;
        background: transparent !important; border-color: transparent !important;
        display: inline-flex !important; align-items: center;
      }
      vortex-switch .vx-sw-track::before {
        content: ''; position: absolute; inset-inline-start: 0; top: 50%; transform: translateY(-50%);
        width: 44px; height: 24px; border-radius: var(--radius-full);
        background: var(--surface-3, var(--surface-2)); border: 1px solid var(--border);
        transition: background var(--dur-snappy) ease, border-color var(--dur-snappy) ease;
      }
      vortex-switch .vx-sw-track[aria-checked="true"]::before { background: var(--accent); border-color: var(--accent); }
      vortex-switch .vx-sw-thumb { top: 50%; }
      vortex-switch .vx-sw-track[aria-checked="true"] .vx-sw-thumb { transform: translateY(-50%) translateX(calc(23px * var(--vx-dir, 1))); }

      /* Checkbox: the native <input> (invisible) becomes 44×44 centered on the 20px visual box
         → WCAG touch target without changing the visual. */
      vortex-checkbox .vx-cb-input {
        width: 44px !important; height: 44px !important; top: -12px !important; inset-inline-start: -12px !important;
      }

      /* Slider: the <input type=range> becomes 44px tall (touch grab area);
         the thumb + the track stay visually centered. */
      vortex-slider .vx-sl-input { height: 44px !important; }
    }
} /* close @layer base */
@layer theme {
/* shell */
/* ── Vortex theme shell (vxt-*) ─────────────────────────────────────── */
body.vxt-theme{margin:0;background:var(--bg);color:var(--text);font-family:var(--font-sans,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif);-webkit-font-smoothing:antialiased;display:block;min-height:100vh;overflow-x:hidden;}
.vxt-main{display:block;min-block-size:60vh;}
.vxt-row{max-inline-size:1360px;margin-inline:auto;padding-inline:clamp(16px,4vw,40px);}
/* header */
/* The header is a section, so Shopify wraps it in an element of its own (.vxt-hd-wrap, named by
   the section's schema). `position:sticky` is bounded by its PARENT: left on .vxt-hd it would
   unstick as soon as that wrapper — only as tall as the header itself — scrolled out of view.
   The wrapper is the direct child of <body>, so it is the one that must stick. */
.vxt-hd-wrap{position:sticky;top:0;z-index:40;}
.vxt-hd{background:color-mix(in oklab,var(--bg) 86%,transparent);backdrop-filter:blur(14px);border-block-end:1px solid var(--border);}
/* backdrop-filter — like transform/filter/perspective/contain:paint — makes this header the
   CONTAINING BLOCK of every position:fixed descendant. <vortex-search>'s mobile overlay is exactly
   that: fixed, inset:0, meant to cover the screen. Trapped here it resolved against the header's
   box instead of the viewport, so the suggestions popup measured 72px tall — the header's height —
   with `overflow:auto` hiding the four results laid out below y=246. The DOM said four suggestions;
   none of them were on screen. Nothing to blur on mobile anyway: the overlay is opaque and covers
   the header whole. Desktop keeps the frosted glass — there the search is inline and never fixed.
   Same fix as .ns-header (storefront, 2026-07-14) — see [[mobile-search-fullscreen-backdrop-trap]]. */
@media(max-width:640px){.vxt-hd{backdrop-filter:none;background:var(--surface);}}
/* gap: the row carries burger + logo + 3 actions at 390px, so the desktop gap is a min-width
   opt-in. At 28px throughout, the cart sat at x=417 on a 390 viewport — off-screen, and Chrome
   answered by widening the layout viewport to 422 (shrink-to-fit), which silently zoomed out
   EVERY page of the store on a phone. */
.vxt-hd-in{position:relative;max-inline-size:1360px;margin-inline:auto;padding:14px clamp(16px,4vw,40px);display:flex;align-items:center;gap:var(--space-2);}
/* min-inline-size:0 lets the logo be the flex item that yields — without it the shop name pushes
   the actions past the viewport instead of ellipsing. */
.vxt-hd-logo{flex:0 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:800;font-size:1.15rem;letter-spacing:.01em;text-decoration:none;color:var(--text);}
@media(min-width:900px){.vxt-hd-in{gap:28px;}.vxt-hd-logo{font-size:1.3rem;}}
.vxt-hd-burger{display:inline-flex;align-items:center;justify-content:center;background:none;border:0;color:var(--text);cursor:pointer;padding:6px;margin-inline-start:-6px;}
.vxt-hd-nav{display:none;gap:24px;flex:1;}
.vxt-hd-nav a{text-decoration:none;color:var(--muted);font-weight:600;font-size:.95rem;transition:color .15s;}
.vxt-hd-nav a:hover,.vxt-hd-nav a[aria-current]{color:var(--text);}
@media(min-width:900px){.vxt-hd-nav{display:flex;}.vxt-hd-burger{display:none;}}
/* mobile menu dropdown */
.vxt-hd[data-menu="open"] .vxt-hd-nav{display:flex;flex-direction:column;position:absolute;inset-inline:0;top:100%;background:var(--surface);border-block-end:1px solid var(--border);padding:12px clamp(16px,4vw,40px);gap:2px;box-shadow:0 12px 24px rgb(0 0 0/.3);}
.vxt-hd[data-menu="open"] .vxt-hd-nav a{padding:13px 0;font-size:1.05rem;}
.vxt-hd-actions{margin-inline-start:auto;display:flex;align-items:center;gap:var(--space-1);}
@media(min-width:900px){.vxt-hd-actions{gap:var(--space-3);}}
/* ── .vxt-ib — the ONE header icon button ────────────────────────────────────
   The four header actions are siblings of equal rank, so they get one shape, one size and one
   hit area. The geometry deliberately copies the framework's own icon button (vortex-search's
   .vx-srch-iconbtn: 40px, full radius, 1px border on --surface) rather than inventing a second
   language — the search trigger is a component we do not restyle, so the theme meets it there.
   `appearance:none` + the explicit background/border are load-bearing, not defensive: a <button>
   that states neither keeps the UA chrome (a 2px outset border on a grey ButtonFace) while the
   <a> cart beside it does not. That single asymmetry is what made one row of four sibling
   controls paint as three unrelated ones. */
.vxt-ib{position:relative;flex:none;inline-size:40px;block-size:40px;display:inline-flex;align-items:center;justify-content:center;padding:0;appearance:none;-webkit-appearance:none;background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-full);text-decoration:none;cursor:pointer;transition:background var(--dur-fast),border-color var(--dur-fast);}
.vxt-ib:hover{background:var(--surface-2,var(--surface));border-color:var(--accent);}
.vxt-ib:active{transform:scale(.94);}
.vxt-ib svg{inline-size:20px;block-size:20px;}
/* WCAG 2.5.5 / D.2: 44px on touch only — the framework's own coarse sweep lists the design-system
   controls by class and never knew about the theme's, so these shipped at 34-40px. */
@media(pointer:coarse){.vxt-ib,vortex-search .vx-srch-iconbtn{inline-size:44px;block-size:44px;}.vxt-hd-burger{inline-size:44px;block-size:44px;padding:0;}}
@media(prefers-reduced-motion:reduce){.vxt-ib{transition:none;}.vxt-ib:active{transform:none;}}
.vxt-hd-cart-n{position:absolute;top:-3px;inset-inline-end:-5px;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:var(--fill-accent,var(--accent));color:var(--on-accent,#0b0f17);font-size:.7rem;font-weight:800;font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;justify-content:center;}
/* ── appearance control ──────────────────────────────────────────────────────
   Below 900px the row cannot hold four 44px targets next to the burger and the shop name, and a
   theme switch is the action a shopper needs least — so it moves into the menu, where it can say
   what it does in words instead of asking anyone to decode a moon. Same control, two mounts, one
   handler; the header keeps search + wishlist + cart. */
.vxt-hd-appearance{display:none;}
@media(min-width:900px){.vxt-hd-appearance{display:inline-flex;}}
.vxt-hd-menu-row{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);inline-size:100%;min-block-size:44px;padding:13px 0;margin-block-start:var(--space-1);border-block-start:1px solid var(--border);background:none;border-inline:0;border-block-end:0;color:var(--text);font:inherit;font-size:1.05rem;font-weight:600;cursor:pointer;}
.vxt-hd-menu-row .vxt-hd-menu-state{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--muted);font-size:.95rem;font-weight:600;}
.vxt-hd-menu-row svg{inline-size:20px;block-size:20px;}
@media(min-width:900px){.vxt-hd-menu-row{display:none;}}
/* footer */
.vxt-ft{display:block;width:100%;margin-block-start:80px;border-block-start:1px solid var(--border);background:var(--surface);}
.vxt-ft-in{max-inline-size:1360px;margin-inline:auto;padding:48px clamp(16px,4vw,40px);display:grid;gap:32px;grid-template-columns:1fr;}
@media(min-width:720px){.vxt-ft-in{grid-template-columns:2fr 1fr 1.4fr;}}
.vxt-ft-brand b{font-size:1.15rem;}.vxt-ft-brand p{color:var(--muted);margin:.5em 0 0;max-inline-size:40ch;line-height:1.6;}
.vxt-ft-links{display:flex;flex-direction:column;gap:10px;}.vxt-ft-links a{color:var(--muted);text-decoration:none;}.vxt-ft-links a:hover{color:var(--text);}
.vxt-ft-pay{display:flex;gap:8px;align-items:center;flex-wrap:wrap;align-content:flex-start;}
.vxt-ft-pay-i,.vxt-ft-pay svg{height:26px;width:auto;border-radius:4px;box-shadow:0 0 0 1px rgb(0 0 0/.06);}
.vxt-ft-legal{text-align:center;color:var(--muted);font-size:.8rem;padding:22px;border-block-start:1px solid var(--border);}
/* hero */
.vxt-hero{position:relative;padding:clamp(56px,11vw,140px) 0;background:radial-gradient(130% 130% at 78% -10%,color-mix(in oklab,var(--accent) 26%,var(--bg)),var(--bg) 62%);border-block-end:1px solid var(--border);}
.vxt-hero-in{max-inline-size:1360px;margin-inline:auto;padding-inline:clamp(16px,4vw,40px);}
.vxt-eyebrow{color:var(--text-accent,var(--accent));font-weight:800;letter-spacing:.16em;text-transform:uppercase;font-size:.78rem;margin:0 0 10px;}
.vxt-hero h1{font-size:clamp(2.6rem,7vw,4.6rem);line-height:1.0;margin:0 0 16px;letter-spacing:-.025em;text-transform:capitalize;}
.vxt-hero-sub{color:var(--muted);font-size:1.15rem;line-height:1.6;max-inline-size:52ch;margin:0 0 28px;}
/* sections + grid */
.vxt-sec{max-inline-size:1360px;margin-inline:auto;padding:clamp(40px,6vw,72px) clamp(16px,4vw,40px);}
.vxt-sec-h{font-size:clamp(1.5rem,3vw,2rem);margin:0 0 24px;letter-spacing:-.01em;}
.vxt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(12px,2vw,24px);width:100%;}
@media(min-width:640px){.vxt-grid{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media(min-width:1000px){.vxt-grid{grid-template-columns:repeat(4,minmax(0,1fr));}}

/* Meridian-style collection: sticky filter SIDEBAR (desktop) + product grid. The component itself
   swaps inline↔sheet at 768px; this just gives the inline panel a column and makes it stick. */
/* minmax(0,1fr), NOT 1fr: a bare `1fr` is `minmax(auto,1fr)`, and the `auto` minimum grows to the
   grid's max-content width (~561px of cards+gaps) → the single mobile column overflowed the 358px
   container and the right edge got clipped off-screen (Jerry's phone, 2026-07-16). minmax(0,…)
   pins the minimum to 0 so the column stays viewport-bound and the grid shrinks to fit. */
.vxt-collection{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(20px,3vw,40px);align-items:start;}
.vxt-col-main{min-inline-size:0;}
.vxt-toolbar-row{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;margin-block-end:clamp(12px,2vw,20px);}
/* Sticky filter bar/sidebar at every width (Meridian pattern) — stays pinned below the sticky
   header so filters are always reachable while browsing (Jerry 2026-07-16). top = header height. */
.vxt-col-side{position:sticky;top:69px;align-self:start;z-index:5;}
@media(max-width:999px){
  /* MOBILE: the filter bar is an opaque pinned bar below the header (was scrolling away). Full-bleed
     background so the grid scrolling underneath never shows through the page gutters. */
  .vxt-col-side{background:var(--bg);border-block-end:1px solid var(--border);
    margin-inline:calc(-1 * clamp(16px,4vw,40px));padding:6px clamp(16px,4vw,40px) 8px;}
}
@media(min-width:1000px){
  .vxt-collection{grid-template-columns:250px minmax(0,1fr);}
  /* DESKTOP sidebar: sits lower (more breathing room). If taller than the viewport it scrolls on
     its own — overscroll-behavior:auto so reaching its end CHAINS to the page (never traps the
     gesture, the class the overscroll gate guards). */
  .vxt-col-side{top:84px;max-block-size:calc(100vh - 100px);overflow-y:auto;overscroll-behavior:auto;}
  /* the main column is narrower now → 3 product columns, not 4 */
  .vxt-collection .vxt-grid{grid-template-columns:repeat(3,minmax(0,1fr));}
}
.vxt-grid>*{min-inline-size:0;max-inline-size:100%;}
/* page chrome */
.vxt-page{padding-block:36px;display:block;}
.vxt-crumb{color:var(--muted);font-size:.85rem;margin:0 0 14px;}.vxt-crumb a{text-decoration:none;color:var(--muted);}.vxt-crumb a:hover{color:var(--text);}
.vxt-page-h{font-size:clamp(1.9rem,4vw,2.8rem);margin:0 0 10px;letter-spacing:-.02em;}
.vxt-page-sub,.rte{color:var(--muted);line-height:1.7;}
.vxt-count{color:var(--muted);font-size:.85rem;font-weight:700;margin:14px 0 22px;}
/* ── search page form ────────────────────────────────────────────────────────
   Same field language as the header's <vortex-search> so the two searches of the site look like
   the same product. flex + minmax-style min-inline-size:0 makes the field fluid — it was frozen at
   the UA's default 181px at every viewport, including 1440. */
.vxt-search-form{display:flex;align-items:center;gap:var(--space-2);margin-block-end:var(--space-4);}
.vxt-search-input{flex:1 1 auto;min-inline-size:0;block-size:44px;padding-inline:var(--space-4);border:1px solid var(--border);border-radius:var(--radius-full);background:var(--surface);color:var(--text);font:inherit;font-size:1rem;appearance:none;-webkit-appearance:none;}
.vxt-search-input:focus-visible{outline:none;border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);}
/* The 2.43:1 placeholder came from the UA's own colour on an unstyled widget, not from a token:
   --muted on --surface measures 7.79:1 dark / 5.87:1 light. Stating it (and opacity:1, which
   Firefox needs) is what keeps the placeholder on the design system instead of on the UA. */
.vxt-search-input::placeholder{color:var(--muted);opacity:1;}
.vxt-search-form .vxt-btn{flex:none;min-block-size:44px;}
.vxt-empty{color:var(--muted);text-align:center;padding:56px 16px;grid-column:1/-1;font-size:1.05rem;}
.vxt-pager{display:flex;justify-content:center;gap:12px;margin:40px 0;}
/* buttons */
.vxt-btn{display:inline-flex;align-items:center;gap:8px;padding:13px 24px;border-radius:var(--radius-full,999px);border:1px solid var(--border);background:var(--surface-2,var(--surface));color:var(--text);font-weight:700;text-decoration:none;cursor:pointer;font-size:.95rem;transition:.15s;}
.vxt-btn:hover{border-color:var(--accent);}
.vxt-btn-accent{background:var(--fill-accent,var(--accent));color:var(--on-accent,#0b0f17);border-color:transparent;}
.vxt-btn-accent:hover{background:var(--fill-accent-hover,var(--accent-hover));transform:translateY(-1px);}
.vxt-btn[disabled]{opacity:.5;cursor:not-allowed;}
/* PDP */
.vxt-pdp-grid{display:grid;gap:clamp(24px,4vw,56px);grid-template-columns:1fr;align-items:start;}
.vxt-pdp-gal{display:block;}
/* Sticky belongs to the two-column layout and only to it: beside a tall info column the image is
   worth keeping in view, but below 820px the gallery is stacked ABOVE that column — so sticky glued
   it under the header and dragged it down the entire page (800px of scroll moved it 67px). One
   media query owns both halves of the same decision. */
@media(min-width:820px){
  .vxt-pdp-grid{grid-template-columns:1.05fr .95fr;}
  .vxt-pdp-gal{position:sticky;top:88px;}
}
.vxt-pdp-brand{color:var(--muted);font-weight:700;text-transform:uppercase;letter-spacing:.1em;font-size:.8rem;margin:0 0 8px;}
.vxt-pdp-title{font-size:clamp(1.7rem,3vw,2.4rem);margin:0 0 16px;letter-spacing:-.02em;}
.vxt-pdp-price{font-size:1.6rem;font-weight:800;display:flex;gap:12px;align-items:baseline;margin:0 0 26px;}
.vxt-pdp-was{color:var(--muted);font-weight:600;font-size:1.05rem;}
/* .vxt-pdp-optlabel / .vxt-pdp-select / .vxt-pdp-atc are gone with the hand-rolled buy block:
   <vortex-swatch> carries its own label, and <vortex-add-to-cart> its own button. Nothing here
   restyles those components — the theme composes them, it does not fight them. */
.vxt-pdp-variants{display:flex;flex-direction:column;gap:var(--space-4);margin:var(--space-5) 0;}
.vxt-pdp-buy{display:flex;align-items:center;gap:var(--space-3);margin:var(--space-5) 0;}
.vxt-pdp-buy vortex-add-to-cart{flex:1;min-inline-size:0;}
.vxt-pdp-wish{flex:none;}
/* Pressed = saved. The heart fills rather than swapping to another glyph, so the state reads at a
   glance without a second icon to learn. */
.vxt-pdp-wish[aria-pressed="true"]{color:var(--accent);border-color:var(--accent);}
.vxt-pdp-wish[aria-pressed="true"] svg{fill:var(--accent);}
.vxt-pdp-desc{margin-block-start:28px;line-height:1.7;}
/* cart */
.vxt-cart-lines{display:flex;flex-direction:column;gap:18px;margin:28px 0;}
/* ── cart line ───────────────────────────────────────────────────────────────
   Mobile stacks the controls under the title; ≥640px the row spreads out. The `1fr` track used to
   be plain `1fr`, whose automatic minimum is min-content — a long product title therefore made the
   grid wider than the screen and pushed the price clean out of the viewport, unreachable at 360px.
   minmax(0,1fr) is what lets the title wrap instead of shoving its siblings off. */
.vxt-cart-line{display:grid;grid-template-columns:64px minmax(0,1fr) auto;grid-template-areas:"img main price" "img ctl ctl";gap:10px 14px;align-items:center;padding:14px 0;border-block-end:1px solid var(--border);}
.vxt-cart-line > img{grid-area:img;}
.vxt-cart-line-main{grid-area:main;min-inline-size:0;}
.vxt-cart-line-ctl{grid-area:ctl;display:flex;align-items:center;gap:var(--space-3);}
.vxt-cart-line-price{grid-area:price;justify-self:end;}
@media(min-width:640px){
  .vxt-cart-line{grid-template-columns:80px minmax(0,1fr) auto auto;grid-template-areas:"img main ctl price";gap:18px;}
}
.vxt-cart-line img{border-radius:var(--radius-lg,12px);object-fit:cover;}
.vxt-cart-line-main a{font-weight:700;text-decoration:none;display:block;overflow-wrap:anywhere;}.vxt-cart-line-main span{color:var(--muted);font-size:.85rem;}
/* 16px min: any smaller and Safari/iOS zooms the page in on focus. */
.vxt-cart-qty{inline-size:72px;min-block-size:44px;padding:9px;border-radius:var(--radius-md,8px);border:1px solid var(--border);background:var(--surface);color:var(--text);text-align:center;font:inherit;font-size:1rem;}
.vxt-cart-rm{min-block-size:44px;display:inline-flex;align-items:center;color:var(--muted);font-size:.9rem;text-decoration:underline;text-underline-offset:3px;}
.vxt-cart-rm:hover{color:var(--err,var(--text));}
/* Right-aligned auto-width buttons wrapped one per line into a staircase; on mobile they take the
   full width in a column, CTA last. */
.vxt-cart-foot{display:flex;flex-direction:column;align-items:stretch;gap:12px;margin-block-start:24px;}
.vxt-cart-foot .vxt-btn{inline-size:100%;}
@media(min-width:640px){
  .vxt-cart-foot{flex-direction:row;align-items:center;justify-content:flex-end;gap:16px;flex-wrap:wrap;}
  .vxt-cart-foot .vxt-btn{inline-size:auto;}
}
.vxt-nowrap{white-space:nowrap;}
.vxt-cart-total{font-size:1.25rem;font-weight:800;margin-inline-end:auto;}
/* collection tiles */
.vxt-coll-card{position:relative;display:block;border-radius:var(--radius-xl,16px);overflow:hidden;text-decoration:none;aspect-ratio:4/3;background:var(--surface-2);}
.vxt-coll-card img{width:100%;height:100%;object-fit:cover;}
.vxt-coll-card span{position:absolute;inset-block-end:0;inset-inline:0;padding:18px;background:linear-gradient(transparent,rgb(0 0 0/.72));color:#fff;font-weight:800;font-size:1.25rem;}
/* collection toolbar (filters + count + sort) */
.vxt-toolbar{display:block;margin-block:22px 6px;}
.vxt-toolbar-row{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-block-start:14px;}
.vxt-toolbar-row .vxt-count{margin:0;}
/* ── contact ─────────────────────────────────────────────────────────────────
   One column on mobile, two once there is room: the form leads (it is the action), the other ways
   to reach us follow. */
.vxt-contact-grid{display:grid;gap:clamp(24px,4vw,48px);grid-template-columns:1fr;align-items:start;margin-block-start:var(--space-5);}
@media(min-width:820px){.vxt-contact-grid{grid-template-columns:1.2fr .8fr;}}
.vxt-contact-intro{color:var(--muted);line-height:1.65;max-inline-size:60ch;}
.vxt-contact-grid vortex-input,.vxt-contact-grid vortex-textarea{display:block;margin-block-end:var(--space-4);}
.vxt-contact-note{display:block;margin-block-end:var(--space-4);}
.vxt-contact-send{inline-size:100%;justify-content:center;min-block-size:44px;}
@media(min-width:560px){.vxt-contact-send{inline-size:auto;}}
.vxt-contact-side{padding:var(--space-5);border:1px solid var(--border);border-radius:var(--radius-lg,12px);background:var(--surface);}
.vxt-contact-h2{font-size:1.05rem;margin:0 0 var(--space-4);}
.vxt-contact-list{list-style:none;margin:0 0 var(--space-5);padding:0;display:flex;flex-direction:column;gap:var(--space-3);}
.vxt-contact-list li{display:flex;align-items:center;gap:var(--space-3);color:var(--muted);font-size:.92rem;}
.vxt-contact-list a{color:var(--text);text-decoration:none;overflow-wrap:anywhere;}
.vxt-contact-list a:hover{color:var(--accent);}
.vxt-contact-list svg{inline-size:18px;block-size:18px;flex:none;}
/* immersive collection header */
.vxt-coll-hero{position:relative;background-size:cover;background-position:center;border-block-end:1px solid var(--border);min-block-size:clamp(240px,32vw,400px);display:flex;align-items:flex-end;}
.vxt-coll-hero-plain{background:radial-gradient(120% 120% at 80% -10%,color-mix(in oklab,var(--accent) 24%,var(--bg)),var(--bg) 60%);min-block-size:auto;}
.vxt-coll-hero-in{width:100%;padding-block:clamp(24px,4vw,48px);}
.vxt-coll-hero-sub{color:color-mix(in oklab,var(--text) 82%,transparent);font-size:1.05rem;margin:0 0 14px;max-inline-size:52ch;}
.vxt-coll-hero .vxt-page-h{margin:0 0 8px;}
.vxt-chip{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:var(--radius-full,999px);background:color-mix(in oklab,var(--bg) 62%,transparent);border:1px solid var(--border);font-weight:700;font-size:.82rem;backdrop-filter:blur(8px);}
/* PDP gallery: packshots are portrait on white — `cover` cropped the shoe. Show it whole,
   on a surface tile, and stop the gallery dominating the page. */
.vxt-pdp-gal{max-inline-size:520px;}
.vxt-pdp-gal .vx-gal-main{background:var(--surface-2);border-radius:var(--radius-lg,12px);overflow:hidden;}
.vxt-pdp-gal .vx-gal-img{object-fit:contain;}
/* The store's descriptions re-embed the product photo full size: a white slab on a dark page.
   Cap it, centre it, and give it the same tile treatment. */
.vxt-pdp-desc img{display:block;max-inline-size:min(100%,360px);height:auto;margin:18px auto;border-radius:var(--radius-lg,12px);background:var(--surface-2);}
/* Search previews: the popup inherits the field's width (inset-inline:0) and the header field is
   narrow, so every product title ellipsised away. Widen it and anchor to the trailing edge. */
#siteSearch .vx-srch-pop{inset-inline:auto 0;inline-size:min(460px,calc(100vw - 32px));}
@media(max-width:640px){#siteSearch .vx-srch-pop{inset-inline:0;inline-size:auto;}}
/* appearance toggle: one button, the icon follows the CURRENT scheme (moon in light, sun in dark) */
/* Keyed off [data-vxt-appearance], not the header class: the control has two mounts (header on
   desktop, menu row on mobile) and both must swap the icon. */
[data-vxt-appearance] .vxt-ic-sun,[data-vxt-appearance] .vxt-ic-moon{display:none;}
[data-theme="dark"] [data-vxt-appearance] .vxt-ic-sun{display:block;}
[data-theme="light"] [data-vxt-appearance] .vxt-ic-moon{display:block;}
/* The image hero keeps a dark scrim in BOTH schemes, so its text must stay light — in light mode
   `--text` flips to near-black and the title vanished into the scrim. The plain (imageless) hero
   uses the normal tokens and is untouched. */
.vxt-coll-hero:not(.vxt-coll-hero-plain){color:#fff;}
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-page-h,
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-coll-hero-sub,
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-crumb,
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-crumb a{color:inherit;}
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-coll-hero-sub{opacity:.86;}
.vxt-coll-hero:not(.vxt-coll-hero-plain) .vxt-chip{color:#fff;background:rgb(0 0 0/.5);border-color:rgb(255 255 255/.28);}
/* Skip link: visually hidden until focused, then a real target (it must be reachable, not a trap) */
.vxt-skip:focus{position:absolute!important;inset-block-start:10px;inset-inline-start:10px;inline-size:auto;block-size:auto;clip:auto;padding:10px 16px;border-radius:var(--radius-md,8px);background:var(--fill-accent,var(--accent));color:var(--on-accent);font-weight:700;z-index:100;}

}