/* === DOCK REFACTOR design tokens =========================================
   Single source of truth for the "Global Dock" redesign (plan: ~/.claude/
   plans/prompt-music-visualizer-ui-rosy-bengio.md). Chunks 7 (panel), 9
   (library overlay), and 13 (idle fade) consume these as well — keep the
   palette in one place so the refactor stays consistent across surfaces. */
:root {
  /* Dock pill */
  --dock-bg:        rgba(0, 0, 0, 0.45);
  --dock-blur:      20px;
  --dock-border:    1px solid rgba(255, 255, 255, 0.10);
  --dock-radius:    999px;              /* pill */
  --dock-shadow:    0 8px 32px rgba(0, 0, 0, 0.5);
  /* Slide-up panel (Viz Settings, chunk 7) */
  --panel-bg:       rgba(18, 18, 18, 0.80);
  --panel-blur:     24px;
  --panel-radius:   20px;
  /* Full-screen overlay (Library, chunk 9) — stronger blur per spec */
  --overlay-bg:     rgba(0, 0, 0, 0.72);
  --overlay-blur:   40px;
  /* Shared timing */
  --chrome-fade-ms:  260ms;
  --idle-timeout-ms: 3000ms;            /* mirrored in app.js resetHideTimer */
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  background: #000;
  overflow: hidden;
  width: 100vw;
  height: 100vh;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
}

#canvas-2d {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Own GPU compositing layer — prevents Chrome's backdrop-filter on #controls
     from pulling the canvas into the same compositing tier and dimming it. */
  will-change: transform;
}

#webgl-container {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
}

#webgl-container canvas {
  width: 100% !important;
  height: 100% !important;
}

#webgpu-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  will-change: transform;
}

/* ─── iPod overlay ───────────────────────────────────────── */

.ipod {
  position: fixed;
  top: 50%;
  left: 50%;
  /* Chunk 14: --ipod-x / --ipod-y default to 0px so the iPod opens centered.
     JS (drag handler) sets them on the inline style as the user drags;
     localStorage persists across reloads. The 0px fallback means existing
     centering behavior is unchanged when no drag has happened. */
  transform: translate(
    calc(-50% + var(--ipod-x, 0px)),
    calc(-50% + var(--ipod-y, 0px))
  ) scale(0.94);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.32s cubic-bezier(0.22,1,0.36,1),
              transform 0.32s cubic-bezier(0.22,1,0.36,1);
  z-index: 10;
  width: 236px;
  background: linear-gradient(160deg, #ffffff 0%, #e8e8e8 100%);
  border-radius: 28px;
  padding: 22px 22px 28px;
  box-shadow:
    0 2px 0 rgba(255,255,255,0.8) inset,
    0 -1px 0 rgba(0,0,0,0.15) inset,
    0 28px 70px rgba(0,0,0,0.65),
    0 8px 24px rgba(0,0,0,0.35);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  user-select: none;
  cursor: grab;
}

.ipod.visible {
  opacity: 1;
  transform: translate(
    calc(-50% + var(--ipod-x, 0px)),
    calc(-50% + var(--ipod-y, 0px))
  ) scale(1);
  pointer-events: all;
}

/* While dragging, kill the open/close transition so the cursor tracks
   1:1 with no easing lag, and switch the cursor cue. */
.ipod.dragging {
  transition: none;
  cursor: grabbing;
}

/* Screen */
.ipod-screen-bezel {
  width: 100%;
  background: #1a1a1a;
  border-radius: 6px;
  padding: 3px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.6) inset, 0 0 0 1px rgba(0,0,0,0.4);
}

.ipod-screen {
  width: 100%;
  aspect-ratio: 4 / 3;
  background: #1c2a1c;
  border-radius: 4px;
  overflow: hidden;
  position: relative;
}

.screen-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
.screen-view.hidden { display: none; }

.screen-titlebar {
  background: linear-gradient(180deg, #4a80d9 0%, #2d5fc4 100%);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  padding: 4px 8px;
  flex-shrink: 0;
}

.ipod-empty {
  flex: 1;
  background: #c8d8b8;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  color: rgba(0,0,0,0.5);
  text-align: center;
  line-height: 1.6;
}

.now-playing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px 8px;
  background: #c8d8b8;
  flex: 1;
}

#ipod-art {
  width: 60px;
  height: 60px;
  border-radius: 3px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.3);
  object-fit: cover;
}

.np-title {
  font-size: 10px;
  font-weight: 600;
  color: #000;
  text-align: center;
  line-height: 1.2;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.np-artist {
  font-size: 9px;
  color: rgba(0,0,0,0.5);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.np-progress { width: 100%; margin-top: 4px; }
.np-bar {
  width: 100%;
  height: 3px;
  background: rgba(0,0,0,0.15);
  border-radius: 2px;
  overflow: hidden;
}
.np-fill {
  height: 100%;
  width: 0%;
  background: #2d5fc4;
  border-radius: 2px;
  transition: width 0.25s linear;
}

/* Click wheel */
.wheel {
  position: relative;
  width: 172px;
  height: 172px;
  border-radius: 50%;
  background: linear-gradient(145deg, #f5f5f5 0%, #dcdcdc 100%);
  box-shadow:
    0 2px 0 rgba(255,255,255,0.9) inset,
    0 -2px 0 rgba(0,0,0,0.12) inset,
    0 4px 12px rgba(0,0,0,0.2),
    0 1px 3px rgba(0,0,0,0.15);
  flex-shrink: 0;
}

.wheel-label {
  position: absolute;
  font-size: 9px;
  font-weight: 700;
  color: #555;
  letter-spacing: 0.05em;
  cursor: pointer;
  z-index: 1;
}
.wheel-label:hover { color: #000; }

.wheel-menu    { top: 10px;    left: 50%; transform: translateX(-50%); }
.wheel-back    { top: 50%;     left: 10px; transform: translateY(-50%); }
.wheel-forward { top: 50%;     right: 10px; transform: translateY(-50%); }
.wheel-play    { bottom: 10px; left: 50%; transform: translateX(-50%); }

.wheel-center {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(145deg, #f8f8f8 0%, #e0e0e0 100%);
  box-shadow:
    0 2px 0 rgba(255,255,255,0.8) inset,
    0 -1px 0 rgba(0,0,0,0.1) inset,
    0 2px 6px rgba(0,0,0,0.18);
  cursor: pointer;
  transition: box-shadow 0.1s;
}
.wheel-center:active {
  box-shadow:
    0 1px 0 rgba(255,255,255,0.6) inset,
    0 -1px 0 rgba(0,0,0,0.15) inset,
    0 1px 3px rgba(0,0,0,0.15);
}

/* ─── Controls pill ──────────────────────────────────────── */

#controls {
  position: fixed;
  /* left + top set by JS; no transform centering */
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 20px;
  padding: 14px 18px;
  z-index: 100;
  min-width: 580px;
  transition: opacity 0.28s ease;
  cursor: grab;
  user-select: none;
}

#controls.dragging {
  cursor: grabbing;
  transition: none;  /* no lag while dragging */
}

#controls.ipod-mode,
#controls.controls-hidden {
  opacity: 0;
  pointer-events: none;
}

/* ─── Utility overlay buttons (always visible unless controls hidden) ── */

#ui-buttons {
  position: fixed;
  bottom: 20px;
  right: 20px;
  display: flex;
  gap: 8px;
  z-index: 200;
  transition: opacity 0.28s ease;
}

#ui-buttons.all-hidden {
  opacity: 0;
  pointer-events: none;
}

.ui-btn {
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: rgba(255, 255, 255, 0.6);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 13px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
  line-height: 1;
}

.ui-btn:hover {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}

/* ─── Keyboard hints (bottom-center, matches sign-in + ui-buttons bottom band) */

#kbd-hints {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 6px 14px;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 16px;
  z-index: 200;
  pointer-events: none;
  user-select: none;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.55);
  transition: opacity 0.28s ease;
  white-space: nowrap;
}

#kbd-hints span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

#kbd-hints kbd {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 4px;
  font-family: "SF Mono", ui-monospace, monospace;
  font-size: 10px;
  color: rgba(255, 255, 255, 0.78);
  background: rgba(255, 255, 255, 0.05);
  text-align: center;
  line-height: 1.3;
}

/* Hide on narrow viewports — bottom band gets crowded and the pill
   itself is already stacked in the 640px media query. */
@media (max-width: 640px) {
  #kbd-hints { display: none; }
}

/* iPod toggle button inside controls */
#ipod-toggle {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.85);
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}
#ipod-toggle:hover { background: rgba(255, 255, 255, 0.2); }

/* ─── Music row ──────────────────────────────────────────── */

#music-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  min-height: 42px;
}

/* Left: album art + track text — must fill 1fr so long titles truncate */
#track-details {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
}

#album-art {
  width: 36px;
  height: 36px;
  border-radius: 5px;
  object-fit: cover;
  flex-shrink: 0;
  border: 1px solid rgba(255,255,255,0.12);
}

#track-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  overflow: hidden;
}

#track-name {
  color: rgba(255, 255, 255, 0.88);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
}

#track-name.ticker {
  overflow: visible;
  text-overflow: clip;
  animation: track-ticker var(--ticker-dur, 6s) ease-in-out infinite alternate;
}

@keyframes track-ticker {
  0%, 18%  { transform: translateX(0); }
  82%, 100% { transform: translateX(var(--ticker-dist, 0px)); }
}

#track-time {
  color: rgba(255, 255, 255, 0.38);
  font-size: 10px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* Right: load button + iPod */
#music-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  min-width: 0;
}

.file-label {
  cursor: pointer;
}

.vol-label {
  display: flex;
  align-items: center;
  gap: 5px;
  color: rgba(255, 255, 255, 0.5);
  font-size: 11px;
  white-space: nowrap;
  cursor: default;
}

.vol-label input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 64px;
  height: 3px;
  background: rgba(255, 255, 255, 0.25);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}

.vol-label input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}

.vol-label input[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  border: none;
  cursor: pointer;
}

.file-label input[type="file"] {
  display: none;
}

.file-label span {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.85);
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}

.file-label span:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* ─── Playlist dropdown ──────────────────────────────────────── */

#playlist-wrap {
  position: relative;
}

#playlist-btn {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.85);
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}
#playlist-btn:hover { background: rgba(255, 255, 255, 0.2); }
#playlist-btn.open  { background: rgba(255, 255, 255, 0.18); }

#playlist-menu {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  background: rgba(18, 18, 18, 0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  padding: 6px;
  list-style: none;
  min-width: 220px;
  max-width: 340px;
  max-height: 260px;
  overflow-y: auto;
  z-index: 200;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}

#playlist-menu.open { display: block; }

#playlist-menu li {
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 12px;
  color: rgba(255,255,255,0.75);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.1s, color 0.1s;
}
#playlist-menu li:hover {
  background: rgba(255,255,255,0.1);
  color: #fff;
}
#playlist-menu li.playing {
  color: #fff;
  font-weight: 600;
}
#playlist-menu .playlist-empty {
  color: rgba(255,255,255,0.3);
  font-style: italic;
  cursor: default;
}
#playlist-menu .playlist-empty:hover {
  background: transparent;
  color: rgba(255,255,255,0.3);
}

/* ─── Sign-in chip (lives in #dock-left) ──────────────────────────── */

/* Followup A1: legacy #signin-btn / #sign-in-buttons selectors removed
   along with their DOM. Only the #dock-signin chip remains. */

#dock-signin {
  position: relative;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
#dock-signin .signin-icon { font-size: 14px; line-height: 1; }
#dock-signin .signin-dot {
  position: absolute;
  bottom: 2px;
  right: 2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid rgba(0, 0, 0, 0.45);
  box-sizing: border-box;
  transition: background 0.2s;
}
#dock-signin.authed .signin-dot {
  background: #1ed760;
  border-color: #fff;
  box-shadow: 0 0 6px rgba(30, 215, 96, 0.75);
}
#dock-signin.open { background: rgba(255, 255, 255, 0.18); color: #fff; }

#streaming-menu {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  width: 280px;
  background: rgba(18, 18, 18, 0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  padding: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.45);
  z-index: 10;
}
#streaming-menu.open { display: block; }

#streaming-sources { display: flex; gap: 6px; margin-bottom: 8px; }
.stream-src-btn {
  flex: 1;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.12);
  color: rgba(255,255,255,0.7);
  padding: 6px 8px;
  border-radius: 8px;
  font-size: 11px;
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}
.stream-src-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
.stream-src-btn.active {
  background: rgba(255,255,255,0.18);
  color: #fff;
  border-color: rgba(255,255,255,0.28);
}

#streaming-auth {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
#streaming-connect {
  background: rgba(30, 215, 96, 0.18);
  border: 1px solid rgba(30, 215, 96, 0.4);
  color: #fff;
  padding: 5px 10px;
  border-radius: 8px;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}
#streaming-connect:hover { background: rgba(30, 215, 96, 0.3); }
#streaming-auth-status {
  font-size: 11px;
  color: rgba(255,255,255,0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#streaming-hint {
  margin: 4px 2px 0;
  font-size: 11px;
  color: rgba(255,255,255,0.55);
  line-height: 1.45;
}
#streaming-hint strong { color: rgba(255,255,255,0.85); font-weight: 600; }

#viz-source {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,0.08);
}
.viz-source-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: rgba(255,255,255,0.45);
  margin-bottom: 6px;
}
.viz-source-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.viz-source-btn {
  /* Size to the label; the flex container wraps whole buttons onto the next
     line instead of squeezing four equal slices into one cramped row and
     ellipsis-truncating ("Capture tab audio" → "Captu…"). Full, readable
     control labels are the point here. */
  flex: 0 0 auto;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.12);
  color: rgba(255,255,255,0.75);
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 11px;
  cursor: pointer;
  white-space: nowrap;
}
.viz-source-btn:hover { background: rgba(255,255,255,0.12); color: #fff; }
.viz-source-btn.active {
  background: rgba(30, 215, 96, 0.25);
  border-color: rgba(30, 215, 96, 0.5);
  color: #fff;
}
#viz-source-status {
  margin-top: 6px;
  font-size: 10px;
  color: rgba(255,255,255,0.55);
  min-height: 14px;
}

/* ─── Bridge token form ──────────────────────────────────────────── */
#viz-bridge-token-form {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  align-items: center;
  flex-wrap: wrap;
}
#viz-bridge-token-input {
  flex: 1 1 180px;
  min-width: 140px;
  padding: 4px 8px;
  border-radius: 6px;
  border: 1px solid rgba(255,255,255,0.18);
  background: rgba(255,255,255,0.07);
  color: #fff;
  font-size: 11px;
  font-family: 'SF Mono', ui-monospace, monospace;
  outline: none;
}
#viz-bridge-token-input:focus {
  border-color: rgba(255,255,255,0.35);
  background: rgba(255,255,255,0.11);
}
#viz-bridge-token-input::placeholder { color: rgba(255,255,255,0.35); }
/* Full-width helper link under the token row (the form is flex-wrap). On
   the hosted build the bridge can never be reached, so this is how remote
   users find the local macOS helper. */
#viz-bridge-help {
  flex: 1 1 100%;
  margin-top: 2px;
  font-size: 10px;
  color: rgba(255,255,255,0.5);
  text-decoration: none;
}
#viz-bridge-help:hover { color: #fff; text-decoration: underline; }

/* ─── iPod menu (library browser) ───────────────────────────────── */

#ipod-view-menu { background: #c8d8b8; }
#ipod-menu-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  background: #c8d8b8;
}
.ipod-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  font-size: 13px;
  color: #1c1c1c;
  cursor: pointer;
  border-bottom: 1px solid rgba(0,0,0,0.06);
}
.ipod-menu-item.selected {
  background: linear-gradient(180deg, #6c9ffb 0%, #2a5fd0 100%);
  color: #fff;
}
.ipod-menu-item .ipod-item-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ipod-menu-item .ipod-item-chev {
  color: rgba(0,0,0,0.3);
  font-size: 14px;
}
.ipod-menu-item.selected .ipod-item-chev { color: rgba(255,255,255,0.7); }
.ipod-menu-note {
  padding: 14px 10px;
  text-align: center;
  font-size: 12px;
  color: rgba(0,0,0,0.45);
  font-style: italic;
}
.ipod-menu-note.error { color: #b00020; font-style: normal; }

/* ─── Transport controls (grid center column — always centered) ── */

#transport {
  display: flex;
  align-items: center;
  gap: 6px;
  justify-self: center;
  padding: 0 12px;
}

.transport-btn {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.55);
  font-size: 13px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.12s, background 0.12s;
  flex-shrink: 0;
  line-height: 1;
}

.transport-btn:hover:not(:disabled) {
  color: #fff;
  background: rgba(255, 255, 255, 0.1);
}

.transport-btn.active {
  color: #fff;
  background: rgba(255, 255, 255, 0.15);
}

.transport-btn:disabled {
  opacity: 0.3;
  cursor: default;
}

/* Play/pause is slightly larger and brighter as primary action */
#play-pause {
  width: 32px;
  height: 32px;
  font-size: 14px;
  background: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

#play-pause:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.24);
  color: #fff;
}

/* ─── Progress bar ───────────────────────────────────────── */

#progress-wrap {
  height: 4px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  cursor: pointer;
  position: relative;
  transition: height 0.12s, margin 0.12s;
}

#progress-wrap:hover {
  height: 7px;
  margin-top: -1.5px;
  margin-bottom: -1.5px;
}

#progress-fill {
  height: 100%;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 3px;
  width: 0%;
  pointer-events: none;
}

#scrub-head {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.1s;
  left: 0%;
}

/* ─── Bottom row (mode + extra controls) ─────────────────── */

#bottom-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ── Sliders row ──────────────────────────────────────────── */

#sliders-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  min-height: 22px;
}

#vortex-controls,
#waves-controls,
#fluid-controls {
  display: none;
  width: 100%;
  align-items: center;
  justify-content: space-evenly;
  white-space: nowrap;
}

#vortex-controls.visible,
#waves-controls.visible,
#fluid-controls.visible {
  display: flex;
  animation: sliders-in 0.22s ease;
}

@keyframes sliders-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

#speed-control {
  display: flex;
  align-items: center;
  gap: 8px;
}

.speed-label {
  display: flex;
  align-items: center;
  gap: 7px;
  color: rgba(255, 255, 255, 0.5);
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
}

.speed-label input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 72px;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.2);
  outline: none;
  cursor: pointer;
}

.speed-label input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}

/* ── Mode indicators ─────────────────────────────────────────
   Positional dots — one per registered viz. Tight gaps + small
   footprint so 15+ viz don't dominate the pill. Label shown
   on hover via native tooltip; full title reveal happens via
   #viz-title-overlay on mode switch. */

#mode-buttons {
  display: flex;
  gap: 6px;
  justify-content: center;
  align-items: center;
  padding: 4px 0;
}

.mode-btn {
  width: 7px;
  height: 7px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
  cursor: pointer;
  transition: background 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
  font-size: 0;               /* hide textContent — label lives in `title` attr */
  color: transparent;
  flex-shrink: 0;
}

.mode-btn:hover {
  background: rgba(255, 255, 255, 0.55);
  transform: scale(1.25);
}

.mode-btn.active {
  background: #fff;
  transform: scale(1.35);
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.55);
}

/* ── Viz title chip (top-left) ───────────────────────────────
   Persistent status chip in the SF / iOS control-center visual
   language: backdrop-blur pill, SF Pro Display semibold, mixed
   case with slightly-negative tracking (Apple display convention).
   Shares the bottom-band shell treatment with #sign-in-buttons,
   #ui-buttons, and #kbd-hints — reads as a system-UI chrome layer
   distinct from the draggable controls pill. */

#viz-title-overlay {
  position: fixed;
  top: 20px;
  left: 20px;
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  font-family: -apple-system, "SF Pro Display", BlinkMacSystemFont, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  user-select: none;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 200;
  white-space: nowrap;
}

/* A subtle leading dot matches Apple's status-chip idiom
   (AirDrop / Now Playing / Voice Memo indicator row). */
#viz-title-overlay::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.75);
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.55);
}

#viz-title-overlay.visible {
  opacity: 1;
  transform: translateY(0);
}

/* ─── Edge cycle buttons ─────────────────────────────────────────────── */
.viz-cycle-btn {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 96px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(20, 20, 22, 0.35);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  color: rgba(255, 255, 255, 0.55);
  font-size: 0;              /* collapses the inline span to just the glyph */
  cursor: pointer;
  z-index: 5;
  transition: background 0.15s, color 0.15s, border-color 0.15s, opacity 0.2s;
  opacity: 0.55;
  display: flex;
  align-items: center;
  justify-content: center;
}

.viz-cycle-btn:hover {
  background: rgba(20, 20, 22, 0.55);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.35);
  opacity: 1;
}

.viz-cycle-btn:active {
  background: rgba(255, 255, 255, 0.12);
}

#viz-cycle-prev {
  left: 0;
  border-left: none;
  border-top-right-radius: 10px;
  border-bottom-right-radius: 10px;
}

#viz-cycle-next {
  right: 0;
  border-right: none;
  border-top-left-radius: 10px;
  border-bottom-left-radius: 10px;
}

.viz-cycle-arrow {
  font-size: 28px;
  line-height: 1;
  font-weight: 300;
}

/* Hidden when the user has the full UI dimmed (btn-hide toggles `.hidden` on body). */
body.hidden .viz-cycle-btn { display: none; }

/* ─── Viz control types (beyond the default slider) ──────────────────── */
.viz-controls-vertical {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  max-width: 280px;
  margin: 0 auto;
}

.viz-controls-vertical .speed-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}

.viz-controls-vertical input[type="text"] {
  flex: 1;
  min-width: 80px;
}

/* Text input styling — matches the glass aesthetic of the rest of chrome. */
.viz-controls input[type="text"] {
  padding: 4px 10px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  font-size: 12px;
  font-family: -apple-system, system-ui, sans-serif;
  outline: none;
  transition: border-color 0.15s, background 0.15s;
}

.viz-controls input[type="text"]:focus {
  border-color: rgba(255, 255, 255, 0.5);
  background: rgba(255, 255, 255, 0.12);
}

/* Button-type viz control — filled pill, sits inline with sliders. */
.viz-controls .viz-ctl-button {
  padding: 6px 16px;
  border-radius: 20px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
}

.viz-controls .viz-ctl-button:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.45);
}

.viz-controls .viz-ctl-button:active {
  transform: scale(0.97);
  background: rgba(255, 255, 255, 0.25);
}

/* Toggle (checkbox-type viz control) styled to match the glass chrome. */
.viz-controls input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  width: 36px;
  height: 20px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.22);
  position: relative;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  flex-shrink: 0;
}

.viz-controls input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  transition: left 0.15s, background 0.15s;
}

.viz-controls input[type="checkbox"]:checked {
  background: rgba(255, 215, 100, 0.45);
  border-color: rgba(255, 215, 100, 0.8);
}

.viz-controls input[type="checkbox"]:checked::after {
  left: 18px;
  background: #fff;
}

/* Fireworks viz has more controls than others; keep each slider narrower
   so the row doesn't feel over-packed. Scoped to its own control div id. */
#viz-ctl-fireworks .speed-label input[type="range"] {
  width: 72px;
}

/* Number-type viz control — tighter than text; match the glass look. */
.viz-controls input[type="number"] {
  padding: 4px 8px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  font-size: 12px;
  font-family: -apple-system, ui-monospace, SFMono-Regular, monospace;
  outline: none;
  transition: border-color 0.15s, background 0.15s;
  text-align: center;
}

.viz-controls input[type="number"]:focus {
  border-color: rgba(255, 255, 255, 0.5);
  background: rgba(255, 255, 255, 0.12);
}

/* Breathing room between adjacent controls in a viz row so labels, values,
   and toggles don't visually collide. */
.viz-controls {
  gap: 18px;
}

/* Live value readout beside a slider (opt-in via control.showValue). */
.viz-controls .viz-ctl-readout {
  font-family: -apple-system, ui-monospace, SFMono-Regular, monospace;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.7);
  min-width: 36px;
  text-align: right;
}

/* ─── Accessibility: focus indicators ───────────────────────────────────── */
/* Visible focus ring for every interactive element that becomes keyboard-
   focused. :focus-visible avoids the ring on mouse clicks; it only activates
   for keyboard / programmatic focus so the UI stays clean for pointer users. */

:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 2px;
}

/* Mode-button dots are tiny — use a larger, rounded outline ring so it's
   visible against any viz background. */
.mode-btn:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 3px;
  border-radius: 50%;
}

/* Transport and utility buttons: subtle glow to stand out against dark canvas */
.transport-btn:focus-visible,
.ui-btn:focus-visible,
.viz-cycle-btn:focus-visible,
.stream-src-btn:focus-visible,
.viz-source-btn:focus-visible,
#playlist-btn:focus-visible,
#ipod-toggle:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Viz-controls inputs */
.viz-controls input:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 1px;
}

/* ─── Accessibility: prefers-reduced-motion ──────────────────────────────── */
/* When the user has "Reduce Motion" enabled in their OS, suppress transitions
   and animations that could be distracting. Viz rendering itself runs via
   requestAnimationFrame and is not affected by CSS — those would need JS
   guards. This covers all CSS-driven motion in the shell/UI. */
@media (prefers-reduced-motion: reduce) {
  /* Kill all CSS transitions in the controls shell */
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration:  0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  /* Title overlay fade (normally 1.1 s) — show briefly then hide instantly */
  #viz-title-overlay {
    transition: none !important;
  }

  /* iPod overlay slide-in */
  .ipod {
    transition: none !important;
  }

  /* Track-name ticker scroll animation */
  #track-name.ticker {
    animation: none !important;
  }
}

/* ─── Responsive: narrow viewport (≤ 640px) ─────────────────────────────── */
/* At 600 px the controls pill's min-width: 580px would clip off-screen.
   Drop the min-width, allow the pill to shrink, and wrap the bottom row. */
@media (max-width: 640px) {
  #controls {
    min-width: 0;
    width: calc(100vw - 24px);
    /* left is JS-managed: initPillPos() + resize listener keep it inside
       viewport. No !important here — the JS writes inline style and we
       want that to win. */
    padding: 10px 12px;
  }

  /* Music row: stack track-details + transport + actions vertically */
  #music-row {
    flex-wrap: wrap;
    gap: 6px;
  }

  /* Transport: center the buttons and let them wrap */
  #transport {
    width: 100%;
    justify-content: center;
  }

  /* Track details: full width */
  #track-details {
    flex: 1 1 100%;
    min-width: 0;
  }

  /* Music actions: full width, right-align */
  #music-actions {
    flex: 1 1 auto;
    justify-content: flex-end;
  }

  /* Bottom row: stack sliders above mode dots */
  #bottom-row {
    flex-direction: column;
    align-items: center;
    gap: 6px;
  }

  #sliders-row {
    width: 100%;
    justify-content: center;
    flex-wrap: wrap;
  }
}

/* === DOCK SHELL (chunk 1 of UI refactor) ===================================
   Empty 3-section pill at bottom-center. Sections fill in chunks 2-4
   (right=utility toggles, center=transport+scrubber, left=sign-in).
   The dashed section outlines + uppercase label text are placeholders
   that come off in chunk 5 once the old #controls pill is retired and
   the new dock is the sole bottom-center surface. Coexists with the old
   draggable #controls pill during chunks 1-4 — the old pill is at
   z-index 100, dock is at 110 so it sits visually above if dragged
   onto it. */
#dock {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  min-width: 480px;
  min-height: 56px;
  background: var(--dock-bg);
  backdrop-filter: blur(var(--dock-blur));
  -webkit-backdrop-filter: blur(var(--dock-blur));
  border: var(--dock-border);
  border-radius: var(--dock-radius);
  box-shadow: var(--dock-shadow);
  z-index: 110;
  transition: opacity var(--chrome-fade-ms) ease;
  user-select: none;
  color: rgba(255, 255, 255, 0.85);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
}

/* Section base styles — final form after chunk 5 cleanup. The chunk 1
   dashed-placeholder treatment + chunk 2 :has() override are gone now
   that all three sections always have real content. */
#dock-left,
#dock-center,
#dock-right {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 40px;
}

.dock-btn {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.65);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 17px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, color 0.15s ease, transform 0.10s ease;
  line-height: 1;
  padding: 0;
  font-family: inherit;
}

.dock-btn:hover {
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
}

.dock-btn:active {
  transform: scale(0.92);
  background: rgba(255, 255, 255, 0.18);
}

.dock-btn:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: -1px;
}

/* Larger, brighter glyph for the chevron arrows so they read at the same
   optical weight as the emoji buttons next to them. Emoji ride at
   intrinsic weight; chevrons are typographic and need help. */
#dock-prev-viz,
#dock-next-viz {
  font-size: 28px;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.85);
}

#dock-prev-viz:hover,
#dock-next-viz:hover {
  color: #fff;
}

/* === DOCK CENTER (chunk 3 of UI refactor) =================================
   Transport (prev / play / next) + thin scrubber + time readout. The dock
   now owns transport, so the legacy #transport buttons inside #music-row
   and the #progress-wrap below it are display:none'd. Their JS listeners
   stay live (chunk 5 retires the markup) so behavior stays equivalent. */

#dock-center {
  /* dock grid 1fr column — gives the scrubber room to breathe */
  min-width: 360px;
  max-width: 640px;
  flex: 1;
}

#dock-scrubber {
  flex: 1;
  height: 4px;
  background: rgba(255, 255, 255, 0.15);
  border-radius: 2px;
  position: relative;
  cursor: pointer;
  margin: 0 8px;
  transition: height 0.12s ease, background 0.15s ease;
}
#dock-scrubber:hover {
  height: 6px;
  background: rgba(255, 255, 255, 0.22);
}
#dock-scrubber:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 4px;
}

#dock-scrubber-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: rgba(255, 255, 255, 0.85);
  border-radius: inherit;
  pointer-events: none;
}

#dock-scrubber-head {
  position: absolute;
  top: 50%;
  left: 0%;
  transform: translate(-50%, -50%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  opacity: 0;
  transition: opacity 0.15s;
  pointer-events: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
#dock-scrubber:hover #dock-scrubber-head {
  opacity: 1;
}

#dock-time {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 76px;
  text-align: right;
  flex-shrink: 0;
}

/* Disabled-state dimming for transport buttons. Matches the existing
   .transport-btn[disabled] feel from the legacy pill so keyboard /
   AT users still get an "inert" cue. */
.dock-btn:disabled,
.dock-btn[disabled] {
  opacity: 0.35;
  cursor: not-allowed;
}
.dock-btn:disabled:hover,
.dock-btn[disabled]:hover {
  background: transparent;
  color: rgba(255, 255, 255, 0.65);
}

/* Narrow viewports: the 44px dock buttons make the full dock ~800px
   wide; below that it would spill off-screen (edge buttons clipped by
   the viewport, since #dock is centre-anchored). Step the buttons /
   scrubber / gaps down in two stops so every control stays on-screen
   and reachable — no functional button is hidden. Placed AFTER the
   base dock rules so equal-specificity overrides actually win. */
@media (max-width: 820px) {
  #dock {
    min-width: 0;
    gap: 10px;
    padding: 6px 12px;
  }
  .dock-btn { width: 38px; height: 38px; font-size: 15px; }
  #dock-prev-viz,
  #dock-next-viz { font-size: 23px; }
  #dock-center { min-width: 220px; }
  #dock-time { min-width: 60px; }
}

@media (max-width: 640px) {
  #dock {
    min-width: 0;
    width: calc(100% - 16px);
    padding: 5px 8px;
    gap: 6px;
  }
  #dock-left,
  #dock-center,
  #dock-right {
    padding: 0;
  }
  #dock-right { gap: 2px; }
  .dock-btn { width: 34px; height: 34px; font-size: 14px; }
  #dock-prev-viz,
  #dock-next-viz { font-size: 20px; }
  #dock-center { min-width: 0; }
  #dock-scrubber { margin: 0 4px; }
  #dock-time { min-width: 0; font-size: 10px; }
}

/* === MOVIE MODE AUTO-HIDE (chunk 13 of UI refactor) =======================
   Idle ≥3s → fade #dock + #viz-name-badge to 0 via .idle class. Any input
   event removes .idle for instant fade-back. Suppressed while panels /
   overlays / dropdowns are open (handled JS-side in chromeShouldStayAwake).
   Both elements already declare `transition: opacity var(--chrome-fade-ms)`
   so the fade picks up the existing curve. */
#dock.idle,
#viz-name-badge.idle {
  opacity: 0;
  pointer-events: none;
}

/* One-time "move the mouse to show controls" hint — fades in where the
   dock sits the first time Movie Mode hides it, then app.js never shows
   it again. Reuses the dock glass tokens; never blocks the viz. */
#dock-idle-hint {
  position: fixed;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  padding: 8px 16px;
  font: 500 12px/1 -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
  color: rgba(255, 255, 255, 0.6);
  background: var(--dock-bg);
  backdrop-filter: blur(var(--dock-blur));
  -webkit-backdrop-filter: blur(var(--dock-blur));
  border: var(--dock-border);
  border-radius: 999px;
  box-shadow: var(--dock-shadow);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  z-index: 109;
  transition: opacity 0.5s ease, transform 0.5s ease;
}
#dock-idle-hint.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* === LEGACY UI RETIREMENT (chunk 5 of UI refactor) ========================
   The dock now owns every interactive surface, so the legacy pill and
   bottom-corner clusters are hidden. DOM stays intact for two reasons:
   (1) #ipod-toggle is in the iPod-preservation list per memory; (2) many
   children (#play-pause, #btn-prev/next, #track-name, #album-art,
   #progress-fill, volume-slider, btn-shuffle/repeat, btn-fullscreen,
   btn-ipod, etc.) have non-null-safe getElementById listeners, so JS
   needs them present even though the user can't see them.

   Future chunk 13 (auto-hide Movie Mode) will retarget the existing
   setControlsHidden() / signInButtons / kbdHints / titleChip fade
   logic to the dock; for now those captured handles are null and the
   if (X) checks no-op harmlessly. */
#controls,
#ui-buttons {
  display: none;
}

/* === DOCK LEFT (chunk 4 of UI refactor) ===================================
   Music-account sign-in chip + relocated #streaming-menu dropdown. The
   menu was moved out of #sign-in-buttons (which is now hidden) and into
   #dock-left so it can position above the dock pill. position:relative
   on #dock-left makes the absolute-positioned menu anchor here. */
#dock-left {
  position: relative;
}

/* Menu now opens upward from inside #dock-left. Existing #streaming-menu
   rules (above) already use bottom: calc(100% + 8px) which positions it
   above its containing block — no need to change those. Adjust the left
   anchor so the wider 280px menu doesn't get clipped by the dock pill's
   left edge when dock-left sits flush against it. */
#dock-left > #streaming-menu {
  /* Override default left:0; align menu's left edge with dock-left's
     left edge while letting it extend rightward over the dock body. */
  left: 0;
}

/* === VIZ-NAME BADGE (chunk 6 of UI refactor) ==============================
   Persistent top-left chip showing the active visualizer's name.
   Replaces the flash-only #viz-title-overlay retired in chunk 5. Reuses
   the dock design tokens so it reads as part of the same chrome layer.
   Chunk 13 (auto-hide Movie Mode) will fade this with the dock on idle. */
#viz-name-badge {
  position: fixed;
  top: 20px;
  left: 20px;
  padding: 6px 14px;
  background: var(--dock-bg);
  backdrop-filter: blur(var(--dock-blur));
  -webkit-backdrop-filter: blur(var(--dock-blur));
  border: var(--dock-border);
  border-radius: var(--dock-radius);
  box-shadow: var(--dock-shadow);
  color: rgba(255, 255, 255, 0.85);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  z-index: 110;
  user-select: none;
  pointer-events: none;
  transition: opacity var(--chrome-fade-ms) ease;
}

/* Hidden when empty so the chip doesn't render an empty pill on
   initial paint before setMode(0) has fired. */
#viz-name-badge:empty {
  display: none;
}

/* === VIZ SETTINGS PANEL (chunk 7 of UI refactor) ==========================
   Slide-up panel anchored to the bottom-right, just above the dock pill.
   Shell-only in chunk 7 — chunk 8 migrates the legacy hard-coded sliders
   (Vortex / Waves / Ferrofluid / generic Speed) into each viz's
   registry controls array, and this panel renders them via a new
   window.Viz.renderControlsInto(vizId, container) helper. */
#viz-settings-panel {
  position: fixed;
  bottom: 100px;          /* clears the dock (~bottom 20 + 56 height + gap) */
  right: 20px;
  width: 240px;
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  padding: 14px;
  background: var(--panel-bg);
  backdrop-filter: blur(var(--panel-blur));
  -webkit-backdrop-filter: blur(var(--panel-blur));
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--panel-radius);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  color: rgba(255, 255, 255, 0.85);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
  font-size: 12px;
  z-index: 115;
  /* Closed state — slide-up from below + fade. */
  opacity: 0;
  transform: translateY(20px);
  pointer-events: none;
  transition: opacity var(--chrome-fade-ms) ease,
              transform var(--chrome-fade-ms) ease;
}
#viz-settings-panel.open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: all;
}

.viz-settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.viz-settings-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.viz-settings-close {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.55);
  width: 24px;
  height: 24px;
  border-radius: 50%;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.viz-settings-close:hover {
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
}

.viz-settings-empty {
  color: rgba(255, 255, 255, 0.45);
  font-size: 11px;
  font-style: italic;
  text-align: center;
  padding: 12px 0;
}

/* Persistent global animation-speed control (always shown, viz-independent). */
#viz-global-controls {
  padding: 10px 2px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  margin-bottom: 8px;
}
.viz-global-ctl {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.viz-global-ctl > span {
  color: rgba(255, 255, 255, 0.75);
  font-size: 11px;
  letter-spacing: 0.02em;
  display: flex;
  justify-content: space-between;
}
.viz-global-ctl #anim-speed-val {
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.viz-global-ctl input[type="range"] {
  width: 100%;
  accent-color: #a855f7;
}

/* === Vertical slider primitives (chunk 7 — populated by chunk 8) ==========
   Reserved class names so chunk 8's registry-driven renderer has stable
   styling targets. Each control is a column: vertical slider track on
   top, label + live readout below. Compact so several controls fit
   side-by-side in the 240px panel. */
.viz-controls-vertical {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-end;
  justify-content: center;
}

.viz-control-vertical {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 44px;
}

.viz-control-vertical input[type="range"] {
  -webkit-appearance: slider-vertical;
  writing-mode: vertical-lr;
  width: 16px;
  height: 100px;
  margin: 0;
}

.viz-control-vertical label {
  font-size: 10px;
  color: rgba(255, 255, 255, 0.65);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: nowrap;
}

.viz-control-vertical .viz-control-value {
  font-size: 10px;
  color: rgba(255, 255, 255, 0.45);
  font-variant-numeric: tabular-nums;
  min-height: 12px;
}

/* === LIBRARY OVERLAY (chunk 9 of UI refactor) =============================
   Full-screen shell with tabbed header. Chunks 10-12 fill #library-body
   with per-tab grids of LibraryItems from MusicSources.current().getLibrary().
   Stronger backdrop blur than the dock so the library reads as a focused
   pane (per the original prompt's "increase the background blur
   significantly" directive). */
#library-overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay-bg);
  backdrop-filter: blur(var(--overlay-blur));
  -webkit-backdrop-filter: blur(var(--overlay-blur));
  display: flex;
  flex-direction: column;
  z-index: 200;            /* above dock (110) + viz-settings (115) */
  color: rgba(255, 255, 255, 0.92);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
  /* Closed state — fade. (Slide / scale animations would compete with
     the strong blur ramp-up; a clean fade reads cleanest.) */
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--chrome-fade-ms) ease;
}
#library-overlay.open {
  opacity: 1;
  pointer-events: all;
}

#library-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 20px 28px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

#library-tabs {
  display: flex;
  gap: 4px;
}

.library-tab {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.55);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  font-family: inherit;
}
.library-tab:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
}
.library-tab.active {
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
  font-weight: 600;
}
.library-tab:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 2px;
}

#library-close {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.55);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}
#library-close:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.10);
}
#library-close:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 1px;
}

#library-body {
  flex: 1;
  overflow-y: auto;
  padding: 24px 28px 40px;
}

.library-empty {
  color: rgba(255, 255, 255, 0.50);
  text-align: center;
  font-size: 13px;
  padding: 60px 24px;
  line-height: 1.6;
}
.library-empty code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 11px;
  background: rgba(255, 255, 255, 0.06);
  padding: 2px 6px;
  border-radius: 4px;
}

/* === LIBRARY DATA LAYER (chunk 10 of UI refactor) =========================
   Responsive album-art grid for tab content + song-row list for
   drill-down views + back button to escape drill state. */

.library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 18px 16px;
  align-items: start;
}

.library-card {
  background: transparent;
  border: none;
  padding: 0;
  text-align: left;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: inherit;
  font-family: inherit;
  transition: transform 0.12s ease;
}
.library-card:hover {
  transform: translateY(-2px);
}
.library-card:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 4px;
  border-radius: 8px;
}

.library-card-art {
  aspect-ratio: 1;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
}
.library-card-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.library-card-name {
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.library-card-subtitle {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.50);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Drill-down song list — narrower than the grid for readability. */
.library-back {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.65);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  padding: 6px 10px;
  margin-bottom: 8px;
  border-radius: 6px;
  font-family: inherit;
  transition: background 0.12s ease, color 0.12s ease;
}
.library-back:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.06);
}
.library-back:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 1px;
}

.library-drill-title {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #fff;
  margin: 6px 0 18px;
  max-width: 800px;
}

.library-songs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: 800px;
}

.library-song-row {
  display: grid;
  grid-template-columns: 44px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 8px 12px;
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font-family: inherit;
  transition: background 0.12s ease;
}
.library-song-row:hover {
  background: rgba(255, 255, 255, 0.06);
}
.library-song-row:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: -1px;
}

.library-song-art {
  width: 44px;
  height: 44px;
  border-radius: 4px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.05);
  flex-shrink: 0;
}
.library-song-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.library-song-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.library-song-name {
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.library-song-subtitle {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.50);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.library-song-duration {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.45);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .library-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 12px;
  }
  .library-drill-title {
    font-size: 18px;
  }
}

/* Chunk 11: infinite-scroll sentinel. Empty visual; IntersectionObserver
   fires on intersection regardless of height, but giving it some height
   keeps the trigger reliable on browsers that bail on 0px elements. */
.library-sentinel {
  height: 1px;
  margin-top: 24px;
}

/* Chunk 12: Search tab — single pill input centered above a flat
   song-row results list. Debounced + cancellation handled in app.js. */
.library-search {
  max-width: 600px;
  margin: 0 auto;
}

.library-search-input {
  width: 100%;
  padding: 12px 18px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  color: #fff;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.library-search-input::placeholder {
  color: rgba(255, 255, 255, 0.45);
}
.library-search-input:focus {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.28);
}
/* Suppress the WebKit clear-x — it conflicts visually with the pill. */
.library-search-input::-webkit-search-cancel-button { display: none; }

.library-search-results {
  margin-top: 16px;
}

@media (max-width: 640px) {
  #library-header {
    padding: 12px 16px;
    flex-wrap: wrap;
  }
  #library-tabs {
    flex: 1;
    overflow-x: auto;
  }
  .library-tab {
    padding: 6px 12px;
    font-size: 13px;
  }
  #library-body {
    padding: 16px;
  }
}

/* === DOCK VOLUME HOVER POPUP (followup B1) ================================
   iTunes-style hover popup: a small vertical slider that opens above
   #dock-volume. Wrapper is position:relative so the popup anchors to
   the button instead of viewport.  Reuses --panel-* tokens from chunk 1
   so the glass treatment matches the viz-settings panel. */
.dock-volume-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

#dock-volume-popup {
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  width: 44px;
  padding: 12px 6px 10px;
  background: var(--panel-bg);
  backdrop-filter: blur(var(--panel-blur));
  -webkit-backdrop-filter: blur(var(--panel-blur));
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  /* Closed state — fade + lift. Use translate(-50%) to keep horizontal
     centering on the button while we animate the y-offset. */
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
  pointer-events: none;
  transition: opacity var(--chrome-fade-ms) ease,
              transform var(--chrome-fade-ms) ease;
  z-index: 116; /* above dock + viz-settings panel */
}
#dock-volume-popup.open {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: all;
}

#dock-volume-slider {
  /* `slider-vertical` is deprecated but the writing-mode + direction
     pair is the standardized replacement. Both are set so older Chrome
     renders correctly even with the deprecation warning. */
  -webkit-appearance: slider-vertical;
  appearance: slider-vertical;
  writing-mode: vertical-lr;
  direction: rtl; /* drag up = louder, down = quieter */
  width: 16px;
  height: 110px;
  margin: 0;
}

#dock-volume-readout {
  font-size: 9px;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
  min-width: 18px;
  text-align: center;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
  letter-spacing: 0.04em;
}

/* ── Debug HUD ──────────────────────────────────────────────────────────────
   Toggle with backtick (`). Hidden by default; shown when body has
   class "hud-visible". Positioned top-right to avoid the viz-name-badge
   (top-left) and dock (bottom). pointer-events:none so it never intercepts
   clicks or hover events on the visualizer. */
#debug-hud {
  display: none;
  position: fixed;
  top: 20px;
  right: 20px;
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  padding: 10px 13px 8px;
  font-family: 'SF Mono', ui-monospace, 'Fira Code', monospace;
  font-size: 11px;
  line-height: 1.5;
  color: #c8c8c8;
  z-index: 9999;
  min-width: 148px;
  backdrop-filter: blur(12px) saturate(1.4);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  pointer-events: none;
  user-select: none;
}
body.hud-visible #debug-hud {
  display: block;
}
.hud-title {
  font-size: 9px;
  letter-spacing: 0.12em;
  color: rgba(255, 255, 255, 0.35);
  margin-bottom: 4px;
}
.hud-fps-row {
  line-height: 1.1;
  margin-bottom: 1px;
}
#hud-fps {
  font-size: 26px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--hud-fps-color, #4eff91);
  transition: color 0.3s;
}
.hud-row {
  font-variant-numeric: tabular-nums;
  margin: 1px 0;
}
.hud-unit {
  color: rgba(255, 255, 255, 0.38);
  font-size: 10px;
}
.hud-dim {
  color: rgba(255, 255, 255, 0.45);
  font-size: 10px;
}
.hud-divider {
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin: 5px 0;
}
#hud-spark {
  display: block;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.04);
  margin: 4px 0;
}
.hud-hint {
  font-size: 9px;
  color: rgba(255, 255, 255, 0.20);
  margin-top: 5px;
  letter-spacing: 0.05em;
}
