/* learnfromx — atmospheric design system.
 *
 * Full-viewport SVG landscape with mountains, stones, birds, water.
 * Canvas particle system, parallax, per-page mood tinting.
 * Nunito for English, Zen Maru Gothic for Japanese.
 *
 * Extracted from mockups/14-full-system.html.
 * Replaces lav.css as the primary design system for SRS pages.
 */

@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&family=Space+Grotesk:wght@400;500;600;700&family=Zen+Maru+Gothic:wght@300;400;500;700&family=Noto+Sans+JP:wght@300;400;500;600;700&display=swap');

:root {
  --primary: #4A4878;
  --primary-soft: #6A68A0;
  --primary-tint: rgba(74, 72, 120, 0.08);
  --primary-tint2: rgba(74, 72, 120, 0.10);
  --gold: #d4a64a;
  --gold-soft: #f0c969;
  --gold-tint: #fbf4dc;
  --green-mature: #3e9963;
  --green-young: #7bc8a3;
  --learn-accent: #3D8B7A;
  --learn-tint: rgba(61, 139, 122, 0.08);
  --learning-bg: #fbf4dc;
  --rule: rgba(74, 72, 120, 0.12);
  --ink: #484668;
  --ink-soft: #504E68;
  --ink-muted: #7E7C94;
  --ink-faint: #A8A6B8;
  /* The drifting/foreground stones (rockFar gradient in atmosphere.html). The
     show title borrows this so it reads as part of the scene, not ink on top. */
  --stone: #627B9A;
  --stone-light: #7E96B2;
  --bg: #ffffff;
  /* Dark base for the logged-out dusk scene: the sunrise dusk-tint is
     translucent, so it composites against this rather than the white --bg.
     Shared by landing/auth/legal so their dusk reads identically. */
  --dusk-base: #41395f;
  --card-bg: rgba(255,255,255,0.55);
  --card-border: rgba(255,255,255,0.5);
  --card-bg-solid: rgba(255,255,255,0.82);
  --font-display: 'Nunito', 'Helvetica Neue', sans-serif;
  --font-en: 'Nunito', 'Helvetica Neue', sans-serif;
  --font-jp: 'Noto Sans JP', 'Hiragino Sans', system-ui, sans-serif;
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 14px;   /* cards — snapped down from 18 (read "too soft") */
  --radius-pill: 999px;

  /* ── Type scale ──────────────────────────────────────────────────────────
     One deliberate scale (~1.22 ratio) so sizes stay consistent across every
     surface. Components reference these roles, never raw px. Sizes were chosen
     to absorb the prior ad-hoc values (9–96px) into named steps. */
  --fs-eyebrow: 11px;   /* uppercase meta labels, badges, kbd */
  --fs-caption: 13px;   /* hints, fine print, secondary subs */
  --fs-sm:      14px;   /* small / secondary body */
  --fs-body:    16px;   /* default body, inputs, button text */
  --fs-lead:    18px;   /* lead text, emphasized body, list items */
  --fs-h3:      22px;   /* card / row titles */
  --fs-h2:      28px;   /* section + page titles (settings, prompts) */
  --fs-h1:      34px;   /* large page titles (auth, calibrate) */
  --fs-display: 42px;   /* hero / celebratory headings */

  /* ── Weights ── (only these five; kills stray 200/800) */
  --fw-light: 300;
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* ── Spacing scale ── (4px base; use for new/edited rules) */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 48px;

  /* Warm accent shared with the sunrise/landing theme (coral). Used for the
     refined-ghost primary CTA tint on logged-out surfaces. */
  --coral: #ec9572;
  --coral-tint: rgba(236,149,114,0.12);

  /* ── Home action ("D") palette ── the primary slot is the one warm/hot accent
     on the cool atmosphere; secondary actions get quiet, distinct chip tints so
     the column reads as a playful set. Big stat numbers echo each chip color. */
  --act-coral: #f07a54;        /* primary fill */
  --act-coral-deep: #e0603a;
  --act-sky: #3f9fd0;          --act-sky-tint: #dcefff;
  --act-amber: #d68a1f;        --act-amber-tint: #fbecca;
  --act-violet: #8a6bd4;       --act-violet-tint: #ece4fb;
  --act-coral-tint: #fde4da;

  /* ── Per-action identity (icon-stack home) ── each action's line-art glyph
     stays neutral stone-slate as a secondary; it lifts to its own colour only
     in the single primary slot. One muted natural-pigment family (L≈38%):
     rose-red (akane), hanada blue, pine green, plum-violet — distinct from the
     gold reserved for Deep review. */
  --act-review:  #9f2256;              /* review — rose-red (pinker) */
  --act-audio:   #22749f;              /* audio  — hanada blue */
  --act-learn:   var(--learn-accent);  /* learn  — pine green (#3D8B7A) */
  --act-reading: #7a4d93;              /* reading — plum-violet */

  /* ── Elevation ── Layered shadows (tight contact + soft ambient) so cards
     read as sitting on a surface, not floating in haze. Was a single diffuse
     `0 4px 40px rgba(...,0.06)` blur that looked unanchored. */
  --shadow-sm: 0 1px 2px rgba(46,40,70,0.05), 0 2px 6px -2px rgba(46,40,70,0.08);
  --shadow-card: 0 1px 1px rgba(46,40,70,0.04), 0 5px 12px -4px rgba(46,40,70,0.10), 0 14px 32px -12px rgba(46,40,70,0.14);
  --shadow-pop: 0 2px 4px rgba(46,40,70,0.06), 0 10px 20px -6px rgba(46,40,70,0.12), 0 24px 48px -16px rgba(46,40,70,0.18);
  --card-edge: 1px solid rgba(74,72,120,0.07);

  /* Rating colors (carry over from lav.css) */
  --rate-again: #e25555;
  --rate-hard: #e89a3a;
  --rate-good: #4caa6e;
  --rate-easy: #5a73d8;

  /* Card states */
  --lapsed-bg: #fde6d6;
  --lapsed-edge: #d97a4a;
}

/* ── Reset ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
button, input, select, textarea { font: inherit; }
html { font-size: 16px; -webkit-font-smoothing: antialiased; }
body {
  color: var(--ink);
  font-family: var(--font-display);
  min-height: 100vh; min-height: 100dvh;
  background: var(--bg);
}

/* ── Atmosphere (fixed SVG landscape) ── */
/* `bottom: -2px` bleeds the decorative SVG just past the viewport's lower edge.
   In a `viewport-fit=cover` PWA a fixed element's bottom rounds a hair short of
   the physical screen, exposing a sub-pixel strip of the white `body` bg (the
   "thin white line that vanishes on scroll"). The SVG uses `slice`, so painting
   a couple px below the fold is free and closes the gap on every tier. */
.atmosphere { position: fixed; inset: 0 0 -2px; z-index: 54; pointer-events: none; }
.atmosphere svg { width: 100%; height: 100%; pointer-events: none; }
#birdCanvas { position: fixed; inset: 0; z-index: 54; pointer-events: none; }
#particles { position: fixed; inset: 0; z-index: 60; pointer-events: none; }

/* Stone/terrain overlay for the marketing scene. Sits BELOW the landing's .ld
   scroll container (z-index 55) but ABOVE the opaque sky + dusk tint (z-index 54),
   so the foreground stones read as part of the scene yet never occlude the
   product-preview cards that scroll over them. It stays a SEPARATE overlay (not
   folded into the main SVG) only to carry its own dusk silhouette filter below;
   pointer-events:none throughout — decorative, like the stones on every in-app
   page. (Earlier it was lifted to z-56 to stay hover-interactive, but that
   floated the stones on top of the left card — readability of the cards wins.) */
.atmosphere-stones { position: fixed; inset: 0; z-index: 54; pointer-events: none; }
.atmosphere-stones svg { width: 100%; height: 100%; pointer-events: none; }
/* The overlay sits above the dusk tint, so its stones would otherwise show
   their raw cool blue-grey against the warm sunrise. Darken them into near
   silhouettes (as if backlit by the rising sun) with a slight warm shift. */
.atmosphere-stones { filter: brightness(0.82) saturate(0.92) hue-rotate(10deg); }

.stone {
  cursor: pointer;
  transition: transform 0.6s var(--ease-out-expo);
  transform-origin: center bottom;
  pointer-events: auto;
}
.stone:hover { transform: translateX(3px); }
.stone .stone-hit { pointer-events: auto !important; }
.stone, .stone .stone-hit { pointer-events: auto !important; }
/* Reflection rides along with its stone's hover transform, but must not
   widen the hover hit area (it lives in the water below the stone). */
.stone .stone-reflection { pointer-events: none; }

.mountain {
  transform-origin: center bottom;
  will-change: transform;
}


/* ── Atmosphere moods (per-page tinting) ── */
.atmosphere { transition: filter 1.4s ease; }
/* Per-page color filters are intentionally absent — every page (including
   marketing) shares the same neutral cool baseline. Visual variation comes
   from user state (caught-up, night), not page identity. The only per-mood
   rule is hiding birds during study to reduce distraction. */
/* State-driven: active tint (things to do). */
.atmosphere:not(.is-caught-up):not(.is-night) {
  filter: saturate(1.12);
}
/* Active state stays on the cool misty baseline (dusk-tint hidden). The single
   warm mark is the primary action's glyph, not the whole field — so the rose /
   blue / green / violet identity colours read as accents on a cool ground. */

/* Caught-up: warm dusk — the sunrise gradient on .atmosphere-dusk-tint (below),
   dimmed + pushed toward amber by this filter. One treatment for every mood, so
   the in-app caught-up dusk matches the landing's. */
.atmosphere.is-caught-up {
  filter: brightness(0.89) saturate(1.12);
}
.atmosphere.is-caught-up ~ .home-vignette { opacity: 0 !important; }

/* Night state: applied when the user has hit NEW_WORDS_DAILY_CAP across
   every active mode (3 sessions × 4 = 12 new words) AND has zero reviews.
   The SVG sky is white, so a CSS filter alone can't add chroma — the tint
   below adds the nocturnal blue. The filter only dims and slightly
   desaturates so the overlay's navy reads cleanly against the underlying
   sky and stones. No hue-rotate — rotating an already-blue palette
   pushes it toward sepia/amber. */
.atmosphere.is-night {
  filter: brightness(0.55) saturate(0.85);
}

/* The night overlay lives inside .atmosphere so it stacks above the SVG
   but below page content (content panels have their own z-index outside
   this wrapper). Default opacity is 0 — only .is-night reveals it, with
   a slow transition that matches the atmosphere filter glide. The
   gradient runs darkest-at-top → slightly-paler-at-horizon to mimic a
   real night sky (overhead is the darkest part). */
.atmosphere-night-tint {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.8s ease;
  background: linear-gradient(to bottom,
    rgba(6, 10, 28, 0.92) 0%,
    rgba(14, 22, 58, 0.82) 55%,
    rgba(28, 40, 88, 0.62) 100%);
}
.atmosphere.is-night .atmosphere-night-tint { opacity: 1; }
/* The caught-up dusk tint: a warm sunrise — a medium violet sky up top warming
   down through mauve/magenta to a coral/amber horizon, with a soft radial
   sun-glow cresting the bottom edge. The mid stops stay opaque enough (~0.56)
   behind hero/card text to keep thin light type legible. Held at opacity 0 until
   JS adds .is-caught-up. This is the SINGLE dusk tint for EVERY mood — landing,
   auth, legal AND the in-app caught-up surfaces (home/study/grid/settings/
   primers) — so the in-app "all caught up" dusk reads identically to the
   landing. (It used to be a flat rgba(120,80,150,0.30) wash with the sunrise
   scoped to public moods only, which left the in-app dusk cool while the landing
   was warm.) */
.atmosphere-dusk-tint {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.4s ease;
  background:
    radial-gradient(120% 58% at 50% 109%,
      rgba(255, 179, 121, 0.34) 0%,
      rgba(255, 149, 108, 0.15) 36%,
      rgba(255, 149, 108, 0) 64%),
    linear-gradient(to bottom,
      rgba(71, 63, 113, 0.64) 0%,
      rgba(102, 68, 119, 0.60) 34%,
      rgba(151, 83, 113, 0.56) 60%,
      rgba(213, 121, 104, 0.50) 85%,
      rgba(242, 168, 113, 0.45) 100%);
}
.atmosphere.is-caught-up .atmosphere-dusk-tint { opacity: 1; }

/* The logged-out dusk scene needs a dark base under the translucent sunrise
   tint; on the default white body it washes out lighter (landing got this via
   landing.css, auth/legal didn't, so their dusk diverged from the landing).
   Single-source it for all three public dusk moods. Keyed on the
   server-rendered mood class, NOT .is-caught-up (added later by JS), so the base
   is dark from first paint with no white flash. */
body:has(.atmosphere--marketing),
body:has(.atmosphere--auth),
body:has(.atmosphere--legal) {
  background: var(--dusk-base);
}

/* (The sunrise dusk tint + its brightness/saturate filter used to be scoped to
   the public moods here, with the in-app moods left on a flat cool wash. They're
   now unified: the sunrise lives on .atmosphere-dusk-tint and the filter on the
   generic .atmosphere.is-caught-up above, so every caught-up surface — public or
   in-app — gets the same warm dusk as the landing.) */

/* ── Enchantment layer ── */
@keyframes glowDrift {
  0%,100% { text-shadow: 0 0 12px rgba(74,72,120,0.0), 0 0 40px rgba(74,72,120,0.0); }
  40% { text-shadow: 0 0 16px rgba(74,72,120,0.12), 0 0 50px rgba(74,72,120,0.06); }
  70% { text-shadow: 0 0 10px rgba(196,162,85,0.10), 0 0 35px rgba(196,162,85,0.04); }
}
@keyframes vignettePulse {
  0%,100% { opacity: 0.4; }
  50% { opacity: 0.55; }
}
@keyframes actionGlow {
  0%,100% {
    text-shadow:
      0 0 12px rgba(196,162,85,0.20),
      0 0 40px rgba(196,162,85,0.12),
      0 0 70px rgba(196,162,85,0.05);
  }
  50% {
    text-shadow:
      0 0 18px rgba(196,162,85,0.38),
      0 0 55px rgba(196,162,85,0.20),
      0 0 90px rgba(96,94,148,0.08);
  }
}


.home-vignette {
  position: fixed; inset: 0; z-index: 45; pointer-events: none;
  background: radial-gradient(ellipse 80% 70% at 50% 50%, rgba(210,185,130,0.06) 0%, rgba(195,170,120,0.03) 40%, transparent 70%);
  animation: vignettePulse 20s ease-in-out infinite;
  transition: opacity 1.4s ease;
}

.noise-overlay {
  position: fixed; inset: 0; z-index: 48; pointer-events: none;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
  transition: opacity 1.4s ease;
}

/* ── Shared animations ── */
@keyframes fadeUp { from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);} }
@keyframes fadeIn { from{opacity:0;}to{opacity:1;} }

/* ── Shared components ── */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 20px 24px 0; flex-shrink: 0; position: relative; z-index: 2;
  pointer-events: none;
}
.topbar > * { pointer-events: auto; }
@media (min-width: 800px) { .topbar { padding: 20px 32px 0; } }

.brand {
  font: var(--fw-light) var(--fs-lead)/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.15em; text-transform: uppercase;
  text-decoration: none;
}

/* The settings gear lives only on home now, anchored to the bottom-left corner
   (out of the topbar so the top is free for the brand + show title). */
.settings-btn {
  position: fixed; left: 16px; bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 58;
  width: 44px; height: 44px; border: none; background: transparent;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; transition: background 0.2s;
}
@media (min-width: 800px) { .settings-btn { left: 24px; bottom: 24px; } }
.settings-btn:hover { background: var(--primary-tint); }
.settings-btn svg { width: 28px; height: 28px; stroke: var(--ink-muted); fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

.back-link {
  font: var(--fw-light) var(--fs-sm)/1 var(--font-en); color: var(--ink-muted);
  letter-spacing: 0.1em; text-transform: lowercase;
  border: none; background: none; cursor: pointer;
  padding: 8px 0; transition: color 0.2s; text-decoration: none;
  opacity: 0; animation: settingsFade 0.3s 0.02s ease forwards;
}
.back-link:hover { color: var(--primary); }

/* ── Floating white panels (landscape pages) ── */
.float-panel {
  background: rgba(255,255,255,0.96);
  border: var(--card-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 28px 24px;
  position: relative; z-index: 2;
}
@media (min-width: 800px) { .float-panel { padding: 36px 32px; } }
.float-panel--full {
  margin: 0 12px; flex: 1; display: flex; flex-direction: column;
  max-height: calc(100dvh - 120px);
  overflow-y: auto;
}
@media (min-width: 800px) { .float-panel--full { margin: 0 auto; max-width: 860px; } }

/* Card panels */
.card-float {
  background: var(--card-bg-solid);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border: var(--card-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 32px 36px;
  width: 100%; max-width: 680px;
}
@media (min-width: 800px) { .card-float { padding: 48px 60px; max-width: 820px; } }

/* Content shell — settings / calibrate */
.content-shell {
  background: var(--card-bg-solid);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border: var(--card-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 28px;
  width: 100%; max-width: 420px;
  margin: 0 auto 24px;
  position: relative; z-index: 2;
  opacity: 0; animation: contentShellIn 0.4s 0.04s cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes contentShellIn {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
@media (min-width: 800px) { .content-shell { padding: 32px 36px; max-width: 480px; } }
/* Vertically center the card in the remaining space below the topbar (keeps it
   from floating high with dead space below — e.g. settings). */
.content-shell--center { margin: auto; }

/* ── Buttons ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font: var(--fw-medium) var(--fs-body)/1 var(--font-en);
  padding: 15px 32px; border-radius: var(--radius-lg);
  letter-spacing: 0.04em; text-decoration: none;
  cursor: pointer; transition: all 0.25s; border: 1px solid transparent;
}
/* In-app primary == solid indigo (matches the study "Show answer" CTA). This
   also gives .btn--primary a real fill: it previously had NO fill rule, so
   calibrate's "Continue" rendered as bare text in a padded box. */
.btn--solid,
.btn--primary { background: var(--primary); color: #fff; }
.btn--solid:hover,
.btn--primary:hover { background: #5A588A; }
.btn--ghost {
  background: var(--card-bg); color: var(--ink-muted);
  border: 1px solid var(--card-border);
  backdrop-filter: blur(8px);
}
.btn--ghost:hover { color: var(--primary); border-color: rgba(74,72,120,0.15); }

/* ── Phase system (for SPA-like page switching) ── */
.phase {
  position: fixed; inset: 0;
  transition: opacity 0.5s ease, transform 0.5s var(--ease-out-expo); z-index: 1;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.phase.hidden { opacity: 0; pointer-events: none; transform: translateY(8px); }
.phase:not(.hidden) { z-index: 55; }

/* ── Grid tiles ── */
.tile-grid {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 3px; max-width: 100%; margin: 0 auto;
}
@media (min-width: 720px) { .tile-grid { grid-template-columns: repeat(8, 1fr); gap: 4px; } }

.tile {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border-radius: 4px; position: relative; overflow: hidden;
  border: 1px solid transparent; transition: background 0.3s, border-color 0.3s;
}
.tile-text {
  font-family: var(--font-jp); font-weight: 400;
  font-size: 21px; line-height: 1; text-align: center;
  transition: color 0.3s; position: relative; z-index: 1;
}
@media (min-width: 720px) { .tile-text { font-size: 24px; } }
.tile-text[data-len="3"] { font-size: 16px; }
.tile-text[data-len="4"],
.tile-text[data-len="5"],
.tile-text[data-len="6"] { font-size: 14px; }

.tile--unseen { background: rgba(195,189,212,0.10); border-color: rgba(195,189,212,0.05); }
.tile--unseen .tile-text { color: rgba(140,130,165,0.28); }
.tile--mature { background: var(--green-mature); border-color: rgba(30,80,50,0.10); }
.tile--mature .tile-text { color: rgba(255,255,255,0.90); }
.tile--young { background: var(--green-young); border-color: rgba(62,153,99,0.12); }
.tile--young .tile-text { color: rgba(255,255,255,0.85); }
.tile--learning { background: var(--learning-bg); border-color: rgba(232,184,100,0.25); }
.tile--learning .tile-text { color: #8a6e20; }
.tile--due { background: rgba(255,255,255,0.75); border-color: var(--gold); animation: duePulse 2.5s ease-in-out infinite; }
.tile--due .tile-text { color: #7a5a10; }
@keyframes duePulse { 0%,100%{box-shadow:0 0 0 0 rgba(196,162,85,0);}50%{box-shadow:0 0 0 3px rgba(196,162,85,0.22);} }
.tile--lapsed { background: var(--lapsed-bg); border-color: var(--lapsed-edge); }
.tile--lapsed .tile-text { color: #8b4020; }

/* New tile (pre-bloom) */
.tile--new { background: rgba(195,189,212,0.12); border-color: rgba(195,189,212,0.06); }
.tile--new .tile-text { color: rgba(140,130,165,0.32); }
.tile--new.bloomed { background: var(--green-young); border-color: rgba(62,153,99,0.15); }
.tile--new.bloomed .tile-text { color: rgba(255,255,255,0.88); }

/* Bloom animation */
.tile--new.blooming { animation: tileBloom 0.9s var(--ease-out-expo) forwards; }
@keyframes tileBloom {
  0%{background:rgba(195,189,212,0.14);border-color:rgba(195,189,212,0.07);box-shadow:none;}
  25%{background:rgba(196,162,85,0.28);border-color:rgba(196,162,85,0.50);box-shadow:0 0 14px rgba(196,162,85,0.30);}
  50%{background:rgba(196,162,85,0.14);border-color:rgba(196,162,85,0.25);}
  100%{background:var(--green-young);border-color:rgba(62,153,99,0.15);box-shadow:none;}
}
.tile--new.blooming .tile-text { animation: tileTextBloom 0.9s var(--ease-out-expo) forwards; }
@keyframes tileTextBloom { 0%{color:rgba(140,130,165,0.38);}25%{color:rgba(170,130,50,0.9);}100%{color:rgba(255,255,255,0.88);} }
.tile-glow {
  position: absolute; inset: -5px; border-radius: 8px;
  background: radial-gradient(circle, rgba(255,235,170,0.50) 0%, rgba(196,162,85,0.25) 40%, transparent 70%);
  opacity: 0; pointer-events: none;
}
.tile--new.blooming .tile-glow { animation: glowFlash 0.9s var(--ease-out-expo) forwards; }
@keyframes glowFlash { 0%{opacity:0;transform:scale(0.5);}15%{opacity:1;transform:scale(1.2);}40%{opacity:0.6;transform:scale(1.4);}70%{opacity:0.2;transform:scale(1.6);}100%{opacity:0;transform:scale(1.8);} }

/* Learn grid: dramatic multi-phase bloom */
.tile--new.bloom-learn { animation: bloomLearn 0.8s ease forwards; }
@keyframes bloomLearn {
  0%{background:rgba(195,189,212,0.14);border-color:rgba(195,189,212,0.07);box-shadow:none;}
  20%{background:rgba(232,160,191,0.45);border-color:rgba(232,160,191,0.60);box-shadow:0 0 20px 6px rgba(232,160,191,0.35);}
  45%{background:rgba(212,166,74,0.50);border-color:rgba(212,166,74,0.60);box-shadow:0 0 22px 4px rgba(212,166,74,0.40);}
  100%{background:var(--green-young);border-color:rgba(110,203,160,0.20);box-shadow:0 0 10px 2px rgba(110,203,160,0.30);}
}
.tile--new.bloom-learn .tile-text { animation: tileTextBloom 0.8s ease forwards; }
.tile--new.bloom-learn .tile-glow { animation: glowFlash 0.8s ease forwards; }
.tile--new.learned-glow { background: var(--green-young); border-color: rgba(110,203,160,0.20);
  box-shadow: 0 2px 12px rgba(110,203,160,0.3), 0 0 20px rgba(110,203,160,0.08), inset 0 1px 0 rgba(255,255,255,0.25);
}
.tile--new.learned-glow .tile-text { color: rgba(255,255,255,0.88); }
.tile--new.learned-breathe { animation: tileBreathGlow 4s ease-in-out infinite; }
@keyframes tileBreathGlow {
  0%,100%{box-shadow:0 2px 10px rgba(110,203,160,0.2), inset 0 1px 0 rgba(255,255,255,0.2);}
  50%{box-shadow:0 2px 14px rgba(110,203,160,0.35), 0 0 20px rgba(110,203,160,0.1), inset 0 1px 0 rgba(255,255,255,0.25);}
}

/* Firefly canvas */
#fireflyCanvas { position:fixed; inset:0; pointer-events:none; z-index:61; opacity:0; transition:opacity 2s ease; }
#fireflyCanvas.visible { opacity:1; }

/* ── Grid layout ── */
.grid-header { text-align: center; padding: 8px 24px 4px; position: relative; z-index: 2; }
.grid-show {
  font: 300 36px/1.1 var(--font-display); color: var(--ink);
  letter-spacing: 0.06em;
}
@media (min-width: 720px) { .grid-show { font-size: 47px; } }
.grid-count {
  font: 200 36px/1 var(--font-display); color: var(--primary-soft);
  opacity: 0; transition: opacity 0.5s ease; margin-top: 2px;
}
.grid-count.visible { opacity: 0.45; }
.grid-wrap { flex: 1; padding: 0 16px 100px; position: relative; z-index: 2; }
@media (min-width: 720px) { .grid-wrap { padding: 0 32px 100px; } }

.grid-bottom {
  position: fixed; bottom: 58px; left: 0; right: 0;
  padding: 16px 28px; display: flex; justify-content: center;
  background: none;
  opacity: 0; transition: opacity 0.6s ease; pointer-events: none; z-index: 10;
}
.grid-bottom.visible { opacity: 1; pointer-events: auto; }
.grid-bottom button {
  font: 300 17px/1 var(--font-en);
  padding: 12px 32px; letter-spacing: 0.1em; text-transform: lowercase;
  background: var(--card-bg); backdrop-filter: blur(12px);
  border: 1px solid var(--card-border); border-radius: 18px;
  color: var(--ink-muted); cursor: pointer; transition: all 0.2s;
}
.grid-bottom button:hover { color: var(--primary); border-color: rgba(74,72,120,0.15); }

/* Grid stats bar */
.grid-stats {
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  justify-content: center; padding: 6px 16px 10px;
  position: relative; z-index: 2;
}
.grid-stat {
  display: flex; align-items: center; gap: 5px;
  font: 300 13px/1 var(--font-en); color: var(--ink-muted);
  letter-spacing: 0.03em;
}
.grid-stat__dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
}
.grid-stat__dot--mature { background: var(--green-mature); }
.grid-stat__dot--young { background: var(--green-young); }
.grid-stat__dot--learning { background: var(--learning-bg); border: 1px solid rgba(196,162,85,0.30); }
.grid-stat__dot--due { background: var(--gold); }
.grid-stat__dot--lapsed { background: var(--lapsed-bg); border: 1px solid var(--lapsed-edge); }
.grid-stat__dot--calibrated { background: rgba(74,72,120,0.25); }
.grid-stat__dot--assumed_known { background: rgba(74,72,120,0.15); }
.grid-stat__dot--unseen { background: rgba(195,189,212,0.25); border: 1px solid rgba(195,189,212,0.15); }
.grid-stat__count { font-weight: 500; }
.grid-stat__link { color: var(--primary-soft); text-decoration: none; }
.grid-stat__link:hover { color: var(--primary); }

/* Tiers */
.grid-tier { padding: 0 0 16px; position: relative; z-index: 2; }
.grid-hint {
  font: 300 13px/1.4 var(--font-en); color: var(--ink-faint);
  text-align: center; padding: 0 16px 8px; letter-spacing: 0.02em;
}

/* Tier separator */
.grid-sep {
  height: 1px;
  background: linear-gradient(90deg,
    transparent,
    var(--lav-purple-soft, #6A68A0) 30%,
    var(--lav-purple-soft, #6A68A0) 70%,
    transparent);
  opacity: 0.25;
  margin: 16px 0;
}

/* Locked-tier teaser (ghost row, mask-faded) */
.grid-teaser {
  padding: 0;
  position: relative;
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
  mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
}
.grid-teaser__lock {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  color: var(--lav-ink-muted, #8d87a4);
  opacity: 0.55;
  pointer-events: none;
}
.grid-teaser__ghost {
  background: rgba(195,189,212,0.18) !important;
  border-color: rgba(195,189,212,0.10) !important;
  filter: blur(6px);
  -webkit-filter: blur(6px);
  user-select: none;
  -webkit-user-select: none;
}
.grid-teaser__ghost .lav-tile__kanji {
  opacity: 0.35;
  color: var(--lav-ink-muted, #8d87a4);
}
.grid-teaser__ghost .lav-tile__count {
  display: none;
}
.grid-teaser .tile-grid {
  grid-template-columns: repeat(8, 1fr) !important;
}

/* Tile count badge */
.tile-count {
  position: absolute; bottom: 2px; right: 3px;
  font: 400 9px/1 var(--font-en); color: rgba(0,0,0,0.18);
  pointer-events: none;
}
.tile--mature .tile-count,
.tile--young .tile-count { color: rgba(255,255,255,0.35); }
.tile--active { outline: 2px solid var(--primary); outline-offset: -1px; z-index: 1; }

/* Grid FAB */
.grid-fab {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  font: 400 15px/1 var(--font-en); letter-spacing: 0.06em;
  color: #fff; background: var(--primary);
  padding: 14px 28px; border-radius: 18px;
  text-decoration: none; z-index: 60;
  box-shadow: 0 4px 20px rgba(74,72,120,0.30);
  transition: transform 0.2s, box-shadow 0.2s;
}
.grid-fab:hover { transform: translateX(-50%) scale(1.03); box-shadow: 0 6px 28px rgba(74,72,120,0.40); }
.grid-fab__count {
  font-weight: 300; opacity: 0.8; font-size: 13px;
}

/* Grid drawer */
.grid-drawer {
  position: fixed; bottom: 0; left: 0; right: 0;
  background: var(--card-bg); backdrop-filter: blur(16px);
  border-top: 1px solid var(--card-border);
  border-radius: 16px 16px 0 0;
  padding: 20px 24px 28px; z-index: 62;
  transform: translateY(100%); transition: transform 0.3s var(--ease-out-expo);
}
.grid-drawer.is-open { transform: translateY(0); }
.grid-drawer__close {
  position: absolute; top: 12px; right: 16px;
  background: none; border: none; color: var(--ink-faint);
  cursor: pointer; padding: 4px;
}
.grid-drawer__jp {
  font: 400 32px/1.2 var(--font-jp); color: var(--ink);
  margin-bottom: 4px;
}
.grid-drawer__rank {
  font: 300 13px/1 var(--font-en); color: var(--ink-faint);
  letter-spacing: 0.04em; margin-bottom: 2px;
}
.grid-drawer__state {
  display: inline-block; font: 400 12px/1 var(--font-en);
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 3px 10px; border-radius: 10px;
  background: rgba(195,189,212,0.15); color: var(--ink-muted);
}
.grid-drawer__state--mature { background: var(--green-mature); color: #fff; }
.grid-drawer__state--young { background: var(--green-young); color: #fff; }
.grid-drawer__state--learning { background: var(--learning-bg); color: #8a6e20; }
.grid-drawer__state--due { background: rgba(196,162,85,0.15); color: #7a5a10; }
.grid-drawer__state--lapsed { background: var(--lapsed-bg); color: #8b4020; }

/* ── Home page ── */
.home-body {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 0 24px; min-height: 0;
  position: relative; z-index: 2;
}
.home-content {
  display: flex; flex-direction: column;
  align-items: center; gap: 0;
  width: 100%; max-width: 400px;
  position: relative;
  height: calc(100dvh - 80px);
}
@media (min-width: 800px) {
  .home-content {
    max-width: none;
    width: min(65vh, 500px);
    height: calc(100dvh - 80px);
    margin: 0 auto;
  }
}

/* Title */
.home-title-area {
  display: flex; flex-direction: column; align-items: flex-end;
  opacity: 0; animation: fadeUp 0.8s ease 0.1s forwards;
  position: fixed; top: 78px; left: calc(50% + 405.5px);
  z-index: 1;
}
@media (max-width: 799px) {
  /* gear moved to bottom-left, so the title takes the freed top-right corner.
     `env(safe-area-inset-top)` keeps the title clear of the notch/status bar in
     a standalone PWA (the topbar already pads for it; this fixed element must
     too) and sits it a touch lower than the brand line. */
  .home-title-area { left: auto; right: 16px; top: calc(28px + env(safe-area-inset-top)); translate: none; }
}
.title-comp {
  display: flex; align-items: flex-start; justify-content: flex-end;
  gap: 12px; padding: 0 0 10px;
  cursor: pointer; pointer-events: auto;
  transition: opacity 0.25s;
  position: relative;
}
.title-comp:hover { opacity: 0.7; }
.title-comp:hover .jp-title { color: #54687f; }
.title-comp:hover .title-side { color: var(--stone); }
.jp-title {
  writing-mode: vertical-rl;
  font-family: var(--font-jp); font-weight: 300;
  font-size: 26px; color: var(--stone);
  letter-spacing: 0.1em; line-height: 1.15;
  text-shadow: 1px 1px 2px rgba(0,0,0,0.08), 0 0 20px rgba(96,94,148,0.08);
  max-height: 45vh;
  animation: glowDrift 16s ease-in-out 1s infinite;
  transition: color 0.25s;
}
@media (min-width: 800px) { .jp-title { font-size: 32px; max-height: 50vh; } }
.title-side {
  writing-mode: vertical-rl;
  font: var(--fw-light) var(--fs-caption)/1 var(--font-en);
  color: var(--stone-light); letter-spacing: 0.15em;
  text-transform: lowercase; margin-top: 2px; order: -1;
  transition: color 0.25s;
}
.show-switcher-menu {
  writing-mode: horizontal-tb;
  position: absolute; top: 0; right: calc(100% + 10px);
  background: rgba(255,255,255,0.92); backdrop-filter: blur(16px);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 6px; min-width: 200px;
  box-shadow: 0 8px 32px rgba(42,36,64,0.12);
  opacity: 0; pointer-events: none; transform: translateX(8px);
  transition: opacity 0.2s, transform 0.2s;
  z-index: 20;
}
.show-switcher-menu.is-open { opacity: 1; pointer-events: auto; transform: none; }
.show-switcher-item {
  all: unset; display: block; width: 100%; box-sizing: border-box;
  font: 300 14px/1.3 var(--font-en); color: var(--ink-soft);
  letter-spacing: 0.04em; padding: 8px 12px; border-radius: var(--radius-sm);
  cursor: pointer; transition: background 0.15s, color 0.15s;
  text-decoration: none;
}
.show-switcher-item:hover { background: var(--primary-tint); color: var(--ink); }
.show-switcher-item.is-active { color: var(--primary); font-weight: 500; }
.show-switcher-add { color: var(--ink-muted); border-top: 1px solid var(--rule); margin-top: 4px; padding-top: 10px; }
.show-info { text-align: right; }
.show-switcher {
  font: 300 17px/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.1em;
  cursor: pointer; border: none; background: none;
  padding: 8px 0; transition: color 0.2s; text-transform: lowercase;
}
.show-switcher:hover { color: var(--primary); }

/* Actions */
.home-actions-area {
  display: flex; flex-direction: column;
  gap: 14px;
  opacity: 0; animation: fadeUp 0.8s ease 0.35s forwards;
  position: fixed; top: 50%; left: 50%;
  translate: -50% -50%;
  width: calc(100% - 32px); max-width: 456px;
  z-index: 10;
}
@media (min-width: 800px) {
  .home-actions-area { max-width: 456px; }
}

.action {
  display: flex; align-items: center; gap: 15px;
  width: 100%;
  position: relative;
  cursor: pointer;
  text-align: left;
  -webkit-tap-highlight-color: transparent;
  padding: 17px 19px;
  background: #fff;
  border: var(--card-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  transition: all 0.3s var(--ease-out-expo);
  text-decoration: none;
}
.action:hover {
  border-color: rgba(74,72,120,0.16);
  box-shadow: var(--shadow-card);
  transform: translateY(-1px);
}
.action:active {
  transform: translateY(0);
  box-shadow: var(--shadow-sm);
}
@keyframes reviewPulse {
  0%, 100% { box-shadow: 0 1px 2px rgba(46,40,70,0.06), 0 6px 16px -6px rgba(46,40,70,0.14), 0 0 0 0 rgba(84,82,130,0); }
  50% { box-shadow: 0 2px 4px rgba(46,40,70,0.07), 0 12px 26px -8px rgba(46,40,70,0.18), 0 0 0 5px rgba(84,82,130,0.05); }
}
/* Primary slot (Python stamps `action--review` on whichever action is primary):
   the one warm/hot accent on the cool field — coral fill, white chip + stat. */
.action--review {
  background: linear-gradient(165deg, #f48a63, var(--act-coral) 52%, var(--act-coral-deep));
  border-color: rgba(255,255,255,0.18);
  box-shadow: 0 1px 2px rgba(180,70,40,0.2), 0 8px 20px -6px rgba(224,96,58,0.5), 0 18px 38px -16px rgba(224,96,58,0.4);
  animation: none;
}
.action--review:hover {
  border-color: rgba(255,255,255,0.22);
  box-shadow: 0 1px 2px rgba(180,70,40,0.2), 0 12px 26px -6px rgba(224,96,58,0.56), 0 24px 48px -18px rgba(224,96,58,0.46);
  transform: translateY(-1px);
}
.action--review .action-label { font-weight: 800; color: #fff; }
.action--review .action-sub { color: rgba(255,255,255,0.88); }
.action--review .action-icon { background: rgba(255,255,255,0.95); }
.action--review .action-icon svg { stroke: var(--act-coral-deep); }
.action--review .action-icon--learn svg { fill: var(--act-coral-deep); stroke: none; }
/* 3 classes so the white primary override beats `.action-stat--TYPE .__n`
   (equal-specificity tie would otherwise let the later per-type color win). */
.action.action--review .action-stat__n { color: #fff; }
.action.action--review .action-stat__u { color: rgba(255,255,255,0.8); }
.action--secondary {
  background: rgba(255,255,255,0.93);
  border-color: rgba(74,72,120,0.06);
  box-shadow: var(--shadow-card);
  width: 100%;
}
#extra-learn-area {
  display: flex; justify-content: center;
  margin-top: 6px;
}
/* Uniform 46px chips across all actions (D reads as one set); the chip *color*
   and the big stat number carry the per-action identity, not the chip size. */
.action--secondary .action-label { color: var(--ink-soft); }
.action--secondary .action-sub { font-size: var(--fs-caption); }
.action--preparing {
  flex-direction: column; align-items: stretch;
  cursor: default;
  padding: 28px 32px;
}
.action--secondary.action--preparing {
  padding: 20px;
}
.action--preparing .preparing-title {
  font-weight: 400; color: var(--ink-soft); margin-bottom: 8px;
}
.action--preparing:hover { transform: none; box-shadow: none; border-color: rgba(74,72,120,0.04); }
.action--muted { cursor: default; opacity: 0.5; }
.action--muted:hover { transform: none; box-shadow: 0 2px 12px rgba(74,72,120,0.06); border-color: rgba(74,72,120,0.08); }

/* ── Building / queued cold show (#0109) ──────────────────────────────────────
   A queued batch build has no honest progress %, so its indicator is an
   indeterminate hourglass that slowly tips. Two surfaces:
     · onboarding (.action--building): the user's only show is still building, so
       the build IS the page — a calm centered column card.
     · switcher slot (.show-switcher-building): a multi-show user's build holds its
       own place in the list of shows, so no show's page carries another's loading.
   Both share the hourglass + a legible "Cancel build" that opens .build-confirm. */
.building-hourglass { position: relative; display: inline-flex; }
.building-hourglass .hg-svg { display: block; position: relative; z-index: 1; overflow: visible; }
.building-hourglass .hg-frame { stroke: var(--ink-faint); }
.building-hourglass .hg-sand,
.building-hourglass .hg-grain { fill: var(--gold, #d4a64a); }
.building-hourglass .hg-grain { transform-box: fill-box; }
/* soft warm halo, as if the sand were faintly lit */
.building-hourglass::before {
  content: ""; position: absolute; inset: -45%; z-index: 0; border-radius: 50%;
  background: radial-gradient(circle, rgba(212,166,74,0.20), transparent 68%);
  animation: hgGlow 3.2s ease-in-out infinite;
}
/* sand drains top->bottom; the reset (full again) happens at opacity 0 so the
   loop has no visible snap -- a gentle turn-over rather than a spin. */
.hg-top { transform-box: fill-box; transform-origin: bottom; animation: hgDrain 3.2s ease-in-out infinite; }
.hg-bot { transform-box: fill-box; transform-origin: bottom; animation: hgFill 3.2s ease-in-out infinite; }
.hg-grain-1 { animation: hgGrain 1.05s ease-in infinite; }
.hg-grain-2 { animation: hgGrain 1.05s ease-in infinite 0.52s; }
@keyframes hgDrain {
  0%        { transform: scaleY(1); opacity: 1; }
  58%       { transform: scaleY(0); opacity: 1; }
  60%       { transform: scaleY(1); opacity: 0; }   /* refill while hidden */
  74%, 100% { transform: scaleY(1); opacity: 1; }
}
@keyframes hgFill {
  0%        { transform: scaleY(0); opacity: 1; }
  58%       { transform: scaleY(1); opacity: 1; }
  60%       { transform: scaleY(0); opacity: 0; }
  74%, 100% { transform: scaleY(0); opacity: 1; }
}
@keyframes hgGrain {                 /* % translate via fill-box: scale-independent */
  0%   { transform: translateY(-40%); opacity: 0; }
  22%  { opacity: 0.95; }
  82%  { opacity: 0.95; }
  100% { transform: translateY(470%); opacity: 0; }
}
@keyframes hgGlow {
  0%, 100% { opacity: 0.45; transform: scale(0.85); }
  50%      { opacity: 0.85; transform: scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
  .hg-top, .hg-bot, .hg-grain, .building-hourglass::before { animation: none; }
  .hg-top { transform: scaleY(0.5); }   /* a calm, half-run hourglass */
}

/* Onboarding building card — centered column. */
.action--building {
  flex-direction: column; align-items: center; text-align: center;
  gap: 12px; padding: 26px 24px; cursor: default;
}
.action--building:hover { transform: none; box-shadow: var(--shadow-card); border-color: rgba(74,72,120,0.06); }
.action--building .building-hourglass .hg-svg { width: 34px; height: 34px; }
.building-name { font: var(--fw-semibold) var(--fs-lead)/1.25 var(--font-display); color: var(--ink); }
.building-sub { font: var(--fw-regular) var(--fs-sm)/1.4 var(--font-display); color: var(--ink-muted); margin-top: 3px; }
/* Cancel recedes to a quiet text link so the show + hourglass is the star; the
   destructive weight lives in the confirm modal, not the list. */
.building-cancel, .ssb-cancel {
  all: unset; box-sizing: border-box; cursor: pointer;
  color: var(--ink-muted); letter-spacing: 0.02em; border-radius: 6px;
  transition: color 0.18s;
}
.building-cancel { font: 500 12.5px/1 var(--font-en); padding: 6px; }
.building-cancel:hover, .ssb-cancel:hover { color: var(--act-review); }
.building-cancel:focus-visible, .ssb-cancel:focus-visible { outline: 2px solid var(--primary-soft); outline-offset: 2px; }

/* Switcher reserved slot — name on its own row (room for long native titles),
   then "Building" + Cancel build beneath, on the frosted-white menu. */
.show-switcher-building {
  display: flex; flex-direction: column; align-items: stretch; gap: 7px;
  cursor: default; padding: 10px;
}
.show-switcher-building:hover { background: none; color: inherit; }
.ssb-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ssb-glyph { flex-shrink: 0; display: inline-flex; color: var(--primary-soft); }
.ssb-glyph .hg-svg { width: 20px; height: 20px; }
.ssb-name {
  flex: 1; min-width: 0; font: 400 14px/1.25 var(--font-display); color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ssb-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-left: 26px; }
.ssb-status { font: 400 11px/1 var(--font-en); color: var(--ink-faint); letter-spacing: 0.04em; }
.ssb-cancel { flex-shrink: 0; font: 500 11.5px/1 var(--font-en); padding: 4px; }

/* Cancel-build confirm modal (shared, partials/building_modal.html). */
.build-confirm {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(42,36,64,0.32); backdrop-filter: blur(4px);
  opacity: 0; transition: opacity 0.22s ease;
}
.build-confirm[hidden] { display: none; }
.build-confirm.is-open { opacity: 1; }
.build-confirm__scrim { position: absolute; inset: 0; cursor: default; }
.build-confirm__card {
  position: relative; z-index: 1;  /* above the absolutely-positioned scrim */
  background: var(--card-bg-solid); backdrop-filter: blur(16px);
  border: 1px solid var(--card-border); border-radius: 16px;
  box-shadow: 0 20px 60px rgba(40,30,80,0.18), 0 1px 0 rgba(255,255,255,0.7) inset;
  width: 100%; max-width: 360px; padding: 24px 24px 20px;
  opacity: 0; transform: translateY(14px) scale(0.985);
  transition: transform 0.32s var(--ease-out-expo), opacity 0.32s var(--ease-out-expo);
}
.build-confirm.is-open .build-confirm__card { opacity: 1; transform: none; }
.build-confirm__title { font: 400 19px/1.25 var(--font-en); color: var(--ink); margin: 0 0 8px; }
.build-confirm__body { font: 400 13.5px/1.5 var(--font-en); color: var(--ink-muted); margin: 0 0 20px; }
.build-confirm__show { color: var(--ink-soft); font-weight: 600; }
.build-confirm__actions { display: flex; gap: 10px; justify-content: flex-end; }
.build-confirm__keep, .build-confirm__go {
  all: unset; box-sizing: border-box; cursor: pointer;
  font: 500 13.5px/1 var(--font-en); padding: 11px 18px; border-radius: 10px;
  transition: background 0.18s, border-color 0.18s, color 0.18s;
}
.build-confirm__keep { color: var(--ink-soft); border: 1px solid var(--rule); }
.build-confirm__keep:hover { background: var(--primary-tint); color: var(--ink); }
.build-confirm__go { color: #fff; background: var(--act-review); }
.build-confirm__go:hover { background: #8a1d49; }
.build-confirm__go.is-busy { --is-busy-color: #fff; }
.build-confirm__keep:focus-visible, .build-confirm__go:focus-visible { outline: 2px solid var(--primary-soft); outline-offset: 2px; }

.action-icon {
  width: 46px; height: 46px;
  border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.action-icon svg { width: 22px; height: 22px; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.action-icon--review { background: var(--act-coral-tint); }
.action-icon--review svg { stroke: var(--act-coral-deep); fill: none; }
.action-icon--audio { background: var(--act-sky-tint); }
.action-icon--audio svg { stroke: var(--act-sky); fill: none; margin-left: 2px; }
.action-icon--reading { background: var(--act-violet-tint); }
.action-icon--reading svg { stroke: var(--act-violet); fill: none; }
.action-icon--learn { background: var(--act-amber-tint); }
.action-icon--learn svg { fill: var(--act-amber); stroke: none; width: 20px; height: 20px; margin-left: 2px; }
.action-body { flex: 1; min-width: 0; }
.action-label { font: var(--fw-semibold) var(--fs-lead)/1.2 var(--font-display); color: var(--ink); }
.action-sub { font: var(--fw-regular) var(--fs-sm)/1.3 var(--font-display); color: var(--ink-muted); margin-top: 2px; }
/* D stat — the big number is the hero, with a small unit label beneath it.
   The number takes the action's accent color (white when on the coral primary). */
.action-stat { flex-shrink: 0; text-align: right; line-height: 1; }
.action-stat__n { display: block; font: var(--fw-bold) 28px/1 var(--font-display); letter-spacing: -0.02em; color: var(--ink); }
.action-stat__u { display: block; font: var(--fw-bold) 10px/1 var(--font-display); text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-muted); margin-top: 3px; }
.action-stat--review .action-stat__n { color: var(--act-coral-deep); }
.action-stat--audio .action-stat__n { color: var(--act-sky); }
.action-stat--reading .action-stat__n { color: var(--act-violet); }
.action-stat--learn .action-stat__n { color: var(--act-amber); }

/* Deep Review action — SECONDARY placement (Review stays primary; pairs with
   .action--secondary), with a quiet gold accent + stone-trail mark and a soft
   breathing icon aura (no count badge: the backlog total is intentionally hidden). */
.action--deep {
  background: rgba(251,244,220,0.5);
  border-color: rgba(212,166,74,0.22);
}
.action--deep:hover {
  border-color: rgba(212,166,74,0.4);
  box-shadow: 0 4px 18px rgba(212,166,74,0.12);
}
.action-icon--deep { background: var(--gold-tint); position: relative; overflow: hidden; }
.action-icon--deep svg { stroke: var(--gold); fill: none; }  /* size from .action--secondary */
.action-icon--deep::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at 50% 60%, rgba(212,166,74,0.28), transparent 70%);
  animation: deepAura 4.5s ease-in-out infinite;
}
@keyframes deepAura {
  0%, 100% { opacity: .35; transform: scale(.9); }
  50% { opacity: .8; transform: scale(1.12); }
}
/* stone-trail mark where the count badge would be (mystery, no number) */
.deep-mark { flex-shrink: 0; display: inline-flex; align-items: center; gap: 5px; padding-right: 2px; }
.deep-mark i { display: block; border-radius: 50%; background: var(--gold); }
.deep-mark i:nth-child(1) { width: 9px; height: 9px; opacity: .92; animation: stoneStep 4.5s ease-in-out infinite; }
.deep-mark i:nth-child(2) { width: 7px; height: 7px; opacity: .6; }
.deep-mark i:nth-child(3) { width: 5px; height: 5px; opacity: .32; }
.deep-mark i:nth-child(4) { width: 3.5px; height: 3.5px; opacity: .14; }
@keyframes stoneStep { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-2px); } }

/* ════════════════════════════════════════════════════════════════════════
   Icon-stack home actions  (.home-actions-area--icons)
   The transparent line-art glyph IS the button: no card, no chip, no label.
   Glyphs stack down the centre of the misty scene with a quiet count beneath.
   Secondary actions stay neutral stone-slate; the single primary slot lifts to
   its action's identity colour (rose / blue / green / violet) via --act-color.
   Scoped so the shared base `.action` card (empty-state CTA, dev tools) is
   untouched. Tokens: --act-review / -audio / -learn / -reading.
   ════════════════════════════════════════════════════════════════════════ */
.home-actions-area--icons { align-items: center; gap: 40px; width: auto; max-width: none; }

/* identity carried on the link itself (the action_class macro stamps act-id--<key>) */
.act-id--review  { --act-color: var(--act-review); }
.act-id--audio   { --act-color: var(--act-audio); }
.act-id--reading { --act-color: var(--act-reading); }
.act-id--learn   { --act-color: var(--act-learn); }
.act-id--deep    { --act-color: var(--gold); }

/* the action: a transparent, centred icon + count, with a generous tap target */
.home-actions-area--icons .action {
  flex-direction: column; align-items: center; gap: 9px;
  width: auto; padding: 6px 18px;
  background: none; border: none; border-radius: 0; box-shadow: none;
}
.home-actions-area--icons .action:hover,
.home-actions-area--icons .action:active {
  background: none; border: none; box-shadow: none; transform: none;
}

/* glyph — line-art, neutral stone-slate by default (secondary) */
/* strip the chip entirely — size, radius AND the per-type tint background */
.home-actions-area--icons .action-icon { width: auto; height: auto; border-radius: 0; background: none; }
.home-actions-area--icons .action-icon svg {
  width: 38px; height: 38px; margin: 0;
  fill: none; stroke: var(--ink-soft); stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round; opacity: .9;
  transition: stroke .35s ease, transform .45s var(--ease-out-expo), opacity .35s ease;
}
.home-actions-area--icons .action:hover .action-icon svg { transform: translateY(-3px); opacity: 1; }

/* labels: visually hidden but kept for screen readers (the link stays meaningful) */
.home-actions-area--icons .action-body {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* count: hidden in the icon stack — each glyph stands alone. display:none drops
   it from the a11y tree too (not just visually); the action's SR-only
   .action-body label (clip-path, above) still names it, so nothing is lost. */
.home-actions-area--icons .action-stat { display: none; }
/* the empty-state add CTA has no count; this visible caption sits in the count's
   place. Neutral (not the primary green) so the plus glyph stays the sole accent. */
.home-actions-area--icons .action-cap {
  font: var(--fw-medium) 13px/1 var(--font-display);
  letter-spacing: .02em; color: var(--ink-soft); opacity: .82;
  text-align: center;
}

/* PRIMARY slot — Python stamps action--review; identity colour + a touch larger */
.home-actions-area--icons .action--review .action-icon svg {
  stroke: var(--act-color); width: 50px; height: 50px; opacity: 1;
}

/* deep review: drop the breathing aura; keep the quiet gold stone-trail beneath */
.home-actions-area--icons .action-icon--deep { overflow: visible; }
.home-actions-area--icons .action-icon--deep::after { display: none; }
.home-actions-area--icons .deep-mark { padding: 0; }

/* Locked pro modes (#0411): reading + deep render visibly locked for free
   users — dimmed glyph, a small "pro" chip in the action's identity colour
   (--act-color via act-id--reading / act-id--deep) sitting where the count
   goes, the whole card linking /pro. Visible-but-locked, never hidden. */
.pro-chip {
  display: inline-block; padding: 4px 9px 3px; border-radius: 999px;
  font: var(--fw-bold) 10px/1 var(--font-display);
  letter-spacing: .09em; text-transform: lowercase;
  color: #fff; background: var(--act-color, var(--ink-soft));
  opacity: .82;
}
.home-actions-area--icons .action--locked .action-icon svg { opacity: .45; }
.home-actions-area--icons .action--locked:hover .action-icon svg {
  transform: none; opacity: .6;
}
.home-actions-area--icons .action--locked:hover .pro-chip { opacity: 1; }

/* preparing status keeps its title + steps (it is a status line, not a glyph) */
.home-actions-area--icons .action--preparing { padding: 8px 0; gap: 10px; align-items: center; }
.home-actions-area--icons .action--preparing .preparing-title { text-align: center; }

/* Desktop: the wide canvas swallows mobile-sized glyphs, so the stack needs
   more presence to anchor against the right-weighted mountain + title. */
@media (min-width: 800px) {
  .home-actions-area--icons { gap: 52px; }
  .home-actions-area--icons .action-icon svg { width: 48px; height: 48px; }
  .home-actions-area--icons .action--review .action-icon svg { width: 62px; height: 62px; }
  .home-actions-area--icons .action-cap { font-size: 15px; }
}

/* Done message (all caught up) */
.done-message {
  /* Mobile: sit just right of centre with a right-edge gutter so お疲れ様 and
     "next review in …" don't wrap against the screen edge. Desktop restores the
     wider asymmetric offset below. */
  position: fixed; bottom: calc(14% + 45px);
  left: calc(50% + 12px); right: 18px;
  padding: 10px 0; pointer-events: auto;
  opacity: 0; animation: fadeUp 0.8s ease 0.3s forwards;
}
@media (min-width: 800px) {
  .done-message { bottom: calc(11% + 35px); left: calc(50% + 77px); right: auto; }
}
.done-label-jp {
  font-family: var(--font-jp); font-weight: 300;
  font-size: 29px; line-height: 1.3; color: var(--ink-soft);
  letter-spacing: 0.05em; margin-bottom: 3px;
}
@media (min-width: 800px) { .done-label-jp { font-size: 36px; } }
.done-label-en {
  font: var(--fw-light) var(--fs-eyebrow)/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.15em;
  text-transform: lowercase; margin-bottom: 14px;
}
.done-sub {
  /* Day-tier ink base, like .done-label-jp/en -- NOT hardcoded white. The
     is-caught-up/is-night cascade recolours it light on the dark tiers; the
     pool-exhausted panel (#0199) can render on the day tier, where white was
     invisible. */
  font: var(--fw-light) var(--fs-sm)/1.35 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.1em;
}
.nowrap { white-space: nowrap; }
.done-keep-going {
  display: inline-block; margin-top: 18px;
  font: var(--fw-light) var(--fs-caption)/1 var(--font-en);
  color: rgba(255,255,255,0.55); letter-spacing: 0.05em;
  background: none; border: none; cursor: pointer;
  padding: 0; text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(255,255,255,0.25);
  transition: color 0.2s;
}
.done-keep-going:hover {
  color: rgba(255,255,255,0.80);
}
.done-message.is-dismissing {
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

/* Pool-exhausted panel (#0199) — replaces the Learn button in the action stack
   when the active show has no i+0-unlockable words left. Reuses the
   position-free .done-label-* type scale; learned_out celebrates (お疲れ様 +
   🎉 + one-time fireworks), locked_out stays calm and points elsewhere. */
.learn-status {
  text-align: center; padding: 6px 4px 2px;
  opacity: 0; animation: fadeUp 0.8s ease 0.2s forwards;
}
.learn-status .done-sub { max-width: 32ch; margin: 4px auto 0; }
.learn-status--learned_out .done-label-en { color: var(--ink-soft); }
/* "Add a show" path for a locked show with no other source of vocabulary.
   Day base = dark-ink outline; frosted-light on dusk/night to match the panel. */
.learn-status__action { margin-top: 16px; }
.learn-status__add {
  display: inline-block; padding: 7px 18px;
  font: var(--fw-light) var(--fs-caption)/1 var(--font-en);
  letter-spacing: 0.08em; color: var(--ink-soft);
  background: rgba(80,78,104,0.06);
  border: 1px solid rgba(80,78,104,0.28); border-radius: 999px;
  text-decoration: none; cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.learn-status__add:hover {
  background: rgba(80,78,104,0.12); border-color: rgba(80,78,104,0.45);
}
.atmosphere.is-caught-up ~ * .learn-status__add,
.atmosphere.is-night ~ * .learn-status__add {
  color: rgba(235,232,245,0.82);
  background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.22);
}
.atmosphere.is-caught-up ~ * .learn-status__add:hover,
.atmosphere.is-night ~ * .learn-status__add:hover {
  color: #fff;
  background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.36);
}

/* Caught-up reminder bell. A hairline bell outline hung to the LEFT of お疲れ様,
   stroke weight tuned to the 300-weight JP glyph. No button chrome — colour
   rides the same per-tier recolour cascade as .done-label-jp (currentColor +
   the stroke transition), so dusk and night come for free. Tapping it fires the
   same reminder as the session-end button; it rings, sparks and settles glowing. */
.done-headline { position: relative; display: inline-block; }
.done-bell {
  /* top > 50% drops the bell toward お疲れ様's baseline (it reads better low and
     small than centred on the tall line box). */
  position: absolute; right: 100%; top: 64%;
  margin-right: 9px; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; background: none; cursor: pointer; color: var(--ink-soft);
  -webkit-tap-highlight-color: transparent;
}
@media (min-width: 800px) {
  .done-bell { width: 26px; height: 26px; margin-right: 11px; }
}
.done-bell__glyph {
  width: 100%; height: 100%;
  transform-origin: 50% 16%;       /* swing from the crown so it reads as a ring */
  transition: transform 0.25s ease, filter 0.5s ease, color 0.5s ease;
}
.done-bell:hover .done-bell__glyph { transform: scale(1.08); }
.done-bell:active .done-bell__glyph { transform: scale(0.9); }
.done-bell:focus-visible { outline: none; }
.done-bell:focus-visible .done-bell__glyph {
  filter: drop-shadow(0 0 4px rgba(255,232,170,0.85));
}

/* Tap: ring swing + a light ripple + a warm spark burst. */
.done-bell.is-ringing .done-bell__glyph { animation: bell-ring 0.8s cubic-bezier(.36,.07,.19,.97); }
.done-bell::after {
  content: ""; position: absolute; inset: -2px; border-radius: 50%;
  border: 1px solid currentColor; opacity: 0; pointer-events: none;
}
.done-bell.is-ringing::after { animation: bell-ripple 0.8s ease-out; }
.done-bell__sparks { position: absolute; inset: 0; pointer-events: none; }
.done-bell__sparks i {
  position: absolute; top: 50%; left: 50%;
  width: 3px; height: 3px; border-radius: 50%;
  background: #ffe6a8; box-shadow: 0 0 4px rgba(255,224,165,0.9); opacity: 0;
}
.done-bell.is-ringing .done-bell__sparks i { animation: bell-spark 0.7s ease-out forwards; }
.done-bell__sparks i:nth-child(1) { --tx: -13px; --ty: -15px; animation-delay: .04s; }
.done-bell__sparks i:nth-child(2) { --tx:  14px; --ty: -12px; animation-delay: .10s; }
.done-bell__sparks i:nth-child(3) { --tx: -10px; --ty:  13px; animation-delay: .02s; }
.done-bell__sparks i:nth-child(4) { --tx:  13px; --ty:  11px; animation-delay: .12s; }

/* Set: the bell lights up warm gold and keeps a soft glow — cool→warm reads as
   "notification on". The colour overrides the inherited lavender via the glyph's
   own currentColor, so it wins over the per-tier recolour on .done-bell. */
.done-bell.is-set { cursor: default; }
.done-bell.is-set .done-bell__glyph {
  /* No `animation` here on purpose: is-set lands when the POST resolves, often
     mid-ring — overriding the animation would snap the swing. bell-ring is a
     one-shot, so it finishes on its own while the colour transitions to gold. */
  color: #ffd98a;
  filter: drop-shadow(0 0 7px rgba(255,205,120,0.9));
}
.done-bell.is-failed .done-bell__glyph { animation: bell-shake 0.45s ease; }

.done-bell-note {
  /* min-height reserves the line in flow at all times; the note only fades its
     opacity, so the bottom-anchored .done-message never grows and お疲れ様 stays
     put when "Reminder set" appears. */
  margin-top: 10px; min-height: 1.05em;
  font: var(--fw-light) var(--fs-caption)/1 var(--font-en);
  letter-spacing: 0.08em; color: rgba(255,255,255,0.62);
  opacity: 0; transition: opacity 0.5s ease;
}
.done-bell-note.is-shown { opacity: 1; }

@keyframes bell-ring {
  0% { transform: rotate(0); }
  14% { transform: rotate(18deg); }
  32% { transform: rotate(-13deg); }
  50% { transform: rotate(9deg); }
  68% { transform: rotate(-6deg); }
  84% { transform: rotate(3deg); }
  100% { transform: rotate(0); }
}
@keyframes bell-ripple {
  0% { opacity: 0.5; transform: scale(0.6); }
  100% { opacity: 0; transform: scale(2.3); }
}
@keyframes bell-spark {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.2); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(1); }
}
@keyframes bell-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  75% { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .done-bell.is-ringing .done-bell__glyph,
  .done-bell.is-failed .done-bell__glyph { animation: none; }
  .done-bell.is-ringing::after,
  .done-bell.is-ringing .done-bell__sparks i { animation: none; }
  .done-bell-note { transition: none; }
}

/* ── Auth (Login / Signup) ── */
.auth-card {
  width: 100%; max-width: 420px;
  padding: 32px 28px;
  background: var(--card-bg-solid);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border: var(--card-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  position: relative; z-index: 2;
  margin: 0 16px;
  opacity: 0; animation: fadeUp 0.6s ease 0.1s forwards;
}
@media (min-width: 800px) { .auth-card { padding: 40px 42px; max-width: 476px; } }
.auth-title { font: var(--fw-regular) var(--fs-h1)/1.15 var(--font-display); color: var(--ink-soft); margin-bottom: var(--sp-2); letter-spacing: 0.01em; }
.auth-sub {
  font: var(--fw-light) var(--fs-lead)/1.5 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.02em;
  margin-bottom: var(--sp-7);
}
.auth-field { margin-bottom: var(--sp-5); }
.auth-label {
  display: block;
  font: var(--fw-regular) var(--fs-sm)/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.12em;
  text-transform: uppercase; margin-bottom: var(--sp-2);
}
.auth-input {
  width: 100%; padding: 14px 16px;
  font: var(--fw-light) var(--fs-lead)/1.3 var(--font-en);
  color: var(--ink); background: rgba(255,255,255,0.6);
  border: 1px solid rgba(74,72,120,0.12);
  border-radius: var(--radius);
  outline: none; transition: border-color 0.2s;
}
.auth-input:focus { border-color: var(--primary-soft); }
.auth-input::placeholder { color: var(--ink-faint); }
.auth-submit {
  width: 100%; padding: 16px;
  font: var(--fw-medium) var(--fs-body)/1 var(--font-en);
  letter-spacing: 0.04em;
  background: var(--ink); color: #fff;
  border: 1px solid transparent; border-radius: var(--radius-lg);
  cursor: pointer; transition: background 0.2s;
  margin-top: var(--sp-3);
}
.auth-submit:hover { background: var(--primary); }
.auth-links {
  margin-top: var(--sp-5);
  font: var(--fw-light) var(--fs-sm)/1.5 var(--font-en);
  color: var(--ink-faint); text-align: center;
}
.auth-links a { color: var(--ink-muted); text-decoration: none; transition: color 0.2s; }
.auth-links a:hover { color: var(--primary); }
.auth-error {
  background: rgba(200,50,50,0.08);
  border: 1px solid rgba(200,50,50,0.15);
  border-radius: var(--radius-sm);
  padding: 10px 14px; margin-bottom: 18px;
  font: var(--fw-light) var(--fs-sm)/1.4 var(--font-en);
  color: #8b2020;
}

/* ── Dark-glass card overrides (logged-out public pages: auth + legal) ──
   Triggered by `body.is-night`. These pages now render the caught_up sunrise
   sky (see .atmosphere--auth/--legal.is-caught-up above) with dark frosted
   cards, matching the landing's glass-over-sunrise look. The class name is
   historical: it selects the dark-card theme, not a night sky. */
body.is-night .brand { color: rgba(210,205,230,0.88); }
body.is-night .back-link { color: rgba(190,185,215,0.78); }
body.is-night .back-link:hover { color: #7A78B0; }

body.is-night .auth-card {
  background: rgba(16,20,42,0.68);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 20px 48px -18px rgba(8,6,26,0.6);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
/* Logged-out auth pages keep the bolder landing-matched heading (the calm
   regular-weight voice is for the logged-in app surfaces). */
body.is-night .auth-title { color: rgba(230,225,245,0.95); font-weight: var(--fw-bold); letter-spacing: -0.01em; }
body.is-night .auth-sub { color: rgba(190,185,215,0.78); }
body.is-night .auth-label { color: rgba(190,185,215,0.78); }
body.is-night .auth-input {
  background: rgba(10,13,32,0.5);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius);
  color: rgba(230,225,245,0.95);
}
body.is-night .auth-input:focus { border-color: #6A68A0; }
body.is-night .auth-input::placeholder { color: rgba(160,155,190,0.55); }
/* Refined-ghost primary CTA: still transparent/editorial, but a faint coral
   tint + brighter coral border + regular weight give it real affordance and
   set it clearly above the muted "log in" link below. */
body.is-night .auth-submit {
  background: var(--coral-tint);
  border: 1px solid rgba(236,149,114,0.45);
  border-radius: var(--radius-lg);
  color: rgba(250,240,234,0.95);
  font-weight: var(--fw-regular);
  letter-spacing: 0.08em;
}
body.is-night .auth-submit:hover {
  background: rgba(236,149,114,0.20);
  border-color: rgba(236,149,114,0.72);
  color: #fff;
}
/* Dim the secondary link so the CTA owns the hierarchy. */
body.is-night .auth-links { color: rgba(170,165,198,0.45); }
body.is-night .auth-links a { color: rgba(198,193,222,0.72); }
body.is-night .auth-links a:hover { color: #9290c4; }
body.is-night .auth-error {
  background: rgba(200,50,50,0.15);
  border-color: rgba(200,50,50,0.25);
  border-radius: 0;
  color: #e8a0a0;
}

/* ── Tier-transition glide (content elements) ──
   These transitions animate color/stroke changes when the atmosphere
   tier class is added after page load (deferred via JS) or toggled
   dynamically (e.g. "keep going" removing is-caught-up). */
.brand,
.settings-btn svg,
.jp-title,
.title-side,
.action-label,
.action-sub,
.done-label-jp,
.done-bell,
.done-label-en,
.done-sub,
.done-keep-going { transition: color 1.4s ease, stroke 1.4s ease, text-shadow 1.4s ease; }

/* ── Dusk-mode overrides (home page) ──
   Near-white with a lavender lean, matching the landing palette direction. */
.atmosphere.is-caught-up ~ * .brand { color: rgba(235,232,245,0.92); }
.atmosphere.is-caught-up ~ * .settings-btn svg { stroke: rgba(225,220,240,0.85); }
.atmosphere.is-caught-up ~ * .jp-title { color: rgba(235,232,245,0.88); text-shadow: none; }
.atmosphere.is-caught-up ~ * .title-side { color: rgba(225,220,240,0.70); }
.atmosphere.is-caught-up ~ * .done-label-jp,
.atmosphere.is-caught-up ~ * .done-bell { color: rgba(235,232,245,0.88); }
.atmosphere.is-caught-up ~ * .done-label-en { color: rgba(225,220,240,0.70); }
.atmosphere.is-caught-up ~ * .done-sub { color: rgba(215,210,235,0.65); }

/* ── Night-mode overrides (home page) ── */
.atmosphere.is-night ~ * .brand { color: rgba(210,205,230,0.88); }
.atmosphere.is-night ~ * .settings-btn svg { stroke: rgba(190,185,215,0.78); }
.atmosphere.is-night ~ * .jp-title { color: rgba(210,205,230,0.78); text-shadow: none; }
.atmosphere.is-night ~ * .title-side { color: rgba(190,185,215,0.55); }
.atmosphere.is-night ~ * .action-label { color: rgba(230,225,245,0.95); }
.atmosphere.is-night ~ * .action-sub { color: rgba(190,185,215,0.78); }
.atmosphere.is-night ~ * .done-label-jp,
.atmosphere.is-night ~ * .done-bell { color: rgba(210,205,230,0.78); }
.atmosphere.is-night ~ * .done-label-en { color: rgba(190,185,215,0.55); }
.atmosphere.is-night ~ * .done-sub { color: rgba(160,155,190,0.55); }

/* ── Study session (card front/back) ── */
.study-shell {
  flex: 1; display: flex; flex-direction: column;
  position: relative; z-index: 2;
  max-width: 860px; width: 100%;
  margin: 0 auto; padding: 0 24px;
}
.study-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 20px 0 0; flex-shrink: 0;
}
.study-progress { display: flex; align-items: center; gap: 8px; }
.study-progress-label {
  font: 200 14px/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.1em;
  text-transform: uppercase;
}
.study-progress-bar {
  width: 100px; height: 4px;
  background: rgba(74,72,120,0.1);
  border-radius: 2px; overflow: hidden;
}
.study-progress-fill {
  height: 100%;
  background: var(--primary-soft);
  border-radius: 2px;
  transition: width 0.3s ease;
}
.study-progress-num {
  font: 200 14px/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.05em;
}
.study-quit {
  font: 200 16px/1 var(--font-en);
  color: var(--ink-faint); text-decoration: none;
  padding: 8px 0; letter-spacing: 0.08em;
  transition: color 0.2s;
}
.study-quit:hover { color: var(--primary); }

/* Card front */
.card-stage {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 0 0 100px; min-height: 0;
}
.card-front {
  text-align: center;
  opacity: 0; animation: fadeUp 0.5s ease 0.1s forwards;
}
.card-source {
  font: italic 400 17px/1 var(--font-display);
  color: var(--ink-faint); margin-bottom: 20px;
}
.card-source em { color: var(--ink-muted); }
.card-surface {
  font-family: var(--font-jp); font-weight: 400;
  font-size: clamp(56px, 14vw, 96px); line-height: 1;
  color: var(--ink); margin-bottom: 28px;
  letter-spacing: 0.02em;
}
.card-sentence-jp {
  font-family: var(--font-jp); font-weight: 400;
  font-size: 23px; line-height: 1.6;
  color: var(--ink-soft); max-width: 500px;
  margin: 0 auto; letter-spacing: 0.02em;
}
@media (min-width: 800px) { .card-sentence-jp { font-size: 26px; } }
.card-audio-row {
  display: flex; gap: 8px; justify-content: center;
  margin-top: 20px;
}
.card-audio-btn {
  display: inline-flex; align-items: center; gap: 5px;
  font: 300 16px/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.08em;
  padding: 12px 20px; border-radius: 14px;
  border: 1px solid rgba(74,72,120,0.10);
  background: rgba(255,255,255,0.5);
  cursor: pointer; transition: all 0.2s;
  text-transform: lowercase;
}
.card-audio-btn:hover { color: var(--primary); border-color: rgba(74,72,120,0.18); background: rgba(255,255,255,0.7); }
.card-audio-btn svg { width: 13px; height: 13px; fill: currentColor; }

/* Card back */
.card-back { width: 100%; opacity: 0; animation: fadeUp 0.4s ease 0.05s forwards; }
.card-back-spread {
  display: flex; flex-direction: column; gap: 24px;
  padding: 24px 0 120px;
}
@media (min-width: 720px) {
  .card-back-spread { flex-direction: row; gap: 40px; padding: 36px 0 120px; }
}
.card-back-left { flex: 1; }
.card-back-right { flex: 1; }
.card-back-word { margin-bottom: 24px; }
.card-back-surface {
  font-family: var(--font-jp); font-weight: 400;
  font-size: clamp(42px, 10vw, 60px); line-height: 1;
  color: var(--ink); margin-bottom: 4px;
}
.card-back-reading {
  font-family: var(--font-jp); font-weight: 400;
  font-size: 21px; color: var(--ink-muted);
  margin-bottom: 10px; letter-spacing: 0.05em;
}
.card-back-meaning { font: 400 30px/1.2 var(--font-display); color: var(--ink-soft); }
.card-back-sent { margin-bottom: 28px; }
.card-back-sent-jp {
  font-family: var(--font-jp); font-weight: 400;
  font-size: 22px; line-height: 1.6;
  color: var(--ink-soft); margin-bottom: 8px;
}
.card-back-sent-en {
  font: 200 16px/1.5 var(--font-en);
  color: var(--ink-muted);
}
.card-block { margin-bottom: 20px; }
.card-block-h {
  font: 300 13px/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.18em;
  text-transform: uppercase; margin-bottom: 10px;
}
.card-block-text { font: 300 18px/1.6 var(--font-en); color: var(--ink-soft); }
.card-kanji-row { display: flex; gap: 20px; flex-wrap: wrap; }
.card-kanji-item {
  display: flex; flex-direction: column; align-items: center;
  padding: 16px 22px;
  background: rgba(255,255,255,0.50);
  border: 1px solid rgba(255,255,255,0.45);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 6px rgba(29,23,48,0.04);
}
.card-kanji-char {
  font-family: var(--font-jp); font-weight: 400;
  font-size: 42px; line-height: 1; color: var(--ink);
  margin-bottom: 6px;
}
.card-kanji-meaning {
  font: 200 13px/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.06em;
}

/* Study actions (fixed bottom) */
.study-actions {
  position: fixed; bottom: 58px; left: 0; right: 0;
  display: flex; justify-content: center;
  padding: 0 24px; z-index: 10;
}
.reveal-btn {
  font: 400 17px/1 var(--font-en);
  padding: 16px 44px; letter-spacing: 0.08em;
  background: var(--card-bg-solid); backdrop-filter: blur(12px);
  border: 1px solid var(--card-border); border-radius: 18px;
  color: var(--ink-muted); cursor: pointer; transition: all 0.2s;
  display: flex; align-items: center; gap: 12px;
}
.reveal-btn:hover { color: var(--primary); border-color: rgba(74,72,120,0.15); }
.reveal-btn kbd {
  font: 200 13px/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.1em;
  padding: 3px 8px; border-radius: 4px;
  background: rgba(74,72,120,0.05);
  border: 1px solid rgba(74,72,120,0.08);
}
@media (max-width: 799px) { .reveal-btn kbd { display: none; } }
.rate-btns { display: flex; gap: 16px; width: 100%; max-width: 420px; }
.rate-btn {
  flex: 1; padding: 18px 0;
  font: 400 18px/1 var(--font-en);
  letter-spacing: 0.06em; border-radius: 18px;
  cursor: pointer; transition: all 0.2s;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  border: 1px solid transparent;
  min-height: 52px;
}
.rate-btn-sub { font: 200 13px/1 var(--font-en); color: inherit; opacity: 0.6; }
.rate-btn--again {
  background: rgba(255,255,255,0.5); color: var(--ink-muted);
  border-color: rgba(74,72,120,0.08);
}
.rate-btn--again:hover { border-color: rgba(74,72,120,0.15); color: var(--ink-soft); }
.rate-btn--good { background: var(--ink); color: #fff; }
.rate-btn--good:hover { background: var(--primary); }

/* ── Settings ── */
.settings-title {
  font: var(--fw-regular) var(--fs-h2)/1.15 var(--font-display); color: var(--ink-soft); margin: 0 0 var(--sp-6); letter-spacing: 0.01em;
  opacity: 0; animation: settingsFade 0.35s 0.08s ease forwards;
}
@keyframes settingsFade {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
.settings-group { margin-bottom: 24px; opacity: 0; animation: settingsFade 0.35s ease forwards; }
.settings-group:nth-child(2) { animation-delay: 0.14s; }
.settings-group:nth-child(3) { animation-delay: 0.20s; }
.settings-group:nth-child(4) { animation-delay: 0.26s; }
.settings-group:last-child { margin-bottom: 0; }
.settings-group + .settings-group {
  padding-top: 28px;
  border-top: 1px solid rgba(74,72,120,0.06);
}
.settings-group-h {
  font: var(--fw-regular) var(--fs-sm)/1 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.18em;
  text-transform: uppercase; margin-bottom: 12px;
}
.settings-item {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px;
  background: rgba(243,238,251,0.5);
  border: 1px solid rgba(74,72,120,0.06);
  border-radius: var(--radius-sm);
  margin-bottom: 8px; cursor: pointer;
  transition: background 0.2s;
  text-decoration: none; color: var(--ink);
}
.settings-item:hover { background: rgba(243,238,251,0.8); }
.settings-item-label { font: var(--fw-medium) var(--fs-body)/1.3 var(--font-en); color: var(--ink-soft); }
.settings-item-desc { font: var(--fw-light) var(--fs-caption)/1.3 var(--font-en); color: var(--ink-faint); margin-top: 2px; }
.settings-item-arrow { font: var(--fw-light) var(--fs-lead)/1 var(--font-en); color: var(--ink-faint); }
.settings-item-value { font: var(--fw-light) var(--fs-sm)/1 var(--font-en); color: var(--ink-muted); }
.settings-toggle {
  position: relative; width: 52px; height: 30px;
  background: rgba(74,72,120,0.12); border-radius: 15px;
  cursor: pointer; transition: background 0.2s;
}
.settings-toggle.on { background: var(--primary-soft); }
.settings-toggle::after {
  content: ''; position: absolute;
  top: 3px; left: 3px; width: 24px; height: 24px;
  background: #fff; border-radius: 50%;
  transition: transform 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.settings-toggle.on::after { transform: translateX(22px); }
.settings-logout {
  font: 300 18px/1 var(--font-en);
  color: var(--ink-muted); letter-spacing: 0.08em;
  border: none; background: none; cursor: pointer;
  padding: 12px 0; margin-top: 16px;
  transition: color 0.2s;
}
.settings-logout:hover { color: var(--primary); }

/* ── Calibration ──────────────────────────────────────────────────────────
   Redesigned (no filled level boxes — the pale-purple fill read as muddy
   "background purple" inside the frosted card). Levels are airy rows separated
   by a single hairline; structure comes from whitespace, not fills. Real
   Japanese words anchor each level; the one indigo accent (left rail + name)
   marks hover/pick. Rows settle in with a short stagger.
   ──────────────────────────────────────────────────────────────────────── */
.cal-title {
  font: var(--fw-regular) var(--fs-h1)/1.12 var(--font-display);
  color: var(--ink-soft); margin: 0 0 var(--sp-1); letter-spacing: -0.005em;
}
.cal-sub {
  font: var(--fw-light) var(--fs-lead)/1.4 var(--font-en);
  color: var(--ink-muted); margin: 0 0 var(--sp-5);
}
.cal-existing {
  font: var(--fw-regular) var(--fs-caption)/1.4 var(--font-en);
  color: var(--ink-muted); margin: 0 0 var(--sp-3);
}

.cal-levels { display: flex; flex-direction: column; }

.cal-level {
  display: grid; grid-template-columns: 40px 1fr 20px;
  align-items: center; gap: 16px;
  width: 100%; padding: 17px 6px 17px 16px;
  background: none; border: 0;
  border-top: 1px solid rgba(74,72,120,0.08);
  border-radius: 11px;
  text-align: left; color: var(--ink); cursor: pointer;
  position: relative;
  transition: background 0.25s ease;
  -webkit-tap-highlight-color: transparent;
  /* staggered settle-in; `backwards` fill holds the start during the delay and
     releases opacity afterward, so .is-loading can dim the row later. */
  opacity: 1; animation: calRowIn 0.5s var(--ease-out-expo) backwards;
}
.cal-level:first-child { border-top: 0; }
@keyframes calRowIn { from { opacity: 0; transform: translateY(9px); } }
.cal-level:nth-child(1) { animation-delay: 0.10s; }
.cal-level:nth-child(2) { animation-delay: 0.16s; }
.cal-level:nth-child(3) { animation-delay: 0.22s; }
.cal-level:nth-child(4) { animation-delay: 0.28s; }
.cal-level:nth-child(5) { animation-delay: 0.34s; }

/* the single accent: an indigo left rail that grows from centre on hover/pick */
.cal-level::before {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 3px; height: 0; border-radius: 0 3px 3px 0;
  background: var(--primary); transform: translateY(-50%);
  transition: height 0.3s var(--ease-out-expo);
}
.cal-level:hover { background: rgba(255,255,255,0.5); }
.cal-level:hover::before { height: 58%; }
.cal-level:active { background: rgba(255,255,255,0.66); }

.cal-rank {
  font: var(--fw-medium) var(--fs-lead)/1 var(--font-display);
  color: var(--stone); letter-spacing: 0.03em;
  align-self: center;
}
.cal-main { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.cal-name {
  font: var(--fw-regular) var(--fs-lead)/1.25 var(--font-en);
  color: var(--ink-soft); transition: color 0.2s ease;
  display: flex; align-items: center; gap: 9px;
}
.cal-level:hover .cal-name { color: var(--primary); }
.cal-exp {
  font: var(--fw-semibold) 9.5px/1 var(--font-en);
  text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--ink-faint); border: 1px solid rgba(74,72,120,0.18);
  border-radius: var(--radius-pill); padding: 3px 7px; flex: none;
}
.cal-eg {
  font-family: var(--font-jp); font-weight: 400; font-size: var(--fs-body);
  color: var(--ink-soft); letter-spacing: 0.04em; line-height: 1.45;
}
.cal-eg-en, .cal-romaji {
  font: var(--fw-light) var(--fs-caption)/1.4 var(--font-en);
  color: var(--ink-faint); letter-spacing: 0.01em;
}
.cal-romaji { font-style: italic; }
.cal-go {
  color: var(--ink-faint); align-self: center;
  transition: transform 0.28s var(--ease-out-expo), color 0.2s ease;
}
.cal-level:hover .cal-go { transform: translateX(4px); color: var(--primary); }

/* states (JS toggles these on click) */
.cal-level.is-picked { background: rgba(255,255,255,0.62); }
.cal-level.is-picked::before { height: 66%; }
.cal-level.is-picked .cal-name { color: var(--primary); font-weight: var(--fw-medium); }
.cal-level.is-picked .cal-go { transform: translateX(4px); color: var(--primary); }
.cal-level.is-loading { opacity: 0.32; pointer-events: none; }

.cal-foot {
  font: var(--fw-light) var(--fs-sm)/1 var(--font-en);
  color: var(--ink-faint); margin: var(--sp-5) 0 0; text-align: center;
  letter-spacing: 0.03em;
}

/* Done state — a calm confirmation, same indigo accent */
.cal-done { text-align: center; padding: 8px 0 4px; }
.cal-done__seal {
  width: 52px; height: 52px; margin: 0 auto var(--sp-3);
  display: flex; align-items: center; justify-content: center;
  color: var(--primary); background: var(--primary-tint); border-radius: 50%;
}
.cal-done__title { font: var(--fw-regular) var(--fs-h1)/1.1 var(--font-display); color: var(--ink-soft); margin: 0 0 var(--sp-2); }
.cal-done__sub { font: var(--fw-regular) var(--fs-lead)/1.4 var(--font-en); color: var(--ink-soft); margin: 0 0 var(--sp-1); }
.cal-done__sub strong { color: var(--primary); font-weight: var(--fw-semibold); }
.cal-done__stat { font: var(--fw-light) var(--fs-sm)/1.5 var(--font-en); color: var(--ink-muted); margin: 0 0 var(--sp-5); }
.cal-done__go { width: 100%; max-width: 240px; }

/* Toast (inline save error) */
.cal-toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  background: var(--lapsed-bg); border: 1px solid var(--lapsed-edge);
  color: #6a1f00; padding: 10px 18px; border-radius: 12px;
  font: var(--fw-semibold) var(--fs-caption)/1 var(--font-en);
  opacity: 0; transition: opacity 0.24s; z-index: 62; pointer-events: none;
}
.cal-toast.is-shown { opacity: 1; }

/* ── Known words settings page ── */
.settings-tabs { display: flex; gap: 6px; margin-bottom: 28px; }
.settings-tab {
  padding: 8px 18px; border: 1.5px solid rgba(74,72,120,0.12); border-radius: 999px;
  font: 400 13px/1 var(--font-en); color: var(--ink-muted);
  background: none; cursor: pointer; transition: all 0.15s;
}
.settings-tab:hover { border-color: var(--primary-soft); color: var(--primary); }
.settings-tab--active { background: var(--primary); border-color: var(--primary); color: #fff; }
.settings-tab--active:hover { border-color: var(--primary); color: #fff; }
.settings-pane--hidden { display: none; }
.settings-section { margin-bottom: 32px; }
.settings-level-row {
  font: 400 13px/1.4 var(--font-en); color: var(--ink-soft);
  margin-bottom: 14px;
}
.settings-level-value { color: var(--ink); }
.settings-change-btn {
  background: none; border: none; font: 400 12px/1 var(--font-en);
  color: var(--primary-soft); cursor: pointer; margin-left: 6px; padding: 2px 6px;
  border-radius: 4px; transition: all 0.15s;
}
.settings-change-btn:hover { color: var(--ink); }
.settings-search {
  width: 100%; padding: 10px 14px; border: 1.5px solid rgba(74,72,120,0.12);
  border-radius: 10px; font: 400 14px/1.4 var(--font-en); color: var(--ink);
  background: rgba(255,255,255,0.6); margin-bottom: 10px;
}
.settings-search:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 4px rgba(138,99,239,0.12); }
.settings-search::placeholder { color: var(--ink-muted); }
.settings-count-row {
  font: 400 12px/1 var(--font-en); color: var(--ink-muted);
  margin-bottom: 8px;
}
.settings-words { max-height: 420px; overflow-y: auto; border-radius: 10px; background: rgba(255,255,255,0.5); }
.settings-word {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-bottom: 1px solid rgba(74,72,120,0.06);
}
.settings-word:last-child { border-bottom: none; }
.settings-word:hover { background: rgba(250,248,255,0.8); }
.settings-word__text { flex: 1; font: 400 15px/1.3 var(--font-jp); color: var(--ink); }
.settings-word__remove {
  flex-shrink: 0; background: none; border: none; font-size: 18px;
  color: var(--ink-muted); cursor: pointer; padding: 2px 6px; border-radius: 4px;
}
.settings-word__remove:hover { color: #8b4020; background: rgba(200,100,50,0.08); }
.settings-word__confirm {
  font: 700 12px/1 var(--font-en); color: var(--ink-soft);
  display: none; align-items: center; gap: 6px;
}
.settings-word__confirm.is-visible { display: inline-flex; }
.settings-word__yes, .settings-word__no {
  background: none; border: none; font: 800 12px/1 var(--font-en);
  cursor: pointer; padding: 3px 8px; border-radius: 4px;
}
.settings-word__yes { color: #8b4020; }
.settings-word__yes:hover { background: rgba(200,100,50,0.08); }
.settings-word__no { color: var(--ink-muted); }
.settings-word__no:hover { background: rgba(74,72,120,0.06); }
.settings-loading, .settings-empty {
  padding: 24px; text-align: center;
  font: 400 13px/1.4 var(--font-en); color: var(--ink-muted);
}

/* Settings dialog overlay */
.settings-dialog-overlay {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(42,36,64,0.35); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.settings-dialog-overlay[hidden] { display: none; }
.settings-dialog {
  background: var(--card-bg); backdrop-filter: blur(16px);
  border: 1px solid var(--card-border); border-radius: 16px;
  padding: 28px 24px; max-width: 400px; width: 100%;
}
.settings-dialog__heading {
  font: 500 18px/1.2 var(--font-display); color: var(--ink); margin-bottom: 16px;
}
.settings-dialog__levels { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.settings-dialog__level {
  padding: 7px 14px; border: 1.5px solid rgba(74,72,120,0.12); border-radius: 8px;
  font: 400 13px/1 var(--font-en); color: var(--ink-soft);
  background: none; cursor: pointer; transition: all 0.15s;
}
.settings-dialog__level:hover:not(:disabled) { border-color: var(--primary-soft); color: var(--primary); }
.settings-dialog__level--current { opacity: 0.4; cursor: default; }
.settings-dialog__level--selected { background: var(--primary-tint); border-color: var(--primary); color: var(--primary); }
.settings-dialog__body {
  font: 300 13px/1.5 var(--font-en); color: var(--ink-muted); margin-bottom: 20px;
}
.settings-dialog__actions { display: flex; gap: 10px; justify-content: flex-end; }

/* ── Spinner (compat for phaseb.css loading states) ── */
.lav-spinner {
  display: inline-block; width: 14px; height: 14px;
  border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff;
  border-radius: 50%; animation: lavSpin 0.6s linear infinite; margin-right: 8px;
}
.lav-spinner--dark {
  border-color: rgba(0,0,0,0.1); border-top-color: var(--ink-muted);
  width: 12px; height: 12px;
}
@keyframes lavSpin { to { transform: rotate(360deg); } }

/* ── Per-button loading spinner ──
   Apply class "is-busy" to any <button> while it's awaiting a network
   response. The button's contents fade out and a centered spinner appears.
   Layout (width/height/padding) is preserved so neighboring elements
   don't reflow. Pair with the withButtonSpinner() helper in loading.js.
   (Named `is-busy` to match aria-busy and avoid collision with existing
   `is-loading` modifiers on calibrate.css which mean "dimmed/inactive".)

   Text hiding: `color: transparent` handles raw text nodes (`<button>Log in</button>`).
   The `> *` rule handles structured content (`<button><span>…`).
   Spinner color is set via `--is-busy-color`; default suits the app's
   dark-purple buttons. Override for light-bg buttons via
   `--is-busy-color: rgba(0,0,0,.55);`. */
.is-busy {
  position: relative !important;
  pointer-events: none;
  cursor: wait;
  color: transparent !important;
  text-shadow: none !important;
  --is-busy-color: rgba(255, 255, 255, 0.85);
}
.is-busy > *,
.is-busy::before {
  opacity: 0 !important;
}
.is-busy::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid var(--is-busy-color);
  border-right-color: transparent;
  border-radius: 50%;
  animation: lavSpin 0.6s linear infinite;
}
/* .btn--primary and .btn--solid now both have a solid indigo fill, so the
   default white spinner is correct on both. (Historically .btn--primary had no
   fill and needed a purple spinner; that's no longer true.) Cancel/ghost
   buttons keep the default and never get .is-busy in current code. */
.btn--primary.is-busy,
.btn--solid.is-busy { --is-busy-color: rgba(255, 255, 255, 0.85); }
/* Show-switcher menu items sit on a translucent light-bg dropdown panel.
   White spinner is invisible there; use purple. */
.show-switcher-item.is-busy { --is-busy-color: var(--primary, #4A4878); }

/* Badge for newly-available words on other shows */
.show-switcher-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px;
  font: 600 10px/1 var(--font-en, sans-serif);
  color: #fff; background: var(--primary, #4A4878);
  border-radius: 9px; padding: 0 5px;
  margin-left: 8px; flex-shrink: 0;
  animation: switcherBadgeIn 0.4s cubic-bezier(.2,.8,.3,1) both;
}
@keyframes switcherBadgeIn {
  from { opacity: 0; transform: scale(0); }
  to   { opacity: 1; transform: scale(1); }
}

/* Pulsing dot on the title-comp when any non-active show has unlocks */
.title-comp__dot {
  width: 8px; height: 8px;
  background: var(--primary, #4A4878);
  border-radius: 50%;
  position: absolute; top: 6px; right: -14px;
  animation: dotPulse 2s ease-in-out infinite;
}
@keyframes dotPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(1.4); }
}

/* ── PWA standalone mode ──
   Safe area insets for notched devices, touch target minimums. */
@supports (padding-top: env(safe-area-inset-top)) {
  .topbar { padding-top: calc(20px + env(safe-area-inset-top)); }
  @media (min-width: 800px) { .topbar { padding-top: calc(20px + env(safe-area-inset-top)); } }
  .study-actions { bottom: calc(58px + env(safe-area-inset-bottom)); }
  .grid-bottom { bottom: calc(58px + env(safe-area-inset-bottom)); }
  .grid-fab { bottom: calc(24px + env(safe-area-inset-bottom)); }
  .done-message { bottom: calc(14% + 45px + env(safe-area-inset-bottom)); }
}

/* Touch target minimums (44px per Apple HIG) */
.show-switcher-item { min-height: 44px; padding: 12px 14px; display: flex; align-items: center; }
.settings-tab { min-height: 44px; display: inline-flex; align-items: center; }
.done-keep-going { padding: 12px 4px; }
.auth-links a { display: inline-block; padding: 10px 8px; }
.auth-links { margin-top: 12px; }
.settings-word__remove { min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; }
.back-link { min-height: 44px; display: inline-flex; align-items: center; padding: 8px 12px; }
.grid-bottom button { min-height: 44px; }

/* Static (non-clickable) title for free users — no hover, no switcher. */
.title-comp--static { cursor: default; pointer-events: none; }

/* Modal shell — shared by the two-shows celebration and the weekly cooldown
   wall. Calm, optional, on-brand; the CTA stays brand-primary for contrast. */
.upsell {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.upsell[hidden] { display: none; }
.upsell__scrim {
  position: absolute; inset: 0;
  background: rgba(28,24,48,0.30); backdrop-filter: blur(6px);
  opacity: 0; transition: opacity 0.32s ease;
}
.upsell.is-open .upsell__scrim { opacity: 1; }
.upsell__card {
  position: relative; width: 100%; max-width: 380px;
  background: var(--card-bg-solid); backdrop-filter: blur(20px);
  border: 1px solid var(--card-border); border-radius: 22px;
  padding: 32px 28px 24px; text-align: center;
  box-shadow: 0 24px 64px rgba(42,36,64,0.22);
  opacity: 0; transform: translateY(12px) scale(0.97);
  transition: opacity 0.42s var(--ease-out-expo),
              transform 0.42s var(--ease-out-expo);
}
.upsell.is-open .upsell__card { opacity: 1; transform: none; }
.upsell__seal {
  width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--gold-tint); color: var(--gold);
}
.upsell__seal svg { width: 28px; height: 28px; }
.upsell__title {
  font: var(--fw-bold) var(--fs-h3)/1.1 var(--font-display); color: var(--ink);
  margin: 0 0 10px; letter-spacing: 0.01em;
}
.upsell__lede {
  font: var(--fw-regular) var(--fs-body)/1.5 var(--font-display); color: var(--ink-muted);
  margin: 0 0 20px;
}
.upsell__actions { display: grid; gap: 8px; }
.upsell__cta {
  all: unset; cursor: pointer; text-align: center; padding: 14px;
  border-radius: var(--radius-lg); background: var(--primary); color: #fff;
  font: var(--fw-bold) var(--fs-body)/1 var(--font-display);
  box-shadow: 0 6px 18px rgba(74,72,120,0.32);
  transition: transform 0.2s, box-shadow 0.2s;
}
.upsell__cta:hover {
  transform: translateY(-1px); box-shadow: 0 8px 24px rgba(74,72,120,0.40);
}
.upsell__dismiss {
  all: unset; cursor: pointer; text-align: center; padding: 10px;
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-display); color: var(--ink-muted);
  transition: color 0.2s;
}
.upsell__dismiss:hover { color: var(--ink); }
.upsell__note {
  font: var(--fw-regular) var(--fs-caption)/1.4 var(--font-display); color: var(--ink-muted);
  margin: 12px 0 0;
}
.upsell__note[hidden] { display: none; }

/* Two-shows celebration reuses the upsell card. Its seal is brand lavender,
   not premium gold — a milestone the app is giving you, not a locked gate. */
.upsell__seal--milestone { background: var(--primary-tint); color: var(--primary); }
.upsell__hint {
  font: var(--fw-regular) var(--fs-caption)/1.5 var(--font-display); color: var(--ink-faint);
  margin: -6px 0 24px;
}

/* ── #0411 tier UI: /pro pricing page, lesson counter, budget wall, pro toast ──
   Light, rounded, quiet — the cute lives in the seal, the soft tints and the
   gentle motion, never in the copy. Every home-surface class here needs the
   is-caught-up / is-night recolours below (the #0199 white-on-day class). */

/* "pro" link (free users only) — a quiet gold pill beside the settings gear
   (the home topbar's right corner belongs to the vertical show title on
   mobile, so both anchor bottom-left). Vertical alignment is derived from the
   gear's box: 44px tall at bottom 16px (24px desktop), so this 30px pill sits
   7px higher to share the gear's centre line (#0434). */
.topbar-side { display: inline-flex; align-items: center; gap: 16px; }
.topbar-pro {
  position: fixed; left: 64px; bottom: calc(23px + env(safe-area-inset-bottom));
  z-index: 58;
  height: 30px; padding: 0 14px; display: inline-flex; align-items: center;
  border-radius: 15px; background: var(--gold-tint);
  border: 1px solid rgba(212,166,74,0.45);
  font: var(--fw-medium) var(--fs-caption)/1 var(--font-en);
  color: var(--ink-soft); letter-spacing: 0.18em; text-transform: uppercase;
  text-decoration: none; transition: color 0.2s, border-color 0.2s;
}
@media (min-width: 800px) { .topbar-pro { left: 74px; bottom: 31px; } }
.topbar-pro:hover { color: var(--gold); border-color: var(--gold); }
.atmosphere.is-caught-up ~ * .topbar-pro {
  background: rgba(240,201,105,0.10); border-color: rgba(240,201,105,0.40);
  color: rgba(225,220,240,0.85);
}
.atmosphere.is-night ~ * .topbar-pro {
  background: rgba(240,201,105,0.08); border-color: rgba(240,201,105,0.30);
  color: rgba(190,185,215,0.80);
}
.atmosphere.is-caught-up ~ * .topbar-pro:hover,
.atmosphere.is-night ~ * .topbar-pro:hover { color: var(--gold-soft); border-color: var(--gold-soft); }

/* Pricing page shell + card. */
.pro-shell {
  width: 100%; max-width: 460px; margin: auto; padding: 24px 20px 40px;
  position: relative; z-index: 2;
}
.pro-card {
  background: var(--card-bg-solid); backdrop-filter: blur(20px);
  border: 1px solid var(--card-border); border-radius: 22px;
  padding: 32px 26px 24px; text-align: center;
  box-shadow: 0 24px 64px rgba(42,36,64,0.16);
  animation: proCardIn 0.6s var(--ease-out-expo) both;
}
@keyframes proCardIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.pro-seal {
  width: 56px; height: 56px; margin: 0 auto 14px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--gold-tint); color: var(--gold);
}
.pro-seal svg { width: 28px; height: 28px; }
.pro-title {
  font: var(--fw-bold) var(--fs-h3)/1 var(--font-display); color: var(--ink);
  margin: 0 0 8px; letter-spacing: 0.14em; text-transform: uppercase;
}
.pro-lede {
  font: var(--fw-regular) var(--fs-sm)/1.5 var(--font-display); color: var(--ink-muted);
  margin: 0 auto 22px; max-width: 30ch;
}
/* What pro gives, in plain sentences (#0434 replaced the free/pro grid). */
.pro-points {
  text-align: left; margin: 0 0 14px; padding: 0; list-style: none;
  display: grid; gap: 12px;
}
.pro-points li {
  position: relative; padding-left: 18px;
  font: var(--fw-regular) var(--fs-sm)/1.5 var(--font-display); color: var(--ink-soft);
}
.pro-points li::before {
  content: ""; position: absolute; left: 2px; top: 0.55em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--gold);
}
.pro-free-line {
  text-align: left; margin: 0 0 22px; padding-left: 18px;
  font: var(--fw-regular) var(--fs-caption)/1.5 var(--font-display); color: var(--ink-muted);
}
.pro-plans { display: grid; gap: 8px; }
.pro-plan {
  all: unset; box-sizing: border-box; cursor: pointer; text-align: center;
  padding: 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--rule); color: var(--primary);
  font: var(--fw-bold) var(--fs-body)/1 var(--font-display);
  transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.pro-plan:hover { transform: translateY(-1px); border-color: var(--primary-soft); }
.pro-plan:disabled { opacity: 0.55; cursor: default; transform: none; }
.pro-plan--annual {
  background: var(--primary); border-color: var(--primary); color: #fff;
  box-shadow: 0 6px 18px rgba(74,72,120,0.32);
}
.pro-plan--annual:hover { box-shadow: 0 8px 24px rgba(74,72,120,0.40); }
.pro-note {
  font: var(--fw-regular) var(--fs-caption)/1.4 var(--font-display);
  color: #9f2256; margin: 4px 0 0;
}
.pro-already__line {
  font: var(--fw-medium) var(--fs-body)/1.4 var(--font-display); color: var(--ink);
  margin: 0 0 10px;
}
.pro-already__billing {
  display: inline-block; padding: 10px 22px; border-radius: var(--radius-pill);
  border: 1px solid var(--rule); color: var(--primary); text-decoration: none;
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-display);
  transition: border-color 0.2s;
}
.pro-already__billing:hover { border-color: var(--primary-soft); }
.pro-foot {
  font: var(--fw-regular) var(--fs-caption)/1.4 var(--font-display);
  color: var(--ink-faint); margin: 18px 0 0;
}
.pro-foot a { color: var(--ink-muted); text-decoration: none; }
/* In-card "back" at the foot of the pro card (the topbar .back-link is a
   thumb-stretch on this narrow centred card). Kept a SEPARATE class from
   .back-link on purpose: the card is opaque, so this keeps the dark base ink on
   every tier and must NOT inherit `body.is-night .back-link`'s light recolour
   (that override is for the topbar sitting over the darkening atmosphere bg). */
.pro-back {
  display: block; text-align: center; margin: 16px auto 0;
  font: var(--fw-light) var(--fs-sm)/1 var(--font-en); color: var(--ink-muted);
  letter-spacing: 0.1em; text-transform: lowercase;
  text-decoration: none; transition: color 0.2s;
}
.pro-back:hover { color: var(--primary); }
.pro-foot a:hover { color: var(--ink); }
/* .pro-plans is display:grid, so the [hidden] attribute needs this override
   (.pro-note/.pro-foot/.pro-already keep the UA default and need nothing). */
.pro-plans[hidden] { display: none; }

/* Home lesson counter — one quiet caption under the Learn action.
   #extra-learn-area is a flex ROW (it centres the lone Learn glyph), so it
   stacks vertically here or the caption sits beside the icon. */
.home-actions-area--icons #extra-learn-area { flex-direction: column; align-items: center; }
.lesson-counter {
  text-align: center; margin-top: 10px;
  font: var(--fw-regular) var(--fs-caption)/1.4 var(--font-display);
  color: var(--ink-muted);
}
.lesson-counter a { color: var(--ink-muted); text-decoration: none; border-bottom: 1px solid var(--rule); transition: color 0.2s; }
.lesson-counter a:hover { color: var(--ink); }
.atmosphere.is-caught-up ~ * .lesson-counter,
.atmosphere.is-caught-up ~ * .lesson-counter a { color: rgba(215,210,235,0.65); }
.atmosphere.is-night ~ * .lesson-counter,
.atmosphere.is-night ~ * .lesson-counter a { color: rgba(160,155,190,0.62); }

/* Budget wall — the free budget is spent. Calm pause, reviews stay open. */
.budget-wall { text-align: center; padding: 18px 12px; animation: fadeIn 0.6s ease both; }
.budget-wall__seal {
  width: 44px; height: 44px; margin: 0 auto 10px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--primary-tint); color: var(--primary-soft);
}
.budget-wall__seal svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.budget-wall__line {
  font: var(--fw-regular) var(--fs-sm)/1.5 var(--font-display); color: var(--ink-muted);
  max-width: 30ch; margin: 0 auto 14px;
}
.budget-wall__cta {
  display: inline-block; padding: 10px 24px; border-radius: var(--radius-pill);
  border: 1px solid var(--primary-soft); color: var(--primary);
  font: var(--fw-medium) var(--fs-sm)/1 var(--font-display); text-decoration: none;
  transition: background 0.2s, color 0.2s;
}
.budget-wall__cta:hover { background: var(--primary-tint); }
.atmosphere.is-caught-up ~ * .budget-wall__seal { background: rgba(235,232,245,0.14); color: rgba(235,232,245,0.85); }
.atmosphere.is-caught-up ~ * .budget-wall__line { color: rgba(215,210,235,0.72); }
.atmosphere.is-caught-up ~ * .budget-wall__cta { border-color: rgba(225,220,240,0.45); color: rgba(235,232,245,0.92); }
.atmosphere.is-caught-up ~ * .budget-wall__cta:hover { background: rgba(235,232,245,0.12); }
.atmosphere.is-night ~ * .budget-wall__seal { background: rgba(210,205,230,0.10); color: rgba(210,205,230,0.70); }
.atmosphere.is-night ~ * .budget-wall__line { color: rgba(190,185,215,0.65); }
.atmosphere.is-night ~ * .budget-wall__cta { border-color: rgba(190,185,215,0.40); color: rgba(210,205,230,0.85); }
.atmosphere.is-night ~ * .budget-wall__cta:hover { background: rgba(210,205,230,0.10); }

/* Post-checkout toast: one line in the pro gold, fades itself out. */
.pro-toast {
  position: fixed; top: 18px; left: 50%; transform: translateX(-50%);
  z-index: 240; pointer-events: none;
  padding: 10px 20px; border-radius: var(--radius-pill);
  background: var(--gold-tint); border: 1px solid rgba(212,166,74,0.35);
  color: #8a6a22; font: var(--fw-medium) var(--fs-sm)/1 var(--font-display);
  box-shadow: 0 8px 24px rgba(42,36,64,0.10);
  animation: proToast 4.6s ease both;
}
@keyframes proToast {
  0% { opacity: 0; transform: translate(-50%, -8px); }
  8%, 82% { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, -6px); }
}

/* Cooldown wall pro line — sits under the lede, links to /pro. */
.upsell__pro { margin: -8px 0 20px; }
.upsell__pro a {
  font: var(--fw-medium) var(--fs-caption)/1.4 var(--font-display);
  color: var(--primary-soft); text-decoration: none;
  border-bottom: 1px solid var(--rule); transition: color 0.2s;
}
.upsell__pro a:hover { color: var(--primary); }
