/* ═══════════════════════════════════════════════════════════════
   VAULT FANTASY — Design System
   vault-ds.css · Single source of truth for tokens + components
   ═══════════════════════════════════════════════════════════════ */

/* ── 1. TOKENS ─────────────────────────────────────────────────── */

:root {
  /* VAPOR SKY — dark (default) */
  --bg:#070d18;--surface:#0e1626;
  --s1:#0e1626;--s2:#131d30;--s3:#1b2740;--s4:#243352;--s-bright:#2c3f60;
  --border:rgba(123,170,240,0.10);--border2:rgba(123,170,240,0.17);--border3:rgba(123,170,240,0.30);
  --dim:#1b2740;
  --text:#eaf2fc;--text2:#bdcce2;--muted:#808fa6;
  --primary:#bec6e0;--on-primary:#0a1424;--primary-c:var(--s1);
  --accent:#2f63c4;--accent2:#7bd0ff;--accent3:#b3e0ff;
  --red:#e23a4e;--red2:#ff5a6e;
  --sky:#4d86f0;--sky-dim:#2f63c4;
  --qb:#ff6680;--rb:#3DCC7A;--wr:#7bd0ff;--te:#f5c842;
  --green:var(--rb);
  --suggest:#7bd0ff;--value:#3DCC7A;--reach:#e23a4e;--gold:#f5c842;--navy:#070d18;
  --violet:#a78bfa;   /* ADP / secondary-source accent. Inverts like the position colours. */
  --glass-1:rgba(14,22,38,0.55);--glass-2:rgba(20,30,48,0.55);
  --blur-md:blur(22px);--blur-sm:blur(14px);
  --font-sans:'Archivo','Hanken Grotesk',system-ui,sans-serif;
  --font-mono:'JetBrains Mono','Space Mono',monospace;
  --radius-sm:6px;--radius-md:10px;--radius-lg:16px;--radius-xl:20px;--radius-pill:999px;
  --shadow-card:0 24px 60px -20px rgba(4,9,20,0.62),0 1px 0 rgba(255,255,255,0.06) inset;

  /* ── MOTION ─────────────────────────────────────────────────────
     Easing vocabulary for anything that MOVES (transforms, entrances,
     expanders). Fast start, gentle settle — never the default `ease`.
     Short color/opacity feedback (≤150ms) can keep plain `ease`.      */
  --ease-out:cubic-bezier(0.16,1,0.3,1);    /* entrances, expanders, movement */
  --ease-sheet:cubic-bezier(0.32,0.72,0,1); /* bottom sheets — iOS/Vaul curve */
  --ease-rail:cubic-bezier(0.65,0,0.35,1);      /* TRAVEL — a thing moving between two known points */
  --ease-spring:cubic-bezier(0.34,1.56,0.64,1); /* ARRIVAL — press-release, pop-in (overshoots) */

  /* Durations. Press is asymmetric on purpose: snap down, spring back. */
  --dur-press:80ms;     /* pointer-down — must beat perception */
  --dur-release:500ms;  /* pointer-up — the spring needs room to settle */
  --dur-rail:400ms;     /* pill / indicator travel */

  /* Press depth is INVERSE to element size. A 90px chip losing 12% moves 11px
     and feels crisp; a 600px card losing 12% moves 72px and looks broken. */
  --press-chip:.88;
  --press-btn:.94;
  --press-card:.975;
  --press-row:.99;

  /* Ink that stays legible on top of a position-coloured pill. The position
     colours invert between themes — vapor/onyx use LIGHT positions (#ff6680 …)
     so the ink is dark, while [data-theme="light"] uses DARK positions
     (#c52d3a …) so the ink flips to white. */
  --pos-ink:#12151c;

  /* ── TYPE SCALE ──────────────────────────────────────────────────
     One ramp for the whole app. iOS-anchored; sizes are theme-agnostic.
     Sans (Archivo) carries all reading + UI text. Mono is reserved for
     numbers/tabular data via .t-data / .t-label — never for UI chrome.  */
  --t-hero:34px;       /* screen hero (onboarding wordmark, empty states) */
  --t-title:24px;      /* screen / page title */
  --t-h1:20px;         /* major section heading */
  --t-h2:17px;         /* card title, list-row title (iOS body-emphasized) */
  --t-body:15px;       /* default reading text (iOS body) */
  --t-secondary:13px;  /* supporting / meta text */
  --t-label:11px;      /* mono uppercase kicker / label */
  --t-micro:10px;      /* smallest badge / tag */
  --t-nano:9px;        /* dense-data floor — stat-table cells, micro-tags */
  /* leading */
  --lh-hero:1.05;--lh-tight:1.15;--lh-snug:1.3;--lh-body:1.5;--lh-relaxed:1.6;
}

[data-theme="light"] {
  /* AMERICANA PAPER */
  --bg:#f4efe4;--surface:#fffdf8;
  --s1:#fffdf8;--s2:#f3eede;--s3:#ece5d4;--s4:#e1d8c4;--s-bright:#ffffff;
  --border:rgba(29,40,66,0.10);--border2:rgba(29,40,66,0.17);--border3:rgba(29,40,66,0.28);
  --text:#1d2433;--text2:#3a4658;--muted:#606774;--dim:#ece5d4;
  --primary:#16233a;--on-primary:#ffffff;
  --accent:#16233a;--accent2:#2c4063;--accent3:#4a5d80;
  /* Semantic colours are tuned to clear WCAG AA (4.5:1) down to --s3, the
     darkest surface they routinely sit on — not just on --s1. */
  --red:#c22a37;--red2:#c12c38;
  --sky:#1f4e87;--sky-dim:#16395f;
  --qb:#c22a37;--rb:#067648;--wr:#1a6aab;--te:#955804;
  --suggest:#1f4e87;--value:#067648;--reach:#c22a37;--gold:#955804;--navy:#16233a;
  --violet:#6d3fc4;
  --glass-1:rgba(255,255,255,0.62);--glass-2:rgba(248,244,235,0.62);
  --pos-ink:#ffffff;   /* light theme's position colours are dark — flip the ink */
  --shadow-card:0 20px 48px -22px rgba(41,50,66,0.24),0 1px 0 rgba(255,255,255,0.5) inset;
}

[data-theme="onyx"] {
  /* ONYX & STEEL FROST — muted steel-blue accent, hero-only glow */
  --bg:#0e0f13;--surface:#16171c;
  --s1:#16171c;--s2:#1c1e24;--s3:#23252c;--s4:#2c2f38;--s-bright:#343844;
  --border:rgba(255,255,255,0.08);--border2:rgba(255,255,255,0.12);--border3:rgba(255,255,255,0.22);
  --text:#f3f4f7;--text2:#c9cdd6;--muted:#868c9b;--dim:#23252c;
  --primary:#8fb4e0;--on-primary:#0b1420;--primary-c:var(--s1);
  --accent:#5e7c9c;--accent2:#8fb4e0;--accent3:#b9d2ec;
  --red:#e23a4e;--red2:#ff5a6e;
  --sky:#5e7c9c;--sky-dim:#43607d;
  --suggest:#8fb4e0;--value:#5fe09a;--reach:#e23a4e;--gold:#8fb4e0;--navy:#0e0f13;
  --violet:#a78bfa;
  --glass-1:rgba(24,25,30,0.55);--glass-2:rgba(30,32,38,0.55);
  --shadow-card:0 24px 60px -20px rgba(6,6,8,0.62),0 1px 0 rgba(255,255,255,0.05) inset;
}


/* ── 2. TYPOGRAPHY UTILITIES ────────────────────────────────────── */

/* Semantic type ramp — bundle size + weight + tracking + leading + family.
   Reach for these instead of hardcoding font-size/family on new UI. */
.t-hero      { font-family:var(--font-sans); font-size:var(--t-hero);      font-weight:800; letter-spacing:-.03em; line-height:var(--lh-hero);  color:var(--text); }
.t-title     { font-family:var(--font-sans); font-size:var(--t-title);     font-weight:800; letter-spacing:-.02em; line-height:var(--lh-tight); color:var(--text); }
.t-h1        { font-family:var(--font-sans); font-size:var(--t-h1);        font-weight:700; letter-spacing:-.02em; line-height:var(--lh-tight); color:var(--text); }
.t-h2        { font-family:var(--font-sans); font-size:var(--t-h2);        font-weight:700; letter-spacing:-.01em; line-height:var(--lh-snug);  color:var(--text); }
.t-body      { font-family:var(--font-sans); font-size:var(--t-body);      font-weight:400; line-height:var(--lh-body);   color:var(--text); }
.t-secondary { font-family:var(--font-sans); font-size:var(--t-secondary); font-weight:400; line-height:var(--lh-body);   color:var(--muted); }
.t-label     { font-family:var(--font-mono); font-size:var(--t-label);     font-weight:700; letter-spacing:.1em;  text-transform:uppercase; color:var(--muted); }
.t-micro     { font-family:var(--font-mono); font-size:var(--t-micro);     font-weight:700; letter-spacing:.08em; }
/* Numbers/tabular data — the *only* place mono belongs by default. */
.t-data      { font-family:var(--font-mono); font-weight:700; font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1; }

.v-h {
  font-family:var(--font-sans);
  font-weight:900;
  letter-spacing:-.04em;
  line-height:.96;
  color:var(--text);
}

.v-kicker {
  font-family:var(--font-mono);
  font-size:10px;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--muted);
}

.v-mono {
  font-family:var(--font-mono);
  font-weight:700;
  color:var(--text);
}

.v-serif {
  font-family:'Newsreader','Georgia',serif;
  font-weight:500;
  color:var(--text2);
}


/* ── 3. GLASS SURFACES ──────────────────────────────────────────── */

.v-glass {
  background:var(--glass-1);
  border:1px solid rgba(255,255,255,0.10);
  backdrop-filter:var(--blur-md);
  -webkit-backdrop-filter:var(--blur-md);
  border-radius:var(--radius-lg);
}

.v-glass-deep {
  background:var(--glass-2);
  border:1px solid rgba(255,255,255,0.13);
  backdrop-filter:blur(28px) saturate(170%);
  -webkit-backdrop-filter:blur(28px) saturate(170%);
  border-radius:var(--radius-lg);
}

.v-glass-lite {
  background:rgba(255,255,255,0.06);
  border:1px solid rgba(255,255,255,0.07);
  backdrop-filter:var(--blur-sm);
  -webkit-backdrop-filter:var(--blur-sm);
  border-radius:var(--radius-md);
}


/* ── 3b. CARD SURFACES ──────────────────────────────────────────────
   Vault has exactly TWO card radii. Reach for these instead of picking
   a number — the audit that introduced this found 9 different radii
   across 102 rendered cards (8/9/10/11/12/13/14/16/18/20/28px).

     .v-card        panel  — a page's primary content card   16px
     .v-card-inset  inset  — a tile/row INSIDE a panel        10px

   Rules:
   · Radius is a function of NESTING, not of page. A card that sits
     inside another card is an inset; everything else is a panel.
   · Never nest an inset inside an inset — flatten the markup instead.
   · Shadows are deliberately NOT part of these primitives. Several
     pages (Portfolio, Lineup, Compare) are intentionally flat; add
     var(--shadow-card) per-surface only when elevation is meaningful.
   ──────────────────────────────────────────────────────────────────── */

.v-card {
  background:var(--s1);
  border:1px solid var(--border2);
  border-radius:var(--radius-lg);
}

.v-card-inset {
  background:var(--s2);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
}


/* ── 4. AMBIENT ORBS ────────────────────────────────────────────── */

.v-orb {
  position:absolute;
  border-radius:50%;
  pointer-events:none;
  filter:blur(60px);
  opacity:.55;
}

.v-orb.sky {
  background:radial-gradient(circle, rgba(77,134,240,0.65), transparent 70%);
}

.v-orb.red {
  background:radial-gradient(circle, rgba(226,58,78,0.55), transparent 70%);
}


/* ── 5. BUTTONS ─────────────────────────────────────────────────── */

.v-btn {
  position:relative;
  overflow:hidden;
  display:inline-flex;
  align-items:center;
  gap:7px;
  padding:11px 22px;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  color:#fff;
  border:none;
  border-radius:var(--radius-md);
  font-size:14px;
  font-weight:600;
  font-family:var(--font-sans);
  letter-spacing:-0.01em;
  cursor:pointer;
  touch-action:manipulation;
  white-space:nowrap;
  transition:opacity 150ms ease, transform var(--dur-release) var(--ease-spring);
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.22),0 8px 18px -10px rgba(4,9,20,0.7);
}
.v-btn:hover { opacity:.88; }
/* Asymmetric press — the whole trick is that down and up differ. See §19. */
.v-btn:active { transform:scale(var(--press-btn)); transition:transform var(--dur-press) ease-out; }
.v-btn.sm:active { transform:scale(var(--press-chip)); transition:transform var(--dur-press) ease-out; }

.v-btn .sheen {
  position:absolute;
  inset:0;
  border-radius:inherit;
  pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,0.35) 50%,transparent 62%);
  transform:translateX(-130%);
}
@media(prefers-reduced-motion:no-preference){
  .v-btn:hover .sheen { transform:translateX(130%); transition:transform .7s ease; }
}

.v-btn.red {
  background:linear-gradient(135deg,var(--red),var(--red2));
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.22),0 8px 18px -10px rgba(4,9,20,0.7);
}

.v-btn.ghost {
  background:var(--glass-1);
  border:1px solid rgba(255,255,255,0.14);
  backdrop-filter:var(--blur-sm);
  -webkit-backdrop-filter:var(--blur-sm);
  color:var(--text);
  box-shadow:none;
}
.v-btn.ghost:hover { background:var(--glass-2); opacity:1; }

.v-btn.sm { padding:7px 14px; font-size:12px; }


/* ── 6. POSITION BADGES ─────────────────────────────────────────── */

.v-pos {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  padding:4px 10px;
  border-radius:var(--radius-sm);
  font-family:var(--font-mono);
  font-size:10px;
  font-weight:700;
  letter-spacing:.06em;
}
.v-pos.QB { background:rgba(255,102,128,0.15); color:var(--qb); }
.v-pos.RB { background:rgba(61,204,122,0.15);  color:var(--rb); }
.v-pos.WR { background:rgba(123,208,255,0.15); color:var(--wr); }
.v-pos.TE { background:rgba(245,200,66,0.15);  color:var(--te); }


/* ── 7. PILLS & CHIPS ───────────────────────────────────────────── */

.v-pill {
  display:inline-flex;
  align-items:center;
  gap:6px;
  padding:5px 11px;
  background:rgba(255,255,255,0.07);
  border:1px solid rgba(255,255,255,0.12);
  border-radius:var(--radius-pill);
  font-size:11px;
  font-weight:600;
  color:var(--text2);
  white-space:nowrap;
}
.v-pill.mono {
  font-family:var(--font-mono);
  font-size:9px;
  letter-spacing:.10em;
}
.v-pill.hot {
  background:rgba(226,58,78,0.18);
  border-color:rgba(226,58,78,0.35);
  color:var(--red2);
  animation:pulse-red 1.8s ease-in-out infinite;
}
@keyframes pulse-red {
  0%,100% { box-shadow:0 0 0 0 rgba(226,58,78,0); }
  50%      { box-shadow:0 0 0 4px rgba(226,58,78,0.18); }
}

.dot {
  width:6px;
  height:6px;
  border-radius:50%;
  flex-shrink:0;
}


/* ── 8. VERDICT TAGS ────────────────────────────────────────────── */

.v-tag {
  display:inline-flex;
  align-items:center;
  padding:4px 9px;
  border-radius:var(--radius-sm);
  font-family:var(--font-mono);
  font-size:9px;
  font-weight:700;
  letter-spacing:.10em;
  white-space:nowrap;
}
.v-tag.value { background:rgba(61,204,122,0.14);  color:var(--value); }
.v-tag.reach { background:rgba(226,58,78,0.14);   color:var(--reach); }
.v-tag.sugg  { background:rgba(123,208,255,0.14); color:var(--suggest); }
.v-tag.gold  { background:rgba(245,200,66,0.14);  color:var(--gold); }


/* ── 9. SEGMENTED CONTROL ───────────────────────────────────────── */

.v-seg {
  display:inline-flex;
  background:rgba(255,255,255,0.06);
  border:1px solid var(--border2);
  border-radius:var(--radius-md);
  padding:3px;
  gap:2px;
}
.v-seg button {
  padding:6px 14px;
  border:none;
  border-radius:7px;
  background:transparent;
  color:var(--muted);
  font-family:var(--font-mono);
  font-size:10px;
  font-weight:700;
  letter-spacing:.08em;
  cursor:pointer;
  transition:all .14s;
}
.v-seg button.on {
  background:var(--accent);
  color:#fff;
  box-shadow:0 2px 8px -2px rgba(47,99,196,0.5);
}


/* ── 10. TOGGLE ─────────────────────────────────────────────────── */

.v-tog {
  width:40px;
  height:22px;
  border-radius:var(--radius-pill);
  background:rgba(255,255,255,0.10);
  border:1px solid var(--border2);
  position:relative;
  cursor:pointer;
  transition:background .2s, border-color .2s;
  flex-shrink:0;
}
.v-tog.on {
  background:var(--red);
  border-color:var(--red);
}
.v-tog .knob {
  position:absolute;
  top:2px;
  left:2px;
  width:16px;
  height:16px;
  border-radius:50%;
  background:#fff;
  box-shadow:0 1px 4px rgba(0,0,0,0.3);
  transition:transform .2s cubic-bezier(.4,0,.2,1);
}
.v-tog.on .knob {
  transform:translateX(18px);
}


/* ── 11. INPUT FIELD ────────────────────────────────────────────── */

.v-field {
  display:inline-flex;
  align-items:center;
  gap:10px;
  padding:10px 16px;
  background:var(--glass-1);
  border:1px solid var(--border2);
  border-radius:var(--radius-pill);
  backdrop-filter:var(--blur-sm);
  -webkit-backdrop-filter:var(--blur-sm);
  color:var(--muted);
  font-family:var(--font-sans);
  font-size:14px;
  cursor:text;
  transition:border-color .15s;
}
.v-field:focus-within {
  border-color:var(--accent2);
  color:var(--text);
}
.v-field input {
  background:transparent;
  border:none;
  outline:none;
  color:var(--text);
  font-family:var(--font-sans);
  font-size:14px;
  width:100%;
}
.v-field input::placeholder { color:var(--muted); }


/* ── 12. NAV TABS ───────────────────────────────────────────────── */

.v-ntab {
  padding:8px 16px;
  border:none;
  border-radius:var(--radius-pill);
  background:transparent;
  color:var(--muted);
  font-family:var(--font-sans);
  font-size:13px;
  font-weight:600;
  cursor:pointer;
  transition:all .14s;
  white-space:nowrap;
}
.v-ntab:hover { color:var(--text); background:rgba(255,255,255,0.06); }
.v-ntab.on {
  background:var(--accent);
  color:#fff;
  box-shadow:0 2px 10px -3px rgba(47,99,196,0.55);
}


/* ── 13. CATEGORY TILES ─────────────────────────────────────────── */

.v-cat {
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:6px;
  cursor:pointer;
  opacity:.6;
  transition:opacity .14s;
}
.v-cat:hover { opacity:.85; }
.v-cat.on { opacity:1; }

.v-cat .tile {
  width:52px;
  height:52px;
  border-radius:var(--radius-lg);
  background:var(--glass-1);
  border:1px solid var(--border2);
  display:flex;
  align-items:center;
  justify-content:center;
  font-family:var(--font-mono);
  font-size:11px;
  font-weight:700;
  color:var(--text2);
  letter-spacing:.06em;
  transition:all .14s;
  backdrop-filter:var(--blur-sm);
  -webkit-backdrop-filter:var(--blur-sm);
}
.v-cat.on .tile {
  background:var(--accent);
  border-color:transparent;
  color:#fff;
  box-shadow:0 6px 18px -6px rgba(47,99,196,0.55);
}

.v-cat .lbl {
  font-family:var(--font-mono);
  font-size:9px;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--muted);
}
.v-cat.on .lbl { color:var(--accent2); }


/* ── 14. PROGRESS BAR ───────────────────────────────────────────── */

.v-bar {
  width:100%;
  height:5px;
  border-radius:var(--radius-pill);
  background:rgba(255,255,255,0.08);
  overflow:hidden;
  position:relative;
}
.v-bar i {
  display:block;
  height:100%;
  border-radius:var(--radius-pill);
  background:linear-gradient(90deg,var(--accent),var(--accent2));
}


/* ── 15. AVATAR ─────────────────────────────────────────────────── */

.v-avatar {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:36px;
  height:36px;
  border-radius:50%;
  background:linear-gradient(135deg,var(--accent),var(--accent2));
  color:#fff;
  font-family:var(--font-mono);
  font-size:11px;
  font-weight:700;
  flex-shrink:0;
}


/* ── 16. ICON CHIP ──────────────────────────────────────────────── */

.v-ichip {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:32px;
  height:32px;
  border-radius:var(--radius-md);
  background:rgba(255,255,255,0.07);
  border:1px solid rgba(255,255,255,0.12);
  color:var(--text2);
  flex-shrink:0;
}
.v-ichip.lg {
  width:40px;
  height:40px;
  border-radius:var(--radius-lg);
}
.v-ichip svg { pointer-events:none; }


/* ── 17. PLACEHOLDER ────────────────────────────────────────────── */

.v-ph {
  display:flex;
  align-items:center;
  justify-content:center;
  background:var(--s2);
  border:1px dashed var(--border2);
  border-radius:var(--radius-lg);
  color:var(--muted);
  font-family:var(--font-mono);
  font-size:10px;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
}


/* ── 18. FOCUS VISIBLE (a11y — keyboard nav) ────────────────────── */
/* :where() keeps specificity 0 so component styles are never overridden;
   only keyboard focus (not mouse click) shows the ring. */
:where(button, a, input, select, textarea, summary, [tabindex]):focus-visible {
  outline:2px solid var(--accent2);
  outline-offset:2px;
  border-radius:var(--radius-sm);
}
/* Elements that are already pill-shaped keep a matching radius on the ring. */
:where(.v-btn, .v-ntab, .v-pill, .v-field, .v-tog):focus-visible {
  outline:2px solid var(--accent2);
  outline-offset:2px;
  border-radius:var(--radius-pill);
}
/* Form controls across the app set `outline:none` at class specificity, which
   would suppress the ring above. Restore a keyboard-only ring authoritatively.
   :focus-visible means mouse clicks still show no ring; only keyboard focus does. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline:2px solid var(--accent2) !important;
  outline-offset:2px;
}
@media (prefers-reduced-motion:no-preference){
  :where(button, a, input, select, textarea, summary, [tabindex]){
    transition:outline-color .12s ease;
  }
}
/* Perpetual attention-pulse must not loop for users who opt out of motion. */
@media (prefers-reduced-motion:reduce){
  .v-pill.hot{ animation:none; }
}


/* ── 19. MOTION PRIMITIVES ──────────────────────────────────────────
   Three reusable behaviours. Any new page or feature should reach for
   these instead of hand-rolling a transition:

     .press + .press-{chip,btn,card,row}  — tactile feedback on tap
     .rail  > .pill                        — a travelling tab indicator
     .spinner                              — indeterminate wait

   Rules of thumb:
   · Things that MOVE between two known points use --ease-rail.
   · Things that ARRIVE in place use --ease-spring (it overshoots).
   · Overshoot is only ever safe on `transform`. Never spring a `width`,
     `height`, or anything else that triggers layout.
   · Colour/opacity feedback under 150ms may keep plain `ease`.
   ──────────────────────────────────────────────────────────────────── */

/* ── 19a. PRESS ── snap down fast, spring back slow ── */
.press          { transition:transform var(--dur-release) var(--ease-spring); }
.press:active   { transform:scale(var(--press-scale,var(--press-btn)));
                  transition:transform var(--dur-press) ease-out; }
.press-chip     { --press-scale:var(--press-chip); }
.press-btn      { --press-scale:var(--press-btn); }
.press-card     { --press-scale:var(--press-card); }
.press-row      { --press-scale:var(--press-row); }

/* ── 19b. RAIL ── one shared indicator that travels between tabs ──
   The pill MUST live inside the strip: it is positioned against .rail,
   so if the strip scrolls horizontally the pill scrolls with it. Move it
   outside and it desyncs the moment anyone swipes.
   JS: vaultPill(rail) in index.html sets left/width from the active button. */
.rail           { position:relative; }
.rail > .pill   { position:absolute; pointer-events:none;
                  transition:left var(--dur-rail) var(--ease-rail),
                             width var(--dur-rail) var(--ease-rail),
                             background-color 260ms ease,
                             box-shadow 260ms ease; }
/* Applied while positioning a strip that was display:none (offsetWidth 0),
   or during a resize, so the pill teleports instead of snaking into place. */
.rail.noanim > .pill { transition:none; }

/* ── 19c. SPINNER ── a real arc, never a rotating text glyph ──
   Two adjacent borders in the accent + a track. `linear` is correct here;
   an eased spin reads as struggling. Size via .sm/.md/.lg or --sp-size. */
@keyframes v-spin { to { transform:rotate(1turn); } }
.spinner {
  display:inline-block;
  vertical-align:middle;  /* a bordered box on the baseline rides high next to text */
  width:var(--sp-size,26px); height:var(--sp-size,26px);
  border-radius:50%;
  border:var(--sp-w,3px) solid var(--s3);
  border-top-color:var(--sp-c,var(--accent2));
  border-right-color:var(--sp-c,var(--accent2));
  animation:v-spin .8s linear infinite;
  flex-shrink:0;
}
.spinner.lg { --sp-size:44px; --sp-w:4px; }
.spinner.md { --sp-size:26px; --sp-w:3px; }
.spinner.sm { --sp-size:15px; --sp-w:2px; }
.spinner.xs { --sp-size:12px; --sp-w:2px; }
.spinner.red   { --sp-c:var(--red2); }
.spinner.value { --sp-c:var(--value); }
/* Centred block wait-state: <div class="v-loading"><div class="spinner lg"></div><span>…</span></div> */
.v-loading {
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; padding:40px 20px; text-align:center;
  color:var(--muted); font-size:13px;
}
.v-loading.row { flex-direction:row; padding:14px 0; gap:10px; }

/* ── 19d. REDUCED MOTION ── everything above degrades here, one block ── */
@media (prefers-reduced-motion:reduce){
  .press, .v-btn                    { transition:none !important; }
  .press:active, .v-btn:active      { transform:none !important; }
  .rail > .pill                     { transition:none !important; }
  .spinner                          { animation:none !important; opacity:.6; }
}

/* ── 20. SEGMENTED CONTROL ── .vseg ──────────────────────────────────
   ONE box for every filter strip in the app. Vault used to carry 28 of
   these, each with its own background, border, radius, padding, type ramp
   and active fill — the same control rendered eight different ways
   depending on which page you were on, and at 24px tall on mobile.

     <div class="vseg"><button class="on">Market</button><button>Mine</button></div>

   Rules of the road:

   1. THE PILL OWNS "ACTIVE". `vaultRails()` (§19.2) paints the active
      background as a travelling pill, so .vseg deliberately sets NO
      background on `.on` — a strip that paints its own active fill gives
      you a chip inside a chip. The only active fill here is the
      `:not(.vrail)` fallback below, which exists purely so an unwired or
      not-yet-wired strip still shows a selection; it switches itself off
      the moment vaultRails() adds .vrail.
   2. CHILDREN ARE `<button>`. Every strip in the app already is. The
      child selector is `>` so nested controls don't inherit.
   3. RETINT WITH THE VARS, don't re-declare the box. Betting runs its own
      token scope (--bt-*), so it sets --vseg-bg/-border/-ink rather than
      redefining .vseg. Any future scoped skin does the same.

   The legacy class names are aliased onto this rule set rather than
   renamed in markup — JS across index.html queries them by name
   (`.rh-seg`, `.dvc-seg .sbtn`, …) and several are built from template
   literals. Aliasing gets one implementation with none of that risk. New
   strips should use `.vseg` and add nothing here.

   NOT aliased, on purpose: .lh-tabsel (mobile dropdown, not a strip),
   .bottom-tab-bar and .nav.desktop-nav (nav — pinned to --primary /
   --on-primary), #vp-platforms (vertical). Same exclusions as RAILS.   */
.vseg, .rh-seg, .rh-modeseg, .res-seg, .dvc-seg, .adp-seg,
.adp-scoring-seg, .bt-seg, .tc-seg, .tc-eng-tabs, .tc-mode-tabs, .mr-seg,
.pp-ptabs, .pp-seg, .src-seg, .rdv-seg, .lr-srctoggle, .lc-fmt, .lc-subnav {
  position:relative;
  display:inline-flex; align-items:center; flex-shrink:0;
  gap:2px; padding:3px; max-width:100%;
  background:var(--vseg-bg, var(--s2));
  border:1px solid var(--vseg-border, var(--border));
  border-radius:9px;
}

/* — the segment — one type ramp, one hit area, no active fill of its own. */
.vseg > button, .rh-seg > button, .rh-modeseg > button, .res-seg > button,
.dvc-seg > button, .adp-seg > button, .adp-scoring-seg > button,
.bt-seg > button, .tc-seg > button, .tc-eng-tabs > button,
.tc-mode-tabs > button, .mr-seg > button, .pp-ptabs > button,
.pp-seg > button, .src-seg > button, .rdv-seg > button,
.lr-srctoggle > button, .lc-fmt > button, .lc-subnav > button,
.vcaps > button, .pp-tabs > button, .pm-sortbar > button,
.la-tabs > button, #page-teams .lr-tabs > button {
  position:relative;
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  min-height:32px; padding:0 14px;
  border:0; background:transparent; border-radius:6px;
  color:var(--vseg-ink, var(--muted));
  font-family:var(--font-sans); font-size:12.5px; font-weight:600;
  letter-spacing:-.01em; white-space:nowrap;
  cursor:pointer;
  /* Colour-only feedback under 150ms may stay on plain ease (§19). The pill
     carries the movement; the segment must not also animate its own box. */
  transition:color .12s ease;
}

/* Several strips are built from buttons that ALSO exist standalone elsewhere
   — .sbtn and .fbtn on the draft toolbar, .la-tab in the league header — and
   those classes carry their own border, fill and uppercase treatment, some of
   it !important. Inside a strip the STRIP is the chrome, so the standalone
   look is stripped here rather than by editing every caller. Specificity is
   (0,1,1); the few ID-scoped !important rules that outranked it were deleted
   or narrowed at their source instead. */
.vseg > button, .rh-seg > button, .rh-modeseg > button, .res-seg > button,
.dvc-seg > button, .adp-seg > button, .adp-scoring-seg > button,
.bt-seg > button, .tc-seg > button, .tc-eng-tabs > button,
.tc-mode-tabs > button, .mr-seg > button, .pp-ptabs > button,
.pp-seg > button, .src-seg > button, .rdv-seg > button,
.lr-srctoggle > button, .lc-fmt > button, .lc-subnav > button,
.vcaps > button, .pp-tabs > button, .pm-sortbar > button,
.la-tabs > button, #page-teams .lr-tabs > button {
  border:0 !important;
  background:transparent !important;
  box-shadow:none !important;
  text-transform:none !important;
}
.vseg > button:hover, .rh-seg > button:hover, .rh-modeseg > button:hover,
.res-seg > button:hover, .dvc-seg > button:hover, .adp-seg > button:hover,
.adp-scoring-seg > button:hover, .bt-seg > button:hover,
.tc-seg > button:hover, .tc-eng-tabs > button:hover,
.tc-mode-tabs > button:hover, .mr-seg > button:hover,
.pp-ptabs > button:hover, .pp-seg > button:hover, .src-seg > button:hover,
.rdv-seg > button:hover, .lr-srctoggle > button:hover,
.lc-fmt > button:hover, .lc-subnav > button:hover, .vcaps > button:hover,
.pp-tabs > button:hover, .pm-sortbar > button:hover,
.la-tabs > button:hover, #page-teams .lr-tabs > button:hover {
  color:var(--text);
}
.vseg > button.on, .rh-seg > button.on, .rh-modeseg > button.on,
.res-seg > button.on, .dvc-seg > button.on, .adp-seg > button.on,
.adp-scoring-seg > button.on, .bt-seg > button.on, .tc-seg > button.on,
.tc-eng-tabs > button.on, .tc-mode-tabs > button.on, .mr-seg > button.on,
.pp-ptabs > button.on, .pp-seg > button.on, .src-seg > button.on,
.rdv-seg > button.on, .lr-srctoggle > button.on, .lc-fmt > button.on,
.lc-subnav > button.on, .vcaps > button.on, .pp-tabs > button.on,
.pm-sortbar > button.on, .la-tabs > button.on,
#page-teams .lr-tabs > button.on {
  color:var(--text);
}

/* Fallback selection for a strip vaultRails() has not claimed — either never
   (.pp-tabs is not in RAILS) or not yet, since strips rebuilt from template
   literals are unwired for a beat. It goes quiet the instant .vrail lands, so
   it can never double up with the pill. !important because the legacy-chrome
   reset above is itself !important; this is more specific (0,3,1 vs 0,1,1),
   which is what wins the tie. */
.vseg:not(.vrail) > button.on, .rh-seg:not(.vrail) > button.on,
.rh-modeseg:not(.vrail) > button.on, .res-seg:not(.vrail) > button.on,
.dvc-seg:not(.vrail) > button.on, .adp-seg:not(.vrail) > button.on,
.adp-scoring-seg:not(.vrail) > button.on, .bt-seg:not(.vrail) > button.on,
.tc-seg:not(.vrail) > button.on, .tc-eng-tabs:not(.vrail) > button.on,
.tc-mode-tabs:not(.vrail) > button.on, .mr-seg:not(.vrail) > button.on,
.pp-ptabs:not(.vrail) > button.on, .pp-seg:not(.vrail) > button.on,
.src-seg:not(.vrail) > button.on, .rdv-seg:not(.vrail) > button.on,
.lr-srctoggle:not(.vrail) > button.on, .lc-fmt:not(.vrail) > button.on,
.lc-subnav:not(.vrail) > button.on, .vcaps:not(.vrail) > button.on,
.pp-tabs:not(.vrail) > button.on, .pm-sortbar:not(.vrail) > button.on,
.la-tabs:not(.vrail) > button.on,
#page-teams .lr-tabs:not(.vrail) > button.on {
  background:var(--s4) !important;
}

.vseg > button svg, .rh-seg > button svg, .rh-modeseg > button svg,
.res-seg > button svg, .dvc-seg > button svg, .adp-seg > button svg,
.adp-scoring-seg > button svg, .bt-seg > button svg, .tc-seg > button svg,
.tc-eng-tabs > button svg, .tc-mode-tabs > button svg,
.mr-seg > button svg, .pp-ptabs > button svg, .pp-seg > button svg,
.src-seg > button svg, .rdv-seg > button svg, .lr-srctoggle > button svg,
.lc-fmt > button svg, .lc-subnav > button svg, .vcaps > button svg,
.pp-tabs > button svg, .pm-sortbar > button svg, .la-tabs > button svg,
#page-teams .lr-tabs > button svg {
  width:15px; height:15px; stroke-linecap:round; stroke-linejoin:round;
}
.vseg > button:disabled, .rh-seg > button:disabled,
.rh-modeseg > button:disabled, .res-seg > button:disabled,
.dvc-seg > button:disabled, .adp-seg > button:disabled,
.adp-scoring-seg > button:disabled, .bt-seg > button:disabled,
.tc-seg > button:disabled, .tc-eng-tabs > button:disabled,
.tc-mode-tabs > button:disabled, .mr-seg > button:disabled,
.pp-ptabs > button:disabled, .pp-seg > button:disabled,
.src-seg > button:disabled, .rdv-seg > button:disabled,
.lr-srctoggle > button:disabled, .lc-fmt > button:disabled,
.lc-subnav > button:disabled, .vcaps > button:disabled,
.pp-tabs > button:disabled, .la-tabs > button:disabled,
#page-teams .lr-tabs > button:disabled {
  opacity:.4; cursor:default;
}

/* — sizes — press depth in §19 works the same way: a denser strip gets a
   smaller step, not a smaller copy of the same step. */
.vseg.sm > button, .rh-seg.sm > button, .tc-seg.sm > button,
.mr-seg.sm > button, .dvc-seg.sm > button {
  min-height:28px; padding:0 11px; font-size:11.5px;
}
.vseg.lg > button { min-height:36px; padding:0 16px; font-size:13px; }

/* — fluid — strip stretches, segments share the width evenly. */
.vseg.fluid, .rh-scope .rh-seg, .rh-scope .rh-modeseg { display:flex; }
.vseg.fluid > button, .rh-scope .rh-seg > button,
.rh-scope .rh-modeseg > button { flex:1 1 0; }

/* — scroll — a strip too wide for its container scrolls rather than wrapping
   or clipping. The 320px finding in the 2026-06-29 responsiveness check was a
   tab strip that did neither and put its third tab out of reach. The pill
   lives INSIDE the strip (§19.2 trap 3), so it scrolls with the segments. */
.vseg.scroll, .vcaps, .pp-tabs, .pm-sortbar, .pp-ptabs, .la-tabs,
#page-teams .lr-tabs {
  overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none;
}
.vseg.scroll::-webkit-scrollbar, .vcaps::-webkit-scrollbar,
.pp-tabs::-webkit-scrollbar, .pm-sortbar::-webkit-scrollbar,
.pp-ptabs::-webkit-scrollbar,
.la-tabs::-webkit-scrollbar,
#page-teams .lr-tabs::-webkit-scrollbar { display:none; }

/* — free — no box; the segments float directly on the page (mobile caps strip,
   player-profile tabs). Same type, same hit area, no container chrome. */
.vseg.free, .vcaps, .pp-tabs, .pm-sortbar {
  position:relative;
  display:flex; align-items:center;
  background:none; border:0; padding:0; gap:6px;
}
.vseg.free > button, .vcaps > button, .pp-tabs > button,
.pm-sortbar > button {
  border-radius:var(--radius-pill);
}

/* — bar — underline tabs. One shared 2px bar travels (.v-pill--bar); the strip
   only draws the hairline it rides on. Segments square off and sit flush. */
.vseg.bar, .la-tabs, #page-teams .lr-tabs {
  position:relative;
  display:flex; align-items:center;
  gap:0; padding:0;
  background:none; border:0; border-radius:0;
  border-bottom:1px solid var(--border);
}
.vseg.bar > button, .la-tabs > button, #page-teams .lr-tabs > button {
  min-height:40px; padding:0 14px; border-radius:0; flex-shrink:0;
}

/* — mobile hit area — WCAG 2.5.5 wants 44px; these strips were shipping 24px
   (26px buttons under a transform:scale(.92) that no longer exists). Growing
   the segment to 44 would make a 50px-tall strip, so the visual box goes to 36
   and a transparent ::after carries the rest. Vertical only: horizontal slop
   would overlap the neighbouring segment and steal its taps. */
@media (max-width:768px){
  .vseg > button, .rh-seg > button, .rh-modeseg > button,
  .res-seg > button, .dvc-seg > button, .adp-seg > button,
  .adp-scoring-seg > button, .bt-seg > button, .tc-seg > button,
  .tc-eng-tabs > button, .tc-mode-tabs > button, .mr-seg > button,
  .pp-ptabs > button, .pp-seg > button, .src-seg > button,
  .rdv-seg > button, .lr-srctoggle > button, .lc-fmt > button,
  .lc-subnav > button, .vcaps > button, .pp-tabs > button,
  .pm-sortbar > button, .la-tabs > button,
  #page-teams .lr-tabs > button {
    min-height:36px; padding:0 12px;
  }
  .vseg > button::after, .rh-seg > button::after,
  .rh-modeseg > button::after, .res-seg > button::after,
  .dvc-seg > button::after, .adp-seg > button::after,
  .adp-scoring-seg > button::after, .bt-seg > button::after,
  .tc-seg > button::after, .tc-eng-tabs > button::after,
  .tc-mode-tabs > button::after, .mr-seg > button::after,
  .pp-ptabs > button::after, .pp-seg > button::after,
  .src-seg > button::after, .rdv-seg > button::after,
  .lr-srctoggle > button::after, .lc-fmt > button::after,
  .lc-subnav > button::after, .vcaps > button::after,
  .pp-tabs > button::after, .pm-sortbar > button::after,
  .la-tabs > button::after,
  #page-teams .lr-tabs > button::after {
    content:''; position:absolute; left:0; right:0; top:-4px; bottom:-4px;
  }
}
