/* =========================================================
   TYPEFLOW — DESIGN SYSTEM
   One source of truth. Components below only ever read these
   tokens, so a theme (the body[data-theme="…"] blocks near the
   end of this file) reskins the whole UI by overriding tokens
   once — and, new in this redesign, by flipping a small set of
   "character kit" knobs (surface finish, corner language, the
   signature accent detail) so themes read as distinct moods,
   not just palettes.

   Token groups: palette · type · shape · depth · character · motion
========================================================= */

:root {
  /* PALETTE — "precision" dark. A near-black cooled a touch blue and an
     INK accent: the accent is the text colour itself (near-white here,
     near-black in the light default), so active states, the caret and
     the primary action read as solid ink with no hue competing. Colour
     is left to semantic states (success / danger / live) and to the
     purchasable themes below, so every unlock is a real change. */
  --bg: #0a0c10;
  --surface: #14171f;
  --surface-2: #1c2029;
  --text: #edeff3;
  --muted: #9ba3b4;

  /* Derived from the tokens above so a theme block that only sets bg /
     surface / surface-2 / text / accent still gets all of them. OPAQUE
     mixes toward --surface (not a transparent tint — that barely
     registered on a dark surface), landing near the hand-picked light
     default's border weight. The light default overrides --border /
     --border-strong below with exact greys. */
  --surface-3: color-mix(in srgb, var(--surface-2) 82%, var(--text));  /* hover / pressed */
  --border: color-mix(in srgb, var(--surface) 87%, var(--text));
  --border-strong: color-mix(in srgb, var(--surface) 70%, var(--text));
  /* Untyped test characters, disabled text, faint labels. */
  --faint: color-mix(in srgb, var(--muted) 55%, var(--bg));

  --accent: #f2f4f7;
  /* Hover brightens toward the text colour (lighter on dark themes,
     darker on light — right either way); press deepens. Both derive so
     a theme only has to set --accent. */
  --accent-hover: color-mix(in srgb, var(--accent) 86%, var(--text));
  --accent-press: color-mix(in srgb, var(--accent) 84%, #000);
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
  /* Text/icons ON a solid --accent fill — dark here (a near-white pill
     with an ink label); white on the light default below, where the
     accent is near-black. */
  --on-accent: #0a0c10;
  /* Kept (≈40 rules reference it) but the redesign retires the
     violet→coral gradient: it's now just a lighter tint of the one
     accent, for the few places a second stop still earns its keep
     (charts, one hero sweep). */
  --accent-2: #b4bbc8;

  --success: #35c98b;
  --danger: #ff5c5c;
  --warning: #f5a623;

  /* TYPE */
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-display: var(--font);   /* headings + big numerals — a theme may swap */
  --test-font: var(--font);      /* the typing passage — code mode forces mono */

  /* SHAPE — tighter than before (was 10/16/24). A theme can go sharper
     (cyber, terminal, obsidian) or softer (sakura, linen, lilac). */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-full: 999px;

  /* DEPTH — elevation reads from a crisp hairline plus a short, tight
     shadow, not a big soft blur.
     A dark drop-shadow is doing nothing here — the page behind a card is
     already near-black, so a black shadow has no ground to show against
     (verified: computed box-shadow renders, but is genuinely invisible).
     Light mode's identical shadow formula works because it sits on white.
     The fix dark UIs actually use: a card "catches light" via a faint
     inset highlight along its top edge instead of relying on a shadow
     that can't read against a dark ground — every --shadow-* below pairs
     one with the drop-shadow (harmless if the drop-shadow doesn't show;
     it's what now gives cards their visible lift). */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.045);
  --shadow-md: 0 10px 30px -10px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  --shadow-lg: 0 28px 64px -18px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --focus-ring: 0 0 0 3px var(--accent-soft), 0 0 0 1px var(--accent);

  /* CARET — one set of knobs, read by the per-character ::before caret,
     the floating smooth caret (.live-caret), and the customize-modal
     preview bar alike. A cursor skin reskins the caret purely by
     overriding these, so a skin now shows in every caret mode
     (previously each skin hard-styled ::before and nothing else). The
     caret-shape display pref layers geometry on top. */
  --caret-bg: var(--accent);
  --caret-bg-size: auto;
  --caret-glow: none;   /* the ink default needs no halo; themes set their own */
  --caret-anim: blink 1.1s var(--ease) infinite;
  --caret-width: 2px;
  --caret-clip: none;

  /* CHARACTER KIT — neutral defaults so component rules can read them
     unconditionally; theme blocks near the end of the file flip them.
     Deliberately NOT here: radius/shadow/font. Every theme shares those,
     so the shell/layout never reshapes when you switch themes — only
     colour, the signature accent below, and a theme's own background
     layer do. */
  --card-border: 1px solid var(--border);  /* premium themes → a gradient frame */
  --pill-active-bg: var(--accent);    /* the signature detail: a solid ink pill */
  --pill-active-fg: var(--on-accent);
  --pill-active-ring: none;

  /* MOTION */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-1: 120ms;
  --dur-2: 200ms;
  --dur-3: 320ms;

  /* LAYOUT — every full-width page region (header, home sections, the
     word box, results, community, dashboard, shop, profile, race,
     achievements, footer) is exactly `min(--container, 100% - 2*--gutter)`
     and centred, so their left/right edges line up down the whole page.
     --container-narrow is for single-column reading pages (legal, contact,
     account settings). Nothing sets its own width outside this system. */
  --container: 1080px;
  --container-narrow: 680px;
  --gutter: 24px;

  color-scheme: dark;
}

/* The one width utility. Every page shell resolves to this. */
.shell,
.page-container,
.site-header-inner,
.results-shell,
.home-today,
.home-community,
.dashboard-shell,
.profile-container,
.race-track-shell,
.race-lobby-shell,
.settings-shell,
.legal-page,
.footer-inner,
.footer-bottom,
.daily-challenge-bar,
#word-container {
  width: min(var(--container), 100% - 2 * var(--gutter));
  margin-inline: auto;
}

.legal-page,
.settings-shell {
  width: min(var(--container-narrow), 100% - 2 * var(--gutter));
}

@media (max-width: 600px) {
  :root { --gutter: 16px; }
}

/* The default LIGHT look — shown to logged-out / no-theme-equipped
   visitors whose OS asks for light. Not an inverted afterthought:
   the ink accent becomes near-black (the light --text), and --on-accent
   flips to white (the filled button becomes a solid black pill). Theme blocks
   set these same properties on <body>, which always beats what :root
   gives here regardless of source order. */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #fafbff;
    --surface: #ffffff;
    --surface-2: #f2f4f9;
    --surface-3: #e8ebf2;
    --border: #e5e8f0;
    --border-strong: #cfd5e2;

    --text: #10131a;
    --muted: #5c6475;

    --accent: #10131a;
    --accent-hover: #2a2f3a;
    --accent-press: #000000;
    --accent-soft: rgba(16, 19, 26, 0.07);
    --on-accent: #ffffff;
    --accent-2: #5c6475;

    --success: #12905c;
    --danger: #d92d2d;
    --warning: #b26a00;

    --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
    --shadow-md: 0 8px 24px -8px rgba(15, 23, 42, 0.12);
    --shadow-lg: 0 24px 60px -16px rgba(15, 23, 42, 0.16);

    color-scheme: light;
  }
}

/* Honour a reduced-motion request — this site has ~40 decorative
   animations (theme shimmer sweeps, particle drifts, ambient pulses,
   the caret blink) and had zero handling for it before. Near-instant
   rather than a hard 0 so state changes still register. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================
   THEMES — token blocks + character kits
   Slugs match the real ShopItem catalog (item_type "theme" /
   "limited"). Each block sets its palette and flips whichever
   "kit" knobs (radii, surface finish, the --pill-active-*
   signature, fonts) give it a personality beyond colour.
   Tokens NOT set here derive from the ones that are (see the
   color-mix defaults in :root) — so --accent-soft / --border /
   --accent-hover / --surface-3 / --border-strong usually don't
   need restating.

   `[data-theme-scope="x"]` is an alias on every block so the
   customize-modal swatch can render a real mini-preview by
   scoping the theme's tokens locally. Decorative ::before /
   background layers stay body-only and live further down.

   Reusable font stacks:
   mono  → ui-monospace, 'Cascadia Code', 'JetBrains Mono', 'Source Code Pro', Menlo, Consolas, monospace
   serif → 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, Cambria, 'Times New Roman', serif
========================================================= */

/* Borders + elevation + the hover surface, one rule for all 28 equippable
   themes. A NEUTRAL translucent edge — light-dark() reads each theme's own
   color-scheme and picks the right one, so no hue pickup on either side.
   (The old `color-mix(--surface 87%, --text 13%)` mixed 13% near-white
   into a saturated dark surface and rendered as a bright coloured
   outline.) The free default (:root + the light @media block) keeps its
   own hand-picked greys and is untouched by this. */
body[data-theme], [data-theme-scope] {
  --border: light-dark(rgba(17, 24, 39, 0.11), rgba(255, 255, 255, 0.12));
  --border-strong: light-dark(rgba(17, 24, 39, 0.22), rgba(255, 255, 255, 0.22));
  --surface-3: color-mix(in srgb, var(--surface-2), light-dark(#0b1120, #ffffff) 7%);
  /* Re-declared here (not just inherited from :root) so its var(--border)
     substitutes THIS theme's border. Premium themes override it below
     with a gradient frame. */
  --card-border: 1px solid var(--border);

  /* Resting-card elevation for the DARK themes — a black drop shadow is
     invisible on a near-black page, so the card reads from a bright
     hairline along its top edge plus a tight ambient shadow. Set here
     (not left to inherit from the @media (prefers-color-scheme) :root
     block) so a dark theme looks right even when the viewer's OS is in
     light mode. All ten light theme blocks set their own soft drop
     shadow and win over this; --shadow-md / --shadow-lg (hover, modals)
     are left alone. */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.07);

  /* Everything that derives from --accent, re-declared on this element so
     var(--accent) re-substitutes per theme. On :root these read the light
     default's --accent (#10131a) and then inherit down as that frozen
     string — which is why the signature "active pill" was blue on every
     theme regardless of its accent. A theme block may still set any of
     these explicitly and win (it's later in the cascade). */
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-hover: color-mix(in srgb, var(--accent) 86%, var(--text));
  --accent-press: color-mix(in srgb, var(--accent) 84%, #000);
  --pill-active-bg: var(--accent-soft);
  --pill-active-fg: var(--accent);
  --pill-active-ring: inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
  --faint: color-mix(in srgb, var(--muted) 55%, var(--bg));
  --caret-bg: var(--accent);
  --caret-glow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent);
}

/* ---- NIGHT / DARK family ---------------------------------------------
   Solid surfaces, soft elevation, the slim bar caret. */

/* The quiet one — even darker and more neutral than the default, zero
   texture, zero motion, a muted steel-indigo accent. "Lights off." */
body[data-theme="focus-dark"],
[data-theme-scope="focus-dark"] {
  --bg: #0a0e17;
  --surface: #131826;
  --surface-2: #1b2233;
  --text: #eef2f8;
  --muted: #93a0b6;
  --accent: #8896ff;
  --accent-2: #55d6d0;
  --on-accent: #0a0e1c;
  color-scheme: dark;
}

/* Moonlight on water — a saturated deep navy (where focus-dark is
   neutral), a clean moonlit-blue accent, pale silver secondary. A low
   shimmer band + sparse far stars (see ::before). */
/* Midnight — moonlight on water. A calm, slightly desaturated navy with a
   muted moonlit-steel accent; the one decorative touch is a soft moon
   glow in a corner (static). */
/* Midnight — the moonlit one, not the starry one (that's Galaxy). Accent
   shifted from a fairly generic sky-blue to a paler, cooler moonlight
   tone so it doesn't just read as "another dark blue theme" — see the
   ::before moon disc below for the other half of this fix. */
body[data-theme="midnight"],
[data-theme-scope="midnight"] {
  --bg: #080d1a;
  --surface: #121a30;
  --surface-2: #1c2540;
  --text: #e8ecf6;
  --muted: #8891ab;
  --accent: #9fb3dc;
  --accent-2: #5f7bb0;
  --accent-soft: rgba(159, 179, 220, 0.13);
  --on-accent: #080d1a;
  color-scheme: dark;
}

/* Deep Ocean — restored to the exact original (2026-09-15, commit
   fc35d28~1) after four straight rejected attempts to "improve" this
   slot (Fathom x2, Cadence, Marble — "the look of marble is horrible
   and doesn't suit the website aesthetic"). This was never actually
   the thing that got complained about — it was next in line for the
   same treatment that worked well on Aurora/Deep Ocean's neighbours,
   and none of the four replacement attempts landed. Reverting to known
   ground instead of guessing a fifth time. Do not touch again without
   much more specific direction than "feel free to be creative." */
body[data-theme="deep-ocean"],
[data-theme-scope="deep-ocean"] {
  --bg: #021316;
  --surface: #08262b;
  --surface-2: #0e3a41;
  --text: #dbf9ff;
  --muted: #78b0b7;
  --accent: #1fcfc0;
  --accent-2: #4aa8ea;
  --accent-soft: rgba(31, 207, 192, 0.14);
  --on-accent: #021e19;
  color-scheme: dark;
}

body[data-theme="deep-ocean"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(3px 3px at 20% 90%, rgba(31, 207, 192, 0.45), transparent),
    radial-gradient(2px 2px at 50% 80%, rgba(74, 168, 234, 0.4), transparent),
    radial-gradient(2.5px 2.5px at 75% 95%, rgba(31, 207, 192, 0.45), transparent),
    radial-gradient(2px 2px at 90% 70%, rgba(74, 168, 234, 0.4), transparent);
  background-size: 300px 300px;
  animation: oceanBubbles 10s linear infinite;
}

@keyframes oceanBubbles {
  from { background-position: 0 0, 0 0, 0 0, 0 0; }
  to { background-position: 20px -300px, -15px -300px, 10px -300px, -20px -300px; }
}

/* Deep space — the flagship "cosmos" look. Solid tinted surfaces (same
   shape/depth system as every other theme) with a dense drifting
   starfield + nebula glow behind them. */
/* Galaxy — deep cosmos. Pulled right back from the saturated purple-on-
   purple it was: a near-black void, a muted cosmic violet accent, and one
   sparse slow starfield (the thing that actually makes it "Galaxy"). */
body[data-theme="galaxy"],
[data-theme-scope="galaxy"] {
  --bg: #0a0912;
  --surface: #151420;
  --surface-2: #1f1d2e;
  --text: #ebe9f2;
  --muted: #948fa6;
  --accent: #a08ad0;
  --accent-2: #c6bbe2;
  --accent-soft: rgba(160, 138, 208, 0.12);
  --on-accent: #100e1a;
  color-scheme: dark;
}

/* ---- CYBER / SYNTH family ------------------------------------------
   Same shape/type system as every other theme — the signature here is a
   hard 1px accent ring on the active segment plus glow, not a reshaped
   UI. */

/* Tokyo at night — a moody near-black with one dusty rose-pink accent
   and a muted amber second. The neon is turned right down: it reads as a
   quiet late-night street, not a sign explosion. */
body[data-theme="neon-tokyo"],
[data-theme-scope="neon-tokyo"] {
  --bg: #0b0910;
  --surface: #16131b;
  --surface-2: #201c27;
  --text: #f0e9ef;
  --muted: #9a909e;
  --accent: #d16590;
  --accent-2: #d0a15f;
  --accent-soft: rgba(209, 101, 144, 0.13);
  --on-accent: #1e0812;
  color-scheme: dark;
}

/* ---- DAYLIGHT / LIGHT family --------------------------------------
   Bright surfaces, crisp hairlines, a soft short shadow. */

/* Sky — clear sky blue with a sunshine-yellow second colour. The yellow
   is decorative only (charts, effects, highlights), deepened to
   sunflower so a dashed chart line still shows on white. Replaced
   Copper's slot (epic, 350 coins). */
body[data-theme="sky"],
[data-theme-scope="sky"] {
  --bg: #e9f4ff;
  --surface: #ffffff;
  --surface-2: #dcebfa;
  --text: #0e2440;
  --muted: #5b7593;
  --accent: #1f6fd1;
  --accent-2: #f0b400;
  --accent-soft: rgba(31, 111, 209, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(14, 36, 64, 0.08);
  color-scheme: light;
}

/* Cherry — warm cream and a glossy cherry red, retro-modern. Near-black
   warm ink for text so the red is the only colour on the page. Replaced
   Graphite's slot (legendary, 600 coins). */
body[data-theme="cherry"],
[data-theme-scope="cherry"] {
  --bg: #fbf7f0;
  --surface: #fffdf9;
  --surface-2: #f2eadd;
  --text: #1e1414;
  --muted: #7a6a64;
  --accent: #d1203a;
  --accent-2: #e88a96;
  --accent-soft: rgba(209, 32, 58, 0.09);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(30, 20, 20, 0.08);
  color-scheme: light;
}

/* Frost — icy blue-white, everything crisp, snow swaying past. */
body[data-theme="frost"],
[data-theme-scope="frost"] {
  --bg: #eef5fb;
  --surface: #ffffff;
  --surface-2: #e3eef8;
  --text: #0e2a44;
  --muted: #5a7891;
  --accent: #0a6a9c;
  --accent-2: #0f8279;
  --accent-soft: rgba(14, 122, 176, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(14, 42, 68, 0.08);
  --shadow-md: 0 10px 30px -10px rgba(14, 42, 68, 0.16);
  color-scheme: light;
}

/* Circuit — the light-tech slot Matrix's removal left open, done as
   clean blueprint/schematic precision instead of a dark-terminal code
   rain. Crisp cool white with typeflow's own precision-blue as the
   accent (a deliberate echo of the site's own brand colour — "this is
   what pure digital precision looks like") and a teal-cyan secondary.
   Distinct from Frost: that's icy cyan-blue and wintry, this is a
   brighter, more indigo "electric" blue with no icy/seasonal framing. */
body[data-theme="circuit"],
[data-theme-scope="circuit"] {
  --bg: #f1f5fb;
  --surface: #ffffff;
  --surface-2: #e4eaf5;
  --text: #101a33;
  --muted: #64708c;
  --accent: #3461e0;
  --accent-2: #14b8a6;
  --accent-soft: rgba(52, 97, 224, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(16, 26, 51, 0.08);
  color-scheme: light;
}

/* Signature: a tiled circuit-trace pattern — the wires themselves stay
   static/faint (crisp schematic lines, not a glow), but a short bright
   dash travels along each one on a loop (SMIL <animate> on
   stroke-dashoffset, embedded in the SVG itself — CSS on ::before can't
   reach inside a background-image, but the SVG can animate its own
   paths regardless of how it's referenced). Reads as live signal
   traversal rather than the whole layer just breathing. */
body[data-theme="circuit"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cg stroke='%233461e0' stroke-width='1' fill='none' opacity='0.4'%3E%3Cpath d='M10,50 L70,50 L70,110 L140,110'/%3E%3Cpath d='M210,30 L210,90 L160,90 L160,160'/%3E%3Cpath d='M30,180 L90,180 L90,220'/%3E%3C/g%3E%3Cg fill='%233461e0' opacity='0.55'%3E%3Ccircle cx='70' cy='50' r='2'/%3E%3Ccircle cx='140' cy='110' r='2'/%3E%3Ccircle cx='210' cy='30' r='2'/%3E%3Ccircle cx='160' cy='160' r='2'/%3E%3Ccircle cx='90' cy='180' r='2'/%3E%3C/g%3E%3Cg stroke='%233461e0' stroke-width='1.5' fill='none' stroke-linecap='round'%3E%3Cpath d='M10,50 L70,50 L70,110 L140,110' stroke-dasharray='6 184'%3E%3Canimate attributeName='stroke-dashoffset' values='0;-190' dur='3.6s' repeatCount='indefinite'/%3E%3C/path%3E%3Cpath d='M210,30 L210,90 L160,90 L160,160' stroke-dasharray='6 174'%3E%3Canimate attributeName='stroke-dashoffset' values='0;-180' dur='3.2s' repeatCount='indefinite'/%3E%3C/path%3E%3Cpath d='M30,180 L90,180 L90,220' stroke-dasharray='6 94'%3E%3Canimate attributeName='stroke-dashoffset' values='0;-100' dur='2.1s' repeatCount='indefinite'/%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
  background-size: 240px 240px;
  opacity: 0.42;
}

/* Sakura — a quiet blush. One dusty-rose accent on a warm paper white;
   the old vivid magenta + bamboo-green pair fought each other. */
/* Sakura — a warm blush. The surface itself is tinted rose (not near-
   white), so the whole UI reads pink; one warm cherry-rose accent. */
body[data-theme="sakura"],
[data-theme-scope="sakura"] {
  --bg: #fbe9ee;
  --surface: #fff4f7;
  --surface-2: #f7dde5;
  --text: #40272f;
  --muted: #8d6771;
  --accent: #bb3e69;
  --accent-2: #e698b0;
  --accent-soft: rgba(195, 68, 112, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(120, 60, 80, 0.09);
  --shadow-md: 0 12px 34px -12px rgba(120, 60, 80, 0.16);
  color-scheme: light;
}

/* Mocha (slug "solar-flare") — a coffee house. Espresso brown on warm
   latte cream, a caramel secondary, faint rising steam. Cosy warm-neutral
   light theme — the counterpart to Linen's cool oatmeal. */
body[data-theme="solar-flare"],
[data-theme-scope="solar-flare"] {
  --bg: #f3ebdd;
  --surface: #fdf8f0;
  --surface-2: #ece0cd;
  --text: #32261a;
  --muted: #877360;
  --accent: #7b4a2d;
  --accent-2: #b5895f;
  --accent-soft: rgba(123, 74, 45, 0.1);
  --on-accent: #fdf8f0;
  /* A muted matcha "success" — a teal Equipped tag would fight the browns. */
  --success: #5f7738;
  --shadow-sm: 0 1px 3px rgba(70, 48, 28, 0.1);
  color-scheme: light;
}

/* Autumn harvest — parchment + rust + olive, a fibre-grain paper,
   leaves drifting down. */
body[data-theme="autumn-harvest"],
[data-theme-scope="autumn-harvest"] {
  --bg: #fbf3e6;
  --surface: #fffdf8;
  --surface-2: #f6e6cf;
  --text: #43280f;
  --muted: #94704e;
  --accent: #a63d1f;
  --accent-2: #5c6b2f;
  --accent-soft: rgba(166, 61, 31, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(80, 50, 20, 0.09);
  color-scheme: light;
}

/* Linen — oatmeal, understated, a woven crosshatch, no motion. The
   quiet-light mirror of focus-dark. */
body[data-theme="linen"],
[data-theme-scope="linen"] {
  --bg: #f8f4ee;
  --surface: #fffdf9;
  --surface-2: #efe8db;
  --text: #2c261e;
  --muted: #776a56;
  --accent: #9a5a1e;
  --accent-2: #a2402a;
  --accent-soft: rgba(154, 90, 30, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 2px rgba(60, 48, 30, 0.08);
  color-scheme: light;
}

/* Meadow — a calm sage. One muted botanical green on a soft green-white;
   the old grass-green + wildflower-gold pair was two competing hues. */
body[data-theme="meadow"],
[data-theme-scope="meadow"] {
  --bg: #eaf4e2;
  --surface: #f6fcf1;
  --surface-2: #dcecd0;
  --text: #223618;
  --muted: #5b7150;
  --accent: #3c7a3f;
  --accent-2: #7fb173;
  --accent-soft: rgba(60, 122, 63, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(35, 58, 25, 0.09);
  color-scheme: light;
}

/* Rose quartz — a cool grey-lilac. Deliberately cooler and more violet
   than Sakura's warm pink: a grey-mauve surface, a muted orchid accent. */
body[data-theme="rose-quartz"],
[data-theme-scope="rose-quartz"] {
  --bg: #f1edf2;
  --surface: #faf7fb;
  --surface-2: #e6dfe9;
  --text: #322c37;
  --muted: #7d7486;
  --accent: #8a5c92;
  --accent-2: #b79bc2;
  --accent-soft: rgba(138, 92, 146, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(70, 60, 78, 0.08);
  color-scheme: light;
}

/* Citrus grove — reframed as warm honey. A single amber on soft cream;
   the old lemon-yellow surfaces + lime-green accent read as a soda
   label, and its filter-based "zing" animation (removed below) was
   shifting the layout. */
/* Citrus grove — sun-warmed gold. A distinctly yellow cream surface
   (not the neutral oatmeal of linen) and a rich amber-gold accent. */
body[data-theme="citrus-grove"],
[data-theme-scope="citrus-grove"] {
  --bg: #fdf5d6;
  --surface: #fffdec;
  --surface-2: #f6ebbf;
  --text: #3a3009;
  --muted: #7e6d2c;
  --accent: #916a04;
  --accent-2: #cba333;
  --accent-soft: rgba(145, 106, 4, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(90, 75, 20, 0.09);
  color-scheme: light;
}

/* Lilac bloom — a soft lavender. One muted violet on a cool grey-white;
   the old electric violet + royal blue pair was loud, and its
   filter-based "breathe" animation is dropped below. */
body[data-theme="lilac-bloom"],
[data-theme-scope="lilac-bloom"] {
  --bg: #efeafb;
  --surface: #f9f6ff;
  --surface-2: #e2d9f4;
  --text: #2c2444;
  --muted: #746a92;
  --accent: #6a4fc0;
  --accent-2: #a48ee0;
  --accent-soft: rgba(106, 79, 192, 0.1);
  --on-accent: #ffffff;
  --shadow-sm: 0 1px 3px rgba(55, 45, 90, 0.09);
  color-scheme: light;
}

/* ---- PREMIUM (gems) — subtle luxury -----------------------------
   A deeper, rarer palette carried with the same restraint as Frost /
   Linen / Meadow, plus at most one refined material touch AND (as of
   2026-09-13, revisiting the earlier "nothing moves" rule below — user
   feedback: the fully flat version read as incomplete, not restrained)
   one quiet signature ::before per theme: static or very slow, low
   opacity, nothing that reads as a shimmer sweep or a glow/flicker —
   the same bar as Midnight's moon or Autumn Harvest's leaves. */

/* Prism Storm — a deep amethyst done quietly. One muted orchid accent on
   a rich aubergine near-black. */
body[data-theme="prism-storm"],
[data-theme-scope="prism-storm"] {
  --bg: #130c19;
  --surface: #1e1626;
  --surface-2: #2a2033;
  --text: #efe8f4;
  --muted: #9c8fa8;
  --accent: #a878c8;
  --accent-2: #c8a8db;
  --accent-soft: rgba(168, 120, 200, 0.12);
  --on-accent: #17101d;
  color-scheme: dark;
}

/* Signature: a soft glow that drifts very slowly through adjacent hues —
   "prism" as a colour-temperature shift, not a rainbow cycle. 32s/30deg
   keeps it in violet-to-blue territory the whole time. */
body[data-theme="prism-storm"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(55% 45% at 50% 0%, rgba(168, 120, 200, 0.12), transparent 70%);
  animation: prismDrift 32s ease-in-out infinite;
}

@keyframes prismDrift {
  0%, 100% { filter: hue-rotate(0deg); }
  50% { filter: hue-rotate(30deg); }
}

/* Celestial Eclipse — deep space at rest. A soft champagne accent and a
   cool silver-blue secondary on charcoal-navy. */
body[data-theme="celestial-eclipse"],
[data-theme-scope="celestial-eclipse"] {
  --bg: #0b0d13;
  --surface: #16181f;
  --surface-2: #20232c;
  --text: #e8eaef;
  --muted: #8f93a0;
  --accent: #c7ba98;
  --accent-2: #a9b4c4;
  --accent-soft: rgba(199, 186, 152, 0.12);
  --on-accent: #1a160c;
  color-scheme: dark;
}

/* No ::before here on purpose — a corona-ring signature was tried
   (2026-09-13) and rejected ("don't like the ring in the background").
   Left flat rather than guess at a replacement shape blind a second
   time this session; see theme-system.md memory before trying again. */

/* ---- LIMITED — a deeper cut of the same restraint ---------------- */

/* Halloween v2 — a carved-pumpkin ember glowing in a warm near-black
   (not the aubergine-purple pairing from before). Body is neutral warm
   dark, not tinted purple, so the ember accent is the one thing with
   colour; a muted plum stays only as a quiet secondary. */
body[data-theme="halloween"],
[data-theme-scope="halloween"] {
  --bg: #0d0907;
  --surface: #181310;
  --surface-2: #221b16;
  --text: #f2e9df;
  --muted: #a89686;
  --accent: #dd7f3a;
  --accent-2: #8868a0;
  --accent-soft: rgba(221, 127, 58, 0.13);
  --on-accent: #1f0d02;
  color-scheme: dark;
}

/* Signature: a carved-pumpkin ember that breathes slowly (6s) rather
   than flickers — a candle guttering, not a strobe. */
body[data-theme="halloween"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(45% 35% at 18% 88%, rgba(221, 127, 58, 0.16), transparent 70%);
  animation: emberBreathe 6s ease-in-out infinite;
}

@keyframes emberBreathe {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* Winter Lights v2 (slug "christmas") — a cold, quiet Christmas Eve:
   deep pine-black with a frosted icy-blue accent (ice/snow, not another
   warm-gold theme — Gold Prestige already owns gold), and a single warm
   berry red kept as a rare secondary, like one ornament in the dark. */
body[data-theme="christmas"],
[data-theme-scope="christmas"] {
  --bg: #08120d;
  --surface: #101d16;
  --surface-2: #182920;
  --text: #e8f2ec;
  --muted: #89a696;
  --accent: #8fc4d9;
  --accent-2: #c85a52;
  --accent-soft: rgba(143, 196, 217, 0.14);
  --on-accent: #0a1811;
  color-scheme: dark;
}

/* Signature: a still row of soft icy-white lights along the top edge —
   a string of lights, deliberately not blinking. */
body[data-theme="christmas"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(2px 2px at 12% 7%, rgba(217, 238, 247, 0.85), transparent),
    radial-gradient(2px 2px at 30% 4%, rgba(217, 238, 247, 0.85), transparent),
    radial-gradient(2px 2px at 48% 8%, rgba(217, 238, 247, 0.85), transparent),
    radial-gradient(2px 2px at 66% 3%, rgba(217, 238, 247, 0.85), transparent),
    radial-gradient(2px 2px at 84% 7%, rgba(217, 238, 247, 0.85), transparent);
  opacity: 0.5;
}

/* Gold Prestige v2 (slug "prestige-gold") — money, unmistakably. The
   antique/muted gold read as dull brass rather than treasure, so this is
   a true near-black and a vivid classic gold (the real "metallic gold"
   hue) with a pale gold shine second. Still just one accent colour and
   the one material touch: a thin brushed-gold hairline on every card. */
body[data-theme="prestige-gold"],
[data-theme-scope="prestige-gold"] {
  --bg: #0b0904;
  --surface: #161206;
  --surface-2: #211a09;
  --text: #f5eed9;
  --muted: #b7a172;
  --accent: #d4af37;
  --accent-2: #e8cf8a;
  --accent-soft: rgba(212, 175, 55, 0.14);
  --on-accent: #241a02;
  --card-border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(212, 175, 55, 0.09);
  color-scheme: dark;
}

/* Signature: a still warm-gold glow rising from one corner — light
   pooling on brushed metal, not a moving shimmer sweep (tried a
   background-position sweep first; the bright band spent most of its
   cycle scrolled out of the visible window and rarely actually showed —
   a static glow is both simpler and reliably visible). */
body[data-theme="prestige-gold"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(60% 45% at 85% 100%, rgba(212, 175, 55, 0.16), transparent 70%);
}

/* Founder's Theme v2 (slug "founder") — indigo was crowded (Galaxy,
   Prism Storm both already live there). Pivoted to something that
   actually says "founder": a deep wine-black, like a founding charter
   bound in leather, sealed with a warm bronze accent. Muted mauve stays
   only as a quiet secondary. */
body[data-theme="founder"],
[data-theme-scope="founder"] {
  --bg: #120a0d;
  --surface: #1d1216;
  --surface-2: #291a20;
  --text: #f0e6e8;
  --muted: #a8919b;
  --accent: #b3894f;
  --accent-2: #8a6270;
  --accent-soft: rgba(179, 137, 79, 0.13);
  --on-accent: #1f1006;
  color-scheme: dark;
}

/* Signature: a slow bronze glow rising up the page — a launch trail,
   echoing the rocket icon, without literally animating a rocket. */
body[data-theme="founder"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: linear-gradient(0deg, transparent, rgba(179, 137, 79, 0.11) 45%, transparent 90%);
  background-size: 100% 260px;
  background-repeat: repeat-y;
  animation: founderRise 18s linear infinite;
}

@keyframes founderRise {
  from { background-position: 0 0; }
  to { background-position: 0 -260px; }
}

/* Obsidian — true black, cool platinum. The one material touch: a faintly
   brighter machined steel edge. Nothing else — deliberately the ONE
   theme in this section that stays effect-free even after 2026-09-13's
   pass added a signature touch to its six siblings: its whole identity
   is "the anti-prestige-gold," understated luxury with no colour at
   all. A background effect here would undercut that on purpose. */
body[data-theme="obsidian"],
[data-theme-scope="obsidian"] {
  --bg: #060709;
  --surface: #101216;
  --surface-2: #191c22;
  --text: #eceef2;
  --muted: #8b909c;
  --accent: #b9c2d0;
  --accent-2: #929aa8;
  --accent-soft: rgba(185, 194, 208, 0.1);
  --on-accent: #0b0d10;
  --card-border: 1px solid rgba(255, 255, 255, 0.14);
  color-scheme: dark;
}

/* =========================================================
   CHARACTER-KIT SUPPORT
   The bits a theme's token block can't express on its own: the
   animated gradient frame for premium/limited, per-theme caret
   shape, grain/scanline textures. Everything below is opt-in per
   theme — the default look never sees any of it. Every theme
   shares one radius / shadow / font scale (no per-theme overrides)
   so the shell/layout never shifts shape when you switch themes —
   only colour and these opt-in accents do.
========================================================= */

/* The premium/limited tier used to get an animated gradient card frame +
   accent glow here. Removed — the user's taste runs to restraint (Frost /
   Linen / Meadow), and "premium" now means a deeper palette + at most one
   material touch, set in each token block above (prestige-gold's gold
   hairline, obsidian's steel edge). Everything else uses the standard
   --card-border. */

/* (matrix / cyber-pulse (both retired) used to force a block caret here; the caret
   shape is a user preference now — Customize → Display → Caret shape —
   so a theme no longer overrides it. Anyone who wants a terminal block
   on those themes just picks "Block".) */

/* (Grain / weave textures for autumn-harvest and linen are
   folded into each of those themes' own body::before layers further
   down — body::before is a child of body so it paints over the opaque
   body background, which a fixed html-level layer cannot.) */

/* Reusable inline-SVG grain (greyscale, low alpha), referenced by the
   themes that want a paper/ash texture. */
/* v2 — 0.4 alpha read as visible static/noise rather than a paper
   texture, especially on dark surfaces (high-contrast
   flecks against a dark ground read as a broken image, not grain).
   Alpha cut to a barely-there 0.05 so it still breaks up flat colour on
   close inspection without registering as texture on its own. */
:root { --grain-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"); }

/* Midnight — an actual moon (small bright disc + soft rim), not just a
   vague lighter patch in the corner — plus a wide, very faint ambient
   moonlight wash so the glow reads as filling the sky, not a blur.
   Completely static; no stars (that's Galaxy's job). */
body[data-theme="midnight"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle at 14% 10%,
      rgba(234, 240, 250, 0.85) 0%,
      rgba(210, 222, 245, 0.4) 2.2%,
      rgba(180, 200, 235, 0.12) 5%,
      transparent 9%),
    radial-gradient(circle at 14% 10%, rgba(159, 179, 220, 0.08), transparent 46%);
}

/* Galaxy — a barely-there violet lift at the top of the page, and one
   sparse starfield drifting very slowly. That's the whole theme now. */
body[data-theme="galaxy"] {
  background:
    radial-gradient(150% 110% at 50% 0%, color-mix(in srgb, var(--accent) 7%, var(--bg)), var(--bg) 62%);
  overflow-x: hidden;
}

body[data-theme="galaxy"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.7), transparent),
    radial-gradient(1px 1px at 70% 60%, rgba(255,255,255,0.6), transparent),
    radial-gradient(1.3px 1.3px at 40% 80%, rgba(255,255,255,0.65), transparent),
    radial-gradient(1px 1px at 90% 20%, rgba(255,255,255,0.6), transparent),
    radial-gradient(1px 1px at 55% 12%, rgba(255,255,255,0.6), transparent);
  background-size: 700px 700px;
  opacity: 0.35;
  animation: driftStars 90s linear infinite;
}

@keyframes driftStars {
  from { background-position: 0 0; }
  to { background-position: -700px 700px; }
}

/* Petals fall and sway from side to side, not an in-place diagonal
   twinkle-drift — driftStars (also used by Galaxy's actual starfield)
   made cherry blossoms move exactly like distant stars.
   v2 — the dots were a pale pink disconnected from the theme's actual
   accent (#d81b60) and its "bamboo-green foliage" half (#128a4d), so the
   effect read as generic pink confetti rather than this specific theme's
   palette. Deepened toward the real accent pink, mixed in a couple of
   small green leaf specks for that green half, and varied the dot
   aspect ratio (elliptical, not perfect circles) for a less uniform
   polka-dot look. */
/* Petals drifting down. v3 — dropped the bamboo-green specks (the green
   half of the palette is gone) and softened the pink toward the new
   dusty accent so it reads as blossom, not confetti. */
/* Denser tile (500px -> 340px) and two more petals so more are visible
   on screen at once, plus a faster fall (24s -> 16s) — same colours and
   sway pattern, "good, just needs more animation" per user feedback. */
body[data-theme="sakura"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(4px 3px at 15% 20%, #dda6b8, transparent),
    radial-gradient(3px 2.5px at 70% 10%, #e8c3ce, transparent),
    radial-gradient(3.5px 3px at 40% 60%, #dda6b8, transparent),
    radial-gradient(2.5px 2px at 85% 70%, #e8c3ce, transparent),
    radial-gradient(3px 2.5px at 55% 85%, #dda6b8, transparent),
    radial-gradient(3px 2.5px at 25% 45%, #e8c3ce, transparent),
    radial-gradient(2.5px 2px at 92% 40%, #dda6b8, transparent);
  background-size: 340px 340px;
  opacity: 0.45;
  animation: sakuraPetalFall 16s linear infinite;
}

@keyframes sakuraPetalFall {
  0%   { background-position: 0px 0px; }
  25%  { background-position: 10px 85px; }
  50%  { background-position: -7px 170px; }
  75%  { background-position: 7px 255px; }
  100% { background-position: 0px 340px; }
}

/* var(--text) instead of hardcoded white — same fix the equippable
   Snowfall background effect already uses (see BACKGROUND EFFECTS below):
   white dots would nearly disappear against Frost's own pale icy-blue
   background. Sway-and-fall like Sakura's petals rather than a straight
   vertical drift, so it reads as snow tumbling in the wind. */
body[data-theme="frost"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(3px 3px at 15% 15%, var(--text), transparent),
    radial-gradient(2.5px 2.5px at 65% 10%, var(--text), transparent),
    radial-gradient(3px 3px at 40% 55%, var(--text), transparent),
    radial-gradient(2px 2px at 85% 65%, var(--text), transparent),
    radial-gradient(2.5px 2.5px at 55% 80%, var(--text), transparent),
    radial-gradient(2px 2px at 10% 85%, var(--text), transparent);
  background-size: 460px 460px;
  opacity: 0.4;
  animation: frostSnowfall 16s linear infinite;
}

@keyframes frostSnowfall {
  0%   { background-position: 0px 0px; }
  25%  { background-position: 12px 115px; }
  50%  { background-position: -8px 230px; }
  75%  { background-position: 10px 345px; }
  100% { background-position: 0px 460px; }
}

/* Mocha — a faint warm wash from the top, plus wisps of steam rising and
   fading the way they curl off a fresh cup. Slow, translucent, cosy. */
body[data-theme="solar-flare"] {
  background:
    radial-gradient(circle at 50% -10%, rgba(123, 74, 45, 0.07), transparent 55%),
    var(--bg);
}

body[data-theme="solar-flare"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(22px 90px at 28% 10%, rgba(255, 255, 255, 0.55), transparent 70%),
    radial-gradient(18px 68px at 53% 5%, rgba(255, 255, 255, 0.45), transparent 70%),
    radial-gradient(24px 100px at 76% 14%, rgba(255, 255, 255, 0.5), transparent 70%);
  opacity: 0.5;
  animation: mochaSteam 15s ease-in-out infinite;
}

@keyframes mochaSteam {
  0%, 100% { background-position: 0 0, 0 0, 0 0; opacity: 0.28; }
  50% { background-position: 5px -20px, -4px -15px, 6px -24px; opacity: 0.55; }
}

/* Halloween / Winter Lights / Gold Prestige / Founder / Obsidian had
   decorative background layers here — an unstable pulse + flickering
   embers, string lights, a gold shimmer sweep, launch streaks, a platinum
   sweep. All removed: the premium/limited tier is restraint now, carried
   by palette alone (prestige-gold's gold hairline and obsidian's steel
   edge are the only flourishes, set in their token blocks). */

/* Paper-fibre grain on the body itself + leaves drifting down on ::before. */
body[data-theme="autumn-harvest"] {
  background-image: var(--grain-svg);
  background-size: 160px 160px;
  background-attachment: fixed;
}

/* Vivid pumpkin-orange/amber (not the theme's own muted --accent/
   --accent-2) on purpose — restored from before an Aug 2026 palette-
   de-collision pass quietly dulled these to a rust+olive pair that
   read as flat/generic instead of like actual fall leaves. Decorative
   layers using their own hardcoded colour independent of the theme's
   functional accent is the established pattern site-wide (see Aurora/
   Halloween) — this doesn't need to match the buttons. */
body[data-theme="autumn-harvest"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(3px 3px at 15% 10%, #ea580c, transparent),
    radial-gradient(2.5px 2.5px at 45% 25%, #b45309, transparent),
    radial-gradient(3px 3px at 70% 5%, #ea580c, transparent),
    radial-gradient(2.5px 2.5px at 90% 30%, #b45309, transparent);
  background-size: 280px 280px;
  opacity: 0.5;
  animation: leavesFall 9s linear infinite;
}

@keyframes leavesFall {
  from { background-position: 0 0; }
  to { background-position: 20px 280px; }
}

/* Meadow — a strip of long grass rooted along the bottom edge: every
   blade sways gently on its own rhythm, and a gust rolls left to right
   every 6s, bending each blade as it passes. Replaced the drifting pollen
   dots: grass is Meadow's alone, where falling things belong to Sakura /
   Autumn Harvest / Frost. Faded toward the top and behind all content,
   so it only shows in the gaps around the cards. The blades
   and their wind are one generated SVG (static/images/meadow-grass.svg);
   the animation lives inside it (SMIL), which the global reduced-motion
   rule can't reach, hence the still copy swapped in below. */
body[data-theme="meadow"]::before {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 180px;
  z-index: 0;
  pointer-events: none;
  background: url("../images/meadow-grass.c500e7990509.svg") left bottom / 1200px 180px repeat-x;
  -webkit-mask-image: linear-gradient(to top, #000 55%, transparent);
  mask-image: linear-gradient(to top, #000 55%, transparent);
  opacity: 0.6;
  transition: background-image 0.6s ease;
}

/* The meadow goes still while a test or race is running (body.test-active,
   set by home.html / race_room.html) — no motion at the edge of vision
   while typing, and no per-frame redraw competing with the caret — then
   comes back to life on the results screen. Chrome cross-fades the swap;
   other browsers switch instantly. */
body[data-theme="meadow"].test-active::before {
  background-image: url("../images/meadow-grass-still.4b99d88f9be1.svg");
}

/* Phones: the fixed bottom tab bar would hide the strip, so the grass
   grows from the bar's top edge instead (~54px rendered, see .bottom-tab-bar). */
@media (max-width: 768px) {
  body[data-theme="meadow"]::before {
    bottom: calc(54px + env(safe-area-inset-bottom, 0px));
    height: 120px;
    background-size: 800px 120px;
  }
}

@media (prefers-reduced-motion: reduce) {
  body[data-theme="meadow"]::before {
    background-image: url("../images/meadow-grass-still.4b99d88f9be1.svg");
  }
}

/* Neon Tokyo — a faint static sign-grid, no flicker. */
body[data-theme="neon-tokyo"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image:
    repeating-linear-gradient(90deg, rgba(209, 101, 144, 0.05) 0px, transparent 2px, transparent 64px),
    repeating-linear-gradient(0deg, rgba(208, 161, 95, 0.04) 0px, transparent 1px, transparent 44px);
}

/* Linen had zero decorative layer at all, which made it read as flat and
   bland next to every other theme's motion — a woven crosshatch texture
   (not particles/motion, this is a "common"-tier fabric theme) gives it
   some life while staying restrained. Two low-opacity diagonal line sets
   at ±45° in var(--border) so it always matches the theme's own palette. */
body[data-theme="linen"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(45deg, var(--border) 0px, var(--border) 1px, transparent 1px, transparent 9px),
    repeating-linear-gradient(-45deg, var(--border) 0px, var(--border) 1px, transparent 1px, transparent 9px);
  opacity: 0.35;
  animation: linenWeaveShift 24s ease-in-out infinite;
}

@keyframes linenWeaveShift {
  0%, 100% { background-position: 0 0, 0 0; opacity: 0.28; }
  50% { background-position: 6px 6px, -6px 6px; opacity: 0.4; }
}

/* A gentle vertical bob, like petals floating on water, rather than the
   one-directional diagonal drift driftStars gives every other user of
   it (Galaxy's stars, now Sakura's falling petals).
   v3 — dropped the warm-gold glint dots (the gold half of the palette is
   gone) and the brightness flicker; just soft mauve motes drifting now. */
body[data-theme="rose-quartz"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(3px 3px at 20% 15%, #ddd4e4, transparent),
    radial-gradient(2.5px 2.5px at 65% 25%, #e7e1ed, transparent),
    radial-gradient(3px 3px at 35% 65%, #ddd4e4, transparent),
    radial-gradient(2px 2px at 80% 75%, #e7e1ed, transparent),
    radial-gradient(2.5px 2.5px at 50% 90%, #ddd4e4, transparent);
  background-size: 480px 480px;
  animation: roseQuartzFloat 8s ease-in-out infinite;
}

@keyframes roseQuartzFloat {
  0%, 100% { background-position: 0px 0px; opacity: 0.35; }
  50% { background-position: 0px -16px; opacity: 0.5; }
}

/* A quick, punchy "zing" — brightness, saturation, and a slight
   yellow-to-lime hue shift — instead of Solar Flare's slow pure-
   brightness breathing. Citrus should feel zesty, not molten. */
/* A soft honey wash — no animation. The old `citrusZing` was a
   brightness/saturate/hue-rotate `filter` on <body>: it made <body> the
   containing block for position:fixed, which is what was shifting the
   theme picker / modal around while this theme was equipped. */
body[data-theme="citrus-grove"] {
  background:
    radial-gradient(circle at 50% -15%, rgba(145, 106, 4, 0.12), transparent 55%),
    var(--bg);
}

/* A gentle radial "breathing bloom" (the clouds expand/soften in place)
   instead of Aurora's directional drift — suits a soft pastel theme
   better than a cosmic sweep. */
/* A single soft lavender wash — static. (Old version breathed via a
   `filter` on <body>, which broke position:fixed.) */
body[data-theme="lilac-bloom"] {
  background:
    radial-gradient(circle at 18% 12%, rgba(106, 79, 192, 0.1), transparent 46%),
    radial-gradient(circle at 82% 100%, rgba(106, 79, 192, 0.08), transparent 48%),
    var(--bg);
}

/* Slow-drifting lavender petals — the only motion this theme has now. */
body[data-theme="lilac-bloom"]::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(3px 2.5px at 18% 18%, #cbbdea, transparent),
    radial-gradient(2.5px 2px at 62% 12%, #bfb0e2, transparent),
    radial-gradient(3px 2.5px at 38% 58%, #cbbdea, transparent),
    radial-gradient(2px 2px at 82% 68%, #bfb0e2, transparent),
    radial-gradient(2.5px 2px at 55% 88%, #cbbdea, transparent);
  background-size: 520px 520px;
  opacity: 0.4;
  animation: lilacPetalDrift 26s linear infinite;
}

@keyframes lilacPetalDrift {
  0%   { background-position: 0px 0px; }
  25%  { background-position: -14px 130px; }
  50%  { background-position: 10px 260px; }
  75%  { background-position: -10px 390px; }
  100% { background-position: 0px 520px; }
}

/* Prism Storm and Celestial Eclipse had decorative glow / corona / star
   layers here. Removed — both are palette-only now (see the restraint
   note in the PREMIUM token blocks above). */

/* =========================================================
   BACKGROUND EFFECTS
   Matches ShopItem slugs (item_type="background"). Layered via
   body::after (themes already use ::before) so any background
   can be combined with any theme. No data-background = plain.
========================================================= */

body[data-background]::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Small live preview inside the customize modal's swatch box — same
   background-image/animation as the real effect, just contained to a
   52px box instead of the full viewport, so equipping (or just browsing
   the picker) shows an unmistakable demo even on pages with no visible
   full-screen effect to compare against. */
.customize-swatch[data-background-demo] {
  position: relative;
  overflow: hidden;
}

.customize-swatch[data-background-demo]::after {
  content: "";
  position: absolute;
  inset: 0;
}


/* Individual falling streaks on a repeating tile — matches the technique
   every other background effect uses (particles/snowfall/starfield are
   all sparse discrete shapes on a large tile with a slow loop). The old
   version was a dense, screen-covering diagonal hatch with no tile at
   all, looping every 0.4s — that read as flickering noise, not rain. */
/* Two streak layers at different speeds/scales for real depth — a fast
   near layer and a slower, fainter far one. */
body[data-background="rain"]::after,
.customize-swatch[data-background-demo="rain"]::after {
  background-image:
    repeating-linear-gradient(94deg, transparent 0 34px,
      color-mix(in srgb, var(--accent) 55%, transparent) 34px 35.5px),
    repeating-linear-gradient(96deg, transparent 0 58px,
      color-mix(in srgb, var(--accent) 30%, transparent) 58px 59px);
  background-size: 90px 220px, 150px 340px;
  opacity: 0.5;
  animation: rainFall 1.1s linear infinite;
}

@keyframes rainFall {
  from { background-position: 0 0, 0 0; }
  to   { background-position: -18px 220px, -10px 340px; }
}

/* var(--text) instead of a hardcoded white — snow/stars need to flip to
   dark dots on light themes (frost, sakura, solar-flare) or they'd be
   nearly invisible against a light background. */
body[data-background="snowfall"]::after,
.customize-swatch[data-background-demo="snowfall"]::after {
  background-image:
    radial-gradient(4px 4px at 20% 10%, var(--text), transparent),
    radial-gradient(3px 3px at 60% 30%, var(--text), transparent),
    radial-gradient(4px 4px at 80% 5%, var(--text), transparent),
    radial-gradient(3px 3px at 40% 50%, var(--text), transparent),
    radial-gradient(3.5px 3.5px at 10% 60%, var(--text), transparent),
    radial-gradient(3px 3px at 90% 45%, var(--text), transparent),
    radial-gradient(2.5px 2.5px at 30% 75%, var(--text), transparent);
  background-size: 320px 320px;
  opacity: 0.5;
  animation: snowDrift 8s linear infinite;
}

@keyframes snowDrift {
  from { background-position: 0 0; }
  to { background-position: 32px 320px; }
}

body[data-background="starfield"]::after,
.customize-swatch[data-background-demo="starfield"]::after {
  background-image:
    radial-gradient(1.5px 1.5px at 15% 25%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 65% 55%, var(--text), transparent),
    radial-gradient(2px 2px at 35% 80%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 85% 15%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 50% 5%, var(--text), transparent),
    radial-gradient(2px 2px at 95% 75%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 25% 45%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 70% 90%, var(--text), transparent);
  background-size: 400px 400px;
  opacity: 0.55;
  animation: starTwinkle 3s ease-in-out infinite;
}

@keyframes starTwinkle {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 0.85; }
}

/* A soft accent-2 spotlight now sweeps diagonally across the grid and
   the whole thing pulses brighter at the sweep's midpoint — the plain
   two-layer grid at a flat 0.28 opacity read as less elaborate than
   Confetti Burst, a legendary (one tier below mythical) background, so
   this needed real motion depth, not just a recolor. */
/* v3 — a brighter grid with a diagonal light-sweep passing over it, so
   it reads as an active surface, not a faint static mesh. */
body[data-background="animated-grid"]::after,
.customize-swatch[data-background-demo="animated-grid"]::after {
  background-image:
    linear-gradient(115deg, transparent 40%,
      color-mix(in srgb, var(--accent-2) 60%, transparent) 50%, transparent 60%),
    repeating-linear-gradient(0deg,  color-mix(in srgb, var(--accent) 45%, transparent) 0 1.5px, transparent 1.5px 40px),
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, transparent) 0 1.5px, transparent 1.5px 40px);
  background-size: 300% 100%, 40px 40px, 40px 40px;
  opacity: 0.5;
  animation: gridScroll 6s linear infinite;
}

@keyframes gridScroll {
  0%   { background-position: 120% 0, 0 0, 0 0; }
  100% { background-position: -120% 0, 40px 40px, 40px 40px; }
}

/* Darker amber + a drop-shadow safety net — same fix as The Vault below,
   the original pale #fde047/#fbbf24 had poor contrast against the
   default light theme's near-white background. */
body[data-background="fireflies"]::after,
.customize-swatch[data-background-demo="fireflies"]::after {
  background-image:
    radial-gradient(2.5px 2.5px at 15% 70%, #d97706, transparent),
    radial-gradient(2px 2px at 35% 30%, #92400e, transparent),
    radial-gradient(3px 3px at 55% 80%, #d97706, transparent),
    radial-gradient(2px 2px at 75% 20%, #92400e, transparent),
    radial-gradient(2.5px 2.5px at 90% 60%, #d97706, transparent),
    radial-gradient(2px 2px at 25% 45%, #92400e, transparent);
  background-size: 280px 280px;
  opacity: 0.75;
  filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.45));
  animation: firefliesDrift 6s ease-in-out infinite;
}

@keyframes firefliesDrift {
  0%, 100% { opacity: 0.5; background-position: 0 0; }
  50% { opacity: 0.85; background-position: 14px -18px; }
}

/* Legendary tier gets real color instead of the accent-only palette every
   other background uses — a burst of festive confetti dots rather than
   a monochrome ambient effect, since this is meant to feel like the
   flashiest background in the shop. */
body[data-background="confetti-burst"]::after,
.customize-swatch[data-background-demo="confetti-burst"]::after {
  background-image:
    radial-gradient(3px 3px at 10% 10%, #ec4899, transparent),
    radial-gradient(3px 3px at 30% 40%, #22d3ee, transparent),
    radial-gradient(3px 3px at 50% 15%, #fde047, transparent),
    radial-gradient(3px 3px at 70% 50%, #a855f7, transparent),
    radial-gradient(3px 3px at 90% 25%, #4ade80, transparent),
    radial-gradient(3px 3px at 20% 65%, #fb923c, transparent),
    radial-gradient(3px 3px at 45% 85%, #ec4899, transparent),
    radial-gradient(3px 3px at 65% 70%, #22d3ee, transparent),
    radial-gradient(3px 3px at 85% 90%, #fde047, transparent);
  background-size: 260px 260px;
  opacity: 0.7;
  /* Several of these (cyan, green, yellow) have poor contrast against the
     default light theme's near-white background — a drop-shadow darkens
     every dot's silhouette regardless of its own color, fixing all of
     them at once instead of needing per-color contrast tuning. */
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.35));
  animation: confettiFall 3s linear infinite;
}

@keyframes confettiFall {
  from { background-position: 0 0; }
  to { background-position: 12px 260px; }
}

/* Limited to 30 units ever — the rarest background in the shop.
   v2 — the original used pale gold (#fde047) at low opacity, which has
   almost no contrast against the default light theme's near-white
   background (#f5f7fb) — pale-on-pale is invisible regardless of dot
   count or opacity. Switched to a darker, richer gold plus a drop-shadow
   filter, which (unlike background-image alone) reads against ANY
   background color since it darkens the whole layer's silhouette. */
body[data-background="the-vault"]::after,
.customize-swatch[data-background-demo="the-vault"]::after {
  background-image:
    radial-gradient(3px 3px at 15% 15%, #b45309, transparent),
    radial-gradient(2.5px 2.5px at 45% 55%, #d4af37, transparent),
    radial-gradient(3px 3px at 75% 25%, #b45309, transparent),
    radial-gradient(2.5px 2.5px at 30% 75%, #d4af37, transparent),
    radial-gradient(3px 3px at 60% 85%, #b45309, transparent),
    radial-gradient(2.5px 2.5px at 85% 60%, #d4af37, transparent),
    radial-gradient(3px 3px at 10% 45%, #b45309, transparent),
    radial-gradient(2.5px 2.5px at 90% 90%, #d4af37, transparent),
    radial-gradient(circle at 50% 50%, rgba(180, 83, 9, 0.35), transparent 70%);
  background-size:
    260px 260px, 260px 260px, 260px 260px, 260px 260px,
    260px 260px, 260px 260px, 260px 260px, 260px 260px,
    100% 100%;
  opacity: 0.85;
  filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.5));
  animation: vaultDrift 8s ease-in-out infinite;
}

@keyframes vaultDrift {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 0.95; }
}

/* Actual leaf shapes (a rounded-diamond clip on each dot) tumbling down
   two layers at different rates, rather than round dots. */
body[data-background="autumn-leaves"]::after,
.customize-swatch[data-background-demo="autumn-leaves"]::after {
  background-image:
    radial-gradient(5px 8px at 15% 10%, #c2410c, transparent),
    radial-gradient(6px 4px at 62% 30%, #b45309, transparent),
    radial-gradient(4px 7px at 88% 12%, #92400e, transparent),
    radial-gradient(7px 5px at 34% 66%, #d97706, transparent),
    radial-gradient(5px 8px at 78% 82%, #c2410c, transparent);
  background-size: 320px 320px;
  opacity: 0.68;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
  animation: leavesDrift 9s linear infinite;
}

@keyframes leavesDrift {
  from { background-position: 0 0; }
  to   { background-position: 40px 320px; }
}

/* Hollow bubbles (a bright ring, transparent centre) of varied size
   rising on two layers — reads as bubbles, not filled dots. */
body[data-background="bubbles"]::after,
.customize-swatch[data-background-demo="bubbles"]::after {
  background-image:
    radial-gradient(circle 7px at 12% 90%, transparent 55%, color-mix(in srgb, var(--accent) 70%, transparent) 58% 72%, transparent 74%),
    radial-gradient(circle 4px at 34% 70%, transparent 50%, color-mix(in srgb, var(--accent-2) 70%, transparent) 55% 74%, transparent 76%),
    radial-gradient(circle 9px at 58% 96%, transparent 58%, color-mix(in srgb, var(--accent) 60%, transparent) 61% 72%, transparent 74%),
    radial-gradient(circle 5px at 80% 66%, transparent 52%, color-mix(in srgb, var(--accent-2) 65%, transparent) 56% 74%, transparent 76%),
    radial-gradient(circle 6px at 92% 88%, transparent 55%, color-mix(in srgb, var(--accent) 65%, transparent) 58% 73%, transparent 75%);
  background-size: 260px 260px, 180px 180px, 260px 260px, 180px 180px, 260px 260px;
  opacity: 0.6;
  animation: bubblesRise 9s linear infinite;
}

@keyframes bubblesRise {
  from { background-position: 0 0, 0 0, 0 0, 0 0, 0 0; }
  to   { background-position: 8px -260px, -12px -180px, 8px -260px, -12px -180px, 8px -260px; }
}

/* A soft ambient wash (like the Aurora theme's background), not sharp
   dots — diffuse gradients read fine at low opacity since there's no
   hard edge needing contrast against the page behind it. */

/* v2 — a CRT/VHS look: fine scanlines + a shifting speckle grain + a
   slow rolling bright band. Was opacity 0.06 (invisible); now it's a
   deliberate, readable texture. */
body[data-background="static-noise"]::after,
.customize-swatch[data-background-demo="static-noise"]::after {
  background-image:
    linear-gradient(color-mix(in srgb, var(--text) 8%, transparent), transparent 8%, transparent 92%, color-mix(in srgb, var(--text) 8%, transparent)),
    repeating-linear-gradient(0deg, color-mix(in srgb, var(--text) 55%, transparent) 0 1px, transparent 1px 3px),
    radial-gradient(circle 0.6px at 20% 30%, var(--text), transparent),
    radial-gradient(circle 0.6px at 65% 55%, var(--text), transparent),
    radial-gradient(circle 0.6px at 40% 80%, var(--text), transparent),
    radial-gradient(circle 0.6px at 85% 20%, var(--text), transparent);
  background-size: 100% 240px, 100% 3px, 7px 7px, 7px 7px, 7px 7px, 7px 7px;
  opacity: 0.13;
  animation: staticNoise 2.4s steps(3) infinite; /* F4: was 0.5s — a whole-screen flicker that fast is a photosensitivity risk */
}

@keyframes staticNoise {
  0%   { background-position: 0 0,   0 0, 0 0,   2px 1px, 1px 3px, 3px 2px; }
  50%  { background-position: 0 40%, 0 0, 3px 2px, 0 3px, 2px 1px, 1px 0; }
  100% { background-position: 0 90%, 0 0, 1px 3px, 3px 0, 0 2px,   2px 1px; }
}

/* Premium (Gems). Real parallax within a single ::after (themes already
   claim ::before, so backgrounds only ever get one pseudo-element) — two
   slow ambient ribbons and a layer of sparkle dots move at different
   rates in the same keyframe, the same multi-layer-differential-position
   technique the Founder theme already uses for its two streak layers.
   mix-blend-mode:screen is new here — no existing background uses it. */
body[data-background="aurora-storm"]::after,
.customize-swatch[data-background-demo="aurora-storm"]::after {
  background-image:
    radial-gradient(ellipse 60% 40% at 30% 0%, var(--accent), transparent 60%),
    radial-gradient(ellipse 50% 35% at 70% 10%, var(--accent-2), transparent 60%),
    radial-gradient(1.5px 1.5px at 20% 70%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 60% 85%, var(--text), transparent),
    radial-gradient(1.5px 1.5px at 85% 60%, var(--text), transparent);
  background-size: 100% 100%, 100% 100%, 300px 300px, 300px 300px, 300px 300px;
  mix-blend-mode: screen;
  opacity: 0.4;
  animation: auroraStormDrift 10s ease-in-out infinite;
}

@keyframes auroraStormDrift {
  0%, 100% { background-position: 0% 0%, 0% 0%, 0 0, 0 0, 0 0; }
  50% { background-position: -15% 8%, 15% -6%, 40px -30px, -30px 20px, 20px -25px; }
}

/* Premium (Gems). Diagonal streaks (Founder's technique) fading in/out
   on a loop, trailing a drifting star layer behind them — every existing
   streak/sparkle background moves in one direction at a constant
   opacity; this one cycles like an actual meteor passing overhead. */
body[data-background="meteor-shower"]::after,
.customize-swatch[data-background-demo="meteor-shower"]::after {
  background-image:
    linear-gradient(115deg, transparent 46%, var(--accent) 49.5%, transparent 53%),
    linear-gradient(115deg, transparent 66%, var(--accent-2) 69%, transparent 72%),
    radial-gradient(2.5px 2.5px at 30% 10%, var(--text), transparent),
    radial-gradient(2px 2px at 70% 40%, var(--text), transparent),
    radial-gradient(2.5px 2.5px at 15% 60%, var(--text), transparent);
  background-size: 100% 100%, 100% 100%, 260px 260px, 260px 260px, 260px 260px;
  animation: meteorStreak 3s linear infinite;
}

@keyframes meteorStreak {
  0% { background-position: -40% 60%, -60% 80%, 0 0, 0 0, 0 0; opacity: 0; }
  10% { opacity: 0.6; }
  60% { opacity: 0.6; }
  100% { background-position: 140% -60%, 120% -40%, 30px -260px, 30px -260px, 30px -260px; opacity: 0; }
}

/* --- New backgrounds: real depth + shape variety, not just dot fields.
   Every layer is theme-adaptive (color-mix off --accent / --text) so it
   reads on light and dark without per-theme patching. --- */

/* Drifting Fog — three soft cloud banks at different depths, each
   crawling at its own rate. */
body[data-background="drifting-fog"]::after,
.customize-swatch[data-background-demo="drifting-fog"]::after {
  background-image:
    radial-gradient(ellipse 55% 40% at 25% 30%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
    radial-gradient(ellipse 60% 45% at 75% 55%, color-mix(in srgb, var(--accent-2) 20%, transparent), transparent 72%),
    radial-gradient(ellipse 70% 50% at 50% 85%, color-mix(in srgb, var(--text) 8%, transparent), transparent 75%);
  background-size: 180% 180%, 200% 200%, 220% 220%;
  opacity: 0.55;
  animation: fogDrift 26s ease-in-out infinite;
}
@keyframes fogDrift {
  0%, 100% { background-position: 0% 0%, 0% 0%, 0% 0%; }
  50%      { background-position: 30% 12%, -25% -10%, 12% -18%; }
}

/* Contours — a slow-drifting topographic line map. */
body[data-background="contours"]::after,
.customize-swatch[data-background-demo="contours"]::after {
  background-image:
    repeating-radial-gradient(circle at 30% 40%,
      transparent 0 26px, color-mix(in srgb, var(--accent) 32%, transparent) 26px 28px, transparent 28px 54px),
    repeating-radial-gradient(circle at 78% 72%,
      transparent 0 30px, color-mix(in srgb, var(--accent-2) 26%, transparent) 30px 32px, transparent 32px 62px);
  background-size: 520px 520px, 640px 640px;
  opacity: 0.4;
  animation: contourDrift 30s linear infinite;
}
@keyframes contourDrift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 120px 90px, -100px 70px; }
}

/* Floating Shapes — translucent triangles + hexagons, rotating as they
   drift. Two clip-path'd gradient tiles. */
body[data-background="floating-shapes"]::after,
.customize-swatch[data-background-demo="floating-shapes"]::after {
  background-image:
    conic-gradient(from 0deg at 20% 30%, color-mix(in srgb, var(--accent) 30%, transparent) 0 25%, transparent 25% 100%),
    conic-gradient(from 90deg at 72% 66%, color-mix(in srgb, var(--accent-2) 26%, transparent) 0 16%, transparent 16% 100%),
    conic-gradient(from 45deg at 50% 88%, color-mix(in srgb, var(--accent) 22%, transparent) 0 12%, transparent 12% 100%);
  background-size: 300px 300px, 240px 240px, 360px 360px;
  background-repeat: repeat;
  opacity: 0.4;
  animation: shapesDrift 22s linear infinite;
}
@keyframes shapesDrift {
  from { background-position: 0 0, 0 0, 0 0; }
  to   { background-position: -300px 180px, 240px -160px, 120px 360px; }
}

/* Dust Motes — a fine warm-gold particle field with gentle parallax
   swirl. Best on warm / light themes but token-safe anywhere. */
body[data-background="dust-motes"]::after,
.customize-swatch[data-background-demo="dust-motes"]::after {
  background-image:
    radial-gradient(1px 1px at 12% 20%, color-mix(in srgb, var(--text) 60%, #f5c451), transparent),
    radial-gradient(1.5px 1.5px at 42% 55%, color-mix(in srgb, var(--text) 40%, #f5c451), transparent),
    radial-gradient(1px 1px at 68% 30%, color-mix(in srgb, var(--text) 55%, #f5c451), transparent),
    radial-gradient(1.5px 1.5px at 85% 70%, color-mix(in srgb, var(--text) 45%, #f5c451), transparent),
    radial-gradient(1px 1px at 30% 85%, color-mix(in srgb, var(--text) 60%, #f5c451), transparent);
  background-size: 160px 160px, 260px 260px, 200px 200px, 320px 320px, 220px 220px;
  opacity: 0.5;
  animation: dustSwirl 24s ease-in-out infinite;
}
@keyframes dustSwirl {
  0%, 100% { background-position: 0 0, 0 0, 0 0, 0 0, 0 0; }
  50%      { background-position: 20px -14px, -24px 18px, 16px 10px, -18px -12px, 10px 20px; }
}

/* Data Stream — sparse thin vertical code-rain streaks in the theme
   accent (not matrix green), dim enough to sit under text. */
body[data-background="data-stream"]::after,
.customize-swatch[data-background-demo="data-stream"]::after {
  background-image:
    linear-gradient(color-mix(in srgb, var(--accent) 55%, transparent), transparent 55%),
    linear-gradient(color-mix(in srgb, var(--accent) 40%, transparent), transparent 40%),
    linear-gradient(color-mix(in srgb, var(--accent-2) 45%, transparent), transparent 50%);
  background-size: 2px 120px, 2px 90px, 2px 150px;
  background-position: 14% 0, 47% 0, 79% 0;
  background-repeat: repeat-y;
  opacity: 0.35;
  animation: dataStream 2.4s linear infinite;
}
@keyframes dataStream {
  from { background-position-y: -120px, -60px, -150px; }
  to   { background-position-y: 120px, 90px, 150px; }
}

/* Ripple Pond — concentric rings expanding from two points on a loop. */
body[data-background="ripple-pond"]::after,
.customize-swatch[data-background-demo="ripple-pond"]::after {
  background-image:
    repeating-radial-gradient(circle at 32% 38%,
      transparent 0 18px, color-mix(in srgb, var(--accent) 30%, transparent) 18px 20px, transparent 20px 40px),
    repeating-radial-gradient(circle at 74% 68%,
      transparent 0 22px, color-mix(in srgb, var(--accent-2) 26%, transparent) 22px 24px, transparent 24px 48px);
  background-size: 100% 100%, 100% 100%;
  opacity: 0.4;
  /* F4: scale + opacity only (compositor), not background-size — the
     old version repainted the whole viewport every frame and measured
     ~25fps idle on a throttled phone. */
  transform-origin: 50% 50%;
  animation: ripplePond 5s ease-out infinite;
}
@keyframes ripplePond {
  0%   { transform: scale(0.45); opacity: 0; }
  15%  { opacity: 0.45; }
  100% { transform: scale(1.8); opacity: 0; }
}

/* =========================================================
   RESET / BASE
========================================================= */

* {
  box-sizing: border-box;
}

html {
  /* Stops the browser's pull-to-refresh/rubber-band gesture from
     swallowing vertical drags meant for the page — the single biggest
     thing that makes scrolling feel like "a website" instead of "an
     app" on mobile. */
  overscroll-behavior-y: contain;
  -webkit-text-size-adjust: 100%;
}

a, button, .icon-btn, .bottom-tab, .duration-btn, .mode-btn, .pill {
  /* Kills the ~300ms delay + double-tap-to-zoom browsers apply to taps
     near clickable elements, and the gray flash Android/iOS show on
     tap by default — both read as "web page", not "app". */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  transition: background var(--dur-3) var(--ease), color var(--dur-3) var(--ease);
  position: relative;

  display: flex;
  flex-direction: column;
}

.site-main {
  flex: 1 1 auto;
  /* The header is sticky/full-bleed now, so page content needs its own
     top breathing room (was the header's old bottom margin). */
  padding-top: 28px;
}

body > * {
  position: relative;
  z-index: 1;
}

a {
  text-decoration: none;
  color: inherit;
}

button {
  font-family: inherit;
  cursor: pointer;
}

img { max-width: 100%; }

::selection {
  background: var(--accent-soft);
  color: var(--text);
}

/* One consistent keyboard-focus ring everywhere. Mouse clicks stay
   clean (:focus-visible, not :focus); individual components can still
   opt into a tighter ring where the default would clip. */
:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.014em;
  line-height: 1.2;
}

/* Every stat / counter / timer reads with fixed-width figures so it
   never jitters as digits change mid-test. */
.tabular,
#timer,
#live-wpm,
#live-accuracy,
#live-wpm-big,
.hero-score,
.mini-value,
.stat-num,
.live-wpm-hero-value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

/* .page-container width + centring come from the shell rule near the top. */
.page-container {
  margin-top: 4px;
}

.legal-page {
  /* width from the narrow-shell rule near the top */
  margin-top: 36px;
  margin-bottom: 40px;
}

.legal-card {
  padding: 40px 44px;
  line-height: 1.65;
  color: var(--text);
}

.legal-card h1 {
  margin: 0 0 4px;
  font-size: 30px;
  font-weight: 700;
}

.legal-card h2 {
  margin: 32px 0 10px;
  font-size: 19px;
  font-weight: 700;
}

.legal-card p,
.legal-card ul {
  margin: 0 0 14px;
  color: var(--muted);
}

.legal-card ul {
  padding-left: 20px;
}

.legal-card li {
  margin-bottom: 6px;
}

.legal-card a {
  color: var(--accent);
  font-weight: 600;
}

.legal-updated {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 24px !important;
}

/* Off-screen, not display:none/visibility:hidden — some spam bots
   specifically skip fields hidden that way, defeating the point of a
   honeypot. Positioning it out of the viewport instead fools that check
   while staying invisible (and untabbable, see tabindex="-1" in the
   template) to a real visitor. */
.contact-honeypot {
  position: absolute;
  left: -9999px;
  top: -9999px;
}

.section-label {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px;
}

/* =========================================================
   SHARED PRIMITIVES
   Every page is built from these. A theme reskins the whole
   site by moving tokens (above); nothing below hard-codes a
   colour, radius, or shadow.
========================================================= */

.card {
  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}

/* Only cards that are themselves a link/button lift on hover — a plain
   informational card (leaderboard, stats) just warms its border. Opt in
   with .card-interactive or by being an <a>/<button>. */
.card:hover {
  border-color: var(--border-strong);
}

a.card:hover,
button.card:hover,
.card-interactive:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

/* ---- Buttons ------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;

  padding: 10px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);

  background: var(--surface-2);
  color: var(--text);

  font-family: inherit;
  font-weight: 600;
  font-size: 13.5px;
  line-height: 1;
  white-space: nowrap;

  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease),
              opacity var(--dur-1) var(--ease);
}

.btn:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.btn:active {
  transform: translateY(1px);
}

.btn:disabled,
.btn[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

/* The single primary action in a view — a solid accent fill with an
   ink label (the crisp "pro tool" look), not a gradient. */
.btn-primary {
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--on-accent);
}

.btn-primary:hover {
  background: var(--accent-hover);
  border-color: transparent;
  box-shadow: 0 6px 18px -6px var(--accent);
}

.btn-primary:active {
  background: var(--accent-press);
}

/* Low-emphasis: text + hairline only, for secondary/cancel actions. */
.btn-ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
}

.btn-ghost:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border-strong);
}

.btn-danger {
  background: var(--danger);
  border-color: transparent;
  color: #fff;
}

.btn-danger:hover {
  background: color-mix(in srgb, var(--danger) 88%, #000);
  border-color: transparent;
}

/* ---- Pills (mode / duration / scope / tab selectors) -------------- */

.pill,
.mode-btn,
.duration-btn,
.lb-scope-btn,
.customize-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;

  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-2);
  color: var(--muted);

  font-family: inherit;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;

  transition: color var(--dur-1) var(--ease), background var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.pill:hover,
.mode-btn:hover,
.duration-btn:hover,
.lb-scope-btn:hover,
.customize-tab:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

/* The signature accent detail — a theme can restyle just this (a hard
   double-stroke for cyber, a metallic fill for prestige) via the
   --pill-active-* knobs without touching component markup. */
.pill.active,
.mode-btn.active,
.duration-btn.active,
.lb-scope-btn.active,
.customize-tab.active {
  background: var(--pill-active-bg);
  color: var(--pill-active-fg);
  border-color: transparent;
  box-shadow: var(--pill-active-ring);
}

/* ---- Chips (nav counters: coins / gems / level) ------------------ */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-weight: 700;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.chip:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
}

/* ---- Form controls --------------------------------------------- */

.field,
.modal-card input,
.modal-card select,
.modal-card textarea,
.settings-card input,
.settings-card select,
.settings-card textarea,
.org-panel input,
.org-panel select,
.org-panel textarea,
.org-challenge-card input {
  width: 100%;
  padding: 10px 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.field::placeholder,
.modal-card input::placeholder,
.modal-card textarea::placeholder,
.org-panel input::placeholder,
.org-panel textarea::placeholder,
.org-challenge-card input::placeholder { color: var(--muted); }

.field:focus,
.modal-card input:focus,
.modal-card select:focus,
.modal-card textarea:focus,
.settings-card input:focus,
.settings-card select:focus,
.settings-card textarea:focus,
.org-panel input:focus,
.org-panel select:focus,
.org-panel textarea:focus,
.org-challenge-card input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---- Section headings ---------------------------------------------- */

.eyebrow {
  display: block;
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* =========================================================
   HEADER
========================================================= */

/* Full-bleed sticky bar with a translucent blurred ground — the inner
   wrapper is what's constrained to the content width. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;

  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--border);
  /* Clears the status-bar / camera cutout when running standalone on a
     notched phone. */
  padding-top: env(safe-area-inset-top, 0px);
}

.site-header-inner {
  /* width + centring from the shell rule near the top */
  height: 60px;

  display: flex;
  align-items: center;
  gap: 20px;
}

.brand-logo {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-shrink: 0;
}

.brand-logo .logo-mark {
  width: 30px;
  height: 30px;
  border-radius: 9px;

  display: flex;
  align-items: center;
  justify-content: center;

  font-size: 12px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--on-accent);

  background: var(--accent);
}

.brand-logo .logo-text {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}

/* Primary nav — quiet text links, active one carries the accent. */
.primary-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-right: auto;
}

.primary-nav-link {
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 600;
  transition: color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.primary-nav-link:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* The soft tint, not the solid active pill: the nav shouldn't outweigh
   the page's own controls. */
.primary-nav-link.active {
  color: var(--text);
  background: var(--accent-soft);
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.nav-chips {
  display: flex;
  align-items: center;
  gap: 6px;
}

.nav-icons {
  display: flex;
  align-items: center;
  gap: 2px;
}

.icon-btn {
  position: relative;
  width: 36px;
  height: 36px;

  display: flex;
  align-items: center;
  justify-content: center;

  border: none;
  border-radius: var(--radius-full);

  background: transparent;
  color: var(--muted);
  font-size: 16px;

  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

.nav-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  font-size: 20px;
  line-height: 1;
}

.account-trigger:hover .nav-avatar,
.account-trigger[aria-expanded="true"] .nav-avatar { color: var(--text); }

.icon-badge {
  position: absolute;
  top: 3px;
  right: 3px;

  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: var(--radius-full);
  border: 2px solid var(--bg);

  align-items: center;
  justify-content: center;

  background: var(--danger);
  color: #fff;

  font-size: 9px;
  font-weight: 800;
  line-height: 1;
}

/* Nav counter chips — inherit the shared .chip primitive. The 🪙 / 💎
   emoji carry each currency's colour; the numbers stay plain text so
   the header reads as one quiet bar. */
.coin-pill,
.gem-pill {
  color: var(--text);
  text-decoration: none;
}

.level-pill { color: var(--muted); }
.level-pill span { color: var(--text); }

/* =========================================================
   ACCOUNT MENU
========================================================= */

.account-menu {
  position: relative;
}

.account-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 500;

  min-width: 190px;
  padding: 6px;
  border-radius: var(--radius-md);

  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);

  animation: popIn 0.15s ease;
}

.account-dropdown-header {
  padding: 8px 10px 6px;
  font-size: 13px;
  font-weight: 800;
  color: var(--text);
}

.account-dropdown-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;

  padding: 9px 10px;
  border: none;
  border-radius: 8px;

  background: none;
  color: var(--text);
  text-decoration: none;

  font-size: 13.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;

  transition: background 0.15s ease;
}

.account-dropdown-item:hover {
  background: var(--surface-2);
}

.account-dropdown-item.danger {
  color: var(--danger);
}

.account-dropdown-item i {
  font-size: 14px;
  width: 16px;
  text-align: center;
}

.account-dropdown-divider {
  height: 1px;
  margin: 6px 4px;
  background: var(--border);
}

/* =========================================================
   MODALS
========================================================= */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;

  display: none;
  align-items: center;
  justify-content: center;
  /* Without this, a modal-card taller than the visible viewport gets
     centered partly above y:0 with no way to scroll up to it — flex
     centering doesn't reflow to the overflow start on its own. */
  overflow-y: auto;
  padding: 24px 0;
  /* Equipping a theme rebuilds #customize-grid's contents in place
     (see renderCustomizeTab) — Chrome's scroll anchoring can read that
     mutation as a reason to silently readjust this container's own
     scrollTop to "preserve" the user's view, which visibly yanks the
     whole modal card away from its centered position. The grid's own
     scroll position is already explicitly saved/restored in JS, so
     anchoring here only ever fights that instead of helping. */
  overflow-anchor: none;

  background: color-mix(in srgb, #05070c 62%, transparent);
  backdrop-filter: blur(8px);
}

.modal-card {
  width: min(480px, 90%);
  max-height: 82vh;
  /* vh is calculated against the browser's largest possible viewport
     (address bar hidden) — on mobile, with the address bar visible,
     that's taller than what's actually on screen, which is exactly
     when this modal most needs to be capped. dvh tracks the real,
     currently-visible viewport instead. Older browsers ignore this
     line and keep the vh value above. */
  max-height: 82dvh;
  overflow-y: auto;

  padding: 26px;
  border-radius: var(--radius-lg);

  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text);

  box-shadow: var(--shadow-lg);
  animation: popIn 0.2s var(--ease-out);
}

@media (max-width: 768px) {
  /* Top-anchored instead of centered — on a short mobile viewport a
     centered tall card has no headroom, so centering math alone can't
     keep the whole thing on screen even with the overlay scroll fix
     above. Anchoring to the top guarantees the card's own header/close
     button are always the first thing visible. */
  .modal-overlay {
    align-items: flex-start;
  }
}

@keyframes popIn {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.modal-card h2 {
  margin: 0 0 16px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
}

/* A <form> wrapper (auth modals) that generates no box of its own, so
   wrapping fields in it for real Enter-submit / password-manager
   behaviour changes nothing about the layout. */
.modal-form {
  display: contents;
}

/* input / select / textarea styling comes from the shared .field rule in
   PRIMITIVES; only the modal's own spacing quirk (fields stacked with a
   top gap instead of a wrapping <label>) lives here. */
.modal-card input,
.modal-card select,
.modal-card textarea {
  margin-top: 10px;
}

.modal-card textarea {
  resize: vertical;
}

.cp-panel-hint {
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--muted);
}

.display-pref-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.display-pref-row:last-child {
  border-bottom: none;
}

.display-pref-row label {
  font-size: 14px;
  font-weight: 600;
}

.display-pref-row select {
  width: auto;
  margin-top: 0;
}

.display-pref-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

#login-error {
  margin-top: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--danger);
  min-height: 1em;
}

.modal-card p {
  margin-top: 14px;
  font-size: 13px;
  color: var(--muted);
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0;

  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.google-auth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 10px;

  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);

  font-weight: 600;
  font-size: 13.5px;

  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.google-auth-btn:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.google-auth-btn i {
  color: #4285f4;
  font-size: 16px;
}

.modal-card p a {
  color: var(--accent);
  font-weight: 600;
}

.modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

.modal-actions button {
  flex: 1;
  border-radius: var(--radius-sm);
  padding: 11px;
  font-family: inherit;
  font-weight: 700;
  font-size: 13.5px;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.modal-actions .secondary {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border-strong);
  font-weight: 600;
}

.modal-actions .secondary:hover {
  background: var(--surface-2);
  color: var(--text);
}

.modal-actions button:not(.secondary) {
  border: 1px solid transparent;
  color: var(--on-accent);
  background: var(--accent);
}

.modal-actions button:not(.secondary):hover {
  background: var(--accent-hover);
}

.modal-actions button.btn-danger {
  background: var(--danger);
  color: #fff;
  box-shadow: none;
}

.modal-actions button.btn-danger:hover {
  background: color-mix(in srgb, var(--danger) 88%, #000);
}

.confirm-modal-card {
  width: min(360px, 90%);
}

#confirm-modal-message {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--text);
}

/* =========================================================
   CUSTOMIZE MODAL (themes / cursors / sounds / backgrounds)
========================================================= */

.customize-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  padding-bottom: 2px;
  overflow-x: auto;
}

/* colour / border / radius / active treatment come from the shared .pill
   rule in PRIMITIVES; size + shrink behaviour is all that's local. */
.customize-tab {
  flex-shrink: 0;
  padding: 7px 13px;
  font-size: 12.5px;
  font-weight: 600;
}

.customize-login-prompt {
  padding: 20px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  text-align: center;
  color: var(--muted);
  font-size: 13px;
}

.customize-login-prompt a {
  color: var(--accent);
  font-weight: 700;
}

/* =========================================================
   FRIENDS MODAL
========================================================= */

#friends-body .section-label {
  display: block;
  margin: 4px 0 10px;
}

#friend-requests-section {
  margin-bottom: 20px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.friends-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 260px;
  overflow-y: auto;
}

.friend-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;

  padding: 10px 12px;
  border-radius: var(--radius-sm);

  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.friend-row:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
}

.friend-row a {
  font-weight: 700;
  font-size: 13.5px;
  color: var(--text);
}

.friend-row a:hover {
  color: var(--accent);
}

.friend-row-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.friend-action-btn {
  padding: 6px 12px;
  border: none;
  border-radius: 8px;

  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.friend-action-btn.accept {
  background: var(--accent);
  color: var(--on-accent);
}

.friend-action-btn.decline {
  background: var(--surface);
  color: var(--muted);
  border: 1px solid var(--border);
}

/* Profile-page friend button states (see .btn in DESIGN SYSTEM) */
.friend-btn-active {
  background: color-mix(in srgb, var(--success) 14%, transparent);
  color: var(--success);
  border-color: transparent;
}

.friend-meta {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 2px;
}

.friend-action-btn.message {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--accent-soft);
  color: var(--accent);
}

/* =========================================================
   MESSAGES
========================================================= */

.conversation-row {
  cursor: pointer;
  transition: transform 0.15s ease;
}

.conversation-row:hover {
  transform: translateX(2px);
}

.conversation-preview {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
}

.thread-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

.thread-header strong {
  font-size: 15px;
}

.thread-messages {
  display: flex;
  flex-direction: column;
  gap: 8px;

  height: 320px;
  overflow-y: auto;

  padding: 4px 2px;
  margin-bottom: 12px;
}

.thread-bubble {
  align-self: flex-start;
  max-width: 75%;

  padding: 9px 13px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  border: 1px solid var(--border);

  font-size: 13.5px;
  line-height: 1.4;
  word-break: break-word;
}

.thread-bubble.mine {
  align-self: flex-end;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
}

.thread-bubble small {
  display: block;
  margin-top: 4px;
  font-size: 10.5px;
  opacity: 0.7;
}

.race-invite-card {
  display: flex;
  align-items: center;
  gap: 8px;
  color: inherit;
  font-size: 13.5px;
  font-weight: 600;
}

.race-invite-card strong {
  text-decoration: underline;
}

.race-invite-icon {
  font-size: 18px;
}

.thread-input-row {
  display: flex;
  gap: 8px;
}

.thread-input-row input {
  flex: 1;
  padding: 10px 14px;
  border-radius: 999px;

  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);

  font-size: 13.5px;
  outline: none;
}

.thread-input-row input:focus {
  border-color: var(--accent);
}

.thread-input-row .btn {
  flex-shrink: 0;
  padding: 10px 18px;
}

/* =========================================================
   GROUP CHAT
========================================================= */

.messages-list-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}

.messages-list-header h2 {
  margin: 0;
}

.thread-bubble-sender {
  display: block;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 2px;
}

.group-role-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: 999px;

  background: var(--accent-soft);
  color: var(--accent);

  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.group-rename-row {
  display: flex;
  gap: 8px;
  margin-bottom: 18px;
}

.group-rename-row input {
  flex: 1;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
}

#group-add-member-row {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.group-friend-picker-row {
  cursor: pointer;
}

.group-friend-picker-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

/* min-height + max-height (NOT a plain `height`) — categories have very
   different item counts (Themes ~23 vs. Limited ~6), so a max-height-
   only grid renders shorter for smaller tabs, and since #theme-modal is
   vertically centered (.modal-overlay's align-items:center), a shorter
   grid shrinks the whole modal-card and shifts its top — including the
   tab row — down the screen when switching between tabs of different
   sizes. A plain `height: 360px` (tried first) fixes that but breaks
   card rendering: with a definite height smaller than the natural
   content size, the grid's implicit auto-sized rows COMPRESS to fit
   instead of overflowing/scrolling, squashing every card down to ~34px
   with its content clipped. min-height alongside the original
   max-height keeps the box always at least 360px (stabilizing the
   modal) while leaving row sizing unconstrained, so cards keep their
   natural size and long tabs still scroll internally past 360px. */
.customize-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  min-height: 360px;
  max-height: 360px;
  overflow-y: auto;
  padding: 2px;
  /* This is the element whose contents actually get replaced on every
     equip — same scroll-anchoring opt-out as .modal-overlay above, and
     for the same reason (its own scrollTop is already explicitly
     preserved in JS, so anchoring here can only misfire). */
  overflow-anchor: none;
}

.customize-card {
  position: relative;
  padding: 10px;
  border-radius: var(--radius-md);
  overflow: hidden;

  background: var(--surface-2);
  border: 1px solid var(--border);

  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.customize-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-2px);
}

.customize-card.equipped {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.customize-card.locked .customize-swatch {
  filter: grayscale(0.4);
  opacity: 0.7;
}

.customize-swatch {
  height: 54px;
  border-radius: var(--radius-sm);
  margin-bottom: 8px;

  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  overflow: hidden;
}

/* Theme swatch — a real mini-preview. [data-theme-scope="<slug>"] on this
   element (emitted by swatchHtml) applies that theme's token block
   locally, so every var() below resolves to the theme's own values. */
.theme-swatch {
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: 6px;
  padding: 9px 11px;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--border);
}

.theme-swatch .ts-row {
  display: flex;
  align-items: center;
  gap: 7px;
}

.theme-swatch .ts-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

.theme-swatch .ts-bar {
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--border);
}

.theme-swatch .ts-text {
  font-family: var(--font-display, var(--font));
  font-size: 12px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
}

.theme-swatch .ts-text b { color: var(--text); font-weight: 700; }
.theme-swatch .ts-text i { color: var(--faint); font-style: normal; font-weight: 600; }

/* The locked-card grayscale would wash the preview out entirely — keep it
   readable, just dim it. */
.customize-card.locked .theme-swatch {
  filter: none;
  opacity: 0.85;
}

/* Limited-tier themes get the same slow single-hue orbiting aura as the
   shop's icon tile (see .shop-card--limited .shop-preview::before) —
   the modal is browsed at least as often as /shop/, so the "this one is
   special" motion needs to live here too. Keyed by data-slug (always set
   by renderCustomizeTab) rather than the .exclusive/.premium classes:
   Halloween / Winter Lights / Gold Prestige / Founder / Obsidian are
   gated by season or an unlock requirement, not max_supply or gems, so
   neither class reliably applies to them. Celestial Eclipse and Prism
   Storm already have their own aura via .customize-card.premium — left
   alone here to avoid two ::before rules fighting over one element. */
.customize-card[data-slug="founder"] .theme-swatch,
.customize-card[data-slug="halloween"] .theme-swatch,
.customize-card[data-slug="christmas"] .theme-swatch,
.customize-card[data-slug="prestige-gold"] .theme-swatch,
.customize-card[data-slug="obsidian"] .theme-swatch {
  position: relative;
}

.customize-card[data-slug="founder"] .theme-swatch::before,
.customize-card[data-slug="halloween"] .theme-swatch::before,
.customize-card[data-slug="christmas"] .theme-swatch::before,
.customize-card[data-slug="prestige-gold"] .theme-swatch::before,
.customize-card[data-slug="obsidian"] .theme-swatch::before {
  content: "";
  position: absolute;
  inset: -60%;
  z-index: -1;
  background: conic-gradient(from 0deg,
    transparent, color-mix(in srgb, var(--accent) 65%, transparent) 35%, transparent 62%);
  filter: blur(8px);
  animation: limitedIconOrbit 7s linear infinite;
}

.customize-card p {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
}

.customize-card small {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 10.5px;
  text-transform: capitalize;
}

.customize-card.exclusive {
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
}

.customize-scarcity {
  display: block;
  margin-top: 3px;
  color: var(--warning);
  font-size: 10.5px;
  font-weight: 700;
}

.customize-scarcity.sold-out {
  color: var(--danger);
}

.customize-card button {
  width: 100%;
  margin-top: 8px;
  padding: 7px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);

  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}

.customize-card .btn-equip {
  background: var(--accent-soft);
  color: var(--accent);
}

.customize-card .btn-equip:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* "Equipped" is a status, not an action — a soft check-tag, not a solid
   green button competing with the real CTAs. */
.customize-card .btn-equipped {
  background: color-mix(in srgb, var(--success) 15%, transparent);
  color: var(--success);
  border-color: color-mix(in srgb, var(--success) 30%, transparent);
  cursor: default;
}

.customize-card .btn-buy {
  background: var(--accent);
  color: var(--on-accent);
}

.customize-card .btn-buy:hover {
  background: var(--accent-hover);
}

@media (max-width: 480px) {
  .customize-grid { grid-template-columns: repeat(2, 1fr); }
}

/* =========================================================
   TYPING TEST
========================================================= */

.test-toolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 auto;
  /* Recedes while a test is running (see body.test-active below) so the
     passage is the only thing asking for attention. */
  transition: opacity var(--dur-3) var(--ease);
}

/* The little vertical rules between groups read as clutter now that each
   group is its own segmented control — a touch of extra gap does the job. */
.toolbar-divider {
  width: 4px;
  height: 1px;
  background: transparent;
}

/* Each control group is one segmented control: a recessed track with
   transparent segments, the active one raised. Fewer distinct shapes to
   parse than a row of separate pills. */
.mode-picker,
.duration-picker {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.mode-btn,
.duration-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 13px;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.mode-btn:hover,
.duration-btn:hover {
  color: var(--text);
  background: transparent;
  border-color: transparent;
}

.mode-btn.active,
.duration-btn.active {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
  box-shadow: var(--shadow-sm);
}

/* The signature-accent moment stays on the *primary* segmented control
   (mode) so the equipped theme still speaks here. */
.mode-btn.active {
  background: var(--pill-active-bg);
  color: var(--pill-active-fg);
  border-color: transparent;
  box-shadow: var(--pill-active-ring);
}

.typing-controls {
  display: flex;
  align-items: center;
  gap: 12px;

  padding: 7px 8px 7px 16px;
  border-radius: var(--radius-full);

  background: var(--surface-2);
  border: 1px solid var(--border);
}

.timer-chip {
  min-width: 30px;
  text-align: center;
  font-size: 14px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.live-stat {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.live-stat span {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

#timer.low {
  color: var(--danger);
  animation: pulse 0.6s infinite;
}

@keyframes pulse {
  50% { transform: scale(1.15); }
}

.restart-btn {
  width: 32px;
  height: 32px;

  display: flex;
  align-items: center;
  justify-content: center;

  border: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--muted);

  transition: transform var(--dur-3) var(--ease), background var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease);
}

.restart-btn:hover {
  background: var(--accent);
  color: var(--on-accent);
  transform: rotate(-180deg);
}

/* Zen mode's manual end-test control — .restart-btn is a fixed 32×32
   circle sized for a single icon glyph, which "Finish" (icon + text)
   overflowed and overlapped the adjacent restart button. This is sized
   to the same 32px row height but lets its width grow with the label. */
.finish-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 15px;
  flex-shrink: 0;

  border: none;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);

  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;

  transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.finish-btn:hover {
  transform: translateY(-1px);
  background: var(--accent-hover);
}

/* The passage — the actual product. Bigger, calmer, and the one
   element on the page with real presence. No hover animation (you're
   about to type into it, not click it). */
#word-container {
  /* width + centring from the shell rule near the top — this box lines
     up with every other section on the page. */
  margin-bottom: 22px;
  padding: 0 34px 30px;

  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);

  font-family: var(--test-font);
  font-size: 1.6rem;
  line-height: 2.5rem;
  letter-spacing: 0.01em;

  position: relative;
  overflow: hidden;
  white-space: nowrap;

  cursor: text;
  transition: box-shadow var(--dur-3) var(--ease), border-color var(--dur-3) var(--ease);
}

/* Running a test: the field gains a quiet accent presence and the rest
   of the page steps back. */
body.test-active #word-container {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border-strong));
  box-shadow: 0 0 0 1px var(--accent-soft),
              0 24px 80px -32px color-mix(in srgb, var(--accent) 70%, transparent);
}

body.test-active .test-toolbar,
body.test-active .daily-challenge-bar {
  opacity: 0;
}

body.test-active .test-toolbar:hover,
body.test-active .test-toolbar:focus-within {
  opacity: 1;
}

@media (max-width: 768px) {
  #word-container { padding: 0 22px 26px; }
}

.guest-teaser-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 18px 22px;
  font-weight: 600;
  color: var(--text);
}

.guest-teaser-banner .buy-btn {
  width: auto;
  padding: 10px 20px;
  white-space: nowrap;
}

/* Quiet marker under the word actually being typed — a soft underline
   rather than a glowing box, so it guides the eye without lighting up. */
body.test-active .word.active-word {
  border-radius: var(--radius-sm);
  box-shadow: inset 0 -2px 0 var(--accent);
  transition: box-shadow var(--dur-1) var(--ease);
}

#word-track {
  display: inline-block;
  white-space: nowrap;
  /* Anchor for the absolutely-positioned #live-caret child even before
     scrollWords() sets the first translateX (a transform would also
     establish the containing block, but not until it exists). */
  position: relative;
  transition: transform 0.12s ease-out;
}

/* Code mode: real snippets have line breaks, unlike every other test
   source which is one long horizontally-scrolling line. #word-track
   switches to a wrapping flex row instead of the nowrap/translateX setup
   above (scrollWords() in home.html has a matching JS branch — it
   scrollIntoView()s instead of setting a horizontal transform here), and
   the container grows a scrollable max-height instead of clipping. */
#word-container:has(.code-mode) {
  white-space: normal;
  max-height: 340px;
  overflow-y: auto;
  overflow-x: hidden;
}

/* #word-track.code-mode (not just .code-mode): a plain class selector
   loses to the #word-track ID rule above regardless of source order —
   ID specificity always beats class specificity. */
#word-track.code-mode {
  display: flex;
  flex-wrap: wrap;
  white-space: normal;
}

/* A code-mode sentinel token — see wordlists.flatten_code_snippet and
   build_render_words. Forces the wrap via flex-basis:100%, carries no
   visible content or indent itself (indent is a margin on the next real
   word's span, applied server-side from render_words). */
.word.linebreak {
  flex-basis: 100%;
  height: 0;
  padding: 0;
  margin: 0;
}

/* Code mode was reusing the word-salad "answer chip" look (16px gaps,
   padded/rounded boxes, proportional font) — fine for prose, but it made
   code read as a row of detached blobs instead of code. Monospace +
   tight single-space-width gaps + no chip padding reads as an actual
   code block. Specificity note: #word-track.code-mode (1 ID) beats the
   plain .word rule below regardless of source order. */
#word-track.code-mode {
  font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'Courier New', monospace;
  line-height: 1.6em;
}

/* Code mode types character by character now (see handleCodeInput in
   home.html), so every .word span is exactly one character — no gap
   between them beyond what real space characters in the code itself
   provide (margin-right:0, not the 0.6em this used when .word meant
   "chunk"; that would now add extra spacing after every character). */
#word-track.code-mode .word {
  margin-right: 0;
  padding: 0 1px;
  border-radius: 3px;
}

/* A lone space character renders at 0 width under white-space:normal
   (confirmed by measuring it) since there's no other content to give the
   inline-block any intrinsic size — it needs to preserve its own glyph
   width to actually show as a gap. */
#word-track.code-mode .char {
  white-space: pre;
}

.word {
  display: inline-block;
  margin-right: 14px;
  padding: 2px 3px;
  border-radius: var(--radius-sm);
  transition: color var(--dur-1) var(--ease);
}

/* The focus-typing-tool convention: untyped text is dim, correctly
   typed text is just normal text (no reward colour on every keystroke),
   and only a wrong word carries a mark. Calmer, and errors read
   instantly because they're the only coloured thing. */
.word.correct-word {
  background: transparent;
}

.word.incorrect-word {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.word.word-shake {
  animation: wordShake 0.28s var(--ease);
  background: color-mix(in srgb, var(--danger) 20%, transparent);
}

@keyframes wordShake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

.char {
  display: inline-block;
  /* Untyped text: between --faint and --muted — dim enough that typed
     text (--text) clearly leads, but still easy to read ahead. */
  color: color-mix(in srgb, var(--muted) 72%, var(--faint));
  transition: color var(--dur-1) var(--ease);
}

.char.correct {
  color: var(--text);
}

.char.incorrect {
  color: var(--danger);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

.char.current {
  position: relative;
}

/* The caret — one shape (a slim bar), one paint path (the --caret-*
   tokens). A cursor skin changes colour/glow/animation by overriding
   those tokens; the caret-shape display pref changes geometry via the
   html[data-caret-shape] rules further down. Both compose. */
.char.current::before {
  content: "";
  position: absolute;
  left: -1px;
  top: -0.06em;
  width: var(--caret-width);
  height: 1.12em;
  border-radius: 1px;
  background: var(--caret-bg);
  background-size: var(--caret-bg-size);
  box-shadow: var(--caret-glow);
  clip-path: var(--caret-clip);
  animation: var(--caret-anim);
}

@keyframes blink {
  0%, 45% { opacity: 1; }
  55%, 95% { opacity: 0; }
  100% { opacity: 1; }
}

/* Block-caret pulse. Two translucent stops (never a hard 0) so the glyph
   under the block never fully disappears, and low enough that a
   semi-transparent accent fill reads on both dark and light themes. Ends
   on the visible stop so the reduced-motion freeze (which holds the
   final keyframe) leaves a clearly-visible steady block, not a ghost. */
@keyframes caretBlockBlink {
  0%, 45% { opacity: 0.42; }
  50%, 95% { opacity: 0.16; }
  100% { opacity: 0.42; }
}

/* =========================================================
   DISPLAY PREFS — free, client-side (typeflow-display-prefs), not
   ShopItems. Set on <html> before <body> exists (base.html head IIFE)
   for no flash-of-wrong-caret.
========================================================= */

/* Caret shape — geometry only, so a cursor skin's colour/glow (carried
   on the --caret-* tokens) still shows through. Scoped `html[…] body …`
   to clear both the base rule above and any skin's own width. "bar" is
   the default and needs no rule. The smooth caret (.live-caret) takes
   its geometry from JS (updateLiveCaret in home.html) instead, except
   "off", handled here for both. */
html[data-caret-shape="block"] body .char.current::before {
  left: 0;
  top: 0;
  bottom: auto;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  clip-path: none;
  /* No mix-blend-mode: plus-lighter here — it adds light, so the block
     vanished against white on every light theme. A plain translucent
     accent fill (opacity from caretBlockBlink) reads on any background. */
  animation: caretBlockBlink 1.05s steps(1) infinite;
}

html[data-caret-shape="underline"] body .char.current::before {
  left: 0;
  top: auto;
  bottom: -0.08em;
  width: 100%;
  height: 0.14em;
  border-radius: 2px;
  clip-path: none;
}

html[data-caret-shape="off"] body .char.current::before,
html[data-caret-shape="off"] body .live-caret {
  display: none !important;
}

/* Font size presets for the typing area — "lg" is the default pref, so
   every size is stated explicitly rather than left to #word-container. */
html[data-font-size="sm"] #word-container {
  font-size: 1.25rem;
  line-height: 2.2rem;
}

html[data-font-size="md"] #word-container {
  font-size: 1.6rem;
  line-height: 2.7rem;
}

html[data-font-size="lg"] #word-container {
  font-size: 2rem;
  line-height: 3.25rem;
}

html[data-font-size="xl"] #word-container {
  font-size: 2.4rem;
  line-height: 3.7rem;
}

html[data-live-wpm="off"] .tc-head .tc-wpm {
  display: none;
}

/* Smooth caret — one floating element that glides between characters
   (the per-character ::before can't be transitioned, it jumps parents).
   A child of #word-track (home.html markup) so the virtual scroll
   carries it; positioned per-frame by the ease loop in home.html, which
   is why there's NO CSS transition on transform here — it would fight
   the JS. Takes the same --caret-* paint as everything else, so an
   equipped cursor skin shows here too. */
.live-caret {
  display: none;
  position: absolute;
  left: 0;
  top: 0;
  width: var(--caret-width);
  height: 1em;
  border-radius: 1px;
  background: var(--caret-bg);
  background-size: var(--caret-bg-size);
  box-shadow: var(--caret-glow);
  clip-path: var(--caret-clip);
  pointer-events: none;
  transform: translate(0, 0);
}

html[data-smooth-caret="on"] .live-caret {
  display: block;
  will-change: transform;
}

html[data-smooth-caret="on"] .char.current::before {
  display: none;
}

/* Block + smooth: the ease loop sizes the floating caret to the whole
   character cell, and it paints on top of the glyph — so drop it to a
   translucent wash and kill the outer glow, or it's a solid slab over the
   letter it marks. Bar and underline stay fully opaque (they sit beside /
   under the glyph, not over it). */
html[data-caret-shape="block"][data-smooth-caret="on"] .live-caret {
  opacity: 0.4;
  border-radius: 3px;
  box-shadow: none;
}

/* =========================================================
   CURSOR SKINS
   Matches ShopItem slugs (item_type="cursor"). No data-cursor
   attribute = the default accent-colored blinking bar above.

   Each rule targets both the real typing-test cursor AND a small
   demo bar in the customize modal ([data-cursor-demo="slug"]) so
   equipping shows an unmistakable live preview even on pages that
   have no typing test (Shop, Dashboard, etc).
========================================================= */

.cursor-demo-bar {
  display: inline-block;
  width: var(--caret-width);
  height: 26px;
  border-radius: 2px;
  background: var(--caret-bg);
  background-size: var(--caret-bg-size);
  box-shadow: var(--caret-glow);
  clip-path: var(--caret-clip);
  animation: var(--caret-anim);
}

/* C7 rework: neon-sign red — the old cyan read as Plasma Cursor's. */
body[data-cursor="neon-cursor"],
[data-cursor-demo="neon-cursor"] {
  --caret-bg: #ff2e63;
  --caret-glow: 0 0 10px #ff2e63, 0 0 22px rgba(255, 46, 99, 0.6);
  --caret-anim: cursorGlowPulse 1s ease-in-out infinite;
}
@keyframes cursorGlowPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

/* Fixed, always-vivid colour, not var(--accent) — reusing the accent
   made this indistinguishable from the default caret on every theme. */
body[data-cursor="pulse-cursor"],
[data-cursor-demo="pulse-cursor"] {
  --caret-width: 4px;
  --caret-bg: #ec4899;
  --caret-glow: 0 0 10px rgba(236, 72, 153, 0.6);
  --caret-anim: cursorPulse 0.7s ease-in-out infinite;
}
@keyframes cursorPulse {
  0%, 100% { transform: scaleY(1) scaleX(1); opacity: 1; }
  50% { transform: scaleY(0.5) scaleX(1.6); opacity: 0.65; }
}

body[data-cursor="lightning-cursor"],
[data-cursor-demo="lightning-cursor"] {
  --caret-width: 3px;
  --caret-bg: #fde047;
  --caret-glow: 0 0 14px #fde047, 0 0 28px rgba(253, 224, 71, 0.5);
  --caret-anim: cursorFlash 0.3s steps(2) infinite;
}
@keyframes cursorFlash { 50% { opacity: 0.25; } }

body[data-cursor="rainbow-trail"],
[data-cursor-demo="rainbow-trail"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #ff5252, #ffb74d, #fff176, #66bb6a, #42a5f5, #ba68c8);
  --caret-bg-size: 100% 300%;
  --caret-glow: 0 0 12px rgba(255, 255, 255, 0.4);
  --caret-anim: rainbowShift 2s linear infinite;
}
@keyframes rainbowShift {
  0% { background-position: 0 0%; }
  100% { background-position: 0 100%; }
}

body[data-cursor="toxic-cursor"],
[data-cursor-demo="toxic-cursor"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #a3e635, #4d7c0f);
  --caret-glow: 0 0 10px rgba(163, 230, 53, 0.7), 0 0 20px rgba(77, 124, 15, 0.4);
  --caret-anim: cursorToxicDrip 1.1s ease-in-out infinite;
}
@keyframes cursorToxicDrip {
  0%, 100% { transform: scaleY(1); opacity: 1; }
  50% { transform: scaleY(1.15) translateY(2px); opacity: 0.8; }
}

/* Legendary — a deep violet glow that visibly pulses outward. */
body[data-cursor="void-cursor"],
[data-cursor-demo="void-cursor"] {
  --caret-width: 4px;
  --caret-bg: #1e1033;
  --caret-glow: 0 0 8px #7c3aed, 0 0 18px rgba(124, 58, 237, 0.7), 0 0 32px rgba(124, 58, 237, 0.4);
  --caret-anim: cursorVoidPulse 1.4s ease-in-out infinite;
}
@keyframes cursorVoidPulse {
  0%, 100% { box-shadow: 0 0 8px #7c3aed, 0 0 18px rgba(124, 58, 237, 0.7), 0 0 32px rgba(124, 58, 237, 0.4); }
  50% { box-shadow: 0 0 14px #a855f7, 0 0 28px rgba(168, 85, 247, 0.85), 0 0 48px rgba(124, 58, 237, 0.6); }
}

/* Limited to 40 — shifts between gold and diamond-white. */
body[data-cursor="crown-jewels-cursor"],
[data-cursor-demo="crown-jewels-cursor"] {
  --caret-width: 4px;
  --caret-bg: linear-gradient(to bottom, #fde047, #fff, #eab308);
  --caret-bg-size: 100% 200%;
  --caret-glow: 0 0 10px #fde047, 0 0 22px rgba(253, 224, 71, 0.6);
  --caret-anim: cursorJewelShimmer 1.6s ease-in-out infinite;
}
@keyframes cursorJewelShimmer {
  0%, 100% { background-position: 0 0%; box-shadow: 0 0 10px #fde047, 0 0 22px rgba(253, 224, 71, 0.6); }
  50% { background-position: 0 100%; box-shadow: 0 0 16px #fff, 0 0 30px rgba(255, 255, 255, 0.8); }
}

/* C7 rework: a hollow frosted-glass bar — First Frost (seasonal) owns the
   solid light-blue glint, so this one is an outline you can see through. */
body[data-cursor="ice-cursor"],
[data-cursor-demo="ice-cursor"] {
  --caret-width: 5px;
  --caret-bg: rgba(186, 230, 253, 0.12);
  --caret-glow: inset 0 0 0 1.5px #7dd3fc, 0 0 8px rgba(125, 211, 252, 0.55);
  --caret-anim: cursorIceGlint 1.6s ease-in-out infinite;
}
@keyframes cursorIceGlint { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }

/* Dark bar, bright silver glow so it stays visible on any theme. */
body[data-cursor="shadow-cursor"],
[data-cursor-demo="shadow-cursor"] {
  --caret-width: 4px;
  --caret-bg: #1c1917;
  --caret-glow: 0 0 8px #a8a29e, 0 0 18px rgba(168, 162, 158, 0.5);
  --caret-anim: cursorShadowWisp 1.8s ease-in-out infinite;
}
@keyframes cursorShadowWisp {
  0%, 100% { transform: scaleX(1); box-shadow: 0 0 8px #a8a29e, 0 0 18px rgba(168, 162, 158, 0.5); }
  50% { transform: scaleX(1.8); box-shadow: 0 0 12px #d6d3d1, 0 0 26px rgba(214, 211, 209, 0.6); }
}

body[data-cursor="plasma-cursor"],
[data-cursor-demo="plasma-cursor"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #22d3ee, #a855f7);
  --caret-glow: 0 0 10px #22d3ee, 0 0 20px rgba(168, 85, 247, 0.6);
  --caret-anim: cursorPlasmaArc 0.5s steps(3) infinite;
}
@keyframes cursorPlasmaArc {
  0%, 100% { transform: translateX(0); }
  33% { transform: translateX(-1px); }
  66% { transform: translateX(1px); }
}

body[data-cursor="starlight-cursor"],
[data-cursor-demo="starlight-cursor"] {
  --caret-width: 3px;
  --caret-bg: #fff;
  --caret-glow: 0 0 8px #fff, 0 0 16px rgba(255, 255, 255, 0.7), 0 0 26px rgba(199, 210, 254, 0.5);
  --caret-anim: cursorStarTwinkle 1.5s ease-in-out infinite;
}
@keyframes cursorStarTwinkle {
  0%, 100% { opacity: 1; box-shadow: 0 0 8px #fff, 0 0 16px rgba(255, 255, 255, 0.7), 0 0 26px rgba(199, 210, 254, 0.5); }
  50% { opacity: 0.6; box-shadow: 0 0 4px #fff, 0 0 10px rgba(255, 255, 255, 0.4); }
}

/* Premium (Gems). clip-path facets the caret into a gem-cut silhouette
   (dropped by the Block/Underline caret shapes). */
body[data-cursor="diamond-cursor"],
[data-cursor-demo="diamond-cursor"] {
  --caret-width: 6px;
  --caret-bg: linear-gradient(160deg, #e0f2fe, #67e8f9 35%, #0ea5e9 70%, #e0f2fe);
  --caret-clip: polygon(50% 0%, 100% 15%, 100% 85%, 50% 100%, 0% 85%, 0% 15%);
  --caret-glow: 0 0 10px #67e8f9, 0 0 20px rgba(14, 165, 233, 0.6);
  --caret-anim: cursorDiamondSparkle 1.6s ease-in-out infinite;
}
@keyframes cursorDiamondSparkle {
  0%, 100% { filter: brightness(1); box-shadow: 0 0 10px #67e8f9, 0 0 20px rgba(14, 165, 233, 0.6); }
  50% { filter: brightness(1.3); box-shadow: 0 0 16px #fff, 0 0 30px rgba(103, 232, 249, 0.8); }
}

/* Premium (Gems). A radial "core" under the flame body, 3-stop glow. */
body[data-cursor="phoenix-cursor"],
[data-cursor-demo="phoenix-cursor"] {
  --caret-width: 4px;
  --caret-bg:
    radial-gradient(circle at 50% 20%, #fff7ed 0%, transparent 55%),
    linear-gradient(to bottom, #fde047, #f97316 45%, #7c2d12);
  --caret-glow: 0 0 10px #f97316, 0 0 22px rgba(249, 115, 22, 0.6), 0 0 34px rgba(124, 45, 18, 0.4);
  --caret-anim: cursorPhoenixRise 0.8s ease-in-out infinite;
}
@keyframes cursorPhoenixRise {
  0%, 100% { transform: scaleY(1) translateY(0); }
  50% { transform: scaleY(1.12) translateY(-1px); }
}

/* --- Limited-collection cursors (see main/limited.py) --- */

/* Founder's Mark — a slow gold specular sweep down a warm bar, matched
   to the Founder theme's brushed-gold frame. */
body[data-cursor="founder-cursor"],
[data-cursor-demo="founder-cursor"] {
  --caret-width: 4px;
  --caret-bg: linear-gradient(to bottom, #fff4d6, #f5c451 45%, #b8860b);
  --caret-bg-size: 100% 220%;
  --caret-glow: 0 0 10px #f5c451, 0 0 24px rgba(245, 196, 81, 0.55);
  --caret-anim: cursorFounderSweep 2.4s ease-in-out infinite;
}
@keyframes cursorFounderSweep {
  0%, 100% { background-position: 0 100%; }
  50%      { background-position: 0 0; }
}

/* Hollow Flame — jack-o'-lantern ember, an uneasy orange flicker. */
body[data-cursor="hollow-flame"],
[data-cursor-demo="hollow-flame"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #fde047, #f97316 55%, #7c2d12);
  --caret-glow: 0 0 10px #f97316, 0 0 20px rgba(124, 45, 18, 0.6);
  --caret-anim: cursorHollowFlicker 0.5s ease-in-out infinite;
}
@keyframes cursorHollowFlicker {
  0%, 100% { opacity: 1; transform: scaleY(1); }
  35% { opacity: 0.72; transform: scaleY(1.1); }
  70% { opacity: 0.9; transform: scaleY(0.94); }
}

/* First Frost — a pale ice shard with a crisp glint. */
body[data-cursor="first-frost"],
[data-cursor-demo="first-frost"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #ffffff, #bae6fd 50%, #38bdf8);
  --caret-glow: 0 0 10px #7dd3fc, 0 0 20px rgba(56, 189, 248, 0.5);
  --caret-anim: cursorFrostGlint 1.8s ease-in-out infinite;
}
@keyframes cursorFrostGlint {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.35); }
}

/* Grandmaster — polished obsidian-and-platinum, a slow authoritative
   pulse. Earned, not bought (110 WPM PB). */
body[data-cursor="grandmaster"],
[data-cursor-demo="grandmaster"] {
  --caret-width: 4px;
  --caret-bg: linear-gradient(to bottom, #f8fafc, #cbd5e1 40%, #64748b 70%, #1e293b);
  --caret-glow: 0 0 8px rgba(226, 232, 240, 0.7), 0 0 22px rgba(148, 163, 184, 0.4);
  --caret-anim: cursorGrandmaster 2s ease-in-out infinite;
}
@keyframes cursorGrandmaster {
  0%, 100% { opacity: 1; box-shadow: var(--caret-glow); }
  50% { opacity: 0.85; box-shadow: 0 0 12px rgba(226, 232, 240, 0.9), 0 0 30px rgba(148, 163, 184, 0.6); }
}

/* =========================================================
   AVATARS
   Matches ShopItem slugs (item_type="avatar"). Rendered as
   [data-avatar="slug"] on any .avatar-badge circle — race lanes, the
   waiting room, results, leaderboard rows, friends list, profile hero.
   No avatar equipped = the fallback initial-letter span stays visible.
========================================================= */

.avatar-badge {
  position: relative;
}

.avatar-badge[data-avatar]:not([data-avatar=""]) .avatar-letter {
  visibility: hidden;
}

/* An equipped avatar is a full-color emoji — a saturated brand-gradient
   disc behind it just fights the emoji's own colors and buries it.
   Swap to a quiet neutral chip instead, so the emoji itself reads
   clearly. Wins on specificity over every individual .race-lane-avatar
   /.race-avatar-circle/.lb-avatar/etc. background rule (class + two
   attribute selectors beats their single class), so one rule covers
   every avatar badge on the site regardless of context. */
.avatar-badge[data-avatar]:not([data-avatar=""]) {
  background: var(--surface-2);
  border: 1px solid var(--border);
  box-shadow: none;
}

/* ::before (not ::after) deliberately — race lane/circle avatars also use
   ::after for the equipped trail cosmetic (see RACE MODE below), and a
   single pseudo-element can't serve both jobs at once. Layout only here,
   no "content", so the per-slug rules below (lower specificity, since
   they don't repeat the :not()) are free to set it without being
   overridden. */
.avatar-badge[data-avatar]:not([data-avatar=""])::before {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1em;
  line-height: 1;
}

.avatar-badge[data-avatar="cool-cat-avatar"]::before { content: "😼"; }
.avatar-badge[data-avatar="sly-fox-avatar"]::before { content: "🦊"; }
.avatar-badge[data-avatar="panda-avatar"]::before { content: "🐼"; }

/* Rare/epic/legendary avatars below all get a filter:drop-shadow glow
   scaled to their tier — before this, only Champion/Superhero/Genesis
   had any treatment and every other avatar (even legendary ones) was a
   bare emoji swap, identical visual weight to a 70-coin common avatar
   regardless of price. Rare = one subtle, slow color pulse; epic = a
   faster two-color pulse; legendary = a three-stop color cycle, one
   step below Genesis's shimmer. Colors picked per emoji. */
.avatar-badge[data-avatar="night-owl-avatar"]::before {
  content: "🦉";
  animation: avatarNightOwlGlow 2.4s ease-in-out infinite;
}

@keyframes avatarNightOwlGlow {
  0%, 100% { filter: drop-shadow(0 0 1.5px rgba(99, 102, 241, 0.5)); }
  50% { filter: drop-shadow(0 0 5px rgba(99, 102, 241, 0.85)); }
}

.avatar-badge[data-avatar="lone-wolf-avatar"]::before {
  content: "🐺";
  animation: avatarLoneWolfGlow 2.6s ease-in-out infinite;
}

@keyframes avatarLoneWolfGlow {
  0%, 100% { filter: drop-shadow(0 0 1.5px rgba(148, 163, 184, 0.5)); }
  50% { filter: drop-shadow(0 0 5px rgba(148, 163, 184, 0.85)); }
}

.avatar-badge[data-avatar="keybot-avatar"]::before {
  content: "🤖";
  animation: avatarKeybotGlow 2.2s ease-in-out infinite;
}

@keyframes avatarKeybotGlow {
  0%, 100% { filter: drop-shadow(0 0 1.5px rgba(34, 211, 238, 0.5)); }
  50% { filter: drop-shadow(0 0 5px rgba(34, 211, 238, 0.85)); }
}

.avatar-badge[data-avatar="dragon-avatar"]::before {
  content: "🐉";
  animation: avatarDragonGlow 1.4s ease-in-out infinite;
}

@keyframes avatarDragonGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(249, 115, 22, 0.6)); }
  50% { filter: drop-shadow(0 0 7px rgba(220, 38, 38, 0.9)); }
}

.avatar-badge[data-avatar="shark-avatar"]::before {
  content: "🦈";
  animation: avatarSharkGlow 1.5s ease-in-out infinite;
}

@keyframes avatarSharkGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(30, 64, 175, 0.6)); }
  50% { filter: drop-shadow(0 0 7px rgba(34, 211, 238, 0.9)); }
}

.avatar-badge[data-avatar="alien-avatar"]::before {
  content: "👽";
  animation: avatarAlienGlow 1.3s ease-in-out infinite;
}

@keyframes avatarAlienGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(163, 230, 53, 0.6)); }
  50% { filter: drop-shadow(0 0 7px rgba(168, 85, 247, 0.9)); }
}

.avatar-badge[data-avatar="phoenix-avatar"]::before {
  content: "🐦‍🔥";
  animation: avatarPhoenixGlow 2.4s ease-in-out infinite;
}

@keyframes avatarPhoenixGlow {
  0%, 100% { filter: drop-shadow(0 0 3px rgba(220, 38, 38, 0.8)); }
  33% { filter: drop-shadow(0 0 6px rgba(249, 115, 22, 0.9)); }
  66% { filter: drop-shadow(0 0 8px rgba(250, 204, 21, 0.95)); }
}

.avatar-badge[data-avatar="cosmic-avatar"]::before {
  content: "🛸";
  animation: avatarCosmicGlow 2.6s ease-in-out infinite;
}

@keyframes avatarCosmicGlow {
  0%, 100% { filter: drop-shadow(0 0 3px rgba(168, 85, 247, 0.8)); }
  33% { filter: drop-shadow(0 0 6px rgba(99, 102, 241, 0.9)); }
  66% { filter: drop-shadow(0 0 8px rgba(34, 211, 238, 0.9)); }
}

.avatar-badge[data-avatar="champion-avatar"]::before {
  content: "🥇";
  animation: avatarChampionGlow 1.6s ease-in-out infinite;
}

@keyframes avatarChampionGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(250, 204, 21, 0.7)); }
  50% { filter: drop-shadow(0 0 7px rgba(250, 204, 21, 0.95)); }
}

/* Limited to 100 units ever — a shifting white-gold shimmer, distinct
   from Champion's steady gold glow, marking it as one of the earliest
   avatars anyone could ever claim. */
.avatar-badge[data-avatar="genesis-avatar"]::before {
  content: "🌟";
  animation: avatarGenesisShimmer 2s ease-in-out infinite;
}

@keyframes avatarGenesisShimmer {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8)); }
  50% { filter: drop-shadow(0 0 8px rgba(250, 204, 21, 1)); }
}

.avatar-badge[data-avatar="ninja-avatar"]::before { content: "🥷"; }

.avatar-badge[data-avatar="wizard-avatar"]::before {
  content: "🧙";
  animation: avatarWizardGlow 2.3s ease-in-out infinite;
}

@keyframes avatarWizardGlow {
  0%, 100% { filter: drop-shadow(0 0 1.5px rgba(168, 85, 247, 0.5)); }
  50% { filter: drop-shadow(0 0 5px rgba(168, 85, 247, 0.85)); }
}

.avatar-badge[data-avatar="astronaut-avatar"]::before {
  content: "🧑‍🚀";
  animation: avatarAstronautGlow 1.5s ease-in-out infinite;
}

@keyframes avatarAstronautGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(226, 232, 240, 0.6)); }
  50% { filter: drop-shadow(0 0 7px rgba(56, 189, 248, 0.9)); }
}

.avatar-badge[data-avatar="superhero-avatar"]::before {
  content: "🦸";
  animation: avatarSuperheroGlow 1.3s ease-in-out infinite;
}

@keyframes avatarSuperheroGlow {
  0%, 100% { filter: drop-shadow(0 0 2px rgba(59, 130, 246, 0.6)); }
  50% { filter: drop-shadow(0 0 6px rgba(220, 38, 38, 0.8)); }
}

#input-field {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* =========================================================
   RESULTS
========================================================= */

.results-shell {
  display: none;
  /* width + centring from the shell rule near the top */
  margin-top: 36px;
  padding-bottom: 56px;
}

.results-shell.active {
  display: block;
  /* A short delay lets scrollIntoView get moving first; fading in with a
     vertical slide (the old translateY) fights the scroll since both are
     vertical motion happening at once — scale instead reads as "arriving"
     rather than "sliding against the scroll". */
  animation: resultsReveal 0.4s ease 0.1s both;
}

@keyframes resultsReveal {
  from { opacity: 0; transform: scale(0.98); }
  to { opacity: 1; transform: scale(1); }
}

.results-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 16px 28px;
  margin-bottom: 20px;
}

.hero-main {
  flex: 1 1 240px;
  padding: 4px 0;
}

.hero-kicker {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

.hero-score-wrap {
  display: flex;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
}

.hero-score {
  font-family: var(--font-display);
  font-size: 92px;
  line-height: 0.95;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--text);
}

.hero-score-meta span {
  font-size: 14px;
  color: var(--muted);
}

.hero-trend {
  margin-top: 6px;
  color: var(--success);
  font-weight: 700;
  font-size: 13px;
}

/* Scoped to .results-hero — a second, unrelated .hero-side (the
   dashboard welcome strip) also exists further down this file. */
.results-hero .hero-side {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  flex: 1 1 320px;
}

.mini-stat {
  padding: 14px 16px;

  border-radius: var(--radius-lg);

  background: var(--surface);
  border: var(--card-border);
  box-shadow: var(--shadow-sm);
}

.mini-label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}

.mini-value {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.performance-card,
.recent-card,
.chart-card {
  padding: 24px 26px;
  margin-bottom: 20px;
}

.section-head h3,
.section-header h3 {
  margin: 0 0 3px;
  font-size: 18px;
  font-weight: 700;
}

.section-head p,
.section-header p {
  margin: 0;
  color: var(--muted);
  font-size: 12.5px;
}

.insight-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
}

.insight-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px;
}

.insight-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;

  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);

  background: var(--accent-soft);
  color: var(--accent);
  font-size: 18px;
}

.insight-card h4 {
  margin: 0 0 2px;
  font-size: 14px;
  font-weight: 700;
}

.insight-card p {
  margin: 0;
  color: var(--muted);
  font-size: 12.5px;
}

.results-actions {
  margin-top: 20px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.primary-action {
  border: 1px solid transparent;
  padding: 12px 22px;
  border-radius: var(--radius-sm);

  font-family: inherit;
  font-weight: 700;
  font-size: 13.5px;
  color: var(--on-accent);

  background: var(--accent);
  transition: background var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.primary-action:hover {
  background: var(--accent-hover);
  transform: translateY(-1px);
}

/* =========================================================
   COMMUNITY (leaderboards + activity)
========================================================= */

.leaderboard-scope-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 12px;
}

/* scope pills inherit the shared .pill rule (colour, active treatment) —
   only the tighter size lives here. */
.lb-scope-btn {
  padding: 5px 10px;
  font-size: 11px;
  font-weight: 600;
}

.live-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;

  padding: 5px 9px;
  border-radius: var(--radius-full);

  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);

  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 25%, transparent);
  animation: pulseDot 1.5s infinite;
}

@keyframes pulseDot {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.4); opacity: 0.6; }
}

.activity-feed {
  flex: 1;
  overflow-y: auto;

  display: flex;
  flex-direction: column;
  gap: 8px;

  padding-right: 4px;
  margin: 0;
  list-style: none;
}

.activity-feed::-webkit-scrollbar {
  width: 5px;
}

/* A single empty line of text at the top of a 420px card left a wall of
   dead space below it — this fills and centers within whatever room the
   parent flex column gives it instead. */
.empty-state-mini {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;

  text-align: center;
  padding: 20px;

  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

.empty-state-mini .empty-state-icon {
  font-size: 28px;
  opacity: 0.6;
}

.activity-feed::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
}

.activity-item {
  display: flex;
  align-items: center;
  gap: 11px;

  padding: 10px 11px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  border: 1px solid transparent;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.activity-item:hover {
  border-color: var(--border-strong);
  background: var(--surface-3);
}

/* Legendary+ purchases get a quiet warm frame so a rare unlock stands
   out from the usual common-tier stream — the whole value of a public
   feed is seeing someone else get something you don't have yet. */
.activity-item-exclusive {
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  background: color-mix(in srgb, var(--warning) 8%, var(--surface-2));
  animation: activityExclusiveGlow 2.6s ease-in-out infinite;
}

@keyframes activityExclusiveGlow {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--warning) 12%, transparent); }
  50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--warning) 32%, transparent),
                    0 0 16px -2px color-mix(in srgb, var(--warning) 22%, transparent); }
}

/* Podium — a metal tint + ring rather than a solid pastel block, so it
   sits right on a dark surface too. */
.rank-1 { background: color-mix(in srgb, #f5c451 20%, var(--surface)); color: #f5c451; border-color: color-mix(in srgb, #f5c451 45%, transparent); }
.rank-2 { background: color-mix(in srgb, #c8cdd8 18%, var(--surface)); color: #cdd2dc; border-color: color-mix(in srgb, #c8cdd8 40%, transparent); }
.rank-3 { background: color-mix(in srgb, #d08a52 18%, var(--surface)); color: #db9666; border-color: color-mix(in srgb, #d08a52 42%, transparent); }

.lb-avatar {
  width: 28px;
  height: 28px;
  flex-shrink: 0;

  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;

  background: var(--accent-soft);
  color: var(--accent);

  font-size: 11px;
  font-weight: 800;
}

.activity-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.activity-text strong {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text);
}

.activity-text strong a:hover {
  color: var(--accent);
}

.activity-meta {
  margin-top: 2px;
  font-size: 11px;
  color: var(--muted);
}

.activity-coins {
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.activity-avatar {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);

  display: flex;
  align-items: center;
  justify-content: center;

  background: var(--accent-soft);
  font-size: 14px;
}

.activity-text {
  font-size: 13px;
  color: var(--text);
}

.activity-text .reward {
  font-weight: 700;
  color: var(--accent);
}

.activity-coins.positive { color: var(--success); }
.activity-coins:not(.positive) { color: var(--danger); }

/* =========================================================
   DASHBOARD
========================================================= */

.dashboard-shell {
  /* width + centring from the shell rule near the top */
  margin-block: 0 80px;

  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* =========================================================
   ACCOUNT SETTINGS
========================================================= */

/* .settings-shell width from the narrow-shell rule near the top. */

.settings-heading {
  font-family: var(--font-display);
  font-size: 22px;
  margin: 0;
}

.settings-card {
  padding: 24px;
}

.settings-card form {
  margin-top: 16px;
}

.settings-card label,
.org-panel label {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 14px 0 5px;
}

.settings-card label:first-child,
.org-panel label:first-child {
  margin-top: 0;
}

/* input / textarea styling comes from the shared .field rule in PRIMITIVES. */
.settings-card textarea,
.org-panel textarea {
  resize: vertical;
}

.settings-field-note {
  min-height: 1.4em;
  margin: 6px 0 0;
  font-size: 13px;
  font-weight: 600;
}

.settings-field-note.available {
  color: var(--success);
}

.settings-field-note.unavailable {
  color: var(--danger);
}

.settings-submit {
  margin-top: 16px;
  width: auto;
}

a.settings-submit {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

/* Live-preview hero — same .hero-profile/.hero-avatar markup as the
   dashboard/public profile; only the "Live preview" pill and caption
   below are settings-specific. */
.settings-preview-hero {
  align-items: center;
}

.settings-preview-caption {
  margin: 8px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

.settings-preview-caption a {
  color: var(--accent);
  font-weight: 600;
}

.settings-tabs {
  margin-bottom: 0;
}

.settings-panel {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Wider than the 680px reading-column token: two-column form rows
   (label + hint | control) need room, and 680 squeezed the control
   column. Two classes so it beats the shared narrow-shell rule. */
.dashboard-shell.settings-shell {
  width: min(880px, 100% - 2 * var(--gutter));
}

.hero-profile {
  flex: 2;
  min-width: 280px;

  display: flex;
  align-items: flex-start;
  gap: 18px;
  padding: 28px;
}

.hero-avatar {
  width: 60px;
  height: 60px;
  flex-shrink: 0;
  border-radius: var(--radius-md);

  display: flex;
  align-items: center;
  justify-content: center;

  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  color: var(--on-accent);

  background: var(--accent);
}

.hero-profile h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 700;
}

.hero-profile p {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 14px;
}

/* The profile hero's XP bar (the dashboard hero uses the kit's .sx-meter). */
.mini-level-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
  margin-bottom: 6px;
}

.mini-level-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}

/* ---- Stat tiles ------------------------------------------------------
   The KPI row on the profile, the dashboard and analytics. A tile is an
   icon chip + label, the number with its unit, and an optional footer that
   carries the context: a trend line, a meter, or one plain sentence. The
   footer is pinned to the bottom, so a tile never has dead space under its
   number. .best is the featured tile — a soft accent wash, not a slab.
   Markup: templates/html/_stat_tiles.html (a <dl>, one group per tile). */

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin: 0; /* it is a <dl> */
}

.stats-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.stat-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 20px;
}

/* <dt>/<dd> ship with UA margins (dd has a 40px start indent). */
.stat-card > * { margin: 0; }

/* A longer label ("Tests completed") wraps to two lines beside the chip on a
   phone. 28px is the chip height and two lines at this line-height fit in
   it, so the row never grows and every number in the strip keeps one
   baseline. */
.stat-label {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 28px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--muted);
}

.stat-icon {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 14px;
}

.stat-value {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

/* Fixed-width figures come from the shared .tabular group near the top. */
.stat-num {
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text);
}

.stat-num.is-empty { color: var(--faint); }

.stat-unit {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

/* "%" belongs to the figure ("88.2%"), unlike WPM which is a separate word. */
.stat-pct {
  margin-left: 2px;
  font-size: 0.55em;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--muted);
}

.stat-foot {
  margin-top: auto;
  min-height: 28px;
  display: flex;
  align-items: center;
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--muted);
}

/* .sx-meter is a block with no intrinsic width — in a flex footer it would
   collapse to nothing. */
.stat-foot > .sx-meter { flex: 1; }

/* The featured tile: the same wash the Social hub's hero uses, a solid chip,
   and the number in the accent. Nothing else changes, so it reads as "the
   headline stat" without turning the row into one loud block. */
.stat-card.best {
  background:
    radial-gradient(120% 150% at 0% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 58%),
    var(--surface);
}

.stat-card.best .stat-icon {
  background: var(--accent);
  color: var(--on-accent);
}

.stat-card.best .stat-num:not(.is-empty) { color: var(--accent); }

/* Trend line under "Average WPM". preserveAspectRatio="none" stretches the
   120x32 path to whatever width the tile has; non-scaling-stroke keeps the
   line 2px regardless. The end dot is HTML, not SVG, so the stretch cannot
   squash it into an ellipse. The wipe reveals the dot last. */
.stat-spark {
  position: relative;
  flex: 1;
  height: 28px;
  animation: statReveal 0.9s var(--ease-out) both;
}

.stat-spark svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.stat-spark-area { fill: color-mix(in srgb, var(--accent) 11%, transparent); }

.stat-spark-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.stat-spark-dot {
  position: absolute;
  right: 0;
  top: calc(var(--y) * 1%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--surface);
  transform: translate(50%, -50%);
}

@keyframes statReveal {
  from { clip-path: inset(-6px 100% -6px -6px); }
  to   { clip-path: inset(-6px -8px -6px -6px); }
}

.dashboard-main {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 20px;
  /* Grid items stretch to match the tallest sibling by default — with up
     to 30 rows in the recent-tests list and nothing to expand into the
     leftover room, the shorter chart card was being stretched taller than
     its own content, leaving blank space below the chart. Size each card
     to its own content instead. */
  align-items: start;
}

.dashboard-main > * {
  /* Grid items default to min-width:auto, which refuses to shrink below
     the widest piece of content — the WPM chart's <canvas> in particular,
     which Chart.js sizes in actual pixels. Without this, the chart card
     forces the whole grid (and the page) wider than the viewport on
     narrow screens instead of the chart shrinking to fit. */
  min-width: 0;
}

.performance-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 24px;
}

/* .eyebrow is defined once in PRIMITIVES (muted, uppercase) — no override here. */

.performance-header h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.performance-stats {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.performance-stats .mini-stat,
.mini-stat {
  min-width: 96px;
  padding: 12px 16px;
}

.performance-stats .mini-stat span {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 4px;
}

.performance-stats .mini-stat strong {
  font-size: 20px;
  font-weight: 800;
}

.chart-wrap {
  position: relative;
  height: 320px;
  display: flex;
}

/* =========================================================
   ANALYTICS (internal, superuser-only dashboard)
========================================================= */

/* Full-width row below the traffic chart — each card gets real room
   instead of being squeezed into a narrow sidebar column, which is what
   was pushing long paths/referrers outside their box. */
.analytics-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  align-items: start;
}

.analytics-detail-grid > .card {
  min-width: 0;
}

/* .card is a bare primitive (background/border/shadow only, no padding —
   see .stat-card/.performance-card for the same pattern) — without this,
   every row's text and bars sit flush against the card's edge, reading
   as "escaping the container" even though nothing technically overflows. */
.analytics-detail-card {
  padding: 24px;
}

.analytics-group-label {
  margin: 18px 0 8px;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.analytics-group-label:first-of-type {
  margin-top: 4px;
}

.analytics-bar-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.analytics-bar-row-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
  font-size: 13px;
}

.analytics-bar-row-label {
  color: var(--text);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1 1 auto;
}

.analytics-bar-row-meta {
  color: var(--muted);
  flex-shrink: 0;
  font-size: 12px;
}

.analytics-bar-track {
  height: 6px;
  border-radius: 4px;
  background: var(--surface-2);
  overflow: hidden;
}

.analytics-bar-fill {
  height: 100%;
  border-radius: 4px;
  background: var(--accent);
}

.analytics-empty {
  color: var(--muted);
  text-align: center;
  padding: 12px 0;
}

.analytics-footnote {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.recent-tests,
.recent-feed {
  display: flex;
  flex-direction: column;
  gap: 10px;

  max-height: 420px;
  overflow-y: auto;
  padding-right: 4px;
}

.test-item,
.activity-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;

  padding: 14px 16px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  transition: transform 0.15s ease;
}

.test-item:hover,
.activity-row:hover {
  background: var(--surface-3);
}

.test-left,
.activity-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.activity-score {
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);

  display: flex;
  align-items: center;
  justify-content: center;

  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.test-main,
.activity-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.test-wpm {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.test-wpm span,
.wpm {
  font-size: 12px;
  color: var(--muted);
  font-weight: 500;
}

.wpm {
  font-weight: 800;
  font-size: inherit;
  color: var(--accent);
}

.mode-pill {
  display: inline-block;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  width: fit-content;
}

.mode-pill-quote {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--surface-2, var(--surface));
  color: var(--muted);
  border: 1px solid var(--border);
}

.mode,
.date {
  font-size: 12px;
  color: var(--muted);
}

.accuracy,
.accuracy-pill {
  font-weight: 700;
  color: var(--success);
}

.accuracy-pill {
  padding: 7px 12px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--success) 14%, transparent);
  font-size: 13px;
}

/* Badge showcase */

.badge-showcase {
  margin-top: 20px;
  width: 100%;
}

.badge-showcase-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 14px;
}

.badge-showcase-head h3 {
  margin: 0;
  font-size: 16px;
}

.badge-link {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
}

/* Flexbox, not grid — with an auto-fit grid, an incomplete last row
   (e.g. 3 items in a 2-column layout) leaves a visible gap instead of
   the lone item stretching to fill it, since grid tracks are fixed by
   the container regardless of how many items land in a given row.
   Flexbox's basis+grow naturally fills whatever space is left. */
.badge-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.badge-grid .badge-card {
  flex: 1 1 220px;
}

/* =========================================================
   PROFILE PAGE EXTRAS — Equipped and Trophy Case are now separate
   cards instead of one card with subsections, so they read as
   distinct moments on the page instead of one dense contained block.
   Reuses the .badge-card/.badge-icon-wrap pattern already proven on
   this page for the real Badges section below.
========================================================= */

.hero-visitors {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--muted);
}

.hero-visitors a {
  color: var(--text);
}

.profile-bio {
  margin: 6px 0 0;
  font-size: 14px;
  color: var(--text);
  max-width: 480px;
}

.profile-bio-empty {
  color: var(--muted);
}

.profile-bio-empty a {
  color: var(--accent);
  font-weight: 600;
}

.hero-level-bar {
  margin: 10px 0 0;
  max-width: 320px;
}

.hero-level-bar small {
  color: var(--muted);
  font-size: 11.5px;
}

/* Loadout: a plain label/value ledger for equipped items and a real
   table for the showcase — no colored chips or tiles, rarity reads as
   text, matching the collection list below instead of fighting it. */
.loadout-card {
  padding: 24px;
}

.ledger {
  display: flex;
  flex-direction: column;
}

.ledger-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin: 0 -12px;
  border-radius: var(--radius-sm);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  transition: background 0.15s ease;
}

.ledger-row:last-child {
  border-bottom: none;
}

a.ledger-row:hover {
  background: var(--surface-2);
}

a.ledger-row:hover .ledger-value {
  text-decoration: underline;
}

.ledger-label {
  /* Wide enough for "background" (the longest category label) — at
     74px it overflowed and visually ran into the value text next to it. */
  width: 94px;
  flex-shrink: 0;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}

.ledger-value {
  flex: 1;
  font-size: 13.5px;
  font-weight: 600;
}

.ledger-row .ic {
  font-size: 15px;
  margin-right: 2px;
}

.ledger-row.is-empty {
  color: var(--muted);
}

.ledger-row.is-empty .ledger-value {
  font-weight: 500;
}

.rt {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.ledger-row .rt {
  margin-left: auto;
  flex-shrink: 0;
}

.loadout-empty-note {
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
}

.table-wrap {
  overflow-x: auto;
}

.showcase-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

.showcase-table th {
  text-align: left;
  padding: 0 8px 8px 0;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.showcase-table td {
  padding: 10px 8px 10px 0;
  border-bottom: 1px solid var(--border);
}

.showcase-table tr:last-child td {
  border-bottom: none;
}

.showcase-table .ic {
  font-size: 15px;
  margin-right: 2px;
}

.showcase-table a {
  color: var(--text);
  font-weight: 600;
}

.clickable-row {
  cursor: pointer;
  transition: background 0.15s ease;
}

.clickable-row:hover {
  background: var(--surface-2);
}

.clickable-row:hover a {
  text-decoration: underline;
}

/* Rarity as plain colored text/tags rather than colored shapes — reuses
   the .rarity-* text colors already defined for the badge system. */
.rtag {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  text-transform: capitalize;
  background: var(--surface-2);
}

.rtag.rarity-rare { background: rgba(59, 130, 246, 0.1); }
.rtag.rarity-epic { background: rgba(168, 85, 247, 0.1); }
.rtag.rarity-legendary { background: rgba(250, 204, 21, 0.15); }
.rtag.rarity-mythical { background: rgba(236, 72, 153, 0.1); }
.rtag.rarity-exclusive { background: rgba(249, 115, 22, 0.1); }
.rtag.rarity-ultra { background: rgba(220, 38, 38, 0.12); }

/* Badges: a two-column collection list — icon, name, rarity as text —
   instead of a grid of colored tiles/cards. */
.badges-card {
  padding: 24px;
}

.badge-count-pill {
  flex-shrink: 0;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.badge-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 24px;
}

.badge-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 8px;
  margin: 0 -8px;
  border-radius: var(--radius-sm);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s ease;
}

.badge-list-row:hover,
.badge-list-row:focus-visible {
  background: var(--surface-2);
}

.badge-list-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.badge-list-row:nth-last-child(2),
.badge-list-row:last-child {
  border-bottom: none;
}

.badge-list-row .ic {
  width: 20px;
  text-align: center;
  font-size: 15px;
  flex-shrink: 0;
}

.badge-list-row .nm {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.badge-list-row .rt {
  flex-shrink: 0;
}

.badge-mini-hidden {
  display: none;
}

.badge-mini-grid-expanded .badge-mini-hidden {
  display: flex;
}

.badge-show-more {
  display: block;
  margin: 10px 0 0;
  padding: 0;
  background: none;
  border: none;

  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

@media (max-width: 640px) {
  .badge-list { grid-template-columns: 1fr; }
}

.badge-show-more:hover {
  text-decoration: underline;
}

.badge-card {
  display: flex;
  align-items: center;
  gap: 12px;

  padding: 14px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: transform 0.2s ease;
}

.badge-card:hover {
  transform: translateY(-3px);
}

.badge-icon-wrap {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);

  display: flex;
  align-items: center;
  justify-content: center;

  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 20px;
}

.badge-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 3px;
}

.badge-body strong {
  font-size: 14px;
}

.badge-body small {
  color: var(--muted);
  font-size: 12px;
}

.badge-rarity,
.rarity-pill,
.shop-rarity {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border-radius: var(--radius-full);

  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;

  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--muted);
}

/* The rarity colour language — kept recognizable but each tier now also
   carries a faint tinted fill so it reads as a chip, and "rare" shifts
   off the exact accent blue so the two don't blur together. */
.rarity-common  { color: var(--muted); }
.rarity-rare      { color: #58b6f0; border-color: color-mix(in srgb, #58b6f0 40%, transparent); background: color-mix(in srgb, #58b6f0 10%, var(--surface)); }
.rarity-epic      { color: #b98cff; border-color: color-mix(in srgb, #b98cff 40%, transparent); background: color-mix(in srgb, #b98cff 10%, var(--surface)); }
.rarity-legendary { color: #e8b23c; border-color: color-mix(in srgb, #e8b23c 42%, transparent); background: color-mix(in srgb, #e8b23c 10%, var(--surface)); }
.rarity-mythical  { color: #f472b6; border-color: color-mix(in srgb, #f472b6 42%, transparent); background: color-mix(in srgb, #f472b6 10%, var(--surface)); }
/* Limited-tab rarities — sit above mythical; ordered by how hard to get. */
.rarity-seasonal  { color: #4ade80; border-color: color-mix(in srgb, #4ade80 42%, transparent); background: color-mix(in srgb, #4ade80 10%, var(--surface)); }
.rarity-prestige  { color: #cbd5e1; border-color: color-mix(in srgb, #cbd5e1 45%, transparent); background: color-mix(in srgb, #cbd5e1 12%, var(--surface)); }
.rarity-founder   { color: #f5c451; border-color: color-mix(in srgb, #f5c451 50%, transparent); background: color-mix(in srgb, #f5c451 14%, var(--surface)); }
/* legacy labels still referenced by pre-0054 badge/activity rows */
.rarity-exclusive { color: #f59e42; border-color: color-mix(in srgb, #f59e42 42%, transparent); background: color-mix(in srgb, #f59e42 10%, var(--surface)); }
.rarity-ultra     { color: #f2604f; border-color: color-mix(in srgb, #f2604f 45%, transparent); background: color-mix(in srgb, #f2604f 10%, var(--surface)); }

.badge-empty {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;

  padding: 18px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  border: 1px solid var(--border);
}

.empty-icon {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);

  display: flex;
  align-items: center;
  justify-content: center;

  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 22px;
}

.badge-empty h3 {
  margin: 0 0 4px;
  font-size: 15px;
}

.badge-empty p {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  max-width: 420px;
}

@media (max-width: 950px) {
  .dashboard-main { grid-template-columns: 1fr; }
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 640px) {
  /* Stays two-up on a phone: four full-width tiles would be a screen of
     scrolling for four numbers. */
  .stats-grid { gap: 12px; }
  .stat-card { gap: 10px; padding: 16px; }
  .stat-num { font-size: 30px; }
  .hero-profile { flex-direction: column; text-align: center; }
}

/* =========================================================
   DAILY CHALLENGE
========================================================= */

.daily-challenge-bar {
  /* width + centring from the shell rule near the top */
  margin-bottom: 16px;

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;

  padding: 12px 16px;
}

.dc-info {
  display: flex;
  align-items: center;
  gap: 12px;
}

.dc-icon {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);

  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;

  background: var(--surface-2);
  border: 1px solid var(--border);
}

.dc-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.dc-text strong {
  font-size: 13px;
  font-weight: 700;
}

.dc-text span {
  font-size: 12.5px;
  color: var(--muted);
}

.dc-reward {
  flex-shrink: 0;
  padding: 8px 16px;
  border: none;
  border-radius: 999px;

  font-size: 13px;
  font-weight: 800;

  background: var(--accent-soft);
  color: var(--accent);
  white-space: nowrap;
}

.dc-reward.done {
  background: color-mix(in srgb, var(--success) 14%, transparent);
  color: var(--success);
}

.dc-reward.locked {
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.dc-reward.locked:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

/* The bar opens the progress sheet at "Next up" (js/progress.js,
   data-open-progress), so it reads as clickable. */
.daily-challenge-bar.is-clickable {
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.daily-challenge-bar.is-clickable:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: var(--shadow-md);
}

.daily-challenge-bar.is-clickable .dc-reward { margin-left: auto; }

.daily-challenge-bar.is-clickable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.dc-chev {
  margin-left: -6px;
  color: var(--muted);
  font-size: 14px;
  transition: transform var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.daily-challenge-bar.is-clickable:hover .dc-chev {
  color: var(--accent);
  transform: translateX(3px);
}

/* =========================================================
   ACHIEVEMENT BAR (a thin progress bar — used by the org dashboard)
========================================================= */

.achievement-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
  margin-bottom: 6px;
}

.achievement-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 0.3s ease;
}

/* Tier pips — the org dashboard's milestone medals (the Awards page now
   draws its own medals: see AWARDS + SHOP below). */
.tier-pip {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px;
  border-radius: var(--radius-sm);

  background: var(--surface-2);
  border: 1px solid var(--border);
  opacity: 0.45;

  transition: transform 0.15s ease, opacity 0.15s ease;
}

.tier-pip.unlocked {
  opacity: 1;
  background: var(--surface);
}

.tier-pip:hover {
  transform: translateY(-2px);
}

.tier-pip-icon {
  font-size: 18px;
}

.tier-pip-label {
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}

/* =========================================================
   SHOP CARDS — the base card chrome and the premium / limited effects.
   The page around them (hero, tabs, state chips, buttons, the phone
   layout) is in AWARDS + SHOP further down.
========================================================= */

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 16px;
}

.shop-card {
  position: relative;
  overflow: hidden;
  padding: 0;
}

/* Transient highlight for a card jumped to via a #shop-item-<slug> link
   (e.g. a Profile Room chip) — added/removed by JS, not meant to persist. */
.shop-card.jump-highlight {
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-lg);
  animation: jumpHighlightPulse 0.9s ease-in-out 2;
}

@keyframes jumpHighlightPulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--accent), var(--shadow-lg); }
  50% { box-shadow: 0 0 0 4px var(--accent-2), var(--shadow-lg); }
}

/* Limited-supply items get their own visual treatment site-wide — a gold
   flag + border — so scarcity reads at a glance, not just in the text. */
.shop-card.exclusive {
  border: 1px solid color-mix(in srgb, var(--warning) 42%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--warning) 14%, transparent), var(--shadow-sm);
}

/* Gems-priced items (ShopItem.currency="gems") get a violet flag
   instead of gold — gold already means scarcity (max_supply/"LIMITED"),
   this means real-money-only, an independent axis (an item is never
   both, see shop.html/base.html's premium/exclusive branching).
   A static border alone read as "just another rarity color" next to
   the existing gold/pink/purple rarity badges — same glowing-pulse +
   gradient-tint treatment .activity-item-exclusive already uses to make
   a rare unlock stand out in the feed, so this reads as an established
   "this one's special" language rather than a one-off effect. */
/* --gem is the fixed "premium currency" indigo used for the gem pill and
   the gem-package cards too, so real-money items read the same on every
   theme. */
.shop-card.premium,
.customize-card.premium {
  --gem: #8b9dff;
  border-color: color-mix(in srgb, var(--gem) 50%, transparent);
  background: linear-gradient(135deg, color-mix(in srgb, var(--gem) 10%, var(--surface)), var(--surface));
  animation: premiumCardGlow 2.6s ease-in-out infinite;
}

@keyframes premiumCardGlow {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--gem) 26%, transparent), var(--shadow-sm); }
  50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--gem) 55%, transparent), 0 0 22px -4px color-mix(in srgb, var(--gem) 40%, transparent), var(--shadow-sm); }
}

/* The icon itself gets a rotating rainbow aura behind it — every other
   shop card's preview box is a flat --surface-2 square, so this is the
   one visual cue that reads as "premium" even before you buy or equip
   it, previewing the shop grid instead of only reskinning the live site
   after purchase. */
.shop-card.premium .shop-preview {
  position: relative;
  background: var(--surface-2);
  overflow: hidden;
}

/* z-index: -1, not the default auto — .shop-preview's icon is plain
   text with no stacking context of its own, so a positioned ::before at
   the default stacking level would paint OVER it, not behind it.
   Negative z-index is the one layer guaranteed to sit below ordinary
   in-flow content within .shop-preview's own stacking context
   (established by its position: relative above). */
.shop-card.premium .shop-preview::before {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: -1;
  background: conic-gradient(from 0deg, #ff2ec4, #a78bfa, #22d3ee, #34d399, #facc15, #ff2ec4);
  opacity: 0.35;
  filter: blur(20px);
  animation: prismSpin 6s linear infinite;
}

/* Same aura, scaled down for the navbar customize modal's smaller
   swatch — the modal is equipped/browsed at least as often as the shop
   page, so premium items need to stand out there too, not just on
   /shop/. */
.customize-card.premium .customize-swatch {
  position: relative;
  overflow: hidden;
}

.customize-card.premium .customize-swatch::before {
  content: "";
  position: absolute;
  inset: -60%;
  z-index: -1;
  background: conic-gradient(from 0deg, #ff2ec4, #a78bfa, #22d3ee, #34d399, #facc15, #ff2ec4);
  opacity: 0.4;
  filter: blur(10px);
  animation: prismSpin 6s linear infinite;
}

/* Prism Storm and Crystal Chime keep the shared rainbow above (both are
   explicitly prismatic concepts) — every other premium item gets its
   own aura color matched to its identity instead of the identical
   rainbow swirl, so each one reads as its own thing in the shop grid
   and the customize modal, not a copy-pasted "premium" stamp.
   base.html's renderCustomizeTab() sets card.dataset.slug so the
   [data-slug=...] selectors below can target the modal's cards too. */
#shop-item-celestial-eclipse .shop-preview::before,
.customize-card.premium[data-slug="celestial-eclipse"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #78350f, #fbbf24, #fef3c7, #fbbf24, #78350f);
}

#shop-item-diamond-cursor .shop-preview::before,
.customize-card.premium[data-slug="diamond-cursor"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #0c4a6e, #38bdf8, #e0f2fe, #38bdf8, #0c4a6e);
}

#shop-item-phoenix-cursor .shop-preview::before,
.customize-card.premium[data-slug="phoenix-cursor"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #7c2d12, #f97316, #fde047, #dc2626, #7c2d12);
}

#shop-item-aurora-storm .shop-preview::before,
.customize-card.premium[data-slug="aurora-storm"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #4c1d95, #2dd4bf, #86efac, #a78bfa, #4c1d95);
}

#shop-item-meteor-shower .shop-preview::before,
.customize-card.premium[data-slug="meteor-shower"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #1e1b4b, #f97316, #facc15, #dc2626, #1e1b4b);
}

#shop-item-synth-wave .shop-preview::before,
.customize-card.premium[data-slug="synth-wave"] .customize-swatch::before {
  background: conic-gradient(from 0deg, #4a044e, #d946ef, #22d3ee, #d946ef, #4a044e);
}

.shop-scarcity {
  font-size: 12px;
  font-weight: 700;
  color: var(--warning);
  margin-top: 2px;
}

.shop-scarcity.sold-out {
  color: var(--danger);
}

.shop-preview {
  height: 116px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 42px;
  background:
    radial-gradient(120% 120% at 50% 0%, var(--accent-soft), transparent 70%),
    var(--surface-2);
  border-bottom: 1px solid var(--border);
}

.shop-info {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px 16px;
}

.shop-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.shop-card h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
}

.shop-price {
  margin: 0;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* Pricing card for a gem package (real money, not a ShopItem) — inherits
   .shop-card's shell (preview/info/title/price layout) so it slots into
   the grid looking consistent, this just adds the indigo accent border
   .shop-card.premium also uses, so "spend real money here" reads the
   same visual language throughout the page. */
.gem-package-card {
  border: 1px solid color-mix(in srgb, #8b9dff 36%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, #8b9dff 12%, transparent), var(--shadow-sm);
}

.gem-earn-card {
  grid-column: 1 / -1;
  padding: 18px 20px;
  border: 1px solid color-mix(in srgb, #8b9dff 30%, transparent);
  background: linear-gradient(135deg, color-mix(in srgb, #8b9dff 8%, var(--surface)), var(--surface));
}

.gem-earn-card h3 {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 800;
}

.gem-earn-card p { color: var(--muted); font-size: 13px; margin-top: 5px; }

.gem-earn-card ul {
  margin: 10px 0 0;
  padding-left: 18px;
  columns: 2;
  font-size: 13px;
}

.gem-earn-card li { margin: 3px 0; }

.gem-earn-card .gem-earn-or {
  margin-top: 12px;
  font-weight: 600;
  color: color-mix(in srgb, #8b9dff 70%, var(--text));
}

@media (max-width: 520px) {
  .gem-earn-card ul { columns: 1; }
}

/* The fixed gem indigo, pulled toward the theme's text colour so a price
   still reads on a light theme (raw #8b9dff was ~2.4:1 on white). */
.gem-package-card .shop-price {
  color: color-mix(in srgb, #8b9dff 60%, var(--text));
}

.buy-btn,
.owned-btn {
  width: 100%;
  padding: 10px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-family: inherit;
  font-weight: 700;
  font-size: 13px;
  transition: background var(--dur-1) var(--ease);
}

.buy-btn {
  color: var(--on-accent);
  background: var(--accent);
}

.buy-btn:hover {
  background: var(--accent-hover);
}

.owned-btn {
  background: transparent;
  color: var(--muted);
  cursor: default;
  border-color: var(--border-strong);
}

/* ---------- Limited tab: Founder / Seasonal / Prestige ---------- */

.vault-collector-card {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 22px;
  margin-top: 4px;
}

.vault-collector-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  font-size: 22px;
  background: var(--accent-soft);
}

.vault-collector-body { flex: 1; min-width: 0; }
.vault-collector-body h3 { font-size: 15px; margin: 0 0 2px; }
.vault-collector-body p { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
.vault-collector-body .achievement-bar { margin: 0; }

/* Complete: a warm gold treatment (matches Gold Prestige's own accent by
   coincidence of both meaning "the good stuff", not a scoped theme) —
   distinct from the in-progress state so finishing the set visibly
   changes something instead of the banner just quietly disappearing. */
.vault-collector-card.complete {
  border-color: color-mix(in srgb, #d4af37 45%, transparent);
  background: linear-gradient(135deg, color-mix(in srgb, #d4af37 8%, var(--surface)), var(--surface));
}

.vault-collector-card.complete .vault-collector-icon {
  background: color-mix(in srgb, #d4af37 20%, transparent);
}

.vault-collector-card.complete .vault-collector-body p { color: var(--text); }

.shop-section-head {
  grid-column: 1 / -1;
  margin: 6px 0 -4px;
  padding-left: 14px;
  border-left: 3px solid var(--accent);
}

.shop-section-head:not(:first-child) { margin-top: 22px; }
.shop-section-head.limited-founder  { border-left-color: #f5c451; }
.shop-section-head.limited-seasonal { border-left-color: #4ade80; }
.shop-section-head.limited-prestige { border-left-color: #cbd5e1; }

.shop-section-head h2 {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.shop-section-head p {
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--muted);
}

/* var(--accent) here (not --limited-accent) — the whole card carries
   data-theme-scope now (theme items), so this is the item's OWN colour:
   Winter Lights gets an icy-blue border, not the shared "seasonal"
   green every vault-mate used to share regardless of its own palette.
   --limited-accent stays defined below for the one thing that's
   deliberately group-level, not item-level: the FOUNDER/SEASONAL/
   PRESTIGE tag pill (.limited-tag) — that's a taxonomy label, not the
   item's own identity. */
.shop-card--limited {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent), var(--shadow-sm);
}

.shop-card--limited.limited-founder  { --limited-accent: #f5c451; }
.shop-card--limited.limited-seasonal { --limited-accent: #4ade80; }
.shop-card--limited.limited-prestige { --limited-accent: #cbd5e1; }

/* Text colour freezes at whatever the AMBIENT page resolved on <body>
   otherwise — data-theme-scope only changes the custom properties
   var()s read fresh, and plain `color` isn't one of those, so without
   this every themed card would keep the viewer's own (often near-black)
   text sitting on a now much darker card. */
.shop-card--limited[data-theme-scope],
.shop-card.premium[data-theme-scope] {
  color: var(--text);
}

.shop-card--limited .shop-preview {
  position: relative;
  overflow: hidden;
  /* var(--accent), not --limited-accent — .shop-preview carries
     data-theme-scope="<slug>" for theme items (shop.html), so this
     resolves to THIS item's own colour (Gold Prestige's gold, Winter
     Lights' ice-blue, ...) instead of one fixed colour shared by every
     item in its vault group. Falls back to the ambient page theme's
     accent for the rare non-theme item here (a Limited cursor). */
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--accent) 24%, transparent), transparent 70%),
    var(--surface-2);
}

/* A slow single-hue aura orbiting behind the icon — the Limited tab's
   answer to the Premium (gems) tier's rotating aura below, just one
   colour instead of a rainbow so it matches the restrained palette
   everywhere else. Contained to the 124px icon tile with the icon glyph
   painted above it (z-index: -1 within .shop-preview's own stacking
   context, same trick as the premium aura) — motion that says "this one
   is alive" while browsing, without the full-card glow/frame effects
   the typing-page complaints were about. */
.shop-card--limited .shop-preview::before {
  content: "";
  position: absolute;
  inset: -45%;
  z-index: -1;
  background: conic-gradient(from 0deg,
    transparent, color-mix(in srgb, var(--accent) 60%, transparent) 35%, transparent 62%);
  filter: blur(14px);
  animation: limitedIconOrbit 7s linear infinite;
}

@keyframes limitedIconOrbit {
  to { transform: rotate(360deg); }
}

.limited-tag {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #1a1305;
  background: var(--limited-accent);
}

.shop-card--limited.limited-prestige .limited-tag { color: #10131a; }

.limited-note {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin: 2px 0 4px;
}

.limited-note.state-open {
  color: color-mix(in srgb, var(--limited-accent) 70%, var(--text));
  font-weight: 700;
}

.limited-note.state-sold_out { color: var(--danger); }
.limited-note.state-out_of_season,
.limited-note.state-locked { color: var(--warning); }

/* Prestige items show real progress toward their unlock requirement
   instead of only the text hint above (see limited.py's progress_pct).
   data-theme-scope on the wrapper makes the fill the item's OWN accent
   (Gold Prestige fills gold, not the shared silvery "prestige" vault
   colour) — but the track is a fixed neutral, not var(--surface-2),
   since scoping that too would drop a small dark rectangle onto a card
   that's still rendered in the viewer's own (often light) theme. */
.limited-progress { margin: 8px 0 4px; }
.limited-progress .achievement-bar {
  background: rgba(128, 128, 128, 0.22);
}

/* dim a card you can't act on yet */
.shop-card--limited.state-locked,
.shop-card--limited.state-out_of_season,
.shop-card--limited.state-sold_out {
  opacity: 0.72;
}

.shop-card--limited.state-locked .shop-preview,
.shop-card--limited.state-out_of_season .shop-preview {
  filter: grayscale(0.5);
}

/* =========================================================
   PROFILE PAGE
========================================================= */

.profile-container {
  /* width + centring from the shell rule near the top */
  margin-block: 0 80px;

  display: flex;
  flex-direction: column;
  gap: 20px;
}

.hero-profile { flex-wrap: wrap; }

.hero-profile > .btn,
.hero-profile > .profile-actions {
  margin-left: auto;
  align-self: center;
}

.profile-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.btn-text-danger {
  background: none;
  border: none;
  padding: 6px 4px;

  color: var(--danger);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.btn-text-danger:hover {
  text-decoration: underline;
}

.profile-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.profile-title-row h1 {
  margin: 0;
  font-size: 26px;
  font-weight: 800;
}

.rank-pill,
.you-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

.rank-pill {
  background: var(--accent-soft);
  color: var(--accent);
}

.you-pill {
  background: var(--surface-2);
  color: var(--muted);
  border: 1px solid var(--border);
}

.badge-detail-card {
  width: min(320px, 90%);
  text-align: center;
  padding: 32px 28px 24px;
}

.badge-detail-icon {
  display: block;
  font-size: 40px;
  margin-bottom: 12px;
}

.badge-detail-card h2 {
  margin: 0 0 8px;
}

.badge-detail-card .rtag {
  font-size: 12px;
  padding: 4px 12px;
}

.badge-detail-date {
  margin: 12px 0 0;
  color: var(--muted);
  font-size: 13px;
}

/* =========================================================
   FOOTER
========================================================= */

.site-footer {
  margin-top: 72px;
  padding: 52px 24px 28px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.footer-inner {
  max-width: var(--container);
  margin: 0 auto;

  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 40px;
}

.footer-left {
  max-width: 320px;
}

.footer-logo {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}

.footer-left p {
  color: var(--muted);
  line-height: 1.65;
  font-size: 0.86rem;
}

.footer-links {
  display: flex;
  gap: 50px;
  flex-wrap: wrap;
}

.footer-column {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.footer-column h4 {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 4px;
}

.footer-column a {
  color: var(--text);
  font-size: 0.86rem;
  transition: color var(--dur-1) var(--ease);
}

.footer-column a:hover {
  color: var(--accent);
}

.footer-bottom {
  max-width: var(--container);
  margin: 40px auto 0;
  padding-top: 20px;
  border-top: 1px solid var(--border);

  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;

  color: var(--muted);
  font-size: 0.85rem;
}

.footer-credit {
  font-weight: 600;
}

@media (max-width: 900px) {
  .footer-inner { flex-direction: column; }
  .footer-links { width: 100%; justify-content: space-between; gap: 24px; }
}

/* =========================================================
   COIN FX
========================================================= */

.coin-fx {
  position: fixed;
  font-size: 18px;
  pointer-events: none;
  z-index: 9999;
  animation: coinPop 0.7s ease-out forwards;
}

@keyframes coinPop {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--x), var(--y)) scale(0.4) rotate(180deg); opacity: 0; }
}

.coin-pop {
  animation: coinBounce 0.3s ease;
}

@keyframes coinBounce {
  0% { transform: scale(1); }
  50% { transform: scale(1.3); color: #facc15; }
  100% { transform: scale(1); }
}

.gem-pop {
  animation: gemBounce 0.3s ease;
}

@keyframes gemBounce {
  0% { transform: scale(1); }
  50% { transform: scale(1.3); color: #c4b5fd; }
  100% { transform: scale(1); }
}

.coin-popup {
  position: fixed;
  top: 20%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;

  padding: 10px 18px;
  border-radius: var(--radius-sm);

  background: var(--text);
  color: var(--bg);

  font-weight: 700;
  font-size: 15px;

  animation: coinFade 1.5s ease forwards;
}

@keyframes coinFade {
  0% { opacity: 0; transform: translate(-50%, 10px); }
  20% { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, -20px); }
}

.error-popup {
  background: var(--danger);
  color: #fff;
  max-width: min(360px, 90vw);
  text-align: center;
  animation: coinFade 2.6s ease forwards;
}

/* =========================================================
   RESPONSIVE — TYPING TEST / RESULTS
========================================================= */

@media (max-width: 768px) {
  .hero-score { font-size: 60px; }
  .performance-header { flex-direction: column; }
}

/* =========================================================
   RESPONSIVE — HEADER
========================================================= */

@media (max-width: 900px) {
  .site-header-inner { gap: 12px; }
  .primary-nav-link { padding: 7px 9px; }
}

@media (max-width: 768px) {
  /* Below here the bottom tab bar owns primary navigation. */
  .primary-nav { display: none; }
}

@media (max-width: 560px) {
  .site-header-inner { gap: 10px; height: 56px; }
  .brand-logo .logo-text { display: none; }

  .nav-actions { gap: 6px; }
  .nav-icons { gap: 0; }

  /* 40px rather than the 44px ideal — anything larger and the row
     (customize / friends / messages / account) stops fitting a narrow
     phone alongside the gems chip. Still a meaningful bump from 36px. */
  .icon-btn { width: 40px; height: 40px; }

  /* Coins + level are on the You / Awards screens on mobile — three
     chips plus three icons don't fit a phone width. Gems stays (it's
     the "spend real money" entry point). */
  .coin-pill,
  .level-pill { display: none; }

  .chip { height: 32px; padding: 0 10px; font-size: 12.5px; }
}

/* =========================================================
   BOTTOM TAB BAR (mobile only)
========================================================= */

.bottom-tab-bar {
  display: none;
}

@media (max-width: 768px) {
  .bottom-tab-bar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 500;

    background: color-mix(in srgb, var(--bg) 82%, transparent);
    backdrop-filter: saturate(1.4) blur(14px);
    border-top: 1px solid var(--border);
    /* Safe-area padding keeps tabs clear of the home-indicator bar on
       notched phones when running standalone (added-to-homescreen). */
    padding-bottom: env(safe-area-inset-bottom, 0);
  }

  /* Content would otherwise sit underneath the fixed bar. */
  body {
    padding-bottom: calc(58px + env(safe-area-inset-bottom, 0));
  }

  /* Customize / Friends / Messages stay reachable from the top bar; the
     bottom bar covers primary navigation, so the top-row primary nav and
     the coin/level chips (both duplicated on the You / Awards screens)
     are trimmed to keep the row from overflowing a phone width. */
  .primary-nav,
  .coin-pill,
  .level-pill {
    display: none;
  }
}

.bottom-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 8px 4px 6px;

  color: var(--muted);
  font-size: 10.5px;
  font-weight: 600;

  -webkit-tap-highlight-color: transparent;
}

.bottom-tab i {
  font-size: 20px;
  line-height: 1;
}

.bottom-tab.active {
  color: var(--accent);
}

/* =========================================================
   RACE MODE
   Lobby, waiting-room starting grid, the live track (rendered by
   static/js/race-track.js), and results. One width, on the design
   tokens, dark-first. Trail cosmetics + the customize-modal trail demo
   stay in their own block further down.
========================================================= */

.race-room {
  width: min(1040px, 100% - 2 * var(--gutter));
  margin-inline: auto;
}

/* ---------- Shared stage (waiting + results) ---------- */

.race-stage {
  padding: 26px 28px 30px;
  border-radius: var(--radius-lg);
  border: var(--card-border);
  box-shadow: var(--shadow-md);
  background: var(--surface);
}

.race-stage-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 22px;
}

.race-code-label {
  display: block;
  margin-bottom: 6px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.race-code-value {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border-strong);
  background: var(--bg);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 23px;
  font-weight: 700;
  letter-spacing: 0.16em;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.race-code-value:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.race-code-value i { font-size: 14px; color: var(--muted); }

/* ---------- Waiting room: the starting grid ---------- */

.race-grid {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-height: 92px;
  padding: 15px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--bg);
  overflow: hidden;
}

/* start line the tokens sit against */
.race-grid::before {
  content: "";
  position: absolute;
  top: 12px;
  bottom: 12px;
  left: 62px;
  width: 2px;
  background: repeating-linear-gradient(var(--text) 0 5px, transparent 5px 11px);
  opacity: 0.25;
}

.race-grid-row {
  display: flex;
  align-items: center;
  gap: 13px;
  font-size: 13px;
  font-weight: 600;
  animation: raceGridJoin 0.4s var(--ease-out);
}

@keyframes raceGridJoin {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}

.race-grid-row .race-avatar { flex-shrink: 0; }
.race-grid-row.is-you { color: var(--accent); font-weight: 700; }

.race-countdown-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin: 22px 0 6px;
  min-height: 58px;
  justify-content: center;
}

.race-countdown-num {
  font-family: var(--font-display);
  font-size: 52px;
  font-weight: 800;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.race-countdown-num.tick {
  animation: raceCountdownPop 0.9s var(--ease-out);
}

.race-countdown-num.go {
  letter-spacing: 0.04em;
  animation: raceCountdownGo 0.6s var(--ease-out);
}

@keyframes raceCountdownPop {
  0%   { transform: scale(1.5); opacity: 0; }
  35%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}

@keyframes raceCountdownGo {
  0%   { transform: scale(0.6); opacity: 0; }
  40%  { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}

.race-stage-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 13.5px;
}

.race-pulse-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  animation: racePulseDot 1.4s ease-in-out infinite;
}

@keyframes racePulseDot {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.3; transform: scale(0.7); }
}

.race-stage .btn-primary,
.race-stage #race-start-btn {
  margin-top: 4px;
}

/* ---------- Avatars (grid rows, standings, podium) ---------- */

.race-avatar {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 13px;
  font-weight: 800;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 32%, transparent);
}

.race-avatar.is-bot { background: var(--surface-3); color: var(--text); box-shadow: none; }

/* ---------- The live track ---------- */

#race-typing {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.race-track {
  position: relative;
  margin-bottom: 16px;
  padding: 16px 18px;
  border-radius: var(--radius-lg);
  border: var(--card-border);
  box-shadow: var(--shadow-sm);
  background: var(--surface);
  overflow: hidden;
  transition: padding var(--dur-3) var(--ease);
}

/* After you finish, the track sits above your result card — keep it
   present but visually secondary. */
#race-results { position: relative; }

.race-status-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 16px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

/* Spectating (you're done, race isn't): the result card shows only your
   placement + a live "still racing" line — the track above is the
   standings until everyone's in. */
.race-stage.is-spectating .race-podium,
.race-stage.is-spectating #race-standings {
  display: none;
}

.race-stage.is-spectating .race-result-hero {
  padding-bottom: 4px;
}

.race-track--lanes {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* the test is running — let the track recede a little so the passage leads */
body.test-active .race-track {
  padding: 12px 18px;
  box-shadow: none;
  opacity: 0.92;
}

/* finish line: dashed rail on the right with an accent flag block */
.race-track-finish {
  position: absolute;
  top: 14px;
  bottom: 14px;
  right: 18px;
  width: 8px;
  border-radius: 2px;
  background: repeating-linear-gradient(45deg,
    color-mix(in srgb, var(--text) 55%, transparent) 0 4px, transparent 4px 8px);
  pointer-events: none;
}

.race-track-finish span {
  position: absolute;
  top: -3px;
  left: 50%;
  transform: translateX(-50%);
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent);
}

.race-lane {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 44px 6px 10px;
  border-radius: var(--radius-sm);
  transition: background var(--dur-2) var(--ease);
}

.race-lane.is-you {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

.race-lane.is-rank-up {
  animation: raceRankUp 0.65s var(--ease);
}

@keyframes raceRankUp {
  0%   { background: color-mix(in srgb, var(--accent) 28%, transparent); }
  100% { background: color-mix(in srgb, var(--accent) 7%, transparent); }
}

.race-lane-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  line-height: 1.2;
}

.race-lane-rank {
  min-width: 30px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.race-lane.is-you .race-lane-rank { color: var(--accent); }

.race-lane-name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.race-lane-stat {
  margin-left: auto;
  color: var(--muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.race-lane-runway {
  position: relative;
  height: 22px;
}

.race-lane-avatar {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(-50%, -50%);
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--surface-3);
  color: var(--text);
  font-size: 11px;
  font-weight: 800;
  box-shadow: 0 0 0 2px var(--surface);
  z-index: 2;
}

.race-lane.is-you .race-lane-avatar {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 2px var(--surface), 0 0 14px color-mix(in srgb, var(--accent) 60%, transparent);
}

.race-lane.is-finished .race-lane-avatar {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px #f5c451;
  animation: raceFinishPop 0.5s var(--ease-out);
}

@keyframes raceFinishPop {
  0%   { transform: translate(-50%, -50%) scale(1); }
  45%  { transform: translate(-50%, -50%) scale(1.35); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* generic speed streak — length + glow scale with WPM (set inline by JS) */
.race-lane-trail {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(-100%, -50%);
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(to left, var(--accent), transparent);
  pointer-events: none;
  z-index: 1;
}

.race-lane:not(.is-you) .race-lane-trail {
  background: linear-gradient(to left, color-mix(in srgb, var(--muted) 80%, transparent), transparent);
}

/* an equipped Trail cosmetic (::after on the token, further down) replaces
   the generic streak so people still see what they bought */
.race-lane-avatar[data-trail]:not([data-trail=""]) ~ .race-lane-trail {
  opacity: 0 !important;
}

/* ---------- Crowd view (13+ racers) ---------- */

.race-track--crowd {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.race-crowd-podium {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.race-crowd-podium-card {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 12px;
  min-width: 0;
}

.race-crowd-podium-card.is-first {
  border-color: transparent;
  background: linear-gradient(135deg,
    color-mix(in srgb, #f5c451 28%, var(--bg)), var(--bg));
}

.race-crowd-podium-rank {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted);
  font-weight: 800;
  font-size: 11px;
}

.race-crowd-podium-card.is-first .race-crowd-podium-rank {
  background: linear-gradient(135deg, #f5c451, #dd9022);
  color: #241700;
}

.race-crowd-podium-name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.race-crowd-podium-stat { margin-left: auto; color: var(--muted); flex-shrink: 0; }

.race-crowd-lane {
  position: relative;
  height: 40px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
}

.race-crowd-lane .race-track-finish {
  top: 8px;
  bottom: 8px;
  right: 10px;
}

.race-crowd-dots {
  position: absolute;
  inset: 0 18px;
}

.race-crowd-dot {
  position: absolute;
  top: 50%;
  left: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--muted);
  border: 2px solid var(--surface);
}

.race-crowd-dot.is-leader {
  width: 12px;
  height: 12px;
  background: linear-gradient(135deg, #f5c451, #dd9022);
  z-index: 2;
}

.race-crowd-dot.is-you {
  width: 15px;
  height: 15px;
  background: var(--accent);
  z-index: 3;
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.race-crowd-dot.is-finished { outline: 2px solid #f5c451; outline-offset: 1px; }

.race-crowd-you {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--accent);
  background: var(--accent-soft);
}

.race-crowd-you-rank { font-weight: 800; color: var(--accent); font-size: 13px; }
.race-crowd-you-name { flex: 1; font-weight: 700; font-size: 13px; }
.race-crowd-you-stat { color: var(--muted); font-size: 12.5px; }

.race-crowd-list {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.race-crowd-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
}

.race-crowd-list-row:last-child { border-bottom: none; }
.race-crowd-list-row.is-you { background: var(--accent-soft); }

.race-crowd-list-rank {
  width: 30px;
  flex-shrink: 0;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.race-crowd-list-name {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.race-crowd-list-stat {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Bots must always read as bots — one badge next to the name everywhere. */
.bot-tag {
  display: inline-block;
  padding: 1px 5px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
  vertical-align: middle;
}

/* Status symbol next to a username: shown only when they have a Limited
   or premium theme equipped, so it reads as rare. Colour keys off the
   same rarity tiers as the shop. Deliberately a `.theme-badge.rarity-*`
   compound (own copy of each tier's hex, not the bare .rarity-* class)
   so it wins regardless of host component — this badge lands inside
   .lb-user/.coin-user-info/.race-*-name/.profile-title-row, and more
   than one of those has its own `… span { color: var(--muted) }` rule
   that would otherwise outrank a same-specificity bare class. */
.theme-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: 999px;
  font-size: 10px;
  line-height: 1;
  vertical-align: middle;
}
.theme-badge.rarity-legendary { color: #e8b23c; border: 1px solid color-mix(in srgb, #e8b23c 45%, transparent); background: color-mix(in srgb, #e8b23c 16%, var(--surface)); }
.theme-badge.rarity-mythical  { color: #f472b6; border: 1px solid color-mix(in srgb, #f472b6 45%, transparent); background: color-mix(in srgb, #f472b6 16%, var(--surface)); }
.theme-badge.rarity-seasonal  { color: #4ade80; border: 1px solid color-mix(in srgb, #4ade80 45%, transparent); background: color-mix(in srgb, #4ade80 16%, var(--surface)); }
.theme-badge.rarity-prestige  { color: #cbd5e1; border: 1px solid color-mix(in srgb, #cbd5e1 45%, transparent); background: color-mix(in srgb, #cbd5e1 16%, var(--surface)); }
.theme-badge.rarity-founder   { color: #f5c451; border: 1px solid color-mix(in srgb, #f5c451 45%, transparent); background: color-mix(in srgb, #f5c451 16%, var(--surface)); }

@media (prefers-reduced-motion: reduce) {
  .race-grid-row,
  .race-countdown-num.tick,
  .race-countdown-num.go,
  .race-pulse-dot,
  .race-lane.is-rank-up,
  .race-lane.is-finished .race-lane-avatar {
    animation: none;
  }
}

@media (max-width: 720px) {
  .race-crowd-podium { grid-template-columns: 1fr; }
}

/* --- Race trail cosmetics ---
   Matches ShopItem slugs (item_type="trail"). Applied both on the live
   lane avatar and the small circle avatars in the waiting room / results
   so equipping a trail is visible everywhere a player shows up. */

.race-lane-avatar[data-trail]::after,
.race-avatar-circle[data-trail]::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 100%;
  transform: translateY(-50%);
  height: 6px;
  width: 26px;
  border-radius: 999px;
  pointer-events: none;
}

.race-avatar-circle[data-trail]::after { display: none; }

.race-lane-avatar[data-trail="spark-trail"]::after {
  background: linear-gradient(to left, #fde047, transparent);
  box-shadow: 0 0 8px #fde047;
  animation: raceTrailFlicker 0.5s ease-in-out infinite;
}

.race-lane-avatar[data-trail="comet-trail"]::after {
  width: 40px;
  background: linear-gradient(to left, #38bdf8, transparent);
  box-shadow: 0 0 10px #38bdf8;
}

/* The flame visibly grows/shrinks, not just fades — raceTrailFlicker
   (opacity-only) is shared with the common-tier Spark/Ice trails, so an
   epic trail needs more than just a faster copy of the same fade. */
.race-lane-avatar[data-trail="flame-trail"]::after {
  width: 34px;
  background: linear-gradient(to left, #f97316, #facc15, transparent);
  box-shadow: 0 0 10px #f97316;
  animation: flameTrailPulse 0.6s ease-in-out infinite;
}

@keyframes flameTrailPulse {
  0%, 100% { width: 34px; opacity: 1; }
  50% { width: 40px; opacity: 0.8; }
}

.race-lane-avatar[data-trail="ghost-trail"]::after {
  width: 34px;
  background: linear-gradient(to left, rgba(226,232,240,0.9), transparent);
  box-shadow: 0 0 10px rgba(226,232,240,0.6);
  animation: raceTrailDrift 1.2s ease-in-out infinite;
}

.race-lane-avatar[data-trail="prism-trail"]::after {
  width: 40px;
  background: linear-gradient(to left, #ec4899, #a855f7, #38bdf8, transparent);
  background-size: 200% 100%;
  box-shadow: 0 0 12px rgba(168,85,247,0.6);
  animation: raceTrailShift 1.4s linear infinite;
}

@keyframes raceTrailFlicker {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

@keyframes raceTrailDrift {
  0%, 100% { transform: translateY(-50%) translateX(0); opacity: 0.9; }
  50% { transform: translateY(-50%) translateX(-4px); opacity: 0.5; }
}

@keyframes raceTrailShift {
  0% { background-position: 0 0; }
  100% { background-position: 200% 0; }
}

/* Limited to 75 units ever — the biggest, brightest trail in the shop,
   wider and hotter than the regular Flame Trail. */
/* Width pulse + a brightness/box-shadow surge toward white-hot at the
   peak — the most dynamic trail in the shop, matching its mythical/
   limited-75 status instead of sharing Spark Trail's plain flicker. */
.race-lane-avatar[data-trail="eternal-flame-trail"]::after {
  width: 48px;
  height: 8px;
  background: linear-gradient(to left, #fff7ed, #fb923c, #dc2626, transparent);
  box-shadow: 0 0 14px #f97316, 0 0 26px rgba(220, 38, 38, 0.5);
  animation: eternalFlameSurge 0.7s ease-in-out infinite;
}

@keyframes eternalFlameSurge {
  0%, 100% { width: 48px; filter: brightness(1); box-shadow: 0 0 14px #f97316, 0 0 26px rgba(220, 38, 38, 0.5); }
  50% { width: 58px; filter: brightness(1.3); box-shadow: 0 0 20px #fff7ed, 0 0 36px rgba(251, 146, 60, 0.7); }
}

.race-lane-avatar[data-trail="ice-trail"]::after {
  background: linear-gradient(to left, #e0f2fe, #38bdf8, transparent);
  box-shadow: 0 0 8px #38bdf8;
  animation: raceTrailFlicker 0.6s ease-in-out infinite;
}

/* Dark core + bright silver glow — same "visible regardless of what's
   behind it" trick as the Shadow Cursor above. */
.race-lane-avatar[data-trail="shadow-trail"]::after {
  background: linear-gradient(to left, #78716c, #1c1917, transparent);
  box-shadow: 0 0 10px rgba(214, 211, 209, 0.7);
  animation: raceTrailDrift 1s ease-in-out infinite;
}

.race-lane-avatar[data-trail="electric-trail"]::after {
  width: 34px;
  background: linear-gradient(to left, #fde047, #22d3ee, transparent);
  box-shadow: 0 0 10px #fde047, 0 0 18px rgba(34, 211, 238, 0.6);
  animation: raceTrailFlicker 0.2s steps(2) infinite;
}

.race-lane-avatar[data-trail="galaxy-trail"]::after {
  width: 40px;
  background: linear-gradient(to left, #a855f7, #6366f1, #38bdf8, transparent);
  background-size: 200% 100%;
  box-shadow: 0 0 12px rgba(99, 102, 241, 0.6);
  animation: raceTrailShift 1.6s linear infinite;
}

/* Small moving-dot demo used in the Customize modal's Race Trails tab */
.trail-demo-track {
  position: relative;
  overflow: hidden;
}

.trail-demo-dot {
  position: absolute;
  top: 50%;
  left: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  transform: translateY(-50%);
  animation: trailDemoMove 1.8s ease-in-out infinite;
}

[data-trail-demo] .trail-demo-dot { background: var(--accent); }

[data-trail-demo="spark-trail"] .trail-demo-dot {
  background: #fde047;
  box-shadow: -14px 0 10px -4px #fde047;
}

[data-trail-demo="comet-trail"] .trail-demo-dot {
  background: #38bdf8;
  box-shadow: -18px 0 12px -4px #38bdf8;
}

[data-trail-demo="flame-trail"] .trail-demo-dot {
  background: #f97316;
  box-shadow: -18px 0 12px -4px #f97316;
}

[data-trail-demo="ghost-trail"] .trail-demo-dot {
  background: #e2e8f0;
  box-shadow: -18px 0 12px -4px rgba(226,232,240,0.8);
}

[data-trail-demo="prism-trail"] .trail-demo-dot {
  background: #a855f7;
  box-shadow: -18px 0 14px -4px #ec4899;
}

@keyframes trailDemoMove {
  0%, 100% { left: 8px; }
  50% { left: calc(100% - 16px); }
}

/* ---------- Results ---------- */

.race-result-hero {
  position: relative;
  text-align: center;
  padding: 8px 0 22px;
}

.race-result-hero-place {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.race-result-hero-place .medal { margin-right: 8px; }

.race-result-hero-sub {
  margin-top: 6px;
  color: var(--muted);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.race-result-hero-reward {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.race-result-hero-reward b { color: var(--accent); }

/* podium — top 3 as blocks of descending height */
.race-podium {
  display: grid;
  grid-template-columns: 1fr 1.15fr 1fr;
  align-items: end;
  gap: 10px;
  max-width: 440px;
  margin: 0 auto 24px;
}

.race-podium-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.race-podium-col .race-avatar { width: 40px; height: 40px; font-size: 15px; }

.race-podium-name {
  font-size: 12px;
  font-weight: 700;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.race-podium-wpm {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.race-podium-block {
  width: 100%;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  border: 1px solid var(--border);
  border-bottom: none;
  background: var(--surface-2);
  display: grid;
  place-items: start center;
  padding-top: 8px;
  font-weight: 800;
  font-size: 13px;
  color: var(--muted);
}

.race-podium-col.p1 .race-podium-block {
  height: 74px;
  background: linear-gradient(180deg, color-mix(in srgb, #f5c451 26%, var(--surface-2)), var(--surface-2));
  border-color: color-mix(in srgb, #f5c451 40%, var(--border));
  color: var(--text);
}
.race-podium-col.p2 .race-podium-block { height: 54px; }
.race-podium-col.p3 .race-podium-block { height: 40px; }

.race-podium-col.p1 .race-avatar {
  box-shadow: 0 0 0 3px color-mix(in srgb, #f5c451 55%, transparent),
              0 6px 18px color-mix(in srgb, #f5c451 35%, transparent);
}

/* full standings */
.race-standings {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 4px 0 20px;
}

.race-standing-row {
  display: grid;
  grid-template-columns: 40px 34px 1fr auto auto;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
}

.race-standing-row.is-winner {
  border-color: color-mix(in srgb, #f5c451 45%, var(--border));
  background: linear-gradient(90deg, color-mix(in srgb, #f5c451 12%, var(--bg)), var(--bg) 60%);
}

.race-standing-row.is-you { border-color: var(--accent); }

.race-standing-place {
  font-weight: 800;
  color: var(--muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.race-standing-row.is-winner .race-standing-place { color: #d99320; }
.race-standing-row.is-you .race-standing-place { color: var(--accent); }

.race-standing-name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.race-standing-wpm { font-weight: 700; font-variant-numeric: tabular-nums; }
.race-standing-wpm span { color: var(--muted); font-weight: 500; font-size: 11px; }
.race-standing-acc { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }

.race-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* win burst — accent + gold confetti flung from the hero */
.race-burst {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}

.race-burst i {
  position: absolute;
  top: 20%;
  left: 50%;
  width: 8px;
  height: 8px;
  border-radius: 1px;
  opacity: 0;
  animation: raceBurst 1s var(--ease-out) forwards;
}

@keyframes raceBurst {
  0%   { opacity: 1; transform: translate(0, 0) rotate(0); }
  100% { opacity: 0; transform: translate(var(--bx), var(--by)) rotate(var(--br)); }
}

@media (prefers-reduced-motion: reduce) {
  .race-burst { display: none; }
}

@media (max-width: 640px) {
  .race-standing-row { grid-template-columns: 32px 30px 1fr; }
  .race-standing-wpm, .race-standing-acc { grid-column: span 3; padding-left: 74px; }
  .race-podium-name { font-size: 11px; }
}

/* =========================================================
   ORGANIZATIONS — shares the redesigned race-room shell +
   mini-card / hero language so the team space reads as part
   of the same product as Race and the shop.
========================================================= */

.org-dash {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding-top: 6px;
}

/* ---------- Dashboard ---------- */

.org-dash-head { display: flex; flex-direction: column; gap: 10px; }

.org-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

.org-back:hover { color: var(--accent); }

.org-dash-title {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.org-dash-title h1 {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 5px 0 0;
}

.org-code-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 12px;
  padding: 6px 12px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.12em;
  color: var(--text);
}

.org-code-chip i { color: var(--accent); font-size: 13px; }

/* Team scorecard — the rank is the headline, four supporting
   stats read like the race-lobby record row. */
.org-scorecard {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 28px;
  align-items: center;
  padding: 24px 28px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 55%),
    var(--surface);
}

.org-scorecard-rank {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-right: 28px;
  border-right: 1px solid var(--border);
}

.org-scorecard-rank strong {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.org-scorecard-rank small { font-size: 11px; color: var(--muted); }

.org-scorecard-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
}

.org-scorecard-stats div { display: flex; flex-direction: column; gap: 5px; }

.org-scorecard-stats strong {
  font-size: 22px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.org-scorecard-stats span {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.org-race-banner,
.org-race-cta {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 22px;

  color: var(--text);
  text-align: left;
  width: 100%;
  transition: transform var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.org-race-banner {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.org-race-banner:hover,
.org-race-cta:hover {
  transform: translateY(-1px);
  border-color: var(--accent);
}

.org-race-banner-icon { font-size: 26px; flex-shrink: 0; }

.org-race-banner-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.org-race-banner-body span {
  font-size: 12.5px;
  color: var(--muted);
}

/* flex-column + gap (not padding alone) so every child — section-head,
   a list, a form, an empty state — gets the same breathing room below
   the header automatically, instead of each content type needing its
   own margin-top to not sit flush against the heading above it. */
.org-panel {
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.org-panel-head,
.org-challenge-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}

.org-challenge-card { padding: 24px; }

.org-challenge-goal {
  font-size: 13.5px;
  color: var(--muted);
  margin: 16px 0;
}

.org-challenge-bars {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.org-challenge-participants {
  margin: 14px 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* Active-challenge meters: the label and the live number sit above the
   bar (instead of a caption under a hairline), and the bar is thick
   enough to actually read as progress toward something. */
.challenge-meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.challenge-meter-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.challenge-meter-value {
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.challenge-meter-value strong {
  font-size: 15px;
  color: var(--text);
}

.org-challenge-bars .achievement-bar {
  height: 10px;
  margin-bottom: 0;
}

.org-challenge-expiry {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 6px;
  padding: 2px 9px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

/* This card isn't a flex-column like .org-panel (its active-challenge
   state spaces its own children with explicit margins), so the builder
   form gets its gap under the header here instead. */
.org-challenge-card > .form-rows {
  margin-top: 16px;
}

/* ---------- Roster / team leaderboard ---------- */

.org-roster {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.org-roster-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  border-radius: var(--radius-md);

  background: var(--surface-2);
  border: 1px solid var(--border);
  flex-wrap: wrap;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.org-roster-row:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
}

.org-roster-row.is-me {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
}

/* Icon-only row actions (message/race/remove, promote/demote) sit on the
   same --surface-2 the row itself uses — .icon-btn's normal hover fill
   is that exact colour, so without this override hovering one did
   nothing visible. */
.org-roster-row .icon-btn:hover,
.friend-row .icon-btn:hover {
  background: var(--surface-3);
}

.org-roster-rank {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  font-weight: 800;
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Podium tints for the top three — compound selector so these win over
   .org-roster-rank's own grey (the shared .rank-1/2/3 podium rule sits
   earlier in the file, same specificity, so it would otherwise lose). */
.org-roster-rank.rank-1 { background: color-mix(in srgb, #f5c451 20%, var(--surface)); color: #f5c451; border-color: color-mix(in srgb, #f5c451 45%, transparent); }
.org-roster-rank.rank-2 { background: color-mix(in srgb, #c8cdd8 18%, var(--surface)); color: #cdd2dc; border-color: color-mix(in srgb, #c8cdd8 40%, transparent); }
.org-roster-rank.rank-3 { background: color-mix(in srgb, #d08a52 18%, var(--surface)); color: #db9666; border-color: color-mix(in srgb, #d08a52 42%, transparent); }

.org-roster-avatar {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: 50%;

  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 15px;
  color: var(--accent);

  background: var(--accent-soft);
}

.org-roster-body {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.org-roster-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.org-roster-body small {
  color: var(--muted);
  font-size: 12px;
}

.org-roster-stats {
  display: flex;
  gap: 18px;
}

.org-roster-stats div {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 56px;
}

.org-roster-stats strong {
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}

.org-roster-stats span {
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.org-roster-actions {
  display: flex;
  gap: 4px;
}

@media (max-width: 700px) {
  .org-scorecard { grid-template-columns: 1fr; }
  .org-scorecard-rank {
    padding-right: 0;
    padding-bottom: 16px;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
  .org-scorecard-stats { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 640px) {
  .org-race-banner, .org-race-cta { flex-wrap: wrap; }
  .org-roster-row { justify-content: space-between; }
  .org-roster-stats { gap: 12px; width: 100%; justify-content: space-between; }
  .org-roster-actions { width: 100%; justify-content: flex-end; }
}

/* =========================================================
   SOCIAL HUB — "your circle"
   A league, not a list: a hero that says where you stand and who to
   beat next, ONE friends leaderboard drawn as race lanes (re-sortable
   by Speed / This week / Streak), and a rail for what is happening
   right now (requests, live races, activity). Reads theme tokens only,
   so it re-tints with every theme; the only fixed colours are the
   shared podium metals. Namespaced with the sx- prefix.
========================================================= */

/* Shared with the org dashboard, organizations and account settings —
   kept from the old Social block, not Social-specific. */
.section-head-icon {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.section-icon-chip {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
}

/* A display:grid/flex rule on a component beats the UA [hidden] rule, so
   the tab panels would never actually hide without this. */
.sx [hidden] { display: none !important; }

/* ---------- Header ---------- */

.sx-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.sx-head h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.sx-kicker {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- Avatars ---------- */

.sx-avatar {
  --size: 40px;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: calc(var(--size) * 0.38);
  line-height: 1;
  background: var(--accent-soft);
  color: var(--accent);
}

.sx-avatar--sm { --size: 34px; }
.sx-avatar--md { --size: 40px; }
.sx-avatar--xl { --size: 76px; }

/* Three in-palette tints for letter avatars (chosen per name in the view)
   so a column of them is not one repeated disc. Derived from tokens, never
   a fixed hue, so every theme keeps its own palette. */
.sx-avatar[data-tone="1"] {
  background: color-mix(in srgb, var(--accent-2) 16%, transparent);
  color: color-mix(in srgb, var(--accent-2) 65%, var(--text));
}
.sx-avatar[data-tone="2"] {
  background: var(--surface-3);
  color: var(--text);
}
.sx-avatar.is-me {
  background: var(--accent);
  color: var(--on-accent);
}

/* An equipped avatar is an emoji — the shared .avatar-badge rule already
   swaps it onto a quiet neutral chip (higher specificity than the tints
   above); it just needs a bigger glyph than a letter does. */
.sx-avatar[data-avatar]:not([data-avatar=""]) {
  font-size: calc(var(--size) * 0.46);
}

.sx-avatar-wrap {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
}

/* Presence: green = active today, accent + pulse = in a race. The border
   matches the row behind it so the dot reads as a cutout, not a sticker. */
.sx-dot {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--success);
  border: 2px solid var(--row-bg, var(--surface));
}

.sx-dot--race {
  background: var(--accent);
  animation: pulseDot 1.5s infinite;
}

/* ---------- Hero: where you stand + who to beat ---------- */

.sx-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  border-radius: var(--radius-lg);
  /* An explicit hairline, not var(--card-border): premium themes zero that
     out for their own frame, and this focal panel is not a .card. */
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  background:
    radial-gradient(120% 150% at 0% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 56%),
    var(--surface);
  overflow: hidden;
}

.sx-hero-me {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 28px;
  min-width: 0;
}

.sx-hero-me-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

.sx-hero-rival {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 14px;
  padding: 28px;
  min-width: 0;
  border-left: 1px solid var(--border);
  background: color-mix(in srgb, var(--text) 2.5%, transparent);
}

/* XP-to-next-level as a ring around your avatar: the conic fill is the
   progress, a mask punches the middle out so the avatar sits inside it. */
.sx-ring {
  --pct: 0;
  position: relative;
  flex-shrink: 0;
  width: 96px;
  height: 96px;
}

.sx-ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(var(--accent) calc(var(--pct) * 1%), color-mix(in srgb, var(--text) 12%, transparent) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
}

.sx-ring .sx-avatar {
  position: absolute;
  inset: 10px;
  width: auto;
  height: auto;
}

.sx-ring-lv {
  position: absolute;
  left: 50%;
  bottom: -6px;
  transform: translateX(-50%);
  padding: 3px 9px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 3px var(--surface);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.sx-rank-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.sx-rank-big {
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.sx-rank-big.is-empty { color: var(--faint); }

.sx-rank-line span {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--muted);
}

.sx-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
}

.sx-stats div {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.sx-stats strong {
  font-size: 20px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.sx-stats span {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The duel: you, a progress meter toward the rival's speed, them. */
.sx-duel {
  width: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
}

.sx-duel-side {
  display: flex;
  align-items: center;
  gap: 10px;
}

.sx-duel-side strong {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.sx-duel-side.is-them strong { color: var(--muted); }

.sx-meter {
  --pct: 0;
  position: relative;
  height: 10px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 10%, transparent);
}

.sx-meter > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--pct) * 1%);
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 28%, transparent), var(--accent));
  transform-origin: left center;
  animation: sxLane 0.8s var(--ease-out) both;
}

/* A finish tick at the rival's end of the meter. */
.sx-meter::after {
  content: "";
  position: absolute;
  right: -1px;
  top: -4px;
  bottom: -4px;
  width: 2px;
  border-radius: 2px;
  background: var(--border-strong);
}

.sx-rival-line {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}

.sx-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.sx-rival-line strong { color: var(--text); font-weight: 800; }
.sx-rival-line a { color: var(--text); font-weight: 700; }
.sx-rival-line a:hover { color: var(--accent); }

/* ---------- Layout ---------- */

.sx-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 328px;
  gap: 24px;
  align-items: start;
}

.sx-main,
.sx-rail {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
}

.sx-panel {
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.sx-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

.sx-panel-head h2 {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.005em;
}

.sx-panel-head p {
  margin: 3px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

.sx-panel-head .leaderboard-scope-toggle { margin: 0; }

/* The shared scope pills are sized for a dense toolbar; these are a
   primary control here. */
.sx .lb-scope-btn {
  padding: 7px 12px;
  font-size: 12px;
}

.sx-count {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.sx-tab-count { opacity: 0.7; font-variant-numeric: tabular-nums; }

/* ---------- Leaderboard: rows as race lanes ---------- */

.sx-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Fixed-width tracks everywhere but the name column, and the lane lives
   inside that column, so every row's lane starts and ends at the same x
   and bar lengths compare across rows. */
.sx-row {
  --row-bg: var(--surface);
  position: relative;
  display: grid;
  grid-template-columns: 30px 44px minmax(0, 1fr) 84px 144px;
  align-items: center;
  column-gap: 14px;
  padding: 12px;
  border-radius: var(--radius-md);
  background: var(--row-bg);
  transition: background var(--dur-1) var(--ease);
}

.sx-row:hover,
.sx-row:focus-within { --row-bg: var(--surface-2); }

.sx-row.is-me {
  --row-bg: color-mix(in srgb, var(--accent) 7%, var(--surface));
  box-shadow: inset 3px 0 0 var(--accent);
}

.sx-row.is-me:hover,
.sx-row.is-me:focus-within { --row-bg: color-mix(in srgb, var(--accent) 10%, var(--surface)); }

/* Hairline between rows — a pseudo-element, not a border, so a hovered
   row can keep its rounded background and simply hide the lines around it. */
.sx-row + .sx-row::before {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  top: -0.5px;
  height: 1px;
  background: var(--border);
  transition: opacity var(--dur-1) var(--ease);
}

.sx-row:hover::before,
.sx-row:hover + .sx-row::before,
.sx-row.is-me::before,
.sx-row.is-me + .sx-row::before { opacity: 0; }

.sx-rank {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  font-weight: 800;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Podium metals. Compound selectors so they beat the base rule above;
   the text tones darken in light themes (pale gold on white is unreadable). */
.sx-rank.rank-1 {
  background: color-mix(in srgb, #f5c451 22%, var(--surface));
  border-color: color-mix(in srgb, #f5c451 50%, transparent);
  color: light-dark(#8a5d00, #f5c451);
}
.sx-rank.rank-2 {
  background: color-mix(in srgb, #c8cdd8 22%, var(--surface));
  border-color: color-mix(in srgb, #c8cdd8 50%, transparent);
  color: light-dark(#566070, #cdd2dc);
}
.sx-rank.rank-3 {
  background: color-mix(in srgb, #d08a52 20%, var(--surface));
  border-color: color-mix(in srgb, #d08a52 50%, transparent);
  color: light-dark(#8a4a1a, #db9666);
}

.sx-who { min-width: 0; }

.sx-name {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
}

.sx-name a,
.sx-name strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}

.sx-name a:hover { color: var(--accent); }
.sx-name .theme-badge { flex-shrink: 0; }

.sx-you {
  flex-shrink: 0;
  padding: 2px 7px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.sx-sub {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  overflow: hidden;
  white-space: nowrap;
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
}

.sx-lv { font-weight: 700; font-variant-numeric: tabular-nums; }
.sx-streak { font-weight: 700; color: var(--warning); font-variant-numeric: tabular-nums; }
.sx-status.is-race { font-weight: 600; color: var(--accent); }

/* The week note only exists on the This-week board, where it replaces the
   presence text. */
.sx-note { display: none; }
.sx-rows[data-board="week"] .sx-note { display: inline; }
.sx-rows[data-board="week"] .sx-status { display: none; }

.sx-lane {
  position: relative;
  height: 8px;
  margin-top: 8px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 9%, transparent);
}

/* Length comes from the server per board (--w-speed / --w-week /
   --w-streak); switching board just re-points width and lets it ease.
   The fill fades in from the left like a speed trail, and stays a neutral
   tint of the text colour so only YOUR lane carries the accent. */
.sx-lane-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w-speed, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--text) 10%, transparent), color-mix(in srgb, var(--text) 40%, transparent));
  transform-origin: left center;
  animation: sxLane 0.8s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 50ms);
  transition: width 0.5s var(--ease-out);
}

.sx-rows[data-board="week"] .sx-lane-fill { width: var(--w-week, 0%); }
.sx-rows[data-board="streak"] .sx-lane-fill { width: var(--w-streak, 0%); }
.sx-row.is-me .sx-lane-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 28%, transparent), var(--accent));
}

@keyframes sxLane {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.sx-val {
  display: none;
  align-items: baseline;
  justify-content: flex-end;
  gap: 4px;
}

.sx-rows[data-board="speed"] .sx-val[data-b="speed"],
.sx-rows[data-board="week"] .sx-val[data-b="week"],
.sx-rows[data-board="streak"] .sx-val[data-b="streak"] { display: flex; }

.sx-val strong {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.sx-val strong.is-none { font-weight: 600; color: var(--faint); }

.sx-val small {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.sx-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}

.sx-icon-btn {
  position: relative;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), opacity var(--dur-1) var(--ease);
}

.sx-icon-btn:hover { background: var(--surface-3); color: var(--text); }

.sx-icon-btn--danger:hover {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
}

/* Remove-friend is rare and destructive: it only appears when you are
   already on that row (and always on touch, where there is no hover). */
.sx-icon-btn--reveal { opacity: 0; }
.sx-row:hover .sx-icon-btn--reveal,
.sx-row:focus-within .sx-icon-btn--reveal,
.sx-icon-btn--reveal:focus-visible { opacity: 1; }

@media (hover: none) {
  .sx-icon-btn--reveal { opacity: 1; }
}

.sx-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--danger);
  color: #fff;
  box-shadow: 0 0 0 2px var(--row-bg, var(--surface));
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
}

.sx-race-btn {
  height: 32px;
  padding: 0 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

/* Soft accent at rest, so a column of them reads as one deliberate action
   rather than eight outlined buttons; solid on hover. */
.sx-race-btn:hover {
  background: var(--accent);
  color: var(--on-accent);
}

.sx-race-btn:disabled { opacity: 0.5; pointer-events: none; }

/* A friend sitting in an open public lobby: the Race button becomes Join. */
.sx-race-btn.is-live {
  border-color: transparent;
  background: var(--accent);
  color: var(--on-accent);
}

.sx-race-btn.is-live:hover { background: var(--accent-hover); color: var(--on-accent); }

.sx-invite-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.sx-invite-row:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.sx-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 36px 24px;
  text-align: center;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
}

.sx-empty-art { font-size: 36px; line-height: 1; }

.sx-empty h3 {
  margin: 4px 0 0;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
}

.sx-empty p {
  margin: 0 0 6px;
  max-width: 42ch;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted);
}

/* ---------- Rail + discover: people rows ---------- */

.sx-list { display: flex; flex-direction: column; }
.sx-list > .sx-person + .sx-person { border-top: 1px solid var(--border); }

.sx-person {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding: 10px 0;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

.sx-person.is-leaving { opacity: 0; transform: translateX(12px); }

.sx-person-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sx-person-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
}

a.sx-person-name:hover { color: var(--accent); }

.sx-person-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--muted);
}

/* Incoming requests: the actions get their own full-width row so they are
   easy targets, and the name never gets squeezed by two buttons. */
.sx-person--stack { flex-wrap: wrap; padding: 12px 0; }

.sx-person--stack .sx-person-actions {
  flex: 0 0 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.sx-person--tile {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  transition: border-color var(--dur-1) var(--ease), opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

.sx-person--tile:hover { border-color: var(--border-strong); }

.sx-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.sx-tiles > .sx-quiet { grid-column: 1 / -1; }

.sx-btn-sm { padding: 8px 12px; font-size: 12.5px; }

.sx-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background: var(--surface-3);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
}

.sx-chip.is-race { background: var(--accent-soft); color: var(--accent); }

.sx-subhead {
  margin-top: 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A request waiting on you is the one thing on this page that is not
   just information, so its card is the one that gets an accent edge. */
.sx-requests.has-incoming {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background:
    radial-gradient(140% 90% at 100% 0%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 60%),
    var(--surface);
}

.sx-quiet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 14px 8px;
  text-align: center;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

.sx-quiet p { margin: 0; }

.sx-group-icon {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 800;
}

.sx-find {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
}

.sx-find > i {
  position: absolute;
  left: 13px;
  font-size: 15px;
  color: var(--muted);
  pointer-events: none;
}

.sx-find .field {
  flex: 1;
  min-width: 0;
  padding-left: 36px;
}

.sx-activity .activity-feed { max-height: 320px; }

/* Anchor target for the empty-leaderboard "Find people" link — clears the
   sticky header. */
.sx-discover { scroll-margin-top: 88px; }

/* ---------- Responsive ---------- */

@media (max-width: 1040px) {
  .sx-row {
    grid-template-columns: 30px 44px minmax(0, 1fr) 76px 136px;
    column-gap: 12px;
  }
}

/* One column. The wrappers dissolve (display: contents) so every panel
   can be ordered on its own — requests first, since they are waiting on
   you, then the leaderboard, then everything ambient. */
@media (max-width: 960px) {
  .sx-hero { grid-template-columns: 1fr; }
  .sx-hero-rival { border-left: 0; border-top: 1px solid var(--border); }

  .sx-grid { display: flex; flex-direction: column; }
  .sx-main,
  .sx-rail { display: contents; }

  .sx-requests { order: 1; }
  .sx-arena { order: 2; }
  .sx-live { order: 3; }
  .sx-discover { order: 4; }
  .sx-activity { order: 5; }
  .sx-groups { order: 6; }
}

/* Phone: each row becomes two lines — identity (with its lane) + score on
   top, the actions right-aligned underneath. */
@media (max-width: 720px) {
  .sx-panel { padding: 18px; }
  .sx-hero-me,
  .sx-hero-rival { padding: 22px; }
  .sx-hero-me { gap: 18px; }
  .sx-rank-big { font-size: 38px; }
  .sx-stats { gap: 8px 20px; }

  .sx-row {
    grid-template-columns: 28px 40px minmax(0, 1fr) auto;
    row-gap: 10px;
    column-gap: 12px;
    padding: 12px 8px;
  }
  .sx-rank { grid-row: 1; grid-column: 1; }
  .sx-avatar-wrap { grid-row: 1; grid-column: 2; }
  .sx-who { grid-row: 1; grid-column: 3; }
  .sx-value { grid-row: 1; grid-column: 4; }
  .sx-actions { grid-row: 2; grid-column: 3 / 5; }

  /* The name column is narrow here: stack the score (number over unit) to
     hand it more width, and wrap the sub-line rather than clip it. */
  .sx-val { flex-direction: column; align-items: flex-end; gap: 0; }
  .sx-val small { line-height: 1.2; }
  .sx-sub { flex-wrap: wrap; row-gap: 0; white-space: normal; }

  .sx-tiles { grid-template-columns: 1fr; }
  .sx-find { flex-wrap: wrap; }
}

@media (max-width: 480px) {
  .sx-ring { width: 80px; height: 80px; }
  .sx-ring .sx-avatar { inset: 9px; }
  .sx-duel { gap: 10px; }
  .sx-head h1 { font-size: 24px; }
}

/* =========================================================
   AWARDS + SHOP — the Social hub's game-UI kit, applied to the two
   pages that still looked like the old site. The hero, panel, ring,
   meter, chip and list primitives all come from the SOCIAL HUB block
   above (the sx- prefix names the kit, it doesn't mean "social only");
   this block only adds what is specific to these two pages.
========================================================= */

/* ---------- Additions to the shared kit ---------- */

/* The duel meter's finish tick stands for "their score"; a plain
   progress meter (tiers unlocked, collection) has no opponent. */
.sx-meter.is-plain::after { display: none; }

.sx-chip.is-gold {
  background: color-mix(in srgb, #f5c451 20%, var(--surface));
  color: light-dark(#8a5d00, #f5c451);
}

/* ---------- Awards ---------- */

.awd-goal {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.awd-goal-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  font-size: 22px;
}

.awd-goal-body {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  min-width: 0;
}

.awd-goal-body strong {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* Tier medals: bronze, silver, gold, then platinum (teal) and diamond
   (violet) so the two top tiers don't read as more silver. Fixed colours,
   like the leaderboard's podium metals. The colour is a custom property so
   a medal takes it when EARNED and stays neutral until then, and the glyph
   is pulled toward --text so a pale metal still reads on a light theme. */
.awd-node[data-t="1"],  .awd-medal[data-t="1"],  .awd-tier-chip[data-t="1"] { --rc: #d08a52; }
.awd-node[data-t="2"],  .awd-medal[data-t="2"],  .awd-tier-chip[data-t="2"] { --rc: #aeb8c8; }
.awd-node[data-t="3"],  .awd-medal[data-t="3"],  .awd-tier-chip[data-t="3"] { --rc: #f0b429; }
.awd-node[data-t="4"],  .awd-medal[data-t="4"],  .awd-tier-chip[data-t="4"] { --rc: #3fc1b4; }
.awd-node[data-t="5"],  .awd-medal[data-t="5"],  .awd-tier-chip[data-t="5"] { --rc: #a78bfa; }

.awd-tier-chip {
  --rc: #aeb8c8;
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border: 1px solid color-mix(in srgb, var(--rc) 50%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--rc) 16%, var(--surface));
  color: color-mix(in srgb, var(--rc) 50%, var(--text));
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
}

.awd-node,
.awd-medal {
  --rc: #aeb8c8;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border: 2px solid var(--border-strong);
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 800;
  line-height: 1;
}

.awd-node.is-done,
.awd-medal.is-done {
  border-color: var(--rc);
  background: color-mix(in srgb, var(--rc) 16%, var(--surface));
  color: color-mix(in srgb, var(--rc) 50%, var(--text));
}

/* The tier you are working toward: accent ring, like the meter's fill. */
.awd-node.is-next {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.awd-medal {
  width: 40px;
  height: 40px;
  font-size: 13px;
}

.awd-list { display: flex; flex-direction: column; }

.awd-row {
  position: relative;
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) 148px;
  column-gap: 16px;
  align-items: start;
  padding: 16px 12px 4px;
}

/* Hairline between rows — same pseudo-element trick as the leaderboard. */
.awd-row + .awd-row::before {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  top: -0.5px;
  height: 1px;
  background: var(--border);
}

.awd-icon {
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  font-size: 22px;
}

.awd-main { min-width: 0; }

.awd-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-height: 26px;
}

.awd-title h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
}

.awd-status {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

.awd-row.is-almost .awd-status {
  font-weight: 700;
  color: var(--accent);
}

/* The stepped track: five medals at even steps, filled to "tiers
   finished + how far through the current one". Room is left underneath
   for the tier names and either side for the end medals. */
.awd-track {
  position: relative;
  height: 8px;
  margin: 18px 14px 30px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 9%, transparent);
}

.awd-track-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--fill, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 28%, transparent), var(--accent));
  transform-origin: left center;
  animation: sxLane 0.8s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}

.awd-row.is-maxed .awd-track-fill {
  background: linear-gradient(90deg, color-mix(in srgb, #f5c451 30%, transparent), #f5c451);
}

.awd-node {
  position: absolute;
  top: 50%;
  left: var(--x);
  transform: translate(-50%, -50%);
}

.awd-node-label {
  position: absolute;
  top: calc(100% + 7px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  pointer-events: none;
}

.awd-node.is-next .awd-node-label { color: var(--accent); }

.awd-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  padding-top: 2px;
  text-align: right;
}

.awd-value {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 2px 5px;
}

.awd-value strong {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.awd-value small {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
}

.awd-reward {
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

/* Phone: the score + reward drop under the track, and the tier names
   go (they would collide at five across) — except the one you are
   working toward. */
@media (max-width: 720px) {
  .awd-row {
    grid-template-columns: 44px minmax(0, 1fr);
    padding: 14px 4px 4px;
  }
  .awd-icon { width: 44px; height: 44px; font-size: 20px; }
  .awd-side {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    text-align: left;
    padding-bottom: 12px;
  }
  .awd-value { justify-content: flex-start; }
  .awd-node-label { display: none; }
  .awd-node.is-next .awd-node-label { display: block; }
}

/* ---------- Shop ---------- */

.shop-goal {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.shop-goal-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  font-size: 22px;
}

.shop-goal-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.shop-goal-body strong {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* "Or grab one you can already afford" — each chip jumps to its card. */
.shop-afford {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.shop-afford-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.shop-afford-chip em {
  font-style: normal;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.shop-afford-chip:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.shop-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Real money reads as its own thing on every theme — the same fixed
   indigo the gem pill and gem packages use. Text is pulled toward --text
   so it still reads on a light theme. */
.shop-tab.gems-tab.active {
  background: color-mix(in srgb, #8b9dff 16%, transparent);
  color: color-mix(in srgb, #8b9dff 70%, var(--text));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #8b9dff 40%, transparent);
}

/* (.shop-grid, .shop-preview, .shop-info, .shop-title-row and .shop-price
   keep their base definitions in the SHOP CARDS section above.) */

.shop-card { min-width: 0; }

.shop-card.is-equipped {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}

.shop-price-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.shop-short {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--muted);
}

/* How close you are to affording it (or how far through a prestige gate) —
   the same speed-trail fill as every other meter in the kit. */
.shop-meter {
  height: 6px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 9%, transparent);
}

.shop-meter > i {
  display: block;
  width: calc(var(--pct, 0) * 1%);
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 28%, transparent), var(--accent));
}

/* Prestige progress sits on a card scoped to that item's own theme; a fixed
   neutral track (not the scoped --text) keeps the fill readable on it. */
.limited-progress .shop-meter { background: rgba(128, 128, 128, 0.22); }

.shop-state,
.shop-flag {
  position: absolute;
  top: 10px;
  z-index: 2;
  padding: 3px 9px;
  border-radius: var(--radius-full);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
}

.shop-state {
  left: 10px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--muted);
}

.shop-state.is-equipped {
  border-color: transparent;
  background: var(--accent);
  color: var(--on-accent);
}

/* A limited card already has its group tag top-left, so its state chip
   moves to the other corner. */
.shop-card--limited .shop-state { left: auto; right: 10px; }

.shop-flag {
  right: 10px;
  background: #f5c451;
  color: #241700;
}

.shop-flag.is-premium {
  background: #5b6ef0;
  color: #fff;
}

/* Soft accent at rest, solid on hover — the same treatment as the
   leaderboard's Race pill, so a wall of Purchase buttons is not a wall of
   solid blue. Scoped to shop cards: .buy-btn is also used on the home page
   and dashboard. A scoped (premium / limited) card resolves --accent to
   that item's own colour, as it always has. */
.shop-card .buy-btn,
.shop-card .owned-btn {
  padding: 10px 12px;
  border-radius: var(--radius-full);
  font-size: 13px;
}

.shop-card .buy-btn {
  background: var(--accent-soft);
  color: var(--accent);
}

.shop-card .buy-btn:hover {
  background: var(--accent);
  color: var(--on-accent);
}

.shop-card.is-equipped .owned-btn {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.vault-collector-body .shop-meter { margin-top: 2px; }

/* Phone: each card becomes a compact row (preview on the left, details on
   the right) instead of a tall block, so a 22-theme tab is a short scroll
   rather than a very long one; the tab row scrolls sideways instead of
   wrapping into a stack. */
@media (max-width: 720px) {
  .shop-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    margin-inline: -18px;
    padding-inline: 18px;
    scrollbar-width: none;
  }
  .shop-tabs::-webkit-scrollbar { display: none; }
  .shop-tab { flex-shrink: 0; }
}

@media (max-width: 560px) {
  .shop-grid { grid-template-columns: 1fr; }

  .shop-card:not(.gem-earn-card) {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
  }

  .shop-card .shop-preview {
    height: auto;
    min-height: 100%;
    border-bottom: 0;
    border-right: 1px solid var(--border);
    font-size: 34px;
  }

  .shop-card .shop-info { padding: 12px 14px; gap: 8px; }

  /* Everything that is a chip lives in the preview column, so none of it
     can land on the rarity chip in the title row: state at the top,
     flag (or a limited card's state) at the bottom. */
  .shop-state, .limited-tag { top: 7px; left: 7px; padding: 2px 6px; font-size: 8.5px; }
  .shop-flag,
  .shop-card--limited .shop-state {
    top: auto;
    right: auto;
    left: 7px;
    bottom: 8px;
    padding: 2px 6px;
    font-size: 8.5px;
  }
}

/* =========================================================
   ORG DASHBOARD v2 — tabs (reusing .customize-tabs), icon/description
   identity, team milestones, challenge history, and the invite-link
   row that sits alongside the Settings tab's admin-only edit form.
========================================================= */

.org-icon {
  font-size: 0.85em;
  margin-right: 4px;
}

.org-description {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13.5px;
  max-width: 480px;
}

/* Bigger than .org-panel's own internal 16px (header-to-content) gap on
   purpose — separate cards need to read as separate "objects" at a
   glance, not just more content in the same flow as the header above. */
.org-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.org-milestone-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.org-milestone-strip .tier-pip {
  flex: 1;
  min-width: 90px;
}

.org-invite-link-row {
  display: flex;
  gap: 8px;
}

.org-invite-link-row input {
  flex: 1;
  font-size: 13px;
  color: var(--muted);
}

.org-icon-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.org-icon-option {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.org-icon-option:hover {
  border-color: var(--border-strong);
}

.org-icon-option.active {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.org-history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.org-history-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 13px;
  flex-wrap: wrap;
  transition: border-color var(--dur-1) var(--ease);
}

.org-history-row:hover {
  border-color: var(--border-strong);
}

.org-history-status {
  font-weight: 700;
  flex-shrink: 0;
}

.org-history-status.hit {
  color: var(--success);
}

.org-history-status.missed {
  color: var(--muted);
}

.org-history-target {
  flex: 1;
  color: var(--text);
}

.org-history-date {
  color: var(--muted);
  font-size: 12px;
}

/* =========================================================
   FORM UI — the settings-page pattern (org Settings/Challenges, and
   reusable anywhere): a row is "what it is + a hint" on the left and a
   right-sized control on the right, so a name field isn't a 1000px
   bar; the primary action lives in a footer, right-aligned, instead of
   a full-width slab; destructive actions get their own danger card.
========================================================= */

.form-rows {
  display: flex;
  flex-direction: column;
}

.form-row {
  display: grid;
  grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
  gap: 8px 32px;
  align-items: start;
  padding: 20px 0;
  border-top: 1px solid var(--border);
}

.form-row:first-child {
  border-top: none;
  padding-top: 4px;
}

/* Two classes on purpose: beats the uppercase micro-label that
   .settings-card / .org-panel apply to every bare <label>. */
.form-row .form-row-title {
  display: block;
  margin: 0 0 3px;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}

.form-row-hint {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

.form-row-control {
  min-width: 0;
  max-width: 480px;
}

.form-card-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 4px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.form-footer-note {
  margin-right: auto;
  font-size: 12.5px;
  color: var(--muted);
}

/* A number field with its unit inside it — the unit is the label the
   bare placeholder used to fake ("WPM target"). Native spinners are
   hidden (they sat on top of the unit); quick-pick chips replace them. */
.input-affix {
  position: relative;
}

.input-affix input {
  padding-right: 62px;
}

.input-affix input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.input-affix input::-webkit-outer-spin-button,
.input-affix input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.input-affix .affix {
  position: absolute;
  right: 13px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  pointer-events: none;
}

.preset-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

/* Textarea with its character counter tucked inside the bottom-right
   corner, instead of a bold "0/200" hanging under it. */
.textarea-wrap {
  position: relative;
}

.textarea-wrap textarea {
  min-height: 96px;
  padding-bottom: 30px;
  resize: none;
}

.textarea-wrap .char-count {
  position: absolute;
  right: 12px;
  bottom: 9px;
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  pointer-events: none;
}

.textarea-wrap .char-count.is-warn {
  color: var(--warning);
}

.textarea-wrap .char-count.is-max {
  color: var(--danger);
}

/* A form-card is a card whose children (header, form, actions) space
   themselves with one consistent gap — same idea as .org-panel, opt-in
   so pages like Contact that share .settings-card keep their own layout. */
.form-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.settings-card.form-card form {
  margin-top: 0;
}

.form-card .org-panel-head {
  align-items: center;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* Toggle switch — replaces a bare checkbox for on/off settings. The real
   <input> stays on top (invisible, full size) so the whole switch is the
   click/focus target and screen readers still see a checkbox. The extra
   selectors beat the shared input rule, which would otherwise stretch it
   to a 100%-wide text field. */
.switch,
.switch input {
  width: 46px;
  height: 26px;
}

.switch {
  position: relative;
  display: inline-block;
  vertical-align: middle;
}

.switch input,
.settings-card .switch input,
.org-panel .switch input {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: 0;
  cursor: pointer;
  z-index: 1;
}

.switch .slider {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  transition: background var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
}

.switch .slider::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease);
}

.switch input:checked + .slider {
  background: var(--accent);
  border-color: transparent;
}

.switch input:checked + .slider::before {
  transform: translateX(20px);
  background: var(--on-accent);
}

.switch input:focus-visible + .slider {
  box-shadow: var(--focus-ring);
}

.org-invite-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--text);
}

/* Danger zone — the one place a destructive action lives, tinted so it
   reads as different from every other card at a glance. */
.org-danger,
.danger-card {
  border-color: color-mix(in srgb, var(--danger) 32%, var(--border));
}

.org-danger .section-icon-chip,
.danger-card .section-icon-chip {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
}

.org-danger .org-panel-head {
  align-items: center;
}

.btn-danger-outline {
  flex-shrink: 0;
  background: transparent;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
}

.btn-danger-outline:hover {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-color: var(--danger);
}

.org-meta-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.org-meta-row .org-code-chip {
  margin-top: 0;
}

@media (max-width: 720px) {
  .form-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    padding: 16px 0;
  }

  .form-row-control {
    max-width: none;
  }

  .form-card-footer {
    flex-wrap: wrap;
  }
}

/* =========================================================
   DASHBOARD HERO
   The kit's .sx-hero (Social / Shop / Awards) with an at-a-glance ledger in
   its tinted column. It replaces a welcome card that was flex-stretched to
   the height of three stacked mini cards, which left ~190px of blank card
   under the bio. One panel cannot be stretched by a sibling, and the ledger
   is about as tall as the identity block, so neither side has dead space.
========================================================= */

/* The shell's flex gap already spaces the challenge bar; its own bottom
   margin (there for the home page) doubled it to 40px on this page. */
.dashboard-shell > .daily-challenge-bar { margin-bottom: 0; }

.dash-hero h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  overflow-wrap: anywhere; /* usernames can be long */
}

/* .profile-bio brings its own top margin; the body's flex gap spaces it. */
.dash-hero .profile-bio { margin: 0; }

.dash-vitals {
  width: 100%;
  margin: 0; /* it is a <dl> */
  display: flex;
  flex-direction: column;
}

.dash-vital {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 10px 16px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.dash-vital:first-child { padding-top: 0; border-top: 0; }
.dash-vital:last-child { padding-bottom: 0; }

.dash-vital dt {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--muted);
}

.dash-vital dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* Level: the whole row is the link to Awards (the anchor's ::after covers it). */
.dash-vital dt a { color: inherit; }
.dash-vital dt a::after { content: ""; position: absolute; inset: 0; z-index: 1; } /* above the meter's absolutely-positioned fill */
.dash-vital--level:hover dt { color: var(--accent); }

.dash-vital--level dd:not(.dash-vital-meter) {
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--muted);
}

.dash-vital-meter { grid-column: 1 / -1; }
.dash-vital-meter .sx-meter { height: 8px; }

@media (max-width: 480px) {
  .dash-hero h1 { font-size: 22px; }
}

/* =========================================================
   PROGRESS — nav wallet sheet, results rewards panel,
   Getting-started journey (js/progress.js, _journey.html).
   Built on the .sx-* kit's tokens.
========================================================= */

/* Nav wallet chips are buttons now (they open the progress sheet). */
.nav-chips button.chip {
  font-family: inherit;
  cursor: pointer;
}

.level-pill {
  position: relative;
  padding-bottom: 3px;
}

/* Always-on XP progress under the level number. */
.level-pill-bar {
  --pct: 0;
  position: absolute;
  left: 11px;
  right: 11px;
  bottom: 5px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    var(--accent) calc(var(--pct) * 1%),
    color-mix(in srgb, var(--text) 14%, transparent) 0);
}

/* ---------- Shared: goal rows + meters ---------- */

.pg-goals,
.rw-goals,
.rw-ledger,
.pg-earn,
.jr-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.pg-goals,
.rw-goals { gap: 8px; }

.pg-goal {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.pg-goal:hover {
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
  transform: translateY(-1px);
}

.pg-goal-icon {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  font-size: 17px;
}

.pg-goal-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.pg-goal-text strong {
  font-size: 13.5px;
  font-weight: 700;
}

.pg-goal-text > span:not(.pg-goal-meter) {
  font-size: 12.5px;
  color: var(--muted);
}

.pg-goal-meter,
.jr-meter {
  --pct: 0;
  position: relative;
  display: block;
  height: 5px;
  margin-top: 5px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  overflow: hidden;
}

.pg-goal-meter > i,
.jr-meter > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--pct) * 1%);
  border-radius: inherit;
  background: var(--accent);
  transform-origin: left center;
  animation: pgGrow 0.9s var(--ease-out) both;
}

.jr-meter { height: 8px; margin: 0; }

@keyframes pgGrow {
  from { transform: scaleX(0); }
}

.pg-goal-reward {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--warning) 14%, transparent);
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ---------- Level block (results panel + sheet) ---------- */

.rw-level,
.pg-level {
  display: flex;
  align-items: center;
  gap: 14px;
}

.rw-level-badge {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--text);
  line-height: 1;
}

.rw-level-badge small {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.rw-level-badge b {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.rw-level-badge.is-levelup {
  animation: rwLevelUp 0.7s var(--ease-out);
  box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 6px var(--accent-soft);
}

@keyframes rwLevelUp {
  0% { transform: scale(1); }
  40% { transform: scale(1.22); }
  100% { transform: scale(1); }
}

.rw-level-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.rw-level-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.rw-level-row strong {
  font-size: 14px;
  font-weight: 800;
  color: var(--accent);
}

.rw-level-row span {
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.rw-xp {
  position: relative;
  height: 10px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  overflow: hidden;
}

.rw-xp > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, transparent), var(--accent));
  transition: width 0.9s var(--ease-out);
}

/* ---------- Results rewards panel ---------- */

.rw-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 28px;
  padding: 24px;
  margin-bottom: 20px;
  animation: resultsReveal 0.4s ease 0.15s both;
}

.rw-panel[hidden] { display: none; }

.rw-earned,
.rw-next {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.rw-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.rw-totals {
  display: flex;
  gap: 8px;
}

.rw-total {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--warning) 14%, transparent);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}

.rw-total b { font-weight: 800; }
.rw-total.is-gems { background: color-mix(in srgb, #8b9dff 16%, transparent); }
.rw-total[hidden] { display: none; }

.rw-ledger { gap: 2px; }

.rw-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  animation: rwRowIn 0.4s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 110ms + 200ms);
}

@keyframes rwRowIn {
  from { opacity: 0; transform: translateX(-8px); }
}

.rw-row-icon {
  width: 28px;
  text-align: center;
  font-size: 16px;
}

.rw-row-label {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  font-weight: 600;
}

.rw-row-label em {
  display: block;
  font-style: normal;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--accent);
}

.rw-row-val {
  font-size: 13.5px;
  font-weight: 800;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.rw-row.is-achievement {
  background: var(--accent-soft);
  animation-name: rwRowIn, rwUnlock;
  animation-duration: 0.4s, 0.6s;
  animation-delay: calc(var(--i, 0) * 110ms + 200ms), calc(var(--i, 0) * 110ms + 600ms);
}

@keyframes rwUnlock {
  50% { box-shadow: 0 0 0 4px var(--accent-soft); }
}

.rw-level {
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.rw-social {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rw-social:empty { display: none; }

.rw-social-win {
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--success) 14%, transparent);
  font-size: 13.5px;
  font-weight: 600;
  animation: rwLevelUp 0.6s var(--ease-out) 0.9s both;
}

.rw-social-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
}

.rw-social-icon { font-size: 20px; }

.rw-social-card p {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.45;
}

.rw-social-card p strong { color: var(--text); }

.rw-social-card > p,
.rw-social-body {
  flex: 1;
  min-width: 0;
}

@media (max-width: 760px) {
  .rw-panel { grid-template-columns: 1fr; padding: 20px; }
}

/* The journey's two-currency pill squeezes its title to one word a line
   on phones; the sheet it opens shows the reward anyway. */
@media (max-width: 480px) {
  .pg-goal.is-journey .pg-goal-reward { display: none; }
}

/* ---------- Getting-started journey ---------- */

@keyframes jrNudge {
  0%, 70%, 100% { transform: scale(1); }
  80% { transform: scale(1.06); }
}

.jr-panel.is-leaving,
#pg-journey.is-leaving {
  opacity: 0;
  transform: scale(0.98);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}

.jr-list { gap: 6px; }

.jr-step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
}

.jr-step.is-next {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  background: var(--accent-soft);
}

.jr-check {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 14px;
}

.jr-step.is-done .jr-check {
  background: var(--accent);
  border-color: transparent;
  color: var(--on-accent);
  font-weight: 800;
}

.jr-step-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.jr-step-text strong { font-size: 13.5px; font-weight: 700; }
.jr-step-text span { font-size: 12.5px; color: var(--muted); }

.jr-step.is-done .jr-step-text strong {
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--muted) 60%, transparent);
}

.jr-cta {
  flex-shrink: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--accent);
  text-decoration: none;
  white-space: nowrap;
}

.jr-cta:hover { text-decoration: underline; }

.jr-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--muted);
}

.jr-foot strong { color: var(--text); }
.jr-claim:disabled { opacity: 0.55; cursor: default; transform: none; }

@media (max-width: 560px) {
  .jr-step { flex-wrap: wrap; }
  .jr-cta { margin-left: 42px; }
}

/* ---------- Progress sheet ---------- */

.pg-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  justify-content: flex-end;
  background: color-mix(in srgb, #000 40%, transparent);
  animation: pgFade 0.2s ease both;
}

.pg-overlay[hidden] { display: none; }

@keyframes pgFade { from { opacity: 0; } }

/* A column: the close button owns a fixed row at the top and only
   .pg-body scrolls, so content can never slide underneath the X. */
.pg-sheet {
  position: relative;
  width: min(440px, 100%);
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  animation: pgSlide 0.32s var(--ease-out) both;
}

@keyframes pgSlide { from { transform: translateX(24px); opacity: 0; } }

body.pg-open { overflow: hidden; }

.pg-close {
  flex-shrink: 0;
  align-self: flex-end;
  margin: 12px 12px 0;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

.pg-close:hover { color: var(--text); background: var(--surface-3); }

.pg-body {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 4px 24px 24px;
}

/* In a scrolling flex column, children must not shrink to fit. */
.pg-body > * { flex-shrink: 0; }

.pg-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.pg-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.pg-head p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted);
}

.pg-head p strong { color: var(--text); }
.pg-head .primary-action { margin-top: 6px; }

.pg-section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.pg-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.pg-h {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.pg-level-hint {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.pg-wallet {
  display: flex;
  gap: 8px;
}

.pg-wallet span {
  flex: 1;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--muted);
}

.pg-wallet b {
  font-size: 15px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.pg-earn-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.pg-earn-head {
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}

.pg-earn-head span {
  margin-right: 4px;
  font-weight: 800;
  color: var(--text);
}

.pg-earn { gap: 2px; }

.pg-earn-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--border);
}

.pg-earn-row:last-child { border-bottom: none; }

.pg-earn-icon {
  width: 24px;
  text-align: center;
  font-size: 16px;
}

.pg-earn-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.pg-earn-text strong { font-size: 13px; font-weight: 700; }
.pg-earn-text span { font-size: 12px; color: var(--muted); line-height: 1.4; }

.pg-earn-val {
  flex-shrink: 0;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--text);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.pg-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 4px;
}

.pg-links a {
  padding: 8px 12px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 700;
  text-decoration: none;
}

.pg-links a:hover { border-color: var(--accent); }

.pg-loading {
  display: flex;
  justify-content: center;
  gap: 6px;
  padding: 80px 0;
}

.pg-loading span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--muted);
  animation: pgDot 1s ease-in-out infinite;
}

.pg-loading span:nth-child(2) { animation-delay: 0.15s; }
.pg-loading span:nth-child(3) { animation-delay: 0.3s; }

@keyframes pgDot { 50% { opacity: 0.25; transform: translateY(-3px); } }

.pg-error { margin: 60px 0; text-align: center; color: var(--muted); }

@media (max-width: 560px) {
  .pg-overlay { align-items: flex-end; }
  .pg-sheet {
    width: 100%;
    height: auto;
    max-height: 88vh;
    border-left: none;
    border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    animation-name: pgRise;
  }
  .pg-body { padding: 4px 16px 28px; }
}

@keyframes pgRise { from { transform: translateY(32px); opacity: 0; } }

/* =========================================================
   SIDE-SHEET FORMS — inputs inside a .pg-sheet (Race / Organizations
   create + join). Self-contained on purpose: the site's input styling is
   a container allowlist, so these set every property themselves.
========================================================= */

.sheet-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.sheet-label {
  margin-top: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.sheet-label small {
  margin-left: 4px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.sheet-input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 13px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  line-height: 1.4;
  outline: none;
  resize: vertical;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.sheet-input::placeholder { color: color-mix(in srgb, var(--muted) 75%, transparent); }

.sheet-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.sheet-input--code {
  padding: 14px;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-align: center;
  text-transform: uppercase;
}

.sheet-error {
  min-height: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--danger);
}

.sheet-error:empty { display: none; }

.rl-sheet-go {
  width: 100%;
  margin-top: 8px;
  padding: 13px 18px;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.rl-sheet-go:disabled { opacity: 0.6; transform: none; }

/* =========================================================
   RACE LOBBY — the race arena on the .sx-* kit (race_lobby.html).
========================================================= */

.rl-quick {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  padding: 28px;
  min-width: 0;
}

.rl-pulse {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px 5px 10px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--muted);
}

.rl-pulse b { color: var(--text); font-variant-numeric: tabular-nums; }

.sx-chip .rl-live-dot { margin-right: 6px; }

.rl-live-dot {
  position: relative;
  display: inline-block;
  width: 8px;
  height: 8px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--success);
}

.rl-live-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--success);
  animation: rlPing 1.8s var(--ease-out) infinite;
}

@keyframes rlPing {
  from { transform: scale(1); opacity: 0.55; }
  to { transform: scale(2.6); opacity: 0; }
}

.rl-title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

/* Three lanes with a speed trail running down each — the lobby's one
   piece of motion, a quiet preview of the race track. */
.rl-track {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.rl-lane {
  position: relative;
  height: 6px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 7%, transparent);
  overflow: hidden;
}

.rl-lane i {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent 55%, color-mix(in srgb, var(--text) 28%, transparent));
  animation: rlRun var(--d, 3s) cubic-bezier(0.45, 0.05, 0.4, 1) var(--o, 0s) infinite;
}

.rl-lane.is-you i {
  background: linear-gradient(90deg, transparent 45%, var(--accent));
}

@keyframes rlRun {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

.rl-go {
  padding: 13px 22px;
  font-size: 15px;
  font-weight: 700;
}

.rl-go.is-searching { animation: rlSearch 1.2s ease-in-out infinite; }

@keyframes rlSearch { 50% { opacity: 0.7; } }

.rl-record { gap: 16px; }

.rl-medal {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.rl-medal-icon {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  font-size: 18px;
}

.rl-medal-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.rl-medal-body .sx-meter { width: 100%; height: 7px; }

.rl-medal-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  color: var(--muted);
}

.rl-medal-row strong { color: var(--text); font-size: 13.5px; }

.rl-payouts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.rl-payout {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.rl-payout b,
.rl-place[data-place="1"],
.rl-place[data-place="2"],
.rl-place[data-place="3"] { font-weight: 800; }

.rl-payout[data-place="1"] b { color: color-mix(in srgb, #c99a1a 70%, var(--text)); }
.rl-payout[data-place="2"] b { color: color-mix(in srgb, #8a94a6 60%, var(--text)); }
.rl-payout[data-place="3"] b { color: color-mix(in srgb, #b8733a 70%, var(--text)); }

/* Open lobbies */

.rl-lobbies {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rl-lobby {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  animation: rlRowIn 0.35s var(--ease-out) both;
}

.rl-lobby.is-mine { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }

@keyframes rlRowIn { from { opacity: 0; transform: translateY(4px); } }

.rl-faces,
.og-faces {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.rl-faces .sx-avatar + .sx-avatar,
.og-faces .sx-avatar + .sx-avatar,
.og-faces .og-more { margin-left: -8px; }

.rl-faces .sx-avatar,
.og-faces .sx-avatar { box-shadow: 0 0 0 2px var(--surface-2); }

.og-faces .sx-avatar { box-shadow: 0 0 0 2px var(--surface); }

.rl-lobby-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.rl-lobby-body strong {
  font-size: 13.5px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rl-lobby-body span {
  font-size: 12px;
  color: var(--muted);
}

.rl-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 22px 16px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  text-align: center;
}

.rl-empty p {
  margin: 0;
  max-width: 380px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

.rl-empty-icon { font-size: 26px; }

/* Recent races */

.rl-recent {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rl-recent-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  color: var(--text);
  text-decoration: none;
  transition: background var(--dur-1) var(--ease);
}

.rl-recent-row:hover { background: var(--surface-2); }
.rl-recent-row:hover .rl-chev { color: var(--accent); transform: translateX(2px); }

.rl-place {
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
}

.rl-place[data-place="1"] { background: color-mix(in srgb, #d4a82a 18%, transparent); color: color-mix(in srgb, #b8860b 65%, var(--text)); border-color: transparent; }
.rl-place[data-place="2"] { background: color-mix(in srgb, #9aa3b2 20%, transparent); color: color-mix(in srgb, #6b7486 55%, var(--text)); border-color: transparent; }
.rl-place[data-place="3"] { background: color-mix(in srgb, #c07a3e 18%, transparent); color: color-mix(in srgb, #a0612b 65%, var(--text)); border-color: transparent; }

.rl-recent-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rl-recent-body strong {
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.rl-recent-body strong small { font-size: 11px; font-weight: 700; color: var(--muted); }

.rl-recent-body span,
.rl-recent-when {
  font-size: 12px;
  color: var(--muted);
}

.rl-recent-when { white-space: nowrap; }

.rl-chev {
  color: var(--muted);
  font-size: 12px;
  transition: transform var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

/* Private-race sheet steps */

.rl-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.rl-steps li {
  display: flex;
  gap: 12px;
}

.rl-steps li > span {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 800;
}

.rl-steps strong { font-size: 13.5px; }

.rl-steps p {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

@media (max-width: 720px) {
  .rl-quick { padding: 22px; }
  .rl-title { font-size: 26px; }
  .rl-go { width: 100%; justify-content: center; }
  .rl-recent-when { display: none; }
}

/* =========================================================
   ORGANIZATIONS HOME — teams on the .sx-* kit (organizations.html).
========================================================= */

.og-emblem {
  width: 84px;
  height: 84px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: 24px;
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
  font-size: 42px;
  line-height: 1;
}

.og-hero-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.og-hero .sx-hero-me { align-items: flex-start; }
.og-hero .sx-hero-me-body { gap: 12px; }
.og-hero .sx-kicker { display: inline-flex; align-items: center; gap: 8px; }

.og-challenge {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.og-challenge .sx-meter { width: 100%; height: 8px; margin-bottom: 4px; }

.og-challenge-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.og-challenge-row strong { color: var(--text); }

.og-perks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.og-perks li {
  display: flex;
  gap: 12px;
}

.og-perks li > span {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 16px;
}

.og-perks strong { font-size: 13.5px; }

.og-perks p {
  margin: 2px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* Team cards */

.og-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 14px;
}

.og-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
  animation: rlRowIn 0.4s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 60ms);
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.og-card:hover {
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.og-card-top {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.og-card-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  font-size: 22px;
  line-height: 1;
}

.og-card-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.og-card-title strong {
  font-size: 15px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.og-card-title span,
.og-card-meter span {
  font-size: 12px;
  color: var(--muted);
}

.og-card-top .sx-chip { gap: 6px; flex-shrink: 0; }

.og-card-desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.og-card-meter {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.og-card-meter .sx-meter { width: 100%; height: 6px; }

.og-card-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  margin: 0;
}

.og-card-stats div {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.og-card-stats dt {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.og-card-stats dd {
  margin: 0;
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.og-card-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.og-card-members {
  flex: 1;
  font-size: 12px;
  color: var(--muted);
}

.og-card-go {
  color: var(--muted);
  transition: transform var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.og-card:hover .og-card-go { color: var(--accent); transform: translateX(3px); }

.og-more {
  display: grid;
  place-items: center;
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  border-radius: var(--radius-full);
  background: var(--surface-3);
  box-shadow: 0 0 0 2px var(--surface-2);
  font-size: 11px;
  font-weight: 800;
  color: var(--muted);
}

.og-card .og-faces .sx-avatar { box-shadow: 0 0 0 2px var(--surface-2); }

.og-card--new {
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 170px;
  border-style: dashed;
  border-color: var(--border-strong);
  background: transparent;
  font: inherit;
  cursor: pointer;
}

.og-card--new strong { font-size: 14px; }
.og-card--new > span:last-child { font-size: 12px; color: var(--muted); }
.og-card--new .og-card-icon { color: var(--accent); font-size: 18px; }

/* Team league */

.og-league {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.og-league-row {
  display: grid;
  grid-template-columns: 26px 28px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border-radius: var(--radius-sm);
}

.og-league-row.is-mine {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

.og-league-icon {
  font-size: 18px;
  text-align: center;
}

.og-league-name {
  min-width: 0;
  display: flex;
  flex-direction: column;
  font-size: 13.5px;
  font-weight: 700;
}

.og-league-name a { color: var(--text); text-decoration: none; }
.og-league-name a:hover { color: var(--accent); }

.og-league-name,
.og-league-name a {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.og-league-name small {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
}

.og-league-score {
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.og-league-score small { font-size: 11px; }

.og-league-gap {
  text-align: center;
  color: var(--muted);
  line-height: 1;
}

.og-league-note { margin: 0; font-size: 12px; }

/* Create sheet: live preview + icon picker */

.og-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
}

.og-preview div {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.og-preview strong {
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.og-preview div span { font-size: 12px; color: var(--muted); }

.og-icons {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 6px;
}

.og-icon-opt { position: relative; }

.og-icon-opt input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.og-icon-opt span {
  display: grid;
  place-items: center;
  height: 44px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 20px;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.og-icon-opt span:hover { transform: translateY(-1px); }

.og-icon-opt input:checked + span {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.og-icon-opt input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Phones: the header's two actions share one row instead of stacking. */
@media (max-width: 560px) {
  .rl .sx-head .sx-hero-actions,
  .og .sx-head .sx-hero-actions { width: 100%; flex-wrap: nowrap; }
  .rl .sx-head .sx-hero-actions .btn,
  .og .sx-head .sx-hero-actions .btn { flex: 1; padding-inline: 10px; }
  .og-hero .sx-hero-me { flex-direction: column; }
}

@media (max-width: 720px) {
  .og-emblem { width: 64px; height: 64px; font-size: 32px; border-radius: 18px; }
  .og-hero-name { font-size: 22px; }
  .og-cards { grid-template-columns: 1fr; }
}

/* =========================================================
   TYPING CONSOLE — the word box has a header bar holding the test
   settings (centred; they fade out while a test runs) and, under it, a
   stats row lined up with the passage: timer, WPM, accuracy, restart.
========================================================= */

.tc-bar {
  position: relative;
  display: flex;
  justify-content: center;
  margin: 0 -34px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface-2) 45%, var(--surface));

  /* The box sets the typing font, size prefs and nowrap for the passage —
     the bar is UI chrome and resets all of it. */
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: normal;
  white-space: normal;
  cursor: default;
}

/* Mid-test the settings fade out (the .test-toolbar rule) — the strip
   fades with them so no empty grey band is left; hover brings both back.
   Height is kept, so the passage never jumps. */
.tc-bar { transition: background var(--dur-3) var(--ease), border-color var(--dur-3) var(--ease); }

body.test-active .tc-bar:not(:hover):not(:focus-within) {
  background: transparent;
  border-bottom-color: transparent;
}

.tc-bar .tc-controls {
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px 6px;
  margin: 0;
}

/* Flat groups split by a hairline — text buttons, no nested tracks. */
.tc-bar .mode-picker,
.tc-bar .duration-picker {
  gap: 2px;
  padding: 0;
  border: 0;
  background: transparent;
}

.tc-bar .toolbar-divider {
  width: 1px;
  height: 20px;
  margin-inline: 6px;
  background: var(--border);
}

.tc-bar .mode-btn,
.tc-bar .duration-btn {
  gap: 6px;
  padding: 7px 12px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1;
  color: var(--muted);
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

.tc-bar .mode-btn:hover,
.tc-bar .duration-btn:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* Active setting: a solid accent pill (ink on the default theme). */
.tc-bar .mode-btn.active,
.tc-bar .duration-btn.active {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: none;
}

.tc-bar .mode-btn .bi,
.tc-bar .duration-btn .bi { font-size: 13px; }

/* Stats row — same column as the passage so the timer sits right above
   the first letter. */
.tc-head {
  max-width: 1000px;
  margin: 28px auto 12px;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.2;
  letter-spacing: normal;
  white-space: normal;
}

.tc-head .tc-stats {
  gap: 24px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.tc-head .timer-chip {
  min-width: 2ch;
  text-align: left;
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
}

.tc-head .live-stat {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}

.tc-head .live-stat span {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}

/* Restart (and zen's Finish before it) sit at the far right. */
.tc-head .restart-btn { width: 38px; height: 38px; margin-left: auto; font-size: 16px; }
.tc-head .finish-btn { margin-left: auto; }
.tc-head .finish-btn + .restart-btn { margin-left: 0; }

/* Code mode scrolls the box vertically — keep the bar pinned on top. */
#word-container:has(.code-mode) .tc-bar {
  position: sticky;
  top: 0;
  z-index: 3;
}

/* Must follow the base .tc-bar rule (same specificity) to win. */
@media (max-width: 768px) {
  .tc-bar { margin-inline: -22px; }
}

/* Phones: the settings are one swipeable row. */
@media (max-width: 720px) {
  .tc-bar { justify-content: flex-start; padding: 10px 12px; }
  .tc-bar .tc-controls {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .tc-bar .tc-controls::-webkit-scrollbar { display: none; }
  .tc-bar .tc-controls > * { flex-shrink: 0; }
  .tc-head { margin-top: 20px; }
  .tc-head .tc-stats { gap: 18px; }
  .tc-head .timer-chip { font-size: 28px; }
  .tc-head .live-stat span { font-size: 19px; }
}

/* ---------- Passage block (home test, every source but code) ----------
   Three lines in a centred column instead of one line sliding sideways:
   you can read a full line ahead and the text only moves once per line.
   Scoped to .is-block — the race room reuses #word-container/#word-track
   with its own single-line scroll. .tc-words' height is set in JS
   (sizeWordBlock) from the measured line step; 3 × line-height is the
   fallback before that runs. */
/* Room between the site header and the console (it sat ~6px under it). */
#word-container.is-block { margin-top: 32px; padding-bottom: 44px; }

#word-container.is-block .tc-words {
  position: relative;
  max-width: 1000px;
  height: calc(3 * 3.25rem + 12px);
  margin: 0 auto;
  overflow: hidden;
}

#word-container.is-block #word-track {
  display: block;
  white-space: normal;
  transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  #word-container.is-block #word-track { transition: none; }
}

/* Phones: the desktop sizes fit only ~3 words a line — step the passage
   down relative to the chosen size pref (sizeWordBlock re-measures). */
@media (max-width: 600px) {
  #word-container.is-block .tc-words { font-size: 0.8em; line-height: 1.65; }
}

/* =========================================================
   HOME — below the test (home.html, _for_you.html): a "Today" panel,
   then two panels side by side, leaderboard | live activity. Every panel
   matches the typing console (the .card look). One rhythm: 24px between
   panels, 12px heading to content, 48px rows in both columns. The accent
   (ink on the default theme) marks what is active and your own row.
========================================================= */

.home-today,
.hc-col {
  padding: 20px 24px 24px;
  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.home-today { margin-top: 24px; }

/* Both panels stretch to the taller one, so their bottoms line up. */
.home-community {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: 24px;
  align-items: stretch;
  margin-block: 24px 64px;
}

.hc-col {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* Section heading: title, optional controls, a hairline under it. */
.hc-h {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

/* Title + its one-line description, as one unit above the hairline. */
.hc-title {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hc-h h2 {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}

.hc-h .rl-live-dot { width: 7px; height: 7px; }

.hc-link {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
}

.hc-link:hover { color: var(--text); }

.hc-sub {
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 12px;
  color: var(--muted);
}

/* Rows start 8px under the hairline in both columns, so they line up. */
.hc-col > .hl-rows,
.hc-col > .hl-feed { margin-top: 8px; }

/* Teams' Week / All-time switch: its own line under the heading. */
.home-community .hc-period { align-self: flex-start; margin-top: 12px; }

/* Scope tabs: quiet text, the active one a solid accent pill. One line,
   scrolling sideways when there is no room. */
.hc-scope {
  margin-left: auto;
  min-width: 0;
}

.home-community .leaderboard-scope-toggle {
  margin: 0;
  gap: 2px;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

.home-community .leaderboard-scope-toggle::-webkit-scrollbar { display: none; }
.home-community .hc-period[hidden] { display: none; }

.home-community .lb-scope-btn {
  flex-shrink: 0;
  padding: 6px 11px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-full);
  box-shadow: none;
}

.home-community .lb-scope-btn:hover { color: var(--text); background: var(--surface-2); }

.home-community .lb-scope-btn.active {
  color: var(--on-accent);
  background: var(--accent);
  box-shadow: none;
}

.home-community .hc-period .lb-scope-btn { padding: 4px 9px; font-size: 12px; }

/* ---------- Rows (both columns) ---------- */

.hl-rows,
.hl-feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.hl-row,
.hl-act {
  display: grid;
  align-items: center;
  gap: 12px;
  height: 48px;
  padding: 0 8px;
  border-radius: var(--radius-md);
}

.hl-row {
  grid-template-columns: 24px 28px minmax(0, 1fr) auto;
  animation: rlRowIn 0.35s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 30ms);
}

.hl-act { grid-template-columns: 28px minmax(0, 1fr) auto; }

.hl-row:hover,
.hl-act:hover { background: var(--surface-2); }

.hc-rank {
  text-align: right;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.hl-row .sx-avatar { --size: 28px; }

.hl-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}

.hl-name a,
.hl-name > span:first-child { color: inherit; text-decoration: none; }
.hl-name a:hover { text-decoration: underline; text-underline-offset: 3px; }
.hl-name .theme-badge { margin-left: 6px; vertical-align: 1px; }

.hl-name small {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
}

.hc-you {
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  vertical-align: 1px;
}

.hl-row.is-me { background: var(--accent-soft); }
.hl-row.is-me .hc-rank { color: var(--accent); }

.hl-val {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.hl-val small {
  margin-left: 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

/* Top 8 by default; your own row stays visible below a dashed break. */
.hl-rows.is-collapsed .hl-row:nth-child(n+9):not(.is-me) { display: none; }

.hl-rows.is-collapsed .hl-row.is-far {
  position: relative;
  margin-top: 13px;
}

.hl-rows.is-collapsed .hl-row.is-far::before {
  content: "";
  position: absolute;
  inset: -7px 8px auto;
  border-top: 1px dashed var(--border);
}

.hc-more {
  align-self: flex-start;
  margin-top: 8px;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.hc-more:hover { color: var(--text); background: var(--surface-2); }

/* Activity: monochrome icon, one line of text, time on the right. */
.hl-act.is-new { animation: hlActIn 0.5s var(--ease-out) both; }

@keyframes hlActIn {
  from { opacity: 0; transform: translateY(-4px); }
}

.hl-act-icon {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 13px;
}

.hl-act-text {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 14px;
  color: var(--muted);
}

.hl-act-text a,
.hl-act-text strong { color: var(--text); font-weight: 600; text-decoration: none; }
.hl-act-text a:hover { text-decoration: underline; text-underline-offset: 3px; }

.hl-act-time {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.hl-empty {
  padding: 24px 8px;
  font-size: 14px;
  color: var(--muted);
}

/* ---------- Today: one row, hairline-split, monochrome icons ---------- */

.home-foryou {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  padding-top: 16px;
}

.fy-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-width: 0;
  padding: 4px 24px 4px 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.fy-card + .fy-card {
  padding-left: 24px;
  border-left: 1px solid var(--border);
}

.fy-card:hover .fy-title { text-decoration: underline; text-underline-offset: 3px; }
.fy-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
.fy-card.is-busy { opacity: 0.6; cursor: progress; }

.fy-icon {
  flex-shrink: 0;
  margin-top: 2px;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
}

.fy-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.fy-kicker,
.fy-detail {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The title may take two lines rather than lose its point to an ellipsis. */
.fy-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.fy-kicker {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.fy-kicker .rl-live-dot { width: 6px; height: 6px; }
.fy-done { color: var(--text); font-weight: 700; }

.fy-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}

.fy-detail {
  font-size: 12px;
  color: var(--muted);
}

/* ---------- Narrow screens ---------- */

@media (max-width: 860px) {
  .home-community { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  .home-foryou {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: 16px;
  }

  .fy-card { padding-right: 12px; }
  .fy-card + .fy-card { padding-left: 12px; }
  .fy-card:nth-child(2n+1) { padding-left: 0; border-left: 0; }

  /* Scope tabs drop under the heading as their own swipe row. */
  .hc-col:first-child .hc-h { flex-wrap: wrap; row-gap: 8px; }
  .hc-scope { order: 3; flex-basis: 100%; margin-left: -8px; }
  .hl-name small { display: none; }
}

/* =========================================================
   SITE SYSTEM — one look for every page's sections (Home, Social, Race,
   Shop, Awards, Dashboard, Profile, Analytics, Organizations, Settings):
   1. Every section is a panel that matches the typing console: surface,
      hairline border, large radius, soft shadow, 24px inside, 24px
      between. Nothing sits loose on the page background.
   2. Inside a panel: a 16px heading + one-line description, a hairline,
      then the content. Section kickers are dropped.
   3. Neutral chips and empty states; colour is left to the accent,
      semantic states and rarity. Tokens only, so every theme works.
========================================================= */

/* ---------- 1. Heroes: plain surface, no wash ---------- */

.sx-hero { background: var(--surface); }
.sx-hero-rival { background: none; }

/* ---------- 2. Panels ---------- */

.card.sx-panel,
.card.loadout-card,
.card.badges-card,
.card.recent-card,
.card.chart-card,
.card.performance-card,
.card.analytics-detail-card,
.card.org-panel,
.card.settings-card {
  padding: 24px;
}

/* Section headings, whichever of the kit's head patterns a page uses. */
.sx-panel > .sx-panel-head,
.loadout-card > .section-head,
.badges-card > .badge-showcase-head,
.recent-card > .section-header,
.chart-card > .section-header,
.chart-card > .section-head,
.performance-card > .performance-header,
.performance-card > .section-head,
.analytics-detail-card > .section-head,
.org-panel:not(.org-danger) > .section-head,
.settings-card > .section-head {
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.sx-panel > .sx-panel-head h2,
.loadout-card > .section-head h3,
.badges-card > .badge-showcase-head h3,
.recent-card > .section-header h3,
.chart-card > :is(.section-header, .section-head) :is(h2, h3),
.performance-card > :is(.performance-header, .section-head) :is(h2, h3),
.analytics-detail-card > .section-head :is(h2, h3),
.org-panel > .section-head :is(h2, h3),
.settings-card > .section-head :is(h2, h3) {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
}

.sx-panel > .sx-panel-head p,
.loadout-card > .section-head p,
.analytics-detail-card > .section-head p,
.performance-card > .section-head p,
.org-panel > .section-head p,
.settings-card > .section-head p {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* A kicker above a section title repeats the title. */
.badges-card > .badge-showcase-head .section-label,
.recent-card > .section-header .eyebrow,
.chart-card > .section-header .eyebrow,
.performance-card > .performance-header .eyebrow { display: none; }

/* Heading → content: 16px wherever the section isn't a flex-gap column. */
.loadout-card > .section-head,
.badges-card > .badge-showcase-head,
.recent-card > .section-header,
.chart-card > :is(.section-header, .section-head),
.performance-card > :is(.performance-header, .section-head),
.analytics-detail-card > .section-head { margin-bottom: 16px; }

/* Settings / org dashboard section icons: a quiet neutral glyph. */
.section-icon-chip {
  width: 32px;
  height: 32px;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 15px;
}

/* ---------- 3. Rhythm: 24px between panels ---------- */

.sx-grid { gap: 24px; }

.sx-main,
.sx-rail,
.dashboard-shell,
.profile-container,
.settings-panel,
.org-tab-panel { gap: 24px; }

.card.daily-challenge-bar { margin-bottom: 0; }

.dc-icon {
  width: auto;
  height: auto;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 18px;
}

/* ---------- 4. Stat tiles: one panel, tiles split by hairlines ---------- */

.stats-grid {
  gap: 0;
  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.stats-grid > .card.stat-card {
  padding: 20px 24px;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.stats-grid > .stat-card + .stat-card { border-left: 1px solid var(--border); }

.stats-grid--3 > .stat-card:nth-child(3n+1) { border-left: 0; }
.stats-grid--3 > .stat-card:nth-child(n+4) { border-top: 1px solid var(--border); }

.stat-icon,
.stat-card.best .stat-icon {
  width: auto;
  height: auto;
  background: none;
  color: var(--muted);
}

@media (max-width: 950px) {
  .stats-grid > .stat-card:nth-child(odd),
  .stats-grid--3 > .stat-card:nth-child(odd) { border-left: 0; }
  .stats-grid > .stat-card:nth-child(even),
  .stats-grid--3 > .stat-card:nth-child(even) { border-left: 1px solid var(--border); }
  .stats-grid > .stat-card:nth-child(n+3),
  .stats-grid--3 > .stat-card:nth-child(n+3) { border-top: 1px solid var(--border); }
}

@media (max-width: 640px) {
  .stats-grid > .card.stat-card { padding: 16px; }
}

/* ---------- 5. Lists inside panels: hairline rows ---------- */

.recent-card .recent-feed { gap: 0; }

.recent-card .activity-row {
  padding: 12px 0;
  border-radius: 0;
  background: none;
  border-bottom: 1px solid var(--border);
}

.recent-card .activity-row:last-child { border-bottom: 0; }

.accuracy-pill {
  padding: 0;
  background: none;
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.badge-count-pill {
  padding: 0;
  background: none;
  border: 0;
}

/* ---------- 6. Neutral chips (colour is for meaning, not decoration) ---------- */

.sx-chip.is-race,
.sx-chip.is-gold {
  background: var(--surface-3);
  color: var(--text);
}

.rl-place[data-place="1"],
.rl-place[data-place="2"],
.rl-place[data-place="3"] {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}

.rl-payout[data-place] b { color: var(--text); }

/* ---------- 7. Empty states: one anchored component ----------
   Without a card around them, an empty list's centred sentence floated
   in open page space. Every "nothing here yet" is now the same soft
   filled block inside its section: left-aligned, the message and its
   action on one line (the action pushed right), a small muted glyph. */

.sx-quiet,
.sx-empty,
.rl-empty,
.hl-empty,
.empty-state-mini,
.analytics-empty,
.loadout-empty-note {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 8px 16px;
  margin: 0;
  padding: 14px 16px;
  border: 0;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--text) 4%, transparent);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
}

:is(.sx-quiet, .sx-empty, .rl-empty, .hl-empty, .empty-state-mini) p {
  flex: 1 1 220px;
  margin: 0;
  max-width: none;
}

:is(.sx-quiet, .sx-empty, .rl-empty, .hl-empty, .empty-state-mini) > :is(.btn, a.btn, button) {
  flex-shrink: 0;
  margin-left: auto;
}

.rl-empty-icon,
.empty-state-icon,
.empty-state-mini .empty-state-icon {
  flex-shrink: 0;
  font-size: 16px;
  line-height: 1;
  opacity: 1;
  color: var(--muted);
}

.empty-icon {
  width: auto;
  height: auto;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 16px;
}

/* The one "big" empty state (Social's friends board) keeps a title line,
   left-aligned in the same block; its emoji art goes. */
.sx-empty {
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 20px;
}

.sx-empty .sx-empty-art { display: none; }
.sx-empty h3 { margin: 0; font-size: 15px; font-weight: 700; color: var(--text); }
.sx-empty > :is(.btn, a.btn, button) { margin: 6px 0 0; }

/* Reward pills: neutral; the coin/gem emoji carry the colour. */
.pg-goal-reward { background: var(--surface-3); }

/* Mini stats in a section header (dashboard, profile, analytics): plain
   figures split by hairlines instead of three more boxes. */
.performance-stats { gap: 0; }

.performance-stats .mini-stat {
  min-width: 0;
  padding: 2px 20px;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.performance-stats .mini-stat + .mini-stat { border-left: 1px solid var(--border); }
.performance-stats .mini-stat:last-child { padding-right: 0; }

/* Narrow screens: the kit grid becomes one flex column whose panels are
   its direct children (.sx-main/.sx-rail go display:contents). Stretch
   them to the column; align-items:start sized each open section to its
   content, so sections ended at different widths. */
@media (max-width: 960px) {
  .sx-grid { align-items: stretch; }
}

/* Org dashboard scorecard: plain surface, no gradient wash. */
.org-scorecard { background: var(--surface); }

/* Stacked empty states: the big friends-board one, and any in a narrow
   column (the kit rail, phones), where message + button wouldn't fit on
   one line. The row layout's flex-basis would otherwise become a height. */
.sx-empty p { flex: none; }

.sx-rail :is(.sx-quiet, .rl-empty, .empty-state-mini) {
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.sx-rail :is(.sx-quiet, .rl-empty, .empty-state-mini) p { flex: none; }
.sx-rail :is(.sx-quiet, .rl-empty, .empty-state-mini) > :is(.btn, a.btn, button) { margin-left: 0; }

@media (max-width: 560px) {
  :is(.sx-quiet, .sx-empty, .rl-empty, .hl-empty, .empty-state-mini) {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  :is(.sx-quiet, .sx-empty, .rl-empty, .hl-empty, .empty-state-mini) p { flex: none; }
  :is(.sx-quiet, .sx-empty, .rl-empty, .hl-empty, .empty-state-mini) > :is(.btn, a.btn, button) { margin-left: 0; }
}

/* ---------- Avatars: rounded squares ----------
   Matches the logo mark and the group-chat / org icons. The big ringed
   avatar in the kit heroes (.sx-avatar--xl) keeps its circle, since the
   progress ring is drawn around it. */
.sx-avatar:not(.sx-avatar--xl) { border-radius: 28%; }

/* Home headings in panels: the title keeps its natural width; when the
   scope tabs don't fit beside it they drop to their own line instead of
   being cut off. */
.hc-h { flex-wrap: wrap; row-gap: 10px; }
.hc-title { flex-shrink: 0; }
.hc-sub { overflow: visible; }

/* Social's incoming-requests panel: keep the stronger edge, lose the wash. */
.card.sx-panel.sx-requests.has-incoming { background: var(--surface); }

/* =========================================================
   TRY-ON + UNLOCK REVEAL (engine in base.html)
   The shop's "Try it on" button, the floating try-on bar that stays
   up while a theme is being worn, and the card that celebrates every
   purchase. The reveal card carries data-theme-scope, so --surface /
   --accent inside it are the purchased theme's own colours; --rc is
   the rarity colour (same hues as the .rarity-* chips) and only ever
   drives the burst and glow, never text.
========================================================= */

.tryon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  margin-top: 6px;
  padding: 8px 12px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.tryon-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tryon-bar {
  position: fixed;
  left: 50%;
  bottom: 18px;
  z-index: 600;
  display: flex;
  align-items: center;
  gap: 12px;
  width: max-content;
  max-width: calc(100% - 32px);
  padding: 8px 8px 8px 14px;
  transform: translateX(-50%);
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  animation: tryonIn 0.35s var(--ease) both;
}

.tryon-icon { font-size: 20px; line-height: 1; }

.tryon-label {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin-right: 4px;
  line-height: 1.2;
}

.tryon-kicker {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.tryon-label strong {
  overflow: hidden;
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.tryon-bar .btn { padding: 9px 14px; border-radius: var(--radius-full); }

@keyframes tryonIn {
  from { opacity: 0; transform: translate(-50%, 16px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* Keep the bar from covering the end of the page. */
body.has-tryon { padding-bottom: 84px; }

@media (max-width: 768px) {
  .tryon-bar {
    bottom: calc(54px + 10px + env(safe-area-inset-bottom, 0px));
    gap: 8px;
  }
  .tryon-back { padding-left: 10px; padding-right: 10px; }
  body.has-tryon { padding-bottom: calc(58px + 84px + env(safe-area-inset-bottom, 0px)); }
}

@media (max-width: 420px) {
  .tryon-kicker { display: none; }
  .tryon-icon { display: none; }
}

/* ---- Unlock reveal ---- */

.unlock-reveal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(6, 8, 12, 0.6);
  backdrop-filter: blur(6px);
  animation: unlockFade 0.25s ease both;
}

.unlock-card {
  --rc: #9aa3b2;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: min(360px, 100%);
  padding: 34px 26px 26px;
  border-radius: 22px;
  border: 1px solid color-mix(in srgb, var(--rc) 45%, var(--border));
  background: var(--surface);
  color: var(--text);
  text-align: center;
  box-shadow: 0 30px 80px -20px color-mix(in srgb, var(--rc) 55%, transparent), var(--shadow-lg);
  isolation: isolate;
  overflow: hidden;
  animation: unlockFlip 0.7s cubic-bezier(0.2, 1.25, 0.35, 1) both;
}

.unlock-rare      { --rc: #58b6f0; }
.unlock-epic      { --rc: #b98cff; }
.unlock-legendary { --rc: #e8b23c; }
.unlock-mythical  { --rc: #f472b6; }
.unlock-seasonal  { --rc: #4ade80; }
.unlock-prestige,
.unlock-founder   { --rc: #e8b23c; }

/* Rotating light rays in the rarity colour behind everything in the card;
   brighter for the top tiers. */
.unlock-burst {
  position: absolute;
  top: 18%;
  left: 50%;
  z-index: -1;
  width: 560px;
  height: 560px;
  margin: -280px 0 0 -280px;
  border-radius: 50%;
  background:
    radial-gradient(circle, color-mix(in srgb, var(--rc) 38%, transparent) 0%, transparent 46%),
    repeating-conic-gradient(color-mix(in srgb, var(--rc) 22%, transparent) 0deg 9deg, transparent 9deg 24deg);
  -webkit-mask-image: radial-gradient(circle, #000 18%, transparent 62%);
  mask-image: radial-gradient(circle, #000 18%, transparent 62%);
  opacity: 0.7;
  animation: unlockRays 14s linear infinite, unlockFade 0.6s 0.15s ease both;
}

.unlock-legendary .unlock-burst,
.unlock-mythical .unlock-burst,
.unlock-prestige .unlock-burst,
.unlock-founder .unlock-burst,
.unlock-seasonal .unlock-burst { opacity: 1; }

.unlock-kicker {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

.unlock-icon {
  display: grid;
  place-items: center;
  width: 92px;
  height: 92px;
  margin: 4px 0 2px;
  border-radius: 26px;
  border: 1px solid color-mix(in srgb, var(--rc) 40%, var(--border));
  background: var(--accent-soft);
  font-size: 48px;
  line-height: 1;
  animation: unlockPop 0.6s 0.25s cubic-bezier(0.2, 1.6, 0.4, 1) both;
}

.unlock-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.unlock-rarity { text-transform: capitalize; }

.unlock-note {
  margin: 2px 0 8px;
  font-size: 14px;
  color: var(--muted);
}

.unlock-go { min-width: 160px; border-radius: var(--radius-full); }

@keyframes unlockFade { from { opacity: 0; } }

@keyframes unlockFlip {
  from { opacity: 0; transform: perspective(900px) rotateY(-75deg) scale(0.85); }
  to   { opacity: 1; transform: perspective(900px) rotateY(0) scale(1); }
}

@keyframes unlockPop {
  from { opacity: 0; transform: scale(0.4) rotate(-12deg); }
  to   { opacity: 1; transform: scale(1) rotate(0); }
}

@keyframes unlockRays { to { transform: rotate(360deg); } }

/* =========================================================
   SHOP PROGRESSION — Today's picks, the pinned goal, matched sets,
   collection milestones, weekly drops and the season pass (main/drops.py,
   main/sets.py, main/season.py). Built on the .sx-* kit; every state
   class here is decided server-side.
========================================================= */

.sx-head-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---------- Today's picks ---------- */

.shop-picks .sx-panel-head { align-items: center; }

.shop-countdown {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 12.5px;
  white-space: nowrap;
}

.shop-countdown b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.shop-picks-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  grid-auto-flow: column;
  gap: 12px;
}

.shop-pick {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 18px 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.shop-pick:hover { border-color: var(--accent); transform: translateY(-2px); }

.shop-pick.is-deal {
  border-color: color-mix(in srgb, var(--warning) 60%, transparent);
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--warning) 14%, transparent), transparent 70%),
    var(--surface-2);
}

.shop-pick.is-owned { opacity: 0.72; }

.shop-pick-flag {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  padding: 2px 9px;
  border-radius: var(--radius-full);
  background: #f5c451;
  color: #241700;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

.shop-pick-flag.is-new { background: var(--accent); color: var(--on-accent); }

.shop-pick-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 4px;
  border-radius: 14px;
  background: radial-gradient(120% 120% at 50% 0%, var(--accent-soft), transparent 70%), var(--surface);
  font-size: 26px;
}

.shop-pick-type {
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.shop-pick-name {
  max-width: 100%;
  overflow: hidden;
  font-size: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shop-pick-price {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.shop-pick-price s,
.shop-was,
.shop-set-price s {
  color: var(--muted);
  font-weight: 500;
}

.shop-drop-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 13px;
}

.shop-drop-new {
  padding: 2px 9px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
}

.shop-drop-next { margin-left: auto; }
.shop-drop-next strong { color: var(--text); }

.shop-link-btn {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

.shop-link-btn:hover { text-decoration: underline; }

.shop-deal-chip {
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: #f5c451;
  color: #241700;
  font-size: 10.5px;
  font-weight: 800;
  text-transform: uppercase;
}

/* ---------- Season promo strip ---------- */

.shop-season {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 20px;
  color: var(--text);
  text-decoration: none;
  background:
    radial-gradient(90% 140% at 0% 50%, var(--accent-soft), transparent 70%),
    var(--surface);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
}

.shop-season:hover { border-color: var(--accent); }

.shop-season-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 14px;
  background: var(--accent-soft);
  font-size: 26px;
}

.shop-season-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.shop-season-body strong { font-size: 16px; }
.shop-season-note { color: var(--muted); font-size: 12.5px; }
.shop-season-cta { flex: none; }

/* ---------- Catalog toolbar ---------- */

.shop-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  margin: -4px 0 16px;
}

.shop-toolbar[hidden] { display: none; }

.shop-sort {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 13px;
}

.shop-sort select {
  padding: 7px 30px 7px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface-2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 12px center;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  appearance: none;
  cursor: pointer;
}

.shop-sort select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.shop-hide-owned {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}

.shop-hide-owned input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); cursor: pointer; }

.shop-toolbar-empty { color: var(--muted); font-size: 13px; }

.shop-tab.is-new-tab { font-weight: 700; }

/* ---------- Card additions ---------- */

.shop-flag.is-deal { background: #f5c451; color: #241700; }
.shop-flag.is-new { background: var(--accent); color: var(--on-accent); }

.shop-state.is-pinned {
  border-color: transparent;
  background: var(--accent-soft);
  color: var(--accent);
}

.shop-card.is-deal {
  border: 1px solid color-mix(in srgb, var(--warning) 50%, transparent);
}

.shop-card.is-pinned {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent), var(--shadow-sm);
}

.shop-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.shop-card-actions:empty { display: none; }

.shop-card-actions .tryon-btn {
  flex: 1 1 calc(50% - 3px);
  width: auto;
  margin-top: 0;
}

.pin-btn.is-pinned {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Live previews inside the card tile — the same demo rules the customize
   modal's swatches use, so a cursor blinks, a background drifts and a
   trail streaks right there in the shop. */
.shop-preview { position: relative; overflow: hidden; }

.shop-preview-icon { position: relative; z-index: 1; }

.shop-demo-layer.customize-swatch {
  position: absolute;
  inset: 0;
  height: auto;
  margin: 0;
  border-radius: 0;
  background: transparent;
}

.shop-demo-trail.customize-swatch {
  position: absolute;
  left: 14%;
  right: 14%;
  bottom: 16px;
  height: 18px;
  margin: 0;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 7%, transparent);
}

.shop-demo-trail + .shop-preview-icon { transform: translateY(-10px); }

.shop-demo-caret {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-family: var(--test-font);
  font-size: 26px;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.shop-demo-caret .shop-demo-text { color: var(--text); }

/* ---------- Matched sets ---------- */

.shop-set {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  background:
    radial-gradient(100% 70% at 100% 0%, var(--accent-soft), transparent 70%),
    var(--surface);
  color: var(--text);
}

.shop-set.is-premium { border-color: color-mix(in srgb, #5b6ef0 55%, transparent); }
.shop-set.is-complete { opacity: 0.8; }

.shop-set-head {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 4px 12px;
  padding-right: 54px;
}

.shop-set-head > div { display: contents; }
.shop-set-head h3 { grid-column: 2; }
.shop-set-head p { grid-column: 1 / -1; margin-top: 6px !important; }

.shop-set-head h3 { margin: 0 0 2px; font-size: 17px; }
.shop-set-head p { margin: 0; color: var(--muted); font-size: 13px; }

.shop-set-icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 12px;
  background: var(--accent-soft);
  font-size: 24px;
}

.shop-set-flag {
  position: absolute;
  top: 18px;
  right: 18px;
  padding: 3px 9px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10.5px;
  font-weight: 800;
  white-space: nowrap;
}

.shop-set.is-premium .shop-set-flag { background: #5b6ef0; color: #fff; }

.shop-set-pieces {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.shop-set-pieces li {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 13.5px;
}

.shop-set-pieces li.is-owned { opacity: 0.6; }
.shop-set-piece-icon { font-size: 18px; text-align: center; }
.shop-set-pieces em { color: var(--muted); font-size: 11.5px; font-style: normal; }

.shop-set-foot {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: auto;
}

.shop-set-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 15px; }
.shop-set-price strong { font-size: 18px; }
.shop-set-price em { width: 100%; color: var(--success); font-size: 12.5px; font-style: normal; font-weight: 600; }

.shop-set .buy-btn,
.shop-set .owned-btn {
  padding: 10px 12px;
  border-radius: var(--radius-full);
  font-size: 13px;
}

/* ---------- Collection milestones ---------- */

.shop-coll-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px;
}

.shop-coll {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.shop-coll.has-ready {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.shop-coll-top { display: flex; align-items: center; gap: 10px; }
.shop-coll-top strong { flex: 1; font-size: 15px; }
.shop-coll-icon { font-size: 20px; width: 24px; text-align: center; }

.shop-coll-count { font-size: 18px; font-weight: 800; font-variant-numeric: tabular-nums; }
.shop-coll-count small { color: var(--muted); font-size: 13px; font-weight: 600; }

.shop-coll-tiers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.shop-coll-tiers li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 6px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  text-align: center;
}

.shop-coll-tiers b { font-size: 13px; }
.shop-coll-tiers span { color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }

.shop-coll-tiers li.is-ready {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent-soft);
}

.shop-coll-tiers li.is-ready b { color: var(--accent); }

.shop-coll-tiers li.is-claimed {
  border-style: solid;
  border-color: transparent;
  background: color-mix(in srgb, var(--success) 14%, transparent);
}

.shop-coll-tiers li.is-claimed b { color: var(--success); }

.shop-coll-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 34px;
}

.shop-coll-next { color: var(--muted); font-size: 12.5px; }
.shop-coll-done { color: var(--success); font-size: 13px; font-weight: 700; }

/* ---------- Nav: new-drop dot + unlock reveal milestone ---------- */

.shop-nav-link.has-drop { position: relative; }

.shop-nav-link.has-drop::after {
  content: "";
  position: absolute;
  top: 6px;
  right: 2px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--surface);
}

.bottom-tab.shop-nav-link.has-drop::after { top: 4px; right: calc(50% - 16px); }

.unlock-milestone {
  display: block;
  margin: 2px 0 14px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
}

/* ---------- Results screen: pinned goal + season ---------- */

.rw-extra { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.rw-extra:empty { display: none; }
.pg-section.rw-extra { margin-top: 0; }

.rw-pinned,
.rw-season {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur-1) var(--ease);
}

.rw-pinned:hover,
.rw-season:hover { border-color: var(--accent); }

.rw-pinned.is-ready { border-color: color-mix(in srgb, var(--success) 60%, transparent); }
.rw-season.is-tierup { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

.rw-pinned-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--accent-soft);
  font-size: 22px;
}

.rw-pinned-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.rw-pinned-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.rw-pinned-nums {
  color: var(--muted);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rw-pinned-nums b { color: var(--text); }
.rw-pinned-body > strong { font-size: 14.5px; }
.rw-pinned-status { color: var(--muted); font-size: 12.5px; }
.rw-pinned-status strong { color: var(--text); }
.rw-pinned.is-ready .rw-pinned-status strong { color: var(--success); }

.rw-deal {
  padding: 1px 6px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10.5px;
  font-style: normal;
  font-weight: 700;
}

.rw-meter {
  position: relative;
  display: block;
  height: 8px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 10%, transparent);
}

.rw-meter > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 35%, transparent), var(--accent));
  transition: width 0.9s var(--ease-out);
}

.rw-pinned.is-ready .rw-meter > i { background: var(--success); }
.rw-meter.is-season > i { background: linear-gradient(90deg, #ffb347, #ff6b1a); }

/* ---------- Season pass page ---------- */

.ssn-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 24px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  background:
    radial-gradient(70% 120% at 0% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
    radial-gradient(60% 100% at 100% 100%, color-mix(in srgb, var(--accent-2) 14%, transparent), transparent 70%),
    var(--bg);
  color: var(--text);
}

.ssn-hero-main { display: flex; flex: 1 1 420px; gap: 18px; min-width: 0; }

.ssn-hero-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 72px;
  height: 72px;
  border-radius: 20px;
  background: var(--accent-soft);
  font-size: 38px;
}

.ssn-hero-text { display: flex; flex: 1; flex-direction: column; gap: 8px; min-width: 0; }
.ssn-hero-text h2 { margin: 0; font-size: 17px; font-weight: 600; line-height: 1.4; }

.ssn-tier-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; color: var(--muted); }

.ssn-tier-big {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 800;
  line-height: 1;
}

.ssn-premium-chip {
  padding: 3px 10px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11.5px;
  font-weight: 800;
}

.ssn-xp {
  position: relative;
  height: 12px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 12%, transparent);
}

.ssn-xp > i {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
}

.ssn-xp-note { margin: 0; color: var(--muted); font-size: 13px; }
.ssn-xp-note strong { color: var(--text); }

.ssn-hero-actions { display: flex; flex-wrap: wrap; gap: 8px; }

.ssn-upsell {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  color: var(--text);
}

.ssn-upsell-body { flex: 1 1 380px; min-width: 0; }
.ssn-upsell-body h2 { margin: 2px 0 6px; font-size: 20px; }
.ssn-upsell-body p { margin: 0 0 10px; color: var(--muted); font-size: 14px; }
.ssn-upsell-body p strong { color: var(--text); }

.ssn-upsell-items {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ssn-upsell-items li {
  padding: 5px 10px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  font-size: 13px;
}

.ssn-upsell-items em { color: var(--muted); font-size: 11.5px; font-style: normal; }

.ssn-upsell-buy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 180px;
  padding: 16px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.ssn-price { font-family: var(--font-display); font-size: 28px; font-weight: 800; }
.ssn-balance { color: var(--muted); font-size: 12.5px; }

.ssn-track {
  display: flex;
  gap: 8px;
  padding: 4px 2px 14px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}

.ssn-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.ssn-rowlabels,
.ssn-tier {
  display: grid;
  grid-template-rows: 28px 104px 104px;
  gap: 8px;
  flex: none;
}

.ssn-rowlabels {
  position: sticky;
  left: 0;
  z-index: 2;
  width: 74px;
  padding-right: 6px;
  background: var(--surface);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ssn-rowlabels span { display: flex; align-items: center; }

.ssn-tier { width: 98px; scroll-snap-align: center; }

.ssn-tier-num {
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.ssn-tier.is-reached .ssn-tier-num { background: var(--accent); color: var(--on-accent); }
.ssn-tier.is-current .ssn-tier-num { box-shadow: 0 0 0 2px var(--accent); background: var(--accent-soft); color: var(--accent); }

.ssn-reward {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  text-align: center;
}

.ssn-reward.is-premium {
  background:
    radial-gradient(100% 80% at 50% 0%, color-mix(in srgb, #5b6ef0 14%, transparent), transparent 70%),
    var(--surface-2);
}

.ssn-reward.kind-item { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.ssn-reward-icon { font-size: 26px; line-height: 1; }

.ssn-reward-name {
  max-width: 100%;
  overflow: hidden;
  font-size: 12px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ssn-reward.is-locked,
.ssn-reward.is-premium-locked { opacity: 0.55; }
.ssn-reward.is-claimed { opacity: 0.6; }

.ssn-reward.is-ready {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  opacity: 1;
}

.ssn-reward-state {
  position: absolute;
  top: 5px;
  right: 6px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 800;
}

.ssn-reward-state.is-ready {
  top: auto;
  right: auto;
  bottom: -8px;
  padding: 1px 8px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  text-transform: uppercase;
}

.ssn-how-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ssn-how-list li { display: flex; gap: 12px; }
.ssn-how-list li > span { font-size: 22px; }
.ssn-how-list strong { font-size: 14.5px; }
.ssn-how-list p { margin: 4px 0 0; color: var(--muted); font-size: 13.5px; }

.ssn-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 48px 24px; text-align: center; }
.ssn-empty-icon { font-size: 42px; }
.ssn-empty h2 { margin: 0; }
.ssn-empty p { max-width: 460px; margin: 0 0 6px; color: var(--muted); }

/* ---------- Phones ---------- */

@media (max-width: 900px) {
  .shop-picks-row {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(140px, 1fr);
    overflow-x: auto;
    padding-top: 10px;
    scroll-snap-type: x proximity;
  }

  .shop-pick { scroll-snap-align: start; }
}

@media (max-width: 640px) {
  .shop-picks .sx-panel-head { flex-wrap: wrap; }
  .shop-season { flex-wrap: wrap; }
  .shop-season-cta { width: 100%; justify-content: center; }
  .shop-drop-next { margin-left: 0; }
  .ssn-hero { padding: 18px; }
  .ssn-hero-main { flex-basis: 100%; }
  .ssn-hero-icon { width: 56px; height: 56px; font-size: 30px; }
  .ssn-upsell-buy { width: 100%; }
  .ssn-tier { width: 86px; }
  .ssn-rowlabels { width: 60px; }
}

@media (prefers-reduced-motion: reduce) {
  .shop-pick, .rw-meter > i { transition: none; }
}

/* =========================================================
   NEW COSMETICS — Season 1 "Ignition" pass rewards and the queued
   weekly drops (migration 0073). Same patterns as the originals: a
   theme is a token block, a cursor sets --caret-*, a trail styles the
   race lane + the swatch demo dot, an avatar swaps the badge glyph.
========================================================= */

/* Ignition — Season 1's premium tier-30 theme. Charcoal ember-glow with a
   hot orange accent and an amber second; palette only, like every
   premium theme. */
body[data-theme="ignition"],
[data-theme-scope="ignition"] {
  --bg: #120d0b;
  --surface: #1c1512;
  --surface-2: #261c17;
  --text: #f6ece4;
  --muted: #b09d90;
  --accent: #ff7a35;
  --accent-2: #ffb347;
  --accent-soft: rgba(255, 122, 53, 0.14);
  --on-accent: #1a0b03;
  color-scheme: dark;
}

/* Cursors */
/* A white-hot spark rather than another fire gradient (Fire, Hollow
   Flame and Phoenix already own that look): a near-white core with an
   orange halo that fires in short bursts. */
body[data-cursor="ignition-cursor"],
[data-cursor-demo="ignition-cursor"] {
  --caret-width: 2px;
  --caret-bg: #fffaf0;
  --caret-glow: 0 0 4px #fff3c4, 0 0 12px #ff8a3d, 0 0 24px rgba(255, 94, 26, 0.55);
  --caret-anim: cursorSparkFire 1.2s steps(1) infinite;
}

@keyframes cursorSparkFire {
  0%, 100% { opacity: 1; }
  42% { opacity: 1; }
  44% { opacity: 0.35; }
  48% { opacity: 1; }
  52% { opacity: 0.5; }
  56% { opacity: 1; }
}

body[data-cursor="molten-cursor"],
[data-cursor-demo="molten-cursor"] {
  --caret-width: 4px;
  --caret-bg: linear-gradient(to bottom, #fff1b8, #ffb347 35%, #ff5e1a 70%, #c81d25);
  --caret-glow: 0 0 12px rgba(255, 94, 26, 0.8), 0 0 26px rgba(200, 29, 37, 0.45);
  --caret-anim: cursorGlowPulse 1.3s ease-in-out infinite;
}

body[data-cursor="sunset-cursor"],
[data-cursor-demo="sunset-cursor"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #ffb36b, #ff5f8f);
  --caret-glow: 0 0 10px rgba(255, 95, 143, 0.55);
  --caret-anim: cursorGlowPulse 1.6s ease-in-out infinite;
}

body[data-cursor="mint-cursor"],
[data-cursor-demo="mint-cursor"] {
  --caret-bg: #3ddc97;
  --caret-glow: 0 0 8px rgba(61, 220, 151, 0.5);
}

body[data-cursor="rose-gold-cursor"],
[data-cursor-demo="rose-gold-cursor"] {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, #8f4f5a, #ffe1d6 45%, #e8a598 55%, #7a3e48);
  --caret-bg-size: 100% 300%;
  --caret-glow: 0 0 10px rgba(232, 165, 152, 0.6);
  --caret-anim: gildedShimmer 2.4s linear infinite;
}

@keyframes gildedShimmer {
  from { background-position: 0 0%; }
  to { background-position: 0 300%; }
}

/* Trails: race lane */
.race-lane-avatar[data-trail="aurora-trail"]::after {
  width: 36px;
  background: linear-gradient(to left, #34d399, #818cf8 60%, transparent);
  box-shadow: 0 0 10px rgba(52, 211, 153, 0.6);
}

.race-lane-avatar[data-trail="petal-trail"]::after {
  width: 30px;
  background: linear-gradient(to left, #f9a8d4, #fbcfe8 60%, transparent);
  box-shadow: 0 0 8px rgba(249, 168, 212, 0.6);
}

.race-lane-avatar[data-trail="lantern-trail"]::after {
  width: 36px;
  background: linear-gradient(to left, #fbbf24, #ef4444 60%, transparent);
  box-shadow: 0 0 12px rgba(251, 191, 36, 0.65);
  animation: lanternTrailGlow 1.4s ease-in-out infinite;
}

@keyframes lanternTrailGlow {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

.race-lane-avatar[data-trail="afterburner-trail"]::after {
  width: 46px;
  background: linear-gradient(to left, #fff7cc, #ffb347 25%, #ff5e1a 60%, transparent);
  box-shadow: 0 0 14px rgba(255, 94, 26, 0.75);
  animation: afterburnerPulse 0.35s ease-in-out infinite;
}

@keyframes afterburnerPulse {
  0%, 100% { width: 46px; }
  50% { width: 54px; }
}

/* Trails: swatch / shop demo dots — the new trails plus the originals
   that only had the plain accent dot. */
[data-trail-demo="aurora-trail"] .trail-demo-dot { background: #34d399; box-shadow: -18px 0 12px -4px #818cf8; }
[data-trail-demo="petal-trail"] .trail-demo-dot { background: #f9a8d4; box-shadow: -16px 0 10px -4px #fbcfe8; }
[data-trail-demo="lantern-trail"] .trail-demo-dot { background: #fbbf24; box-shadow: -18px 0 12px -4px #ef4444; }
[data-trail-demo="afterburner-trail"] .trail-demo-dot { background: #fff1b8; box-shadow: -14px 0 8px -3px #ffb347, -26px 0 14px -4px #ff5e1a; }
[data-trail-demo="ice-trail"] .trail-demo-dot { background: #a5f3fc; box-shadow: -16px 0 10px -4px #67e8f9; }
[data-trail-demo="shadow-trail"] .trail-demo-dot { background: #475569; box-shadow: -18px 0 12px -4px rgba(15, 23, 42, 0.8); }
[data-trail-demo="electric-trail"] .trail-demo-dot { background: #facc15; box-shadow: -18px 0 12px -4px #60a5fa; }
[data-trail-demo="galaxy-trail"] .trail-demo-dot { background: #c4b5fd; box-shadow: -18px 0 14px -4px #6366f1; }
[data-trail-demo="eternal-flame-trail"] .trail-demo-dot { background: #fde68a; box-shadow: -14px 0 8px -3px #f97316, -26px 0 14px -4px #dc2626; }

/* Avatars */
.avatar-badge[data-avatar="otter-avatar"]::before { content: "🦦"; }

.avatar-badge[data-avatar="koi-avatar"]::before {
  content: "🎏";
  --tier-glow: rgba(244, 114, 182, 0.85);
  animation: avatarTierGlow 2.2s ease-in-out infinite;
}

.avatar-badge[data-avatar="octopus-avatar"]::before {
  content: "🐙";
  --tier-glow: rgba(168, 85, 247, 0.9);
  animation: avatarTierGlow 1.6s ease-in-out infinite;
}

.avatar-badge[data-avatar="solstice-avatar"]::before {
  content: "🌞";
  --tier-glow: rgba(251, 191, 36, 0.95);
  animation: avatarTierGlow 1.6s ease-in-out infinite;
}

.avatar-badge[data-avatar="caldera-avatar"]::before {
  content: "🌋";
  --tier-glow: rgba(255, 94, 26, 1);
  animation: avatarTierGlow 1.2s ease-in-out infinite;
}

@keyframes avatarTierGlow {
  0%, 100% { filter: drop-shadow(0 0 1.5px var(--tier-glow)); }
  50% { filter: drop-shadow(0 0 6px var(--tier-glow)); }
}

/* Background: Cinders — warm embers drifting up. Fixed ember hues (it's
   fire, it shouldn't turn blue on a blue theme) at low opacity so text
   stays readable on light and dark themes alike. */
body[data-background="cinders"]::after,
.customize-swatch[data-background-demo="cinders"]::after {
  background-image:
    radial-gradient(2.5px 2.5px at 12% 88%, #ff7a2f, transparent),
    radial-gradient(2px 2px at 28% 70%, #ffb347, transparent),
    radial-gradient(3px 3px at 47% 95%, #ff5e1a, transparent),
    radial-gradient(2px 2px at 63% 60%, #ffd36b, transparent),
    radial-gradient(2.5px 2.5px at 81% 82%, #ff7a2f, transparent),
    radial-gradient(1.5px 1.5px at 36% 30%, #ffb347, transparent),
    radial-gradient(2px 2px at 71% 18%, #ff5e1a, transparent),
    radial-gradient(1.5px 1.5px at 92% 40%, #ffd36b, transparent);
  background-size: 260px 260px;
  opacity: 0.55;
  animation: cindersRise 11s linear infinite;
}

@keyframes cindersRise {
  from { background-position: 0 0; }
  to { background-position: 40px -260px; }
}

/* =========================================================
   THEME REACTIONS (static/js/theme-reactions.js) — the streak glow and
   the personal-best bursts. Prototype: Meadow + Sakura only.
========================================================= */

#word-container {
  transition: box-shadow 0.6s var(--ease), border-color 0.6s var(--ease);
}

body.tr-streak #word-container {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent),
    0 0 34px -4px color-mix(in srgb, var(--accent) 40%, transparent),
    var(--shadow-sm);
}

.tr-streak-chip {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  padding: 3px 10px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  animation: trChipIn 0.35s var(--ease-out) both;
}

.tr-streak-chip + .restart-btn,
.tr-streak-chip + .finish-btn { margin-left: 8px; }

@keyframes trChipIn {
  from { opacity: 0; transform: translateY(4px) scale(0.92); }
  to { opacity: 1; transform: none; }
}

.tr-burst {
  position: fixed;
  inset: 0;
  z-index: 550;
  overflow: hidden;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  #word-container { transition: none; }
  .tr-streak-chip { animation: none; }
  .tr-burst { display: none; }
}

/* Nightshift (C1, legendary 600) — graphite under sodium streetlights: a warm amber lamp
   accent on neutral near-black, a dim teal second. */
body[data-theme="nightshift"],
[data-theme-scope="nightshift"] {
  --bg: #101114;
  --surface: #191b20;
  --surface-2: #22252b;
  --text: #ecebe6;
  --muted: #9c9a92;
  --accent: #f2b13c;
  --accent-2: #5fb3a8;
  --accent-soft: rgba(242, 177, 60, 0.13);
  --on-accent: #1c1404;
  color-scheme: dark;
}


/* ---------- A5: results moments ---------- */

.hero-trend { color: var(--muted); font-variant-numeric: tabular-nums; }
.hero-trend.positive { color: var(--success); }
.hero-trend[hidden] { display: none; }

.hero-trend.is-pb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  animation: pbPop 0.5s var(--ease-out) both;
}

.results-hero.is-pb .hero-kicker { color: var(--accent); }

.results-hero.is-pb .hero-score {
  color: var(--accent);
  animation: pbScore 0.9s var(--ease-out) both;
}

@keyframes pbPop {
  0% { opacity: 0; transform: scale(0.6); }
  60% { transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes pbScore {
  0% { transform: scale(1); text-shadow: none; }
  35% { transform: scale(1.12); text-shadow: 0 0 28px color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { transform: scale(1); text-shadow: 0 0 18px color-mix(in srgb, var(--accent) 25%, transparent); }
}

/* The run's coin total lands with a small pop once it has counted up. */
.rw-total.is-landed { animation: pbPop 0.4s var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  .hero-trend.is-pb, .results-hero.is-pb .hero-score, .rw-total.is-landed { animation: none; }
}

/* ---------- A6: daily login reward card ---------- */

.dr-card {
  position: fixed;
  left: 18px;
  bottom: 18px;
  z-index: 520;
  width: min(340px, calc(100vw - 32px));
  padding: 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  animation: drIn 0.45s var(--ease-out) both;
}

.dr-card[hidden] { display: none; }
.dr-card.is-leaving { opacity: 0; transform: translateY(10px); transition: opacity 0.4s, transform 0.4s; }

@keyframes drIn {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}

.dr-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  cursor: pointer;
}

.dr-close:hover { background: var(--surface-2); color: var(--text); }

.dr-head { display: flex; align-items: center; gap: 12px; padding-right: 24px; }
.dr-head strong { display: block; font-size: 14.5px; }
.dr-head span { color: var(--muted); font-size: 12.5px; }

.dr-gift {
  display: grid;
  place-items: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--accent-soft);
  font-size: 20px;
}

.dr-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin: 14px 0 12px;
  padding: 0;
  list-style: none;
}

.dr-days li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.dr-days b { font-size: 12px; }
.dr-days span { color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums; line-height: 1.2; text-align: center; }
.dr-days em { display: block; font-style: normal; }

.dr-days li.is-claimed { background: color-mix(in srgb, var(--success) 14%, transparent); border-color: transparent; }
.dr-days li.is-claimed b { color: var(--success); }

.dr-days li.is-today {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.dr-days li.is-today b,
.dr-days li.is-today span { color: var(--accent); font-weight: 700; }
.dr-days li.is-just { animation: pbPop 0.45s var(--ease-out); }

.dr-claim { width: 100%; justify-content: center; }
.dr-done { margin: 0; color: var(--success); font-size: 13px; font-weight: 700; text-align: center; }

@media (max-width: 768px) {
  .dr-card { left: 16px; bottom: calc(64px + env(safe-area-inset-bottom, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
  .dr-card, .dr-days li.is-just { animation: none; }
}

/* =========================================================
   B5: THEME MASTERY (main/mastery.py)
   Level 2 — alternate accent: each theme's own second colour, nudged
   until it passes 4.5:1 on that theme's bg AND surface (it is used as
   text), or rotated ~40deg off the accent where the second colour was
   too close to read as an alternate. Generated, then hand-checked; the
   ratio is noted per block. Applied via body[data-accent-alt] by
   applyMasteryFlags() in base.html.
========================================================= */

body[data-theme="focus-dark"][data-accent-alt],
[data-theme-scope="focus-dark"][data-accent-alt] {
  --accent: #55d6d0;
  --accent-soft: rgba(85, 214, 208, 0.13);
  --on-accent: #111111;
}  /* 10.0:1 */

body[data-theme="midnight"][data-accent-alt],
[data-theme-scope="midnight"][data-accent-alt] {
  --accent: #9275cd;
  --accent-soft: rgba(146, 117, 205, 0.13);
  --on-accent: #111111;
}  /* 4.7:1 */

body[data-theme="deep-ocean"][data-accent-alt],
[data-theme-scope="deep-ocean"][data-accent-alt] {
  --accent: #4aa8ea;
  --accent-soft: rgba(74, 168, 234, 0.13);
  --on-accent: #111111;
}  /* 6.1:1 */

body[data-theme="galaxy"][data-accent-alt],
[data-theme-scope="galaxy"][data-accent-alt] {
  --accent: #e2bae3;
  --accent-soft: rgba(226, 186, 227, 0.13);
  --on-accent: #111111;
}  /* 10.7:1 */

body[data-theme="neon-tokyo"][data-accent-alt],
[data-theme-scope="neon-tokyo"][data-accent-alt] {
  --accent: #d0a15f;
  --accent-soft: rgba(208, 161, 95, 0.13);
  --on-accent: #111111;
}  /* 7.8:1 */

body[data-theme="sky"][data-accent-alt],
[data-theme-scope="sky"][data-accent-alt] {
  --accent: #8a6700;
  --accent-soft: rgba(138, 103, 0, 0.13);
  --on-accent: #ffffff;
}  /* 4.7:1 */

body[data-theme="cherry"][data-accent-alt],
[data-theme-scope="cherry"][data-accent-alt] {
  --accent: #a66219;
  --accent-soft: rgba(166, 98, 25, 0.13);
  --on-accent: #ffffff;
}  /* 4.5:1 */

body[data-theme="frost"][data-accent-alt],
[data-theme-scope="frost"][data-accent-alt] {
  --accent: #0e7d75;
  --accent-soft: rgba(14, 125, 117, 0.13);
  --on-accent: #ffffff;
}  /* 4.5:1 */

body[data-theme="circuit"][data-accent-alt],
[data-theme-scope="circuit"][data-accent-alt] {
  --accent: #0e7c70;
  --accent-soft: rgba(14, 124, 112, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="sakura"][data-accent-alt],
[data-theme-scope="sakura"][data-accent-alt] {
  --accent: #a05735;
  --accent-soft: rgba(160, 87, 53, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="solar-flare"][data-accent-alt],
[data-theme-scope="solar-flare"][data-accent-alt] {
  --accent: #6b6d28;
  --accent-soft: rgba(107, 109, 40, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="autumn-harvest"][data-accent-alt],
[data-theme-scope="autumn-harvest"][data-accent-alt] {
  --accent: #5c6b2f;
  --accent-soft: rgba(92, 107, 47, 0.13);
  --on-accent: #ffffff;
}  /* 5.3:1 */

body[data-theme="linen"][data-accent-alt],
[data-theme-scope="linen"][data-accent-alt] {
  --accent: #667316;
  --accent-soft: rgba(102, 115, 22, 0.13);
  --on-accent: #ffffff;
}  /* 4.7:1 */

body[data-theme="meadow"][data-accent-alt],
[data-theme-scope="meadow"][data-accent-alt] {
  --accent: #397664;
  --accent-soft: rgba(57, 118, 100, 0.13);
  --on-accent: #ffffff;
}  /* 4.7:1 */

body[data-theme="rose-quartz"][data-accent-alt],
[data-theme-scope="rose-quartz"][data-accent-alt] {
  --accent: #a34f78;
  --accent-soft: rgba(163, 79, 120, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="citrus-grove"][data-accent-alt],
[data-theme-scope="citrus-grove"][data-accent-alt] {
  --accent: #4d7b03;
  --accent-soft: rgba(77, 123, 3, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="lilac-bloom"][data-accent-alt],
[data-theme-scope="lilac-bloom"][data-accent-alt] {
  --accent: #a43fb0;
  --accent-soft: rgba(164, 63, 176, 0.13);
  --on-accent: #ffffff;
}  /* 4.6:1 */

body[data-theme="prism-storm"][data-accent-alt],
[data-theme-scope="prism-storm"][data-accent-alt] {
  --accent: #dba8ce;
  --accent-soft: rgba(219, 168, 206, 0.13);
  --on-accent: #111111;
}  /* 8.8:1 */

body[data-theme="celestial-eclipse"][data-accent-alt],
[data-theme-scope="celestial-eclipse"][data-accent-alt] {
  --accent: #a9b4c4;
  --accent-soft: rgba(169, 180, 196, 0.13);
  --on-accent: #111111;
}  /* 8.5:1 */

body[data-theme="halloween"][data-accent-alt],
[data-theme-scope="halloween"][data-accent-alt] {
  --accent: #9275a8;
  --accent-soft: rgba(146, 117, 168, 0.13);
  --on-accent: #111111;
}  /* 4.7:1 */

body[data-theme="christmas"][data-accent-alt],
[data-theme-scope="christmas"][data-accent-alt] {
  --accent: #cc655e;
  --accent-soft: rgba(204, 101, 94, 0.13);
  --on-accent: #111111;
}  /* 4.6:1 */

body[data-theme="prestige-gold"][data-accent-alt],
[data-theme-scope="prestige-gold"][data-accent-alt] {
  --accent: #c0e68c;
  --accent-soft: rgba(192, 230, 140, 0.13);
  --on-accent: #111111;
}  /* 13.3:1 */

body[data-theme="founder"][data-accent-alt],
[data-theme-scope="founder"][data-accent-alt] {
  --accent: #9c7482;
  --accent-soft: rgba(156, 116, 130, 0.13);
  --on-accent: #111111;
}  /* 4.5:1 */

body[data-theme="obsidian"][data-accent-alt],
[data-theme-scope="obsidian"][data-accent-alt] {
  --accent: #8d7bbf;
  --accent-soft: rgba(141, 123, 191, 0.13);
  --on-accent: #111111;
}  /* 5.1:1 */

body[data-theme="ignition"][data-accent-alt],
[data-theme-scope="ignition"][data-accent-alt] {
  --accent: #ffff47;
  --accent-soft: rgba(255, 255, 71, 0.13);
  --on-accent: #111111;
}  /* 16.8:1 */

body[data-theme="nightshift"][data-accent-alt],
[data-theme-scope="nightshift"][data-accent-alt] {
  --accent: #5fb3a8;
  --accent-soft: rgba(95, 179, 168, 0.13);
  --on-accent: #111111;
}  /* 7.0:1 */


/* Level 3 — matching caret: the theme's two colours as a gradient with a
   soft glow. Never overrides an equipped cursor skin. */
body[data-mastery-caret]:not([data-cursor]) {
  --caret-width: 3px;
  --caret-bg: linear-gradient(to bottom, var(--accent), var(--accent-2));
  --caret-glow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* Level 4 — mastered frame: a ring in the mastered theme's own accent
   around the avatar, wherever the frame is rendered (profile hero,
   leaderboard rows). The wrapper carries data-theme-scope=<theme>. */
.mastery-frame {
  position: relative;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  padding: 2px;
  background: conic-gradient(from 210deg, var(--accent), var(--accent-2), var(--accent));
}

.mastery-frame > .sx-avatar,
.mastery-frame > .avatar-badge { box-shadow: 0 0 0 2px var(--surface); }

.mastery-star {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10.5px;
  font-weight: 800;
  white-space: nowrap;
}

/* Awards: theme mastery panel. Each row is scoped to its own theme, so
   the meter, chips and swatches show that theme's colours. */
.mst-list { display: flex; flex-direction: column; gap: 10px; }

.mst-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
}

.mst-row.is-worn { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }

.mst-icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--accent-soft);
  font-size: 21px;
}

.mst-main { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.mst-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
.mst-title h3 { margin: 0; font-size: 15px; }
.mst-worn { padding: 1px 8px; border-radius: var(--radius-full); background: var(--accent); color: var(--on-accent); font-size: 10.5px; font-weight: 800; }
.mst-level { color: var(--muted); font-size: 12.5px; }
.mst-row.is-mastered .mst-level { color: var(--accent); font-weight: 700; }

.mst-meter {
  position: relative;
  height: 7px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--text) 10%, transparent);
}

.mst-meter > i {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
}

.mst-next { color: var(--muted); font-size: 12px; }

.mst-perks { display: flex; flex-direction: column; gap: 6px; }

.mst-perk {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.mst-perk:disabled { opacity: 0.45; cursor: not-allowed; }
.mst-perk.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.mst-swatch { width: 12px; height: 12px; border-radius: 50%; background: var(--accent); }
.mst-caret { width: 3px; height: 14px; border-radius: 2px; background: linear-gradient(var(--accent), var(--accent-2)); }

@media (max-width: 560px) {
  .mst-row { grid-template-columns: auto 1fr; }
  .mst-perks { grid-column: 1 / -1; flex-direction: row; }
}

.hero-mastery-frame { flex: none; padding: 5px; border-radius: 22px; }
.hero-mastery-frame > .hero-avatar { margin: 0; }
.hero-mastery-frame > .hero-avatar { box-shadow: 0 0 0 2px var(--surface); }

/* ---------- B6: profile trophy shelf ---------- */

.shelf-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }

.shelf-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.shelf-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 20px 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background:
    radial-gradient(110% 80% at 50% 0%, var(--accent-soft), transparent 70%),
    var(--surface-2);
  color: var(--text);
  font-family: inherit;
  text-align: center;
  text-decoration: none;
  transition: transform var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.shelf-item:hover { transform: translateY(-2px); border-color: var(--accent); }
.shelf-item strong { max-width: 100%; overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }

.shelf-item.is-empty {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.shelf-icon { font-size: 40px; line-height: 1.1; }
.shelf-item.is-empty .shelf-icon { font-size: 26px; }
.shelf-meta { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 11.5px; }

.shelf-options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}

.shelf-option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.shelf-option.is-picked { border-color: var(--accent); background: var(--accent-soft); }
.shelf-option-icon { font-size: 22px; }
.shelf-option-name { font-size: 13px; font-weight: 700; }
.shelf-option-meta { color: var(--muted); font-size: 11px; text-transform: capitalize; }

.shelf-option-n {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 800;
}

.shelf-option-n:empty { display: none; }
.shelf-save { width: 100%; }

@media (max-width: 560px) {
  .shelf-row { gap: 8px; }
  .shelf-item { padding: 14px 6px 12px; }
  .shelf-icon { font-size: 30px; }
  .shelf-item strong { font-size: 12.5px; }
  .shelf-meta { display: none; }
}

/* C3: one-line mood under the shop card title and in the try-on bar. */
.shop-blurb {
  margin: -4px 0 0;
  color: var(--muted);
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.4;
}

.tryon-mood {
  display: block;
  max-width: 260px;
  overflow: hidden;
  color: var(--muted);
  font-size: 11.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 560px) { .tryon-mood { display: none; } }

/* C5: the home page's drop announcement, in the Today header row. */
.home-drop {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  overflow: hidden;
  color: var(--text);
  font-size: 13px;
  text-decoration: none;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.home-drop:hover { color: var(--accent); }

.home-drop-tag {
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* =========================================================
   D1: racers' lanes in their own theme. The runway carries
   data-theme-scope=<their theme>, so its track line, the default trail
   and the avatar chip resolve to THEIR accent while the lane's name and
   stats stay in the viewer's theme.
========================================================= */
.race-lane-runway[data-theme-scope]::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 3px;
  transform: translateY(-50%);
  border-radius: 999px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 40%, transparent), color-mix(in srgb, var(--accent) 8%, transparent));
}

.race-lane-runway[data-theme-scope] .race-lane-avatar {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 12px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* D3: theme of the week */
.shop-pick.is-week { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.shop-pick.is-week .shop-pick-flag { background: var(--accent); color: var(--on-accent); }

.shop-week-line {
  margin: 14px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.shop-week-line strong { color: var(--text); }
.shop-week-line a { color: var(--accent); font-weight: 700; }

/* D3: race lobby theme-of-the-week card, drawn in the featured theme. */
.rl-event {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  padding: 18px 20px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  background:
    radial-gradient(80% 140% at 0% 50%, var(--accent-soft), transparent 70%),
    var(--surface);
  color: var(--text);
}

.rl-event-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 14px;
  background: var(--accent-soft);
  font-size: 26px;
}

.rl-event-body { display: flex; flex: 1 1 320px; flex-direction: column; gap: 4px; min-width: 0; }
.rl-event-body strong { font-size: 17px; }
.rl-event-body > span:last-child { color: var(--muted); font-size: 13.5px; }
.rl-event-body b { color: var(--text); }

/* =========================================================
   D2: WEEKLY LEAGUES (/league/)
========================================================= */
.lg-hero { align-items: stretch; }

.lg .lg-ladder {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: center;
  gap: 8px;
}

.lg-rung {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 14px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  text-align: center;
  opacity: 0.6;
}

.lg-rung.is-below { opacity: 0.85; }
.lg-rung.is-current { opacity: 1; border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 3px var(--accent-soft); }
.lg .lg-rung .lg-rung-icon { font-size: 28px; line-height: 1.1; }
.lg-rung strong { font-size: 13.5px; }
.lg-rung span { color: var(--muted); font-size: 11px; }

.lg .lg-hero-side { justify-content: center; }

.lg-last {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  border: 1px solid var(--border);
}

.lg-last.is-up { border-color: color-mix(in srgb, var(--success) 55%, transparent); }
.lg-last-icon { font-size: 26px; }
.lg-last strong { display: block; }
.lg-last span { color: var(--muted); font-size: 13px; }

.lg-board { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }

.lg-row {
  display: grid;
  grid-template-columns: 34px auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
}

.lg-row.is-me { background: var(--accent-soft); }
.lg-row.zone-up .lg-rank { color: var(--success); }
.lg-row.zone-down .lg-rank { color: var(--danger); }
.lg-rank { font-weight: 800; font-variant-numeric: tabular-nums; text-align: center; }
.lg-name { overflow: hidden; color: var(--text); font-weight: 600; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; }
.lg-name em { margin-left: 4px; padding: 1px 6px; border-radius: var(--radius-full); background: var(--accent); color: var(--on-accent); font-size: 10px; font-style: normal; }
.lg-points { font-weight: 800; font-variant-numeric: tabular-nums; }

.lg-line { margin: 2px 0; border-top: 1px dashed color-mix(in srgb, var(--success) 50%, transparent); text-align: center; }
.lg-line span { position: relative; top: -9px; padding: 0 8px; background: var(--surface); color: var(--success); font-size: 10.5px; font-weight: 700; text-transform: uppercase; }

@media (max-width: 640px) {
  .lg-ladder { grid-template-columns: repeat(5, minmax(56px, 1fr)); overflow-x: auto; }
  .lg-rung span { display: none; }
}

/* =========================================================
   D4: FRIEND CHALLENGES — the banner above a challenge run and the
   ghost: a dotted underline on the word the friend was on at this
   moment of their run.
========================================================= */
.fc-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  width: min(var(--container), 100% - 2 * var(--gutter));
  margin: 0 auto 14px;
  padding: 14px 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  border-radius: var(--radius-lg);
  background: radial-gradient(80% 140% at 0% 50%, var(--accent-soft), transparent 70%), var(--surface);
}

.fc-banner > div { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.fc-banner strong { font-size: 15px; }
.fc-banner span { color: var(--muted); font-size: 13px; }

.fc-ghost-dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent-2);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent-2) 25%, transparent);
}

.fc-banner .fc-target { color: var(--text); font-family: var(--font-display); font-size: 30px; font-weight: 800; }
.fc-banner .fc-target small { margin-left: 3px; color: var(--muted); font-size: 12px; font-weight: 600; }

/* A background dash line, not text-decoration: the letters are
   inline-block .char spans, which text-decoration doesn't reach. */
#word-track .word.is-ghost {
  background-image: linear-gradient(to right, var(--accent-2) 55%, transparent 0);
  background-position: 0 92%;
  background-size: 9px 3px;
  background-repeat: repeat-x;
}

/* D5: share card sheet */
.sc-formats { display: flex; gap: 8px; margin-bottom: 14px; }
.sc-formats [aria-pressed="true"] { background: var(--pill-active-bg, var(--accent-soft)); color: var(--pill-active-fg, var(--accent)); box-shadow: var(--pill-active-ring, none); }

.sc-preview {
  display: grid;
  place-items: center;
  margin-bottom: 14px;
  padding: 12px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.sc-preview canvas {
  display: block;
  max-width: 100%;
  max-height: 52vh;
  height: auto;
  border-radius: 12px;
  box-shadow: var(--shadow-md);
}

.sc-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.sc-actions .primary-action { flex: 1 1 auto; }

/* E2: early access strip */
.shop-early-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }

.shop-early-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, #5b6ef0 35%, var(--border));
  border-radius: var(--radius-md);
  background: radial-gradient(100% 120% at 0% 50%, color-mix(in srgb, #5b6ef0 10%, transparent), transparent 70%), var(--surface-2);
}

.shop-early-item.is-owned { opacity: 0.7; }
.shop-early-icon { font-size: 26px; }
.shop-early-body { display: flex; flex: 1; flex-direction: column; min-width: 0; }
.shop-early-body strong { font-size: 14px; }
.shop-early-body span { color: var(--muted); font-size: 12px; }
.shop-early-owned { color: var(--success); font-size: 12.5px; font-weight: 700; }

/* =========================================================
   E3: THEME BUILDER (/theme-builder/). The preview takes the derived
   tokens as inline custom properties, so it renders exactly as the
   saved theme will.
========================================================= */
.tb-grid { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 24px; align-items: start; }

.tb-name { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; font-size: 13px; color: var(--muted); }
.tb-name input {
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}

.tb-pickers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

.tb-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  cursor: pointer;
}

.tb-pick input[type="color"] {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: none;
  cursor: pointer;
}

.tb-pick span { display: flex; flex-direction: column; }
.tb-pick strong { font-size: 13.5px; }
.tb-pick code { color: var(--muted); font-size: 12px; }

.tb-h { margin: 20px 0 8px; font-size: 14px; }

.tb-checks { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
.tb-checks li { display: grid; grid-template-columns: 1fr auto auto; gap: 10px; align-items: baseline; padding: 6px 10px; border-radius: var(--radius-sm); font-size: 13px; }
.tb-checks li.is-ok { background: color-mix(in srgb, var(--success) 10%, transparent); }
.tb-checks li.is-ok span { color: var(--success); }
.tb-checks li.is-bad { background: color-mix(in srgb, var(--warning) 14%, transparent); }
.tb-checks li.is-bad span { color: color-mix(in srgb, var(--warning) 55%, var(--text)); font-weight: 700; }
.tb-verdict { margin: 8px 0 0; padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px; }
.tb-verdict.is-ok { color: var(--success); }
.tb-verdict.is-warn { background: color-mix(in srgb, var(--warning) 10%, transparent); color: var(--text); }
.tb-verdict.is-block { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--text); font-weight: 600; }
.tb-checks b { font-variant-numeric: tabular-nums; }
.tb-checks em { color: var(--muted); font-size: 11.5px; font-style: normal; }

.tb-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; margin-top: 18px; }
.tb-note { margin: 0; color: var(--muted); font-size: 13px; }
.tb-note strong { color: var(--text); }

.tb-preview-wrap { position: sticky; top: 90px; }

.tb-preview {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  color: var(--text);
  box-shadow: var(--shadow-md);
}

.tb-p-nav { display: flex; align-items: center; gap: 10px; }
.tb-p-logo { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 8px; background: var(--accent); color: var(--on-accent); font-size: 12px; font-weight: 800; }
.tb-p-chip { margin-left: auto; padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); font-size: 12px; font-weight: 700; }

.tb-p-console { padding: 18px 20px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface); }
.tb-p-stats { display: flex; align-items: baseline; gap: 14px; margin-bottom: 8px; }
.tb-p-stats b { color: var(--accent); font-size: 26px; }
.tb-p-stats span { font-weight: 700; }
.tb-p-words { margin: 0; color: var(--muted); font-family: var(--test-font); font-size: 22px; line-height: 1.7; }
.tb-p-words .ok { color: var(--text); }
.tb-p-words .bad { color: #e5484d; text-decoration: underline; }
.tb-p-caret { display: inline-block; width: 3px; height: 1.1em; margin: 0 1px -3px; border-radius: 2px; background: var(--accent); }

.tb-p-card { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface-2); }
.tb-p-card div { display: flex; flex: 1; flex-direction: column; }
.tb-p-card span { color: var(--muted); font-size: 13px; }
.tb-p-btn { padding: 8px 16px; border: 0; border-radius: 999px; background: var(--accent); color: var(--on-accent); font: inherit; font-weight: 700; }

@media (max-width: 860px) {
  .tb-grid { grid-template-columns: 1fr; }
  .tb-preview-wrap { position: static; order: -1; }
}

/* =========================================================
   F4: PERFORMANCE — every ambient theme layer (body::before) and
   background effect (body::after) holds still while a test or race is
   running, the way Meadow's grass already did. These animate
   background-position on full-viewport layers, i.e. a full repaint
   every frame; on a 4x-throttled phone profile some dropped below
   30fps mid-test. Typing is the point — the scenery can wait.
========================================================= */
body.test-active::before,
body.test-active::after {
  animation-play-state: paused !important;
}

/* =========================================================
   F1/F2: onboarding — first-visit coach, the stronger guest save card,
   and the welcome sheet's saved-run + starter pick.
========================================================= */
.ob-coach {
  display: flex;
  align-items: center;
  gap: 14px;
  width: min(var(--container), 100% - 2 * var(--gutter));
  margin: 0 auto 14px;
  padding: 14px 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  border-radius: var(--radius-lg);
  background: radial-gradient(80% 140% at 0% 50%, var(--accent-soft), transparent 70%), var(--surface);
  animation: drIn 0.45s var(--ease-out) both;
}

.ob-coach[hidden] { display: none; }
.ob-coach-wave { font-size: 26px; }
.ob-coach > div { display: flex; flex: 1; flex-direction: column; gap: 2px; }
.ob-coach strong { font-size: 15px; }
.ob-coach span { color: var(--muted); font-size: 13px; }
.ob-coach-x { display: grid; place-items: center; width: 30px; height: 30px; border: 0; border-radius: 50%; background: transparent; color: var(--muted); font-size: 18px; cursor: pointer; }
.ob-coach-x:hover { background: var(--surface-2); color: var(--text); }

.guest-teaser-banner #guest-teaser-text { display: flex; flex-direction: column; gap: 3px; }
.gt-head { font-size: 16px; }
.gt-sub { color: var(--muted); font-size: 13px; }

.ob-saved {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--success) 12%, transparent);
}

.ob-saved span { font-size: 22px; }
.ob-saved p { margin: 0; font-size: 13.5px; }
.ob-sub { margin: -4px 0 10px; color: var(--muted); font-size: 13px; }

.ob-starters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }

.ob-starter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 14px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.ob-starter:hover { border-color: var(--accent); transform: translateY(-2px); }
.ob-starter:disabled { opacity: 0.6; cursor: default; transform: none; }
.ob-starter-icon { font-size: 28px; }
.ob-starter strong { font-size: 13px; }
.ob-starter span { color: var(--muted); font-size: 11.5px; }

/* F3: the personal-best screen nudge (skipped under reduced motion). */
.tf-nudge { animation: tfNudge 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97); }

@keyframes tfNudge {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(3px); }
  30%, 50%, 70% { transform: translateX(-5px); }
  40%, 60% { transform: translateX(5px); }
}

.display-pref-row small { color: var(--muted); font-size: 11.5px; font-weight: 500; }

@media (prefers-reduced-motion: reduce) { .tf-nudge { animation: none; } }

/* F5: analytics funnel */
.fn-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.fn-col h4 { margin: 0 0 8px; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.fn-stats { margin: 0; }
.fn-stats div { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 13.5px; }
.fn-stats dt { color: var(--muted); }
.fn-stats dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; }
.fn-stats small { color: var(--accent); font-weight: 700; }
.fn-top { margin: 8px 0 0; color: var(--muted); font-size: 12px; }
@media (max-width: 860px) { .fn-grid { grid-template-columns: 1fr; } }

/* A4: the burst engine's shapes and motions. Every particle carries
   --c (colour), --s (scale), --d/--delay (timing), --x/--y (start),
   --dx/--dy (radial travel), --sway and --spin. */
.tr-p {
  position: absolute;
  display: block;
  opacity: 0;
  background: var(--c);
  animation-duration: var(--d);
  animation-delay: var(--delay);
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(0.25, 0.1, 0.35, 1);
  will-change: transform, opacity;
}

.shape-dot { width: calc(8px * var(--s)); height: calc(8px * var(--s)); border-radius: 50%; box-shadow: 0 0 calc(10px * var(--s)) var(--c); }
.shape-puff { width: calc(34px * var(--s)); height: calc(34px * var(--s)); border-radius: 50%; filter: blur(8px); background: color-mix(in srgb, var(--c) 70%, transparent); }
.shape-rain { width: 2px; height: calc(34px * var(--s)); border-radius: 2px; box-shadow: 0 0 8px var(--c); }
.shape-dash { width: calc(46px * var(--s)); height: 3px; border-radius: 3px; box-shadow: 0 0 10px var(--c); }
.shape-beam { width: calc(140px * var(--s)); height: 6px; border-radius: 6px; filter: blur(2px); background: linear-gradient(90deg, transparent, var(--c)); }
.shape-streak { width: calc(120px * var(--s)); height: 2px; border-radius: 2px; background: linear-gradient(90deg, var(--c), transparent); box-shadow: 0 0 8px var(--c); }
.shape-confetti { width: calc(9px * var(--s)); height: calc(14px * var(--s)); border-radius: 2px; }
.shape-shard { width: calc(14px * var(--s)); height: calc(18px * var(--s)); clip-path: polygon(50% 0, 100% 70%, 30% 100%, 0 40%); }
.shape-coin { width: calc(18px * var(--s)); height: calc(18px * var(--s)); border-radius: 50%; box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.18), inset 0 0 0 5px rgba(255, 255, 255, 0.25); }
.shape-flake { background: none; color: var(--c); font-size: calc(18px * var(--s)); line-height: 1; text-shadow: 0 0 6px var(--c); }
.shape-ring { width: 120px; height: 120px; margin: -60px 0 0 -60px; border-radius: 50%; background: none; border: 3px solid var(--c); box-shadow: 0 0 24px var(--c), inset 0 0 24px var(--c); }

.motion-fall { top: -40px; left: var(--x); animation-name: trFall; }
.motion-rise { bottom: -40px; left: var(--x); animation-name: trRise; }
.motion-across { left: -160px; top: var(--y); animation-name: trAcross; }
.motion-radial { left: 50%; top: 38%; animation-name: trRadial; }
.motion-streak { left: calc(55% + var(--x) * 0.5); top: var(--y); transform: rotate(-35deg); animation-name: trStreak; }
.motion-ring { left: 50%; top: 38%; animation-name: trRing; }

@keyframes trFall {
  0% { opacity: 0; transform: translate(0, 0) rotate(0); }
  8% { opacity: 1; }
  50% { transform: translate(var(--sway), 55vh) rotate(calc(var(--spin) * 0.5)); }
  88% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(var(--sway) * -0.5), 110vh) rotate(var(--spin)); }
}

@keyframes trRise {
  0% { opacity: 0; transform: translate(0, 0) scale(0.8); }
  12% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--sway), -100vh) scale(1.2); }
}

@keyframes trAcross {
  0% { opacity: 0; transform: translate(0, 0); }
  10% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(100vw + 320px), var(--sway)); }
}

@keyframes trRadial {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.4) rotate(0); }
  10% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1) rotate(var(--spin)); }
}

@keyframes trStreak {
  0% { opacity: 0; transform: rotate(-35deg) translateX(0); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: rotate(-35deg) translateX(-70vw); }
}

@keyframes trRing {
  0% { opacity: 0.95; transform: scale(0.2); }
  100% { opacity: 0; transform: scale(7); }
}

/* =========================================================
   SHOP v3 — a storefront you can read at a glance.
   Every rule is scoped to .shp (the shop page) so the customize modal,
   home and dashboard — which share .shop-* / .buy-btn names — are
   untouched. Principles: show the product (a theme card is a miniature
   typing screen in that theme's colours), one clear action per card
   (solid Buy / Equip / a progress bar — never a disabled button),
   price next to the name, at most one tag per card, quiet chrome.
========================================================= */

.shp-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.shp-head .shp-sub {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 14px;
}

.shp-kicker {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
}

.shp-muted { color: var(--muted); font-weight: 500; }

/* Rarity: a coloured dot and a word, not an all-caps pill. The word is
   pulled toward --text so yellow "Legendary" still reads on white. */
.shp-rarity {
  --rc: #8a93a3;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: color-mix(in srgb, var(--rc) 62%, var(--text));
  font-weight: 600;
  white-space: nowrap;
}

.shp-rarity::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--rc);
}

.shp-rarity[data-r="common"] { color: var(--muted); }
.shp-rarity[data-r="rare"] { --rc: #3b9be0; }
.shp-rarity[data-r="epic"] { --rc: #a678f5; }
.shp-rarity[data-r="legendary"] { --rc: #e0a526; }
.shp-rarity[data-r="mythical"] { --rc: #ec5fa8; }
.shp-rarity[data-r="seasonal"] { --rc: #2fbf6b; }
.shp-rarity[data-r="prestige"] { --rc: #94a3b8; }
.shp-rarity[data-r="founder"] { --rc: #d9a520; }

/* One small tag per card, top-left of the picture. */
.shp-tag {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
}

.shp-tag.is-deal,
.shp-tag.is-week { background: #f5c451; color: #241700; }
.shp-tag.is-new { background: var(--accent); color: var(--on-accent); }
.shp-tag.is-premium { background: #5b6ef0; color: #fff; }
.shp-tag.is-limited { background: #1d1a14; color: #f5d27a; }

/* ---------- Header + spotlight ---------- */

.shp-spot {
  display: grid;
  grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
  gap: 24px;
}

.shp-spot.is-single { grid-template-columns: minmax(0, 1fr); }

.card.shp-goal {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 22px;
}

.shp-goal-thumb {
  flex: none;
  width: 210px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  cursor: pointer;
}

.shp-goal-thumb .shop-preview {
  height: 140px;
  border-radius: var(--radius-md);
  box-shadow: inset 0 0 0 1px var(--border);
}

.shp-goal-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 72px;
  height: 72px;
  border-radius: 20px;
  background: var(--surface-2);
  font-size: 30px;
}

.shp-goal-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.shp-goal-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  font-size: 13px;
}

.shp-goal-name strong {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.015em;
}

.shp-goal-name .shp-tag { position: static; box-shadow: none; }

.shp-goal-meter {
  display: flex;
  align-items: center;
  gap: 12px;
}

.shp-goal-meter .shop-meter { flex: 1; height: 8px; }

.shp-goal-meter > span {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.shp-goal-meter b { color: var(--text); }

.shp-goal-line {
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
}

.shp-goal-line strong { color: var(--text); }

.shp-goal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.shp-afford {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
}

.shp-afford > span { color: var(--muted); font-size: 12.5px; }

.card.shp-season {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 22px;
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.card.shp-season:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }

.shp-season-top {
  display: flex;
  align-items: center;
  gap: 12px;
}

.shp-season-top strong {
  display: block;
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.shp-season-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 13px;
  background: var(--surface-2);
  font-size: 23px;
}

.shp-season-meter {
  display: flex;
  align-items: center;
  gap: 12px;
}

.shp-season-meter .shop-meter { flex: 1; height: 8px; }

.shp-season-meter > span:last-child {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.shp-season-meter b { color: var(--text); }

.shp-season-note { color: var(--muted); font-size: 13px; }
.shp-season-cta { align-self: flex-start; margin-top: auto; }

/* ---------- The picture: what you are buying ---------- */

.shp .shop-preview {
  position: relative;
  display: grid;
  place-items: center;
  height: 140px;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  font-size: 44px;
}

.shp .shop-preview::before { display: none; }

/* A theme: its page background with a typing card sitting on it. */
.shp .shop-preview.shp-pv--theme,
.shp .shop-preview.shp-pv--limited {
  display: block;
  background: var(--bg);
}

.shp-mock {
  position: absolute;
  inset: 18px 16px 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  border: var(--card-border);
  border-bottom: 0;
  border-radius: 10px 10px 0 0;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  text-align: left;
}

.shp-mock-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.shp-mock-pill {
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.5;
}

.shp-mock-wpm {
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 600;
}

.shp-mock-wpm b {
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 800;
}

.shp-mock-icon {
  margin-left: auto;
  font-size: 17px;
  line-height: 1;
}

.shp-mock-text {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  color: var(--muted);
  font-family: var(--test-font);
  font-size: 14px;
  line-height: 1.55;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.shp-mock-text .is-typed { color: var(--text); }

.shp-mock-caret {
  display: inline-block;
  width: 2px;
  height: 1.1em;
  margin: 0 2px 0 1px;
  border-radius: 1px;
  background: var(--accent);
  vertical-align: -0.18em;
  animation: shpCaret 1.1s steps(1) infinite;
}

@keyframes shpCaret { 50% { opacity: 0; } }

.shp .shop-demo-caret { font-size: 30px; }

.shp-pv-glyph { position: relative; z-index: 1; font-size: 44px; line-height: 1; }

.shp-pv--sound .shp-pv-glyph { transform: translateY(-8px); }

/* A sound: a still equaliser under the icon — the Play button is the demo. */
.shp-pv-wave {
  position: absolute;
  bottom: 18px;
  left: 50%;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 18px;
  transform: translateX(-50%);
}

.shp-pv-wave i {
  width: 4px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--text) 22%, transparent);
}

.shp-pv-wave i:nth-child(1) { height: 30%; }
.shp-pv-wave i:nth-child(2) { height: 70%; }
.shp-pv-wave i:nth-child(3) { height: 45%; }
.shp-pv-wave i:nth-child(4) { height: 100%; }
.shp-pv-wave i:nth-child(5) { height: 55%; }
.shp-pv-wave i:nth-child(6) { height: 80%; }
.shp-pv-wave i:nth-child(7) { height: 35%; }

/* An avatar: shown the way it appears in lists — a rounded square. */
.shp-pv-avatar {
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border-radius: 28%;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font-size: 40px;
  line-height: 1;
}

.shp .shp-pv--gems {
  background: radial-gradient(90% 90% at 50% 30%, color-mix(in srgb, #8b9dff 26%, transparent), transparent 75%), var(--surface-2);
}

/* Compact (Today's picks, the goal thumbnail). */
.shp .shop-preview.is-compact { height: 100px; font-size: 36px; }
.shp .is-compact .shp-mock { inset: 12px 10px 0; gap: 7px; padding: 9px 10px; }
.shp .is-compact .shp-mock-text { font-size: 12px; }
.shp .is-compact .shp-mock-wpm { display: none; }
.shp .is-compact .shp-mock-icon { font-size: 14px; }
.shp .is-compact .shp-pv-avatar { width: 58px; height: 58px; font-size: 30px; }
.shp .is-compact .shp-pv-glyph { font-size: 34px; }
.shp .is-compact .shp-pv-wave { bottom: 12px; height: 14px; }
.shp .is-compact .shop-demo-caret { font-size: 24px; }
.shp-goal-thumb .shop-preview.is-compact { height: 140px; }
.shp-goal-thumb .is-compact .shp-mock { inset: 16px 14px 0; }

/* ---------- Product cards ---------- */

.shp .shop-grid {
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 16px;
}

.shp .shop-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: none;
  animation: none;
  transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.shp .shop-card:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.shp .shop-card.is-equipped { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.shp .shop-card.is-pinned { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.shp .shop-card.jump-highlight { box-shadow: 0 0 0 2px var(--accent), var(--shadow-lg); }

.shp-pv-wrap {
  position: relative;
  padding: 8px 8px 0;
}

.shp-state {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
}

.shp-state.is-equipped { background: var(--accent); color: var(--on-accent); }
.shp-state.is-pinned { color: var(--accent); }

.shp-pv-actions {
  position: absolute;
  right: 16px;
  bottom: 8px;
  z-index: 2;
  display: flex;
  gap: 6px;
}

.shp-pv-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  backdrop-filter: blur(6px);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.shp-pv-btn:hover { border-color: var(--accent); background: var(--surface); }

/* Desktop: the overlay buttons appear with the pointer, so the picture
   stays clean. Touch screens always show them. */
@media (hover: hover) {
  .shp .shop-card .shp-pv-actions { opacity: 0; transition: opacity var(--dur-1) var(--ease); }
  .shp .shop-card:hover .shp-pv-actions,
  .shp .shop-card:focus-within .shp-pv-actions,
  .shp .shop-card:has(.shp-pv--sound) .shp-pv-actions { opacity: 1; }
}

.shp .shop-card .shop-info {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6px;
  padding: 12px 16px 16px;
}

.shp .shop-title-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.shp .shop-card h3 {
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-size: 15.5px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shp .shop-price {
  flex: none;
  color: var(--text);
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.shp .shop-price s,
.shp-price s,
.shp .shop-set-price s {
  margin-right: 2px;
  color: var(--muted);
  font-size: 0.86em;
  font-weight: 500;
}

.shp-meta {
  display: -webkit-box;
  min-height: calc(2 * 1.45em);
  margin: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.45;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.shp .shop-scarcity { margin: 0; font-size: 12.5px; }

.shp .limited-note {
  margin: 0;
  color: var(--muted);
  font-size: 12.5px;
}

.shp-action {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 38px;
  margin-top: auto;
  padding-top: 8px;
}

.shp-btn { flex: 1; padding: 10px 14px; }

.shp-need {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  color: var(--muted);
  font-size: 12.5px;
}

.shp-need b { color: var(--text); }
.shp-need .shop-meter { height: 6px; }

.shp-pin {
  display: grid;
  flex: none;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.shp-pin:hover { border-color: var(--accent); color: var(--text); }
.shp-pin.is-pinned { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.shp-done {
  display: inline-flex;
  flex: 1;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.shp .shop-card.is-equipped .shp-done { color: var(--text); }
.shp .shop-card.is-equipped .shp-done i { color: var(--success); }

/* Quiet down the old "special card" effects — the tag and the picture
   carry that now. */
.shp .shop-card.premium,
.shp .shop-card.exclusive,
.shp .shop-card.is-deal,
.shp .shop-card--limited,
.shp .gem-package-card {
  border-color: var(--border);
  background: var(--surface);
  box-shadow: none;
  animation: none;
}

.shp .shop-card.premium:hover,
.shp .shop-card--limited:hover,
.shp .gem-package-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }

.shp .limited-progress .shop-meter { height: 6px; }

/* ---------- Tabs + toolbar ---------- */

.shp .shop-catalog > .sx-panel-head {
  align-items: center;
  padding-bottom: 6px;
  border-bottom: 0;
}

.shp .shop-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin: 0;
}

.shp .shop-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 0;
  margin: 0 0 20px;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  scrollbar-width: none;
}

.shp .shop-tabs::-webkit-scrollbar { display: none; }

.shp .shop-tab {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6px;
  margin-bottom: -1px;
  padding: 11px 8px 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.shp .shop-tab:hover { color: var(--text); }

.shp .shop-tab.active {
  border-bottom-color: var(--accent);
  color: var(--text);
}

.shp .shop-tab.gems-tab.active {
  border-bottom-color: #6f80f5;
  background: none;
  box-shadow: none;
  color: var(--text);
}

.shop-tab-count {
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.shp .shop-toolbar-empty {
  display: block;
  margin: -6px 0 16px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--text) 4%, transparent);
}

.shp .shop-toolbar-empty[hidden] { display: none; }

/* ---------- Today's picks ---------- */

.shp-picks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.shp-pick {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.shp-pick:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.shp-pick.is-deal { border-color: color-mix(in srgb, #e0a526 55%, var(--border)); }

.shp-pick .shop-preview { border-radius: 8px; }

.shp-pick .shp-tag { top: 12px; left: 12px; font-size: 11px; }

.shp-pick-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding: 9px 6px 4px;
}

.shp-pick-body strong {
  overflow: hidden;
  font-size: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shp-pick-meta { color: var(--muted); font-size: 12px; }

.shp-price {
  margin-top: 5px;
  font-size: 14px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.shp-pick.is-owned .shop-preview { opacity: 0.6; }

.shp-notes {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.shp-notes p {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
}

.shp-notes strong { color: var(--text); }
.shp-notes a { color: var(--text); font-weight: 600; }
.shp-note-icon { color: var(--text); }
.shp-notes .shop-link-btn { font-size: 13.5px; color: var(--text); text-decoration: underline; }

.shp .shop-early-row { margin-top: 12px; }

.shp .shop-early-item {
  border-color: var(--border);
  background: var(--surface-2);
}

/* ---------- Bundles, gems, collections ---------- */

.shp .shop-set {
  border: 1px solid var(--border);
  background: var(--surface);
}

.shp .shop-set-head { grid-template-columns: auto minmax(0, 1fr) auto; padding-right: 0; }
.shp .shop-set .shp-tag { position: static; grid-row: 1; grid-column: 3; align-self: start; box-shadow: none; }
.shp .shop-set-price strong { font-size: 20px; }
.shp .shop-set-foot .shp-need { flex-direction: row; gap: 4px; font-size: 13px; }

.shp .gem-earn-card {
  border-color: var(--border);
  background: var(--surface-2);
  box-shadow: none;
}

.shp .gem-package-card .shop-price { font-size: 17px; }

.shp .shop-coll { background: var(--surface-2); }

.shp .shop-coll-tiers li {
  border: 0;
  background: var(--surface);
}

.shp .shop-coll .shop-meter { height: 6px; }

/* ---------- Narrow screens ---------- */

@media (max-width: 960px) {
  .shp-spot { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  .card.shp-goal { flex-direction: column; align-items: stretch; gap: 16px; padding: 18px; }
  .shp-goal-thumb { width: 100%; }
  .shp-goal-icon { display: none; }
  .card.shp-season { padding: 18px; }

  /* Picks scroll sideways rather than stacking into a long column. */
  .shp-picks {
    grid-auto-columns: 150px;
    grid-auto-flow: column;
    grid-template-columns: none;
    margin-inline: -18px;
    padding: 2px 18px 6px;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .shp-picks::-webkit-scrollbar { display: none; }

  .shp .shop-catalog > .sx-panel-head { align-items: flex-start; }
}

/* Phone: each card is a row — picture left, details right — so a long
   tab is a short scroll. */
@media (max-width: 560px) {
  .shp .shop-grid { grid-template-columns: minmax(0, 1fr); gap: 10px; }

  .shp .shop-card:not(.gem-earn-card) {
    display: grid;
    grid-template-columns: 132px minmax(0, 1fr);
  }

  .shp .shop-card:hover { transform: none; }

  .shp .shp-pv-wrap { padding: 6px 0 6px 6px; }

  .shp .shop-card .shop-preview {
    height: 100%;
    min-height: 120px;
    border-right: 0;
    font-size: 36px;
  }

  .shp .shop-card .shp-mock { inset: 30px 8px 0; gap: 6px; padding: 8px 9px; }
  .shp .shop-card .shp-state { display: none; }
  .shp .shop-card:has(.shp-state) .shp-pv-actions { display: flex; }
  .shp .shop-card h3 { white-space: normal; }
  .shp-goal-thumb .shop-preview.is-compact { height: 112px; }
  .shp .shop-card .shp-mock-text { font-size: 11.5px; }
  .shp .shop-card .shp-mock-wpm { display: none; }
  .shp .shop-card .shp-pv-avatar { width: 56px; height: 56px; font-size: 30px; }

  .shp .shop-card .shp-tag,
  .shp .shop-card .shp-state { top: 9px; left: 11px; right: auto; padding: 2px 7px; font-size: 10.5px; }
  .shp .shop-card .shp-state { top: auto; bottom: 11px; }
  .shp .shop-card .shp-pv-actions { right: auto; left: 11px; bottom: 11px; }
  .shp .shop-card:has(.shp-state) .shp-pv-actions { display: none; }
  .shp .shp-pv-btn { padding: 4px 9px; font-size: 11px; }

  .shp .shop-card .shop-info { gap: 4px; padding: 11px 12px 12px; }
  .shp .shop-card h3 { font-size: 14.5px; }
  .shp .shop-price { font-size: 14px; }
  .shp-meta { min-height: 0; font-size: 12px; }
  .shp-action { min-height: 34px; padding-top: 6px; }
  .shp-btn { padding: 8px 12px; }
}

/* =========================================================
   PRACTICE (/practice/) — per-key heatmap + drills (main/practice.py).
   A key's colour runs green -> amber -> red with --w (0..1 weakness).
========================================================= */

.pr-sub { margin: 6px 0 0; color: var(--muted); font-size: 14px; }

.pr .sx-panel-head { align-items: center; }

.pr-toggle {
  display: inline-flex;
  padding: 3px;
  border-radius: var(--radius-full);
  background: var(--surface-2);
}

.pr-toggle button {
  padding: 6px 12px;
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.pr-toggle button.is-on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }

.pr-kb {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 26px 0 10px;
}

.pr-row { display: flex; gap: 8px; }
.pr-row-2 { margin-left: 22px; }
.pr-row-3 { margin-left: 60px; }

.pr-key {
  --heat: color-mix(in oklab, #e5484d calc(var(--w, 0) * 100%), #2fa86b);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: 58px;
  height: 58px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--muted);
  font-family: var(--test-font);
  font-size: 19px;
  font-weight: 600;
  text-decoration: none;
  text-transform: uppercase;
  box-shadow: 0 2px 0 var(--border);
  transition: transform var(--dur-1) var(--ease), background var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
}

.pr-key.has-data {
  border-color: color-mix(in srgb, var(--heat) 55%, var(--border));
  background: color-mix(in srgb, var(--heat) 24%, var(--surface));
  color: var(--text);
  box-shadow: 0 2px 0 color-mix(in srgb, var(--heat) 45%, var(--border));
}

.pr-key:hover { transform: translateY(-2px); border-color: var(--accent); }

.pr-key-stat {
  color: var(--muted);
  font-family: var(--font);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: none;
}

.pr-kb.is-empty .pr-key { opacity: 0.7; }

.pr-legend {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 6px;
  color: var(--muted);
  font-size: 12.5px;
}

.pr-legend i {
  width: 140px;
  height: 8px;
  border-radius: var(--radius-full);
  background: linear-gradient(90deg, #2fa86b, #e8a33d, #e5484d);
}

.pr-empty { margin-top: 12px; }

.pr-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}

.pr-weak {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}

.pr-weak li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.pr-weak-key {
  --heat: color-mix(in oklab, #e5484d calc(var(--w, 0) * 100%), #2fa86b);
  display: grid;
  flex: none;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--heat) 26%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--heat) 55%, var(--border));
  font-family: var(--test-font);
  font-size: 16px;
  font-weight: 700;
  text-transform: uppercase;
}

.pr-weak-body { flex: 1; color: var(--muted); font-size: 13px; }
.pr-weak-body b { color: var(--text); }

.pr-weak-go {
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

.pr-weak-go:hover { text-decoration: underline; }

.pr-cta { width: 100%; }

.pr-drills {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.pr-drill {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.pr-drill:hover { border-color: var(--accent); transform: translateY(-1px); }
.pr-drill strong { font-size: 14.5px; }
.pr-drill span { color: var(--muted); font-size: 12.5px; }

.pr-drill code {
  margin-top: 6px;
  color: var(--muted);
  font-family: var(--test-font);
  font-size: 12.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

@media (max-width: 860px) {
  .pr-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .pr-kb { gap: 5px; padding-top: 18px; }
  .pr-row { gap: 4px; }
  .pr-row-2 { margin-left: 12px; }
  .pr-row-3 { margin-left: 30px; }
  .pr-key { width: calc((100vw - 120px) / 10); height: 46px; border-radius: 8px; font-size: 15px; }
  .pr-key-stat { font-size: 8.5px; }
  .pr-toggle button { padding: 5px 9px; font-size: 12px; }
}

/* Language picker in the "What do you want to type?" panel (main/languages.py). */
.cp-lang-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 10px;
}

.cp-lang {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}

.cp-lang:hover { border-color: var(--accent); }
.cp-lang.is-on { border-color: var(--accent); background: var(--accent-soft); }
.cp-lang-flag { font-size: 22px; line-height: 1; }
.cp-lang strong { display: block; font-size: 14px; }
.cp-lang small { display: block; color: var(--muted); font-size: 12px; }

/* =========================================================
   CLASSROOM (team Assignments tab + the test-page banner), main/classroom.py
========================================================= */
.cls-form {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  align-items: end;
}

.cls-field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--text); }
.cls-field em { color: var(--muted); font-style: normal; font-weight: 500; }
.cls-title { grid-column: 1 / -1; }

.cls-field input,
.cls-field select {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-weight: 500;
}

.cls-field input:focus-visible,
.cls-field select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.cls-actions { display: flex; grid-column: 1 / -1; justify-content: flex-end; }
.org-panel .cls-field,
.org-panel .cls-field span { letter-spacing: normal; text-transform: none; }

.cls-list { display: flex; flex-direction: column; gap: 8px; }

.cls-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.cls-item.is-met { border-color: color-mix(in srgb, var(--success) 45%, var(--border)); }
.cls-item.is-overdue { border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); }
.cls-item-body { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.cls-item-body strong { font-size: 14.5px; }
.cls-item-body span { color: var(--muted); font-size: 12.5px; }
.cls-status { color: var(--muted); font-size: 13px; font-weight: 600; white-space: nowrap; }
.cls-item.is-met .cls-status { color: var(--success); }

.cls-table-wrap { overflow-x: auto; }

.cls-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
}

.cls-table th,
.cls-table td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  white-space: nowrap;
}

.cls-table thead th { color: var(--muted); font-size: 12px; font-weight: 600; vertical-align: bottom; }
.cls-table thead small,
.cls-table tbody th small { display: block; color: var(--muted); font-size: 11px; font-weight: 500; }
.cls-table tbody th a { color: var(--text); font-weight: 600; text-decoration: none; }
.cls-table tbody th a:hover { text-decoration: underline; }
.cls-table td.is-up { color: var(--success); font-weight: 600; }
.cls-table td.is-down { color: var(--danger); font-weight: 600; }
.cls-table td.is-met { color: var(--success); font-weight: 700; }
.cls-table td.is-tried { color: var(--text); }

.cls-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 1080px;
  margin: 0 auto 14px;
  padding: 10px 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--text);
  font-size: 13.5px;
}

.cls-banner span { flex: 1; }
.cls-banner a { color: var(--text); font-weight: 600; }

@media (max-width: 720px) {
  .cls-form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cls-item { flex-wrap: wrap; }
  .cls-item-body { flex-basis: 100%; }
}

/* =========================================================
   SEARCH LANDING PAGES (main/landing.py): heading above the test,
   a readable guide + FAQ below it.
========================================================= */
.ld-head { max-width: 1080px; margin: 0 auto 18px; }
.ld-head h1 { margin: 0; font-family: var(--font-display); font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
.ld-head p { max-width: 720px; margin: 6px 0 0; color: var(--muted); font-size: 15px; line-height: 1.55; }

.ld-guide { display: flex; flex-direction: column; gap: 22px; max-width: 1080px; margin: 24px auto 0; padding: 26px 28px; }
.ld-guide h2 { margin: 0 0 8px; font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
.ld-guide p { max-width: 760px; margin: 0 0 10px; color: var(--muted); font-size: 15px; line-height: 1.65; }

.ld-faq details { border-top: 1px solid var(--border); }
.ld-faq details:last-child { border-bottom: 1px solid var(--border); }
.ld-faq summary { padding: 12px 0; color: var(--text); font-size: 15px; font-weight: 600; cursor: pointer; }
.ld-faq details p { margin: 0 0 14px; }

.ld-more { display: flex; flex-wrap: wrap; gap: 8px; }
.ld-more a {
  padding: 7px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}
.ld-more a:hover { border-color: var(--accent); }

@media (max-width: 640px) {
  .ld-head h1 { font-size: 24px; }
  .ld-guide { padding: 20px 18px; }
}

/* =========================================================
   RANKED (race lobby card + ladder, race result line) — main/ranked.py
========================================================= */
.rk { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; }
.rk-card { display: flex; flex-direction: column; gap: 14px; padding: 24px; }
.rk-top { display: flex; align-items: center; gap: 14px; }
.rk-badge { display: grid; place-items: center; width: 54px; height: 54px; border-radius: 16px; background: var(--surface-2); font-size: 28px; }
.rk-top h2 { margin: 2px 0 0; font-family: var(--font-display); font-size: 22px; font-weight: 800; letter-spacing: -0.015em; }
.rk-meter { display: flex; align-items: center; gap: 12px; }
.rk-meter .shop-meter { flex: 1; height: 8px; }
.rk-meter span { color: var(--muted); font-size: 13px; white-space: nowrap; }
.rk-note { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
.rk-card .btn { align-self: flex-start; margin-top: auto; }

.rk-rows { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.rk-rows li { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--border); font-size: 14px; }
.rk-rows li:last-child { border-bottom: 0; }
.rk-rows li.is-me { background: var(--accent-soft); border-radius: var(--radius-sm); }
.rk-pos { width: 22px; color: var(--muted); font-variant-numeric: tabular-nums; text-align: right; }
.rk-rows a { flex: 1; color: var(--text); font-weight: 600; text-decoration: none; }
.rk-rows a:hover { text-decoration: underline; }
.rk-rows b { font-variant-numeric: tabular-nums; }

.race-rating { display: inline-flex; align-items: center; gap: 6px; margin-top: 10px; padding: 6px 12px; border-radius: var(--radius-full); background: var(--surface-2); font-size: 14px; }
.race-rating span { font-weight: 800; }
.race-rating.is-up span { color: var(--success); }
.race-rating.is-down span { color: var(--danger); }

@media (max-width: 860px) {
  .rk { grid-template-columns: minmax(0, 1fr); }
}

/* Team settings: Discord webhook row (main/discord.py) */
.dc-control { display: flex; flex-wrap: wrap; gap: 8px; }
.dc-control input { flex: 1 1 260px; min-width: 0; }

/* =========================================================
   STREAMER TOOLS (/stream/) - main/streaming.py
========================================================= */
.st-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }
.st-steps { margin: 0 0 14px; padding-left: 20px; color: var(--muted); font-size: 14px; line-height: 1.8; }
.st-steps strong { color: var(--text); }
.st-link { display: flex; gap: 8px; }
.st-link input {
  flex: 1;
  min-width: 0;
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}
.st-hint { margin: 10px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }
.st-hint code { padding: 1px 5px; border-radius: 5px; background: var(--surface-2); font-size: 12px; }
.st-regen { margin-top: 12px; }
.st-preview {
  display: grid;
  place-items: start;
  height: 250px;
  margin-bottom: 14px;
  border-radius: var(--radius-md);
  background: repeating-conic-gradient(color-mix(in srgb, var(--text) 6%, transparent) 0 25%, transparent 0 50%) 0 0 / 20px 20px;
  overflow: hidden;
}
.st-preview iframe { width: 100%; height: 100%; border: 0; background: transparent; }
.st-race-body { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
.st-race-line { margin: 0; color: var(--muted); font-size: 14px; }
.st-race-line a { color: var(--text); }
.st-race { margin-top: 0; }
@media (max-width: 860px) {
  .st-grid { grid-template-columns: minmax(0, 1fr); }
}
