* { box-sizing: border-box; }

/* One Spectrum: the HUD chrome is keyed to the song like the world and
   the characters -- these vars are rewritten from the live spectral
   palette (main.js, cache-gated) so combo/score/roleDots/buttons all
   shift hue with the song's key and section, as one family. DO NOT
   rename -- main.js writes to these exact custom-property names. */
:root {
  --spec-gold: #ffd76a;    /* combo, grade, hover accents */
  --spec-cool: #7ad1ff;    /* BASS role, ghost-button hover */
  --spec-warm: #ff7ab6;    /* primary file button, warm accents */
  --spec-pad: #b9a3ff;     /* PAD role dot */
  --spec-rhythm: #ff5a7a;  /* RHYTHM role dot, hide action */
  --spec-hue: 45;

  /* Structural design tokens -- static, not song-driven. Every color,
     spacing, radius, and motion value in this file is built on these so
     the whole chrome reads as one deliberately-designed surface instead
     of each panel drifting to its own nearby purple. */
  --bg-void: #050308;                    /* page/stage background */
  --bg-surface: rgba(14, 11, 22, 0.86);  /* card background */
  --bg-surface-2: rgba(255, 255, 255, 0.05); /* nested/raised surface: inputs, chips, rows */
  --bg-surface-3: rgba(0, 0, 0, 0.3);    /* recessed surface: settings wells */
  --bg-scrim: rgba(5, 3, 8, 0.72);       /* modal/drag backdrop */

  --border-subtle: rgba(255, 255, 255, 0.1);
  --border-strong: rgba(255, 255, 255, 0.22);

  --text-primary: #f2f0f8;
  --text-secondary: rgba(242, 240, 248, 0.65);
  --text-tertiary: rgba(242, 240, 248, 0.45);

  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-7: 32px; --space-8: 40px;

  --radius-sm: 8px; --radius-md: 12px; --radius-lg: 20px; --radius-pill: 999px;

  --ease-standard: cubic-bezier(0.2, 0.7, 0.3, 1);
  --dur-fast: 120ms; --dur-base: 200ms; --dur-slow: 320ms;

  --font-sans: 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'SF Mono', Consolas, ui-monospace, monospace;

  --shadow-card: 0 24px 64px rgba(0, 0, 0, 0.45);
  --shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.5);
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg-void);
  color: var(--text-primary);
  font-family: var(--font-sans);
  overflow: hidden;
  height: 100%;
}

#app {
  position: relative;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

#stage {
  /* Logical stage is 1280×720; backing store can be 720p–4K (see stageRes).
     CSS letterboxes to the window; the compositor scales drawing into the buffer. */
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: #0a0a14;
  position: relative;
  z-index: 1;
  image-rendering: auto;
}

/* Optional WebGL post-FX overlay (created only when ?renderer=webgl).
   Pointer-events none so drops/clicks still hit the page/stage handlers. */
#stage-webgl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  z-index: 2;
  display: block;
  background: transparent;
}

.panel {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  background: radial-gradient(circle at 50% 40%, rgba(60, 30, 90, 0.5), rgba(5, 3, 8, 0.92));
  text-align: center;
  padding: var(--space-6);
}

.panel.hidden, .hidden { display: none !important; }

/* ── Error banner ─────────────────────────────────────────────────────────── */
/* Replaces the old native alert() surface (see showErrorBanner in main.js):
   a dismissible, styled-in-frame message instead of a browser dialog
   dumping raw exception text. Sits above every other layer, including
   modals, since a failure can happen mid-modal too. */
.errorBanner {
  position: absolute;
  top: var(--space-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: min(560px, calc(100% - var(--space-6)));
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid var(--spec-rhythm);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 0.86rem;
  line-height: 1.4;
}
.errorBanner.hidden { display: none !important; }
.errorBannerText { flex: 1; }
.errorBannerClose {
  flex: none;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--space-1);
}
.errorBannerClose:hover { color: var(--text-primary); }

@media (max-width: 540px) {
  .errorBanner { top: var(--space-2); font-size: 0.8rem; }
}

/* ── Sync prompt ──────────────────────────────────────────────────────────── */
/* An offer, not a failure -- gold-toned rather than the error banner's red,
   and anchored at the bottom so it never competes with it for the same
   space. See SyncMonitor.js / the consumePrompt() check in main.js. */
.syncPrompt {
  position: absolute;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: min(480px, calc(100% - var(--space-6)));
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid color-mix(in srgb, var(--spec-gold) 55%, transparent);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 0.86rem;
  line-height: 1.4;
}
.syncPrompt.hidden { display: none !important; }
.syncPromptText { flex: 1; }
.syncPromptFixBtn {
  flex: none;
  border: 1px solid color-mix(in srgb, var(--spec-gold) 55%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--spec-gold) 18%, transparent);
  color: var(--text-primary);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 5px var(--space-3);
  cursor: pointer;
}
.syncPromptFixBtn:hover { background: color-mix(in srgb, var(--spec-gold) 28%, transparent); }
.syncPromptDismissBtn {
  flex: none;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--space-1);
}
.syncPromptDismissBtn:hover { color: var(--text-primary); }

@media (max-width: 540px) {
  .syncPrompt { bottom: var(--space-3); font-size: 0.8rem; }
}

/* ── Title screen ─────────────────────────────────────────────────────────── */
#loader.titleScreen {
  gap: 0;
  padding: var(--space-4) var(--space-5) var(--space-3);
  overflow: auto;
  /* The stage canvas already draws TitleBackdrop (stars, nebula, the trio).
     An opaque void here was covering that entire living world, so the first
     impression was a flat card on black instead of a Midio stage. */
  background: transparent;
  justify-content: flex-start;
}

.titleBg {
  /* CSS hills/stars are a no-JS fallback only. Once the canvas backdrop is
     painting, showing both doubles the sky and muddies the glyphs. */
  display: none;
}

.titleStars {
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1.2px 1.2px at 8% 18%, rgba(255,255,255,0.85), transparent),
    radial-gradient(1px 1px at 22% 42%, rgba(255,255,255,0.55), transparent),
    radial-gradient(1.5px 1.5px at 38% 12%, rgba(255,255,255,0.75), transparent),
    radial-gradient(1px 1px at 55% 28%, rgba(255,255,255,0.45), transparent),
    radial-gradient(1.2px 1.2px at 71% 8%, rgba(255,255,255,0.7), transparent),
    radial-gradient(1px 1px at 84% 36%, rgba(255,255,255,0.5), transparent),
    radial-gradient(1.4px 1.4px at 92% 16%, rgba(255,255,255,0.65), transparent),
    radial-gradient(1px 1px at 14% 62%, rgba(255,255,255,0.35), transparent),
    radial-gradient(1px 1px at 48% 55%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.2px 1.2px at 66% 48%, rgba(255,255,255,0.55), transparent),
    radial-gradient(1px 1px at 6% 78%, rgba(255,255,255,0.4), transparent),
    radial-gradient(1.2px 1.2px at 33% 86%, rgba(255,255,255,0.5), transparent),
    radial-gradient(1px 1px at 61% 74%, rgba(255,255,255,0.35), transparent),
    radial-gradient(1.3px 1.3px at 88% 82%, rgba(255,255,255,0.45), transparent);
  animation: titleTwinkle 4.5s ease-in-out infinite alternate;
  opacity: 0.85;
}

@keyframes titleTwinkle {
  from { opacity: 0.55; }
  to { opacity: 1; }
}

.titleGlow {
  position: absolute;
  border-radius: 50%;
  filter: blur(48px);
  opacity: 0.5;
  animation: titleDrift 14s ease-in-out infinite alternate;
}
.titleGlowA {
  width: 42vw; height: 42vw; max-width: 420px; max-height: 420px;
  left: 8%; top: 12%;
  background: color-mix(in srgb, var(--spec-warm) 60%, transparent);
}
.titleGlowB {
  width: 36vw; height: 36vw; max-width: 360px; max-height: 360px;
  right: 6%; top: 18%;
  background: color-mix(in srgb, var(--spec-cool) 55%, transparent);
  animation-delay: -4s;
}
.titleGlowC {
  width: 50vw; height: 28vw; max-width: 520px; max-height: 280px;
  left: 28%; bottom: 8%;
  background: color-mix(in srgb, var(--spec-gold) 32%, transparent);
  animation-delay: -8s;
}

@keyframes titleDrift {
  from { transform: translate(0, 0) scale(1); }
  to { transform: translate(18px, -22px) scale(1.08); }
}

@media (prefers-reduced-motion: reduce) {
  .titleStars, .titleGlow { animation: none; }
}

.titleHills {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 28%;
  background:
    radial-gradient(ellipse 70% 100% at 20% 100%, #1a1030 0%, transparent 70%),
    radial-gradient(ellipse 80% 120% at 55% 100%, #120c28 0%, transparent 72%),
    radial-gradient(ellipse 60% 100% at 85% 100%, #0e1a30 0%, transparent 70%);
  opacity: 0.9;
}

/* Shared card language -- .titleCard, .completeCard, and .slskPanel all
   read as the same family of surface now (same radius/border/blur/shadow
   recipe), differentiated only by content, not by three near-identical
   but slightly different purples. */
.titleCard {
  position: relative;
  z-index: 1;
  width: min(520px, 100%);

  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
  margin: 0 auto var(--space-4);
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-radius: var(--radius-lg);
  background: linear-gradient(165deg, rgba(24, 16, 38, 0.78), rgba(14, 11, 22, 0.82));
  border: 1px solid var(--border-subtle);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--spec-gold) 6%, transparent) inset,
    var(--shadow-card),
    0 0 80px rgba(120, 60, 180, 0.1);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.titleHero {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  text-align: left;
}

.titleMascot {
  position: relative;
  flex: 0 0 72px;
  width: 72px;
  height: 88px;
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--spec-gold) 45%, transparent));
  animation: mascotBob 2.8s ease-in-out infinite;
}
/* Same angular shard language as Midio's in-game glyph -- the old round
   gold blob read as a cartoon mascot on a title card for a wireframe world. */
.mascotShard {
  position: absolute;
  inset: 0 10px 8px;
  background: linear-gradient(165deg, #fff6cf 0%, var(--spec-gold) 42%, #e08a18 100%);
  clip-path: polygon(50% 0%, 72% 20%, 80% 48%, 66% 78%, 62% 100%, 50% 88%, 38% 100%, 34% 76%, 20% 48%, 28% 20%);
}
.mascotCore {
  position: absolute;
  top: 42%;
  left: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: #1a1020;
  box-shadow: 0 0 8px color-mix(in srgb, var(--spec-warm) 70%, transparent);
}

@keyframes mascotBob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}
@keyframes mascotBlink {
  0%, 92%, 100% { transform: scaleY(1); }
  95% { transform: scaleY(0.12); }
}
@media (prefers-reduced-motion: reduce) {
  .titleMascot { animation: none; }
}

.titleBrand { flex: 1; min-width: 0; }
.titleEyebrow {
  margin: 0 0 var(--space-1);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--spec-gold) 75%, transparent);
}
#loader.titleScreen h1 {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  margin: 0;
  line-height: 0.95;
  background: none;
  color: inherit;
  -webkit-background-clip: unset;
  background-clip: unset;
}
.titleWord {
  display: block;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.titleWordSuper {
  font-size: 0.95rem;
  letter-spacing: 0.28em;
  color: var(--text-secondary);
}
.titleWordMidio {
  font-size: clamp(2.1rem, 6vw, 2.75rem);
  letter-spacing: 0.1em;
  background: linear-gradient(90deg, var(--spec-gold), var(--spec-warm), var(--spec-cool));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 18px color-mix(in srgb, var(--spec-warm) 35%, transparent));
}
.titleWordWorld {
  font-size: 1.05rem;
  letter-spacing: 0.32em;
  color: color-mix(in srgb, var(--spec-cool) 80%, var(--text-primary));
}
.titleTagline {
  margin: var(--space-3) 0 0;
  font-size: 0.92rem;
  line-height: 1.4;
  color: var(--text-secondary);
}

/* The game is autoplay -- Midio performs the song himself, and every key
   that isn't F/J/C/etc. otherwise falls through to an unroled beat-anchor
   tap (see main.js's keydown handler). Without this line a first-time
   visitor reaches for arrows/WASD/Space expecting to control him, gets no
   feedback, and concludes the controls are broken. */
.titleSub {
  margin: var(--space-2) 0 0;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--text-tertiary);
}
.titleSub kbd {
  display: inline-block;
  min-width: 1.2em;
  padding: 0 5px;
  border-radius: 4px;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: color-mix(in srgb, var(--spec-gold) 85%, transparent);
}

#loader #dropzone {
  border: 1.5px dashed color-mix(in srgb, var(--spec-gold) 35%, transparent);
  border-radius: var(--radius-md);
  padding: 0;
  display: block;
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-standard), background var(--dur-base), box-shadow var(--dur-base), transform var(--dur-base);
  background: rgba(255, 255, 255, 0.03);
  outline: none;
}
#loader #dropzone:hover,
#loader #dropzone:focus-visible {
  border-color: color-mix(in srgb, var(--spec-gold) 70%, transparent);
  background: color-mix(in srgb, var(--spec-gold) 6%, transparent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--spec-gold) 8%, transparent);
}
#loader #dropzone.drag {
  border-color: var(--spec-gold);
  border-style: solid;
  background: color-mix(in srgb, var(--spec-gold) 12%, transparent);
  box-shadow: 0 0 32px color-mix(in srgb, var(--spec-gold) 20%, transparent);
  transform: scale(1.015);
}
.dropzoneInner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5) var(--space-4);
}
.dropIcon {
  font-size: 1.8rem;
  line-height: 1;
  color: var(--spec-gold);
  text-shadow: 0 0 16px color-mix(in srgb, var(--spec-gold) 55%, transparent);
  animation: dropNote 2.2s ease-in-out infinite;
}
@keyframes dropNote {
  0%, 100% { transform: translateY(0) rotate(-6deg); }
  50% { transform: translateY(-4px) rotate(6deg); }
}
@media (prefers-reduced-motion: reduce) {
  .dropIcon { animation: none; }
}
.dropPrimary {
  font-size: 1.05rem;
  font-weight: 650;
  color: var(--text-primary);
}
.dropSecondary {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  margin-bottom: var(--space-2);
}

/* Primary/action pill button (file picker, Soulseek search). One recipe
   reused wherever an accent-filled call-to-action is needed. */
.filebtn {
  background: linear-gradient(135deg, var(--spec-warm), #ff9a6a);
  color: #1a0d16;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
  font-weight: 650;
  cursor: pointer;
  display: inline-block;
  box-shadow: 0 4px 16px color-mix(in srgb, var(--spec-warm) 30%, transparent);
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base);
}
.filebtn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--spec-warm) 40%, transparent);
}

/* Live listening: the mobile entry point. A phone cannot read a streaming
   service's audio (DRM, cross-origin embeds, no tab capture), but its
   speaker is already playing the song -- so this is the primary path on a
   phone and is styled as the confident option, not as a fallback. */
.listenPanel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-4) var(--space-4) var(--space-3);
  border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
  border-radius: var(--radius-lg, 14px);
  background: color-mix(in srgb, var(--spec-cool) 6%, transparent);
}
.listenHeading {
  margin: 0;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.85;
}
.listenLead { margin: 0; font-size: 0.9rem; opacity: 0.8; max-width: 42ch; }
.listenNote { margin: 0; font-size: 0.76rem; opacity: 0.6; max-width: 46ch; }
.listenStatus { margin: 0; font-size: 0.8rem; }

#listenBtn.listenBtn {
  width: 100%;
  max-width: 22rem;
  background: linear-gradient(135deg, var(--spec-warm), var(--spec-gold) 120%);
  border: none;
  color: #1a0d16;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: 1rem;
  /* Comfortably past the 44px touch-target floor: this is the one control
     that matters on a phone. */
  min-height: 48px;
  box-shadow: 0 4px 16px color-mix(in srgb, var(--spec-warm) 30%, transparent);
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base), filter var(--dur-base);
}
#listenBtn.listenBtn:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--spec-warm) 40%, transparent);
}
#listenBtn.listenBtn[disabled] { opacity: 0.55; cursor: default; transform: none; }
#playAgainBtn:hover {
  border-color: var(--spec-cool);
  color: var(--spec-cool);
  background: color-mix(in srgb, var(--spec-cool) 8%, transparent);
  box-shadow: 0 0 24px color-mix(in srgb, var(--spec-cool) 12%, transparent);
}

#playAgainBtn {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: 0.9rem;
  transition: border-color var(--dur-base), color var(--dur-base);
}

.titleMeta {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: center;
}
.titleFlashNote {
  margin: 0;
  max-width: 44ch;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--text-tertiary);
}
.titleFlashNote strong { color: var(--text-secondary); font-weight: 600; }
#loader .prefRow {
  margin-top: 0;
  flex-wrap: wrap;
}
.chipbtn {
  border-radius: var(--radius-pill) !important;
  font-size: 0.78rem !important;
  padding: 5px var(--space-3) !important;
}

.titleAdvanced {
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-3);
  text-align: left;
  overflow: hidden;
}
.titleAdvanced summary {
  cursor: pointer;
  list-style: none;
  padding: var(--space-2) var(--space-4);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  user-select: none;
  transition: color var(--dur-fast);
}
.titleAdvanced summary::-webkit-details-marker { display: none; }
.titleAdvanced summary::before {
  content: '▸ ';
  color: color-mix(in srgb, var(--spec-gold) 70%, transparent);
}
.titleAdvanced[open] summary::before { content: '▾ '; }
.titleAdvanced[open] summary {
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-primary);
}
.titleAdvanced .sfRow {
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-1);
  flex-wrap: wrap;
  justify-content: center;
}
.titleAdvanced .hint {
  margin: 0;
  padding: var(--space-1) var(--space-4) var(--space-3);
  text-align: center;
}
.titleAdvanced .hint a { color: color-mix(in srgb, var(--spec-gold) 85%, transparent); }

/* Music-search rows (Jamendo/Soulseek panels) */
.jamendoRow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-4);
}
.jamendoRow input {
  flex: 1;
  min-width: 0;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 6px var(--space-3);
  font-size: 0.82rem;
}
.jamendoRow input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--spec-gold) 55%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--spec-gold) 15%, transparent);
}
.jamendoStatus.hidden { display: none; }
.jamendoStatus { color: var(--text-secondary); }
.jamendoStatusError { color: var(--spec-rhythm); }
.jamendoResults {
  list-style: none;
  margin: 0;
  padding: 0 var(--space-4) var(--space-3);
  max-height: 180px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.jamendoResultBtn {
  width: 100%;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.progressHint {
  min-height: 1.2em;
  color: var(--spec-cool) !important;
  opacity: 0.9 !important;
}

.titleKeys {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.72rem;
  color: var(--text-tertiary);
}
.titleKeys kbd {
  display: inline-block;
  min-width: 1.4em;
  padding: 1px 6px;
  margin-right: 3px;
  border-radius: 4px;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface-2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: color-mix(in srgb, var(--spec-gold) 85%, transparent);
}

@media (max-width: 540px) {
  .titleHero { flex-direction: column; text-align: center; }
  #loader.titleScreen h1 { align-items: center; }
  .titleCard { padding: var(--space-5) var(--space-4) var(--space-4); }
}

.sub { opacity: 0.8; margin: 0; }
.hint { opacity: 0.5; font-size: 0.8rem; margin-top: var(--space-3); }

/* Generic dropzone (also used if non-title contexts ever share the id) */
#dropzone {
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-7) var(--space-8);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  transition: border-color var(--dur-base), background var(--dur-base);
}
#dropzone.drag { border-color: var(--spec-gold); background: color-mix(in srgb, var(--spec-gold) 8%, transparent); }

/* ── Complete screen ─────────────────────────────────────────────────────── */
#completePanel.completePanel {
  gap: 0;
  padding: var(--space-6);
  background:
    radial-gradient(ellipse 80% 60% at 50% 30%, rgba(70, 40, 110, 0.4), transparent 60%),
    radial-gradient(circle at 50% 100%, rgba(10, 30, 50, 0.5), rgba(5, 3, 8, 0.94));
}
.completeCard {
  width: min(520px, 94vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-6) var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(10px);
}
.completeEyebrow {
  margin: 0;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#completePanel h1 {
  font-size: 2.4rem;
  letter-spacing: 0.08em;
  margin: 0;
  color: var(--spec-gold);
  text-shadow: 0 0 28px color-mix(in srgb, var(--spec-gold) 35%, transparent);
}
.completeSeedRow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: center;
  font-size: 0.9rem;
}
.completeSeedRow .seedLabel {
  opacity: 0.55;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.72rem;
}
.completeSeedRow code {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  letter-spacing: 0.12em;
  color: color-mix(in srgb, var(--spec-cool) 88%, var(--text-primary));
  background: var(--bg-surface-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  padding: 6px var(--space-3);
}
.resultsGrid {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-1) 0 2px;
  text-align: left;
}
.statCell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  border: 1px solid var(--border-subtle);
}
.statCell .statLabel {
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.statCell .statValue {
  font-size: 1.15rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.statCell.statWide { grid-column: 1 / -1; }
.completeActions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
  width: 100%;
  margin-top: var(--space-1);
}
.completeActions button {
  min-width: 120px;
}
/* #replaySameSeedBtn ships with no class in the markup (browser-default
   button otherwise) -- give it the same primary-pill treatment as the
   title screen's #demoBtn/.filebtn family so the results screen's main
   call-to-action doesn't read as an unstyled leftover. */
#replaySameSeedBtn {
  background: linear-gradient(135deg, var(--spec-warm), var(--spec-gold));
  color: #1a0d16;
  border: none;
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-pill);
  font-weight: 650;
  font-size: 0.9rem;
  cursor: pointer;
  box-shadow: 0 4px 16px color-mix(in srgb, var(--spec-warm) 30%, transparent);
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base);
}
#replaySameSeedBtn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--spec-warm) 40%, transparent);
}
.completeNewSeedRow {
  margin-top: 2px;
}

/* Title seed + stage resolution */
.seedRow, .resRow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-1);
  font-size: 0.85rem;
}
.seedRow label, .resRow label {
  opacity: 0.6;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.72rem;
}
#seedInput, #completeNewSeedInput {
  width: min(160px, 40vw);
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 6px var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.06em;
}
#seedInput:focus, #completeNewSeedInput:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--spec-gold) 55%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--spec-gold) 15%, transparent);
}
#stageRes {
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 6px var(--space-3);
  font-size: 0.85rem;
}

#hud {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  right: var(--space-4);
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
  z-index: 5;
}

/* Right-hand cluster of the HUD row: difficulty, fullscreen, settings gear,
   track badge. #hud disables pointer-events so the canvas underneath stays
   clickable; this wrapper opts its interactive children back in.
   Auto-fades after a few seconds of inactivity (main.js); while faded it's
   both invisible and unclickable, and the first tap anywhere just wakes it
   back up rather than also acting on whatever's underneath. */
#hudRight {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  pointer-events: auto;
  opacity: 1;
  transition: opacity var(--dur-slow) ease;
}
#hudRight.hud-faded {
  opacity: 0;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  #hudRight { transition: none; }
}
#settingsBtn, #fullscreenBtn {
  line-height: 1;
}

/* On-device FPS HUD (mobile performance round): small, always-on-top,
   toggled by the P key or the ?fps URL param -- deliberately much lighter
   than #debugOverlay so it's cheap enough to leave on during play. */
#fpsHud {
  position: absolute;
  left: var(--space-2); bottom: var(--space-2);
  z-index: 20;
  background: rgba(5, 5, 10, 0.75);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: var(--space-1) var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  color: #d8f7c8;
  pointer-events: none;
  white-space: pre;
}

#debugOverlay {
  position: absolute;
  top: 0; right: 0;
  /* #stage carries z-index:1, so an auto-stacked overlay painted UNDERNEATH
     the canvas and was invisible however much it rendered. Sits with the
     other HUD chrome now. */
  z-index: 8;
  color: #d8f7c8;
  width: 380px;
  height: 100%;
  background: rgba(5, 5, 10, 0.9);
  border-left: 1px solid var(--border-strong);
  padding: var(--space-4);
  overflow-y: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.5;
  white-space: pre-wrap;
}

/* Mario Paint-style wobbling title letters on the COMPLETE panel. */
.wobble-letter {
  display: inline-block;
  animation: letterWobble 1.5s ease-in-out infinite;
}
@keyframes letterWobble {
  0%, 100% { transform: rotate(-5deg) translateY(0); }
  50% { transform: rotate(5deg) translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) {
  .wobble-letter { animation: none; }
}

/* SoundFont loader row */
.sfRow {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-top: var(--space-3);
}

.prefRow {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  justify-content: center;
  margin-top: var(--space-3);
}

/* Preference toggles read as engaged when on. */
#noLyricsBtn[aria-pressed="true"] {
  border-color: var(--spec-cool);
  color: var(--spec-cool);
}

/* Ghost button (outline style) -- the shared secondary-action recipe used
   across the title screen, HUD, and every modal footer. */
.ghostbtn {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-secondary);
  padding: 6px var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.85rem;
  transition: border-color var(--dur-base), color var(--dur-base);
}
.ghostbtn:hover {
  border-color: var(--spec-cool);
  color: var(--spec-cool);
}

/* Compact square variant for single-glyph buttons (gear, ×). */
.iconbtn {
  padding: var(--space-1) 10px;
  font-size: 1rem;
  border-radius: var(--radius-sm);
}

/* Fullscreen sits at the end of the row and is the one HUD button people
   reach for mid-song without looking -- bigger target, more breathing room
   from Stop so a hurried tap doesn't clip it instead. */
#fullscreenBtn {
  padding: 10px 16px;
  font-size: 1.35rem;
  margin-left: var(--space-3);
}

/* Auto-hiding SoundFont pill (bottom-center) — click to open the switcher
   popup (#fontModal). */
#fontBar {
  position: absolute;
  bottom: 96px; /* clears the composer strip, now bottom-anchored */
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) ease;
  z-index: 6;
}
#fontBar.visible {
  opacity: 1;
  pointer-events: auto;
}
#fontBarBtn {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: rgba(5, 5, 10, 0.72);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  font-size: 0.85rem;
  color: var(--text-primary);
  cursor: pointer;
  transition: border-color var(--dur-base), color var(--dur-base);
}
#fontBarBtn:hover { border-color: var(--spec-cool); color: var(--spec-cool); }
#fontName {
  min-width: 100px;
  max-width: 220px;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Track visibility badge (top-right of the HUD row) — click/tap or press T
   to expand the full per-track breakdown of the loaded MIDI. */
#trackBadge {
  position: relative;
  pointer-events: auto;
}
#trackBadgeBtn {
  background: rgba(5, 5, 10, 0.72);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  padding: 6px var(--space-4);
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  cursor: pointer;
  transition: border-color var(--dur-base), color var(--dur-base);
}
#trackBadgeBtn:hover { border-color: var(--spec-cool); color: var(--spec-cool); }

#trackList {
  display: none;
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  min-width: 260px;
  max-width: 340px;
  max-height: 320px;
  overflow-y: auto;
  background: rgba(5, 5, 10, 0.94);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-2);
  font-size: 0.78rem;
}
#trackBadge.expanded #trackList { display: block; }

.trackRow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 6px;
  border-radius: var(--radius-sm);
}
.trackRow.intertwined { background: var(--bg-surface-2); }
.trackRow .roleDot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}
.roleDot.role-MELODY { background: var(--spec-gold); }
.roleDot.role-BASS   { background: var(--spec-cool); }
.roleDot.role-PAD    { background: var(--spec-pad); }
.roleDot.role-RHYTHM { background: var(--spec-rhythm); }

.trackRow .trackName {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trackRow .trackMeta {
  flex: none;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}
/* Casting tag: which character performs this track (Casting.js lanes). */
.trackRow .laneTag {
  flex: none;
  font-size: 0.68rem;
  opacity: 0.75;
  padding: 0 5px;
  border-radius: var(--radius-sm);
  background: var(--bg-surface-2);
  white-space: nowrap;
}
.trackRow .panGlyph {
  flex: none;
  width: 1.4em;
  text-align: center;
  opacity: 0.8;
}
.trackListHint {
  opacity: 0.5;
  font-size: 0.72rem;
  padding: 4px 6px 2px;
  border-top: 1px solid var(--border-subtle);
  margin-top: 4px;
}

/* Full-window "drop to play" feedback — shown while dragging a file over
   the page mid-song (the loader screen's own #dropzone already gives its
   own drag feedback pre-game, so this only activates once #loader is
   hidden; see main.js's isLoaderVisible()). pointer-events:none keeps it
   purely decorative — the actual dragenter/dragover/drop listeners live on
   `window`, not on this element. */
#dragOverlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) ease;
  z-index: 20;
}
#dragOverlay.visible { opacity: 1; }
.dragOverlayMsg {
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: var(--space-5) var(--space-7);
  border: 3px dashed var(--spec-gold);
  border-radius: var(--radius-md);
  color: var(--spec-gold);
  background: rgba(5, 5, 10, 0.5);
}

.landscapeHint {
  display: none;
  position: absolute;
  top: var(--space-2);
  left: 50%;
  transform: translateX(-50%);
  z-index: 25;
  pointer-events: none;
  padding: 4px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  font-size: 0.72rem;
  white-space: nowrap;
}
/* A portrait phone letterboxes the 16:9 stage down to a thin strip
   (#stage's object-fit: contain against a tall viewport) -- narrow AND
   portrait AND short enough that landscape would meaningfully help,
   so a wide-but-portrait tablet doesn't get nagged for no reason. */
@media (orientation: portrait) and (max-width: 540px) and (max-height: 820px) {
  .landscapeHint { display: block; }
}

/* SoundFont switcher popup — lists every loaded font with a click-to-
   activate row and a × (hide) action; #fontHiddenToggle swaps the same
   list into the "settings" view of hidden fonts with a + (unhide) action.
   Shared modal shell (.modalBackdrop/.modalPanel) reused by the filmstrip
   modal below -- one recipe instead of two near-duplicates. */
.modalBackdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-scrim);
  z-index: 30;
}
.modalPanel {
  width: min(420px, 90vw);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  background: rgba(10, 8, 16, 0.97);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-modal);
}
.modalHeader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  flex: none;
}
.modalHeader h2 {
  margin: 0;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  color: var(--spec-gold);
}
.fontList {
  overflow-y: auto;
  padding: var(--space-2);
  flex: 1 1 auto;
  min-height: 60px;
}
.fontListEmpty {
  opacity: 0.55;
  font-size: 0.85rem;
  padding: var(--space-5) var(--space-3);
  text-align: center;
}
.fontRow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--dur-base);
}
.fontRow:hover { background: var(--bg-surface-2); }
.fontRow.active { background: color-mix(in srgb, var(--spec-gold) 12%, transparent); }
.fontRow.active .fontRowName { color: var(--spec-gold); font-weight: 600; }
.fontRowName {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fontRowAction {
  flex: none;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  transition: border-color var(--dur-base), color var(--dur-base);
}
/* Hide (×) reads as a removal — red accent, matching RHYTHM's roleDot.
   Unhide (+) reads as a restore — blue accent, matching the shared hover. */
.fontRowAction[data-action="hide"]:hover { border-color: var(--spec-rhythm); color: var(--spec-rhythm); }
.fontRowAction[data-action="unhide"]:hover { border-color: var(--spec-cool); color: var(--spec-cool); }

/* Per-song audition verdicts (FontRecommender) */
.fontRowStar {
  flex: none;
  color: var(--spec-gold);
  font-size: 0.9rem;
  cursor: default;
}
.fontRowBadge {
  flex: none;
  min-width: 30px;
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  font-size: 0.72rem;
  text-align: center;
  cursor: default;
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
}
.fontRowBadge.ok { border-color: color-mix(in srgb, var(--spec-cool) 45%, transparent); color: var(--spec-cool); }
.fontRowBadge.dq { border-color: color-mix(in srgb, var(--spec-rhythm) 55%, transparent); color: var(--spec-rhythm); }
.fontRowBadge.pending { opacity: 0.55; }
.auditionStatus {
  flex: none;
  padding: var(--space-2) var(--space-4);
  font-size: 0.78rem;
  color: var(--text-secondary);
  border-top: 1px solid var(--border-subtle);
}
.auditionStatus.hidden { display: none; }
.modalFooter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-subtle);
  flex: none;
}
.modalFooter .filebtn, .modalFooter .ghostbtn {
  font-size: 0.78rem;
  padding: 6px var(--space-3);
}
#fontHiddenToggle { margin-left: auto; }

/* The Reel (Movement VI): the COMPLETE panel's highlight filmstrip. */
#filmstrip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  max-width: min(560px, 84vw);
}
.filmstripFrame {
  width: 96px;
  height: 54px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
  cursor: pointer;
  background: #000;
  transition: border-color var(--dur-base), transform var(--dur-base);
}
.filmstripFrame:hover { border-color: var(--spec-gold); transform: translateY(-2px); }
.filmstripFrame img { width: 100%; height: 100%; object-fit: cover; display: block; }

.filmstripModalPanel { width: min(720px, 92vw); }
#filmstripModalImg { width: 100%; display: block; background: #000; }

/* --- Audition load screen (font ratings gate the show) --- */
#auditionCanvas {
  display: block;
  margin: var(--space-2) auto;
  background: transparent;
}
#auditionBar {
  width: min(420px, 80%);
  height: 6px;
  margin: var(--space-3) auto 4px;
  border-radius: 3px;
  background: var(--bg-surface-2);
  overflow: hidden;
}
#auditionBarFill {
  width: 0%;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--spec-cool), var(--spec-gold));
  transition: width var(--dur-slow) ease;
}

/* ---------------------------------------------------------------------------
   Tap recalibration (RecalibrationOverlay.js / SyncMonitor.js)

   The overlay is a HUD, not a modal: the song keeps playing, the world keeps
   moving behind it, and pointer-events stay off so a tap anywhere still
   reaches the canvas handler that feeds BeatAnchor. Everything here is
   deliberately low-contrast except the numeral -- the player should be
   watching the count and listening, not reading.
--------------------------------------------------------------------------- */
#recalPanel {
  position: absolute;
  inset: 0;
  z-index: 6; /* above #hud (3), below the modals (20+) */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  pointer-events: none;
  animation: recalFadeIn 260ms ease-out;
}

/* The big block count. Sized off the viewport so it reads at any stage
   resolution, and translucent enough that the show is never hidden. */
#recalNumber {
  font-family: var(--font-sans);
  font-size: 40vh;
  line-height: 0.82;
  font-weight: 900;
  letter-spacing: -0.04em;
  color: rgba(255, 255, 255, 0.16);
  text-shadow: 0 0 60px color-mix(in srgb, var(--spec-cool) 22%, transparent);
  font-variant-numeric: tabular-nums;
  /* Scale/opacity are driven per-beat from JS (same swell-then-snap shape as
     the old calibration beacon), so no CSS transition -- it would smear it. */
  will-change: transform, opacity;
}

#recalInstruction {
  max-width: 46ch;
  margin: 0;
  text-align: center;
  font-size: 15px;
  line-height: 1.55;
  color: var(--text-primary);
  opacity: 0.82;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.85);
  transition: opacity 700ms ease;
}
#recalInstruction strong { display: block; margin-bottom: 4px; color: #fff; font-size: 17px; }
#recalInstruction.faded { opacity: 0.2; }

/* Beat pips within the current measure. */
#recalPips { display: flex; gap: var(--space-3); }
#recalPips span {
  width: 10px; height: 10px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  transition: background 90ms linear, transform 90ms linear;
}
#recalPips span.on { background: color-mix(in srgb, var(--spec-cool) 95%, white); transform: scale(1.45); }

/* Live BeatAnchor.confidence -- the player can watch the tapping land. */
.recalConf {
  width: 220px; height: 4px; border-radius: 2px;
  background: var(--bg-surface-2);
  overflow: hidden;
}
.recalConfFill {
  height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--spec-cool), #8ef0c8);
  transition: width 160ms ease-out;
}

#recalStatus {
  margin: 0; min-height: 1.2em;
  font-size: 13px;
  color: var(--text-secondary);
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
}

@keyframes recalFadeIn { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  #recalPanel { animation: none; }
}

/* ── Soulseek search panel ────────────────────────────────────────────────── */
/* Same card language as .titleCard/.completeCard/.modalPanel now -- it used
   to be a visually distinct violet-only block; the accent still comes
   through (border tint, badge colors) without a whole separate palette. */
.slskPanel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  text-align: left;
  padding: var(--space-3) var(--space-3) var(--space-2);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--spec-pad) 8%, transparent) inset;
}

.slskHeader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.slskTitleRow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.slskHeading {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.slskModeBadge {
  flex: 0 0 auto;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-3);
  color: var(--text-secondary);
}
.slskModeBadge[data-mode="free"],
.slskModeBadge[data-mode="demo"] {
  color: var(--spec-gold);
  border-color: color-mix(in srgb, var(--spec-gold) 35%, transparent);
  background: color-mix(in srgb, var(--spec-gold) 10%, transparent);
}
.slskModeBadge[data-mode="slskd"] {
  color: var(--spec-cool);
  border-color: color-mix(in srgb, var(--spec-cool) 40%, transparent);
  background: color-mix(in srgb, var(--spec-cool) 12%, transparent);
}
.slskModeBadge[data-mode="direct"] {
  color: var(--spec-warm);
  border-color: color-mix(in srgb, var(--spec-warm) 40%, transparent);
  background: color-mix(in srgb, var(--spec-warm) 12%, transparent);
}
.slskModeBadge[data-mode="offline"] {
  color: var(--spec-rhythm);
  border-color: color-mix(in srgb, var(--spec-rhythm) 40%, transparent);
}

.slskSearchForm {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
}

.slskSearchForm input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  height: 40px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-3);
  color: var(--text-primary);
  font-size: 0.95rem;
  outline: none;
  transition: border-color var(--dur-base), box-shadow var(--dur-base);
}
.slskSearchForm input[type="search"]::placeholder {
  color: var(--text-tertiary);
}
.slskSearchForm input[type="search"]:focus {
  border-color: color-mix(in srgb, var(--spec-pad) 65%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--spec-pad) 20%, transparent);
}

.slskSearchBtn {
  flex: 0 0 auto;
  height: 40px;
  padding: 0 var(--space-4);
  border: none;
  border-radius: var(--radius-sm);
  font-weight: 700;
  font-size: 0.88rem;
  letter-spacing: 0.03em;
  cursor: pointer;
  color: #1a1020;
  background: linear-gradient(135deg, var(--spec-warm), var(--spec-gold) 120%);
  box-shadow: 0 4px 16px color-mix(in srgb, var(--spec-warm) 28%, transparent);
  transition: transform var(--dur-fast), filter var(--dur-fast), opacity var(--dur-fast);
}
.slskSearchBtn:hover:not(:disabled) {
  filter: brightness(1.08);
  transform: translateY(-1px);
}
.slskSearchBtn:disabled {
  opacity: 0.55;
  cursor: wait;
}

.slskSettings {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-surface-3);
  border: 1px solid var(--border-subtle);
}

.slskField {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.75rem;
  color: var(--text-secondary);
  text-align: left;
}
.slskField input,
.slskField select {
  height: 34px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-3);
  color: var(--text-primary);
  font-size: 0.85rem;
}
.slskFieldGroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.slskSettingsActions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}
.slskHint {
  margin: 0;
  font-size: 0.7rem;
  line-height: 1.35;
  color: var(--text-tertiary);
}
.slskHint code {
  font-size: 0.68rem;
  color: color-mix(in srgb, var(--spec-pad) 85%, var(--text-primary));
}

.slskStatus {
  margin: 0;
  min-height: 1.1em;
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.slskEmpty {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--text-tertiary);
  text-align: center;
  padding: 4px 4px 0;
}

/* needs-login badge state */
.slskModeBadge[data-mode="needs-login"] {
  color: var(--spec-gold);
  border-color: color-mix(in srgb, var(--spec-gold) 45%, transparent);
  background: color-mix(in srgb, var(--spec-gold) 12%, transparent);
}

.slskResults {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: min(280px, 38vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 2px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--spec-pad) 35%, transparent) transparent;
}

.slskResult {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  gap: var(--space-2);
  align-items: start;
  width: 100%;
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-2);
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast), border-color var(--dur-fast), transform var(--dur-fast);
}
.slskResult:hover {
  background: color-mix(in srgb, var(--spec-pad) 14%, transparent);
  border-color: color-mix(in srgb, var(--spec-pad) 35%, transparent);
  transform: translateY(-1px);
}
.slskResult.loading {
  opacity: 0.7;
  pointer-events: none;
}
.slskResultIcon {
  font-size: 1.05rem;
  opacity: 0.85;
  text-align: center;
  margin-top: 2px;
}
.slskResultBody {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.slskResultTitle {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: 0.01em;
}
.slskResultFields {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
}
.slskFieldLine {
  display: grid;
  grid-template-columns: 52px 1fr;
  gap: 6px;
  align-items: baseline;
  min-width: 0;
}
.slskFieldLabel {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.slskFieldValue {
  font-size: 0.78rem;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.slskResultMeta {
  font-size: 0.65rem;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.slskResultAction {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--spec-gold);
  padding: 4px var(--space-2);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--spec-gold) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--spec-gold) 25%, transparent);
  align-self: center;
}

.slskLegal {
  margin: 0;
  font-size: 0.65rem;
  line-height: 1.35;
  color: var(--text-tertiary);
  text-align: center;
}

.slskDivider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-tertiary);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.slskDivider::before,
.slskDivider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border-subtle), transparent);
}

@media (max-width: 420px) {
  .slskResults { max-height: min(220px, 34vh); }
  .slskSearchForm { flex-direction: column; }
  .slskSearchBtn { width: 100%; }
  .slskFieldLine { grid-template-columns: 48px 1fr; }
}


/* Free-first search: advanced slskd stays collapsed */
.slskAdvanced {
  margin-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-2);
}
.slskAdvanced > summary {
  cursor: pointer;
  font-size: 0.78rem;
  opacity: 0.75;
  list-style: none;
}
.slskAdvanced > summary::-webkit-details-marker { display: none; }
.slskHintLead {
  margin: 0 0 var(--space-2);
  line-height: 1.35;
}
.slskField em {
  font-style: normal;
  opacity: 0.6;
  font-weight: 400;
}

/* --- World select ------------------------------------------------------- */
.worldSelect {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: var(--bg-scrim);
  pointer-events: auto;
}
.worldSelect.hidden { display: none; }
.worldSelectInner {
  width: min(720px, 100%);
  padding: var(--space-6) var(--space-6) var(--space-5);
  border-radius: var(--radius-lg);
  background: linear-gradient(165deg, rgba(24, 16, 38, 0.88), rgba(14, 11, 22, 0.92));
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.worldSelectEyebrow {
  margin: 0 0 var(--space-2);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.worldSelectInner h2 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xl, 1.6rem);
  font-weight: 600;
  letter-spacing: -0.03em;
}
.worldSelectHint {
  margin: 0 0 var(--space-5);
  color: var(--text-secondary);
  font-size: 0.92rem;
  line-height: 1.45;
  max-width: 52ch;
}
.worldSelectGrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
}
.worldSelectBack {
  margin-top: var(--space-5);
  min-height: 44px;
}
.fileInputVisuallyHidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.worldCard {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-surface-2);
  border: 1px solid var(--border-subtle);
  color: inherit;
  text-align: left;
  cursor: pointer;
  min-height: 44px;
  transition: border-color var(--dur-base) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard),
              background var(--dur-base) var(--ease-standard);
}
.worldCard:hover,
.worldCard:focus-visible {
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.07);
  transform: translateY(-1px);
  outline: none;
}
.worldCard.is-best {
  border-color: color-mix(in srgb, var(--spec-gold) 45%, var(--border-subtle));
}
.worldCardPreview {
  height: 72px;
  border-radius: var(--radius-sm);
  position: relative;
  overflow: hidden;
}
.worldCardPreview.alpine {
  background: linear-gradient(180deg, #1a1a3e 0%, #4a3b6b 55%, #2b2145 100%);
}
.worldCardPreview.alpine::after {
  content: '';
  position: absolute;
  left: -8%; right: -8%; bottom: 0;
  height: 58%;
  background: #1a1428;
  clip-path: polygon(0 100%, 0 62%, 12% 28%, 22% 48%, 38% 8%, 52% 42%, 64% 22%, 78% 50%, 90% 30%, 100% 70%, 100% 100%);
}
.worldCardPreview.city {
  background: linear-gradient(180deg, #05060c 0%, #12182a 70%, #1a2233 100%);
}
.worldCardPreview.city::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 72%;
  background:
    linear-gradient(90deg,
      transparent 6%, #0a0e18 6%, #0a0e18 18%, transparent 18%,
      transparent 22%, #0c121c 22%, #0c121c 31%, transparent 31%,
      transparent 36%, #0a1018 36%, #0a1018 58%, transparent 58%,
      transparent 64%, #0e141e 64%, #0e141e 72%, transparent 72%,
      transparent 78%, #0a0e16 78%, #0a0e16 94%, transparent 94%);
  clip-path: polygon(0 100%, 6% 100%, 6% 42%, 18% 42%, 18% 100%, 22% 100%, 22% 28%, 31% 28%, 31% 100%, 36% 100%, 36% 18%, 58% 18%, 58% 100%, 64% 100%, 64% 36%, 72% 36%, 72% 100%, 78% 100%, 78% 24%, 94% 24%, 94% 100%, 100% 100%);
}
.worldCardPreview.airless {
  background: linear-gradient(180deg, #000000 0%, #040610 60%, #0a0e1a 100%);
}
.worldCardPreview.airless::after {
  content: '';
  position: absolute;
  left: -4%; right: -4%; bottom: 0;
  height: 42%;
  background: #141822;
  clip-path: polygon(0 100%, 0 70%, 8% 60%, 20% 72%, 35% 55%, 50% 68%, 65% 50%, 80% 65%, 92% 58%, 100% 72%, 100% 100%);
}
.worldCardPreview.abyssal {
  background: linear-gradient(180deg, #0a2e3a 0%, #062430 50%, #020d14 100%);
}
.worldCardPreview.abyssal::after {
  content: '';
  position: absolute;
  left: -4%; right: -4%; bottom: 0;
  height: 50%;
  background: #04202a;
  clip-path: polygon(0 100%, 0 68%, 10% 58%, 25% 70%, 40% 52%, 55% 64%, 70% 48%, 85% 62%, 100% 56%, 100% 100%);
}
.worldCardPreview.strip {
  background: linear-gradient(180deg, #1a1030 0%, #4a2050 40%, #e05a50 100%);
}
.worldCardPreview.strip::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 28%;
  background: #180c28;
  clip-path: polygon(0 100%, 0 50%, 100% 50%, 100% 100%);
}
.worldCardPreview.foundry {
  background: linear-gradient(180deg, #08080a 0%, #301000 50%, #6a2400 100%);
}
.worldCardPreview.foundry::after {
  content: '';
  position: absolute;
  left: -4%; right: -4%; bottom: 0;
  height: 54%;
  background: #0e0604;
  clip-path: polygon(0 100%, 0 58%, 6% 42%, 14% 56%, 22% 32%, 28% 48%, 38% 22%, 48% 45%, 56% 28%, 66% 52%, 72% 36%, 82% 54%, 90% 40%, 100% 60%, 100% 100%);
}
.worldCardPreview.overgrowth {
  background: linear-gradient(180deg, #0e2418 0%, #1e5030 50%, #78c088 100%);
}
.worldCardPreview.overgrowth::after {
  content: '';
  position: absolute;
  left: -4%; right: -4%; bottom: 0;
  height: 60%;
  background: #0a1e14;
  clip-path: polygon(0 100%, 0 50%, 5% 38%, 12% 52%, 20% 30%, 28% 48%, 35% 24%, 42% 42%, 50% 28%, 58% 45%, 65% 22%, 72% 40%, 80% 32%, 88% 50%, 95% 36%, 100% 48%, 100% 100%);
}
.worldCardPreview.nave {
  background: linear-gradient(180deg, #101018 0%, #242436 55%, #4a4460 100%);
}
.worldCardPreview.nave::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 68%;
  background: #16161e;
  clip-path: polygon(0 100%, 0 30%, 8% 30%, 8% 100%, 16% 100%, 16% 20%, 24% 10%, 32% 20%, 32% 100%, 42% 100%, 42% 28%, 42% 28%, 58% 28%, 58% 100%, 68% 100%, 68% 20%, 76% 10%, 84% 20%, 84% 100%, 92% 100%, 92% 30%, 100% 30%, 100% 100%);
}
.worldCardTop {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.worldCardName {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.worldCardScore {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 1.15rem;
  color: var(--text-primary);
}
.worldCardTag {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
  line-height: 1.4;
}
.worldCardBadge {
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
@media (max-width: 560px) {
  .worldSelectInner { padding: var(--space-5) var(--space-4); }
  .worldSelectGrid { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .worldCard { transition: none; }
}

