/* ==========================================================================
   VibeDial — Phase 50 Stylesheet
   Single clean stylesheet replacing all prior phase files (layout53-58,
   mobile54-55, etc.). Architecture:

   * Container queries on <body> drive all layout. Zero JS layout state.
   * One safe-area token, one viewport unit (dvh), no platform-specific shims.
   * Targets the EXISTING production HTML class names so vibedial_app.html
     and all 54 JS modules keep working unchanged.
   * Zero !important in layout rules. Specificity flows via container scope.
   * Pinch zoom is enabled (the viewport meta has been corrected in HTML).

   Breakpoints (container = body width):
     < 720     phone           single panel + bottom dock
     720-1179  tablet          split: player + sidebar (340)
     >= 1180   desktop         split: player + sidebar (410), generous spacing
   ========================================================================== */


/* ==========================================================================
   1. DESIGN TOKENS
   ========================================================================== */

:root {
  /* Brand surface */
  --bg-deep: #070812;
  --bg-mid:  #101326;
  --bg-high: #161f3e;

  /* Brand accents (preserved from original VibeDial) */
  --c1: #8df7ee;   /* cyan */
  --c2: #ff78b5;   /* pink */
  --c3: #f9df6a;   /* amber */

  /* Text scale */
  --text:  #f7f8ff;
  --muted: rgba(247, 248, 255, .68);
  --dim:   rgba(247, 248, 255, .42);
  --ink:   rgba(4, 6, 16, .82);

  /* Status */
  --good: #69ffc4;
  --warn: #ffd166;
  --bad:  #ff647f;

  /* Surface lines & glass */
  --line:         rgba(255, 255, 255, .14);
  --line-strong:  rgba(255, 255, 255, .26);
  --glass:        rgba(255, 255, 255, .07);
  --glass-strong: rgba(255, 255, 255, .12);

  /* Spatial */
  --radius:      26px;
  --radius-sm:   16px;
  --radius-pill: 999px;

  /* One safe-area token, used everywhere */
  --safe-block:  max(14px, env(safe-area-inset-bottom, 0px));
  --safe-inline: max(14px, env(safe-area-inset-left, 0px));

  /* Shadows */
  --shadow:    0 24px 90px rgba(0, 0, 0, .42);
  --shadow-sm: 0 6px 18px rgba(0, 0, 0, .25);

  /* Type */
  --font-body:    "Manrope", ui-sans-serif, system-ui, -apple-system,
                  BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: "Fraunces", "Manrope", Georgia, serif;

  /* Type scale */
  --text-xs:      11px;
  --text-sm:      13px;
  --text-base:    15px;
  --text-lg:      18px;
  --text-display: clamp(1.85rem, 4.6cqi, 3.2rem);

  color-scheme: dark;
}

/* ==========================================================================
   AMBIENT COLOR — per-station background bloom
   --------------------------------------------------------------------------
   --ambient-1 / --ambient-2 are written by the inline script (function
   applyAmbientFromStation) when the active station's favicon yields a usable
   dominant color. They paint two soft radial gradients on top of the brand
   atmosphere (see body{} below) to give each station a recognizable hue
   without changing the VibeDial signature look.

   @property registers the variables as actual <color> values so transitions
   can interpolate them smoothly when the station changes. Browsers without
   @property support (older Firefox) snap to the new color instead of fading;
   the fallback initial-value of transparent keeps the layer invisible until
   extraction completes.
   ========================================================================== */
@property --ambient-1 {
  syntax: '<color>';
  inherits: true;
  initial-value: transparent;
}
@property --ambient-2 {
  syntax: '<color>';
  inherits: true;
  initial-value: transparent;
}


/* ==========================================================================
   2. RESET / BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
}

html {
  -webkit-text-size-adjust: 100%;
  touch-action: manipulation;
}

body {
  font-family: var(--font-body);
  color: var(--text);
  font-size: var(--text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
  background: var(--bg-deep);
  /* Clamp body to viewport. Used dvh, which handles iOS Safari URL bar correctly
     and is well-supported across all modern browsers. */
  min-height: 100dvh;
  overflow: hidden;
  /* THE CONTAINER SCOPE. Every layout @container rule queries this. */
  container: vd-app / inline-size;
}

/* Inputs/textareas should be selectable; rest of UI mostly chrome */
input, select, textarea, [contenteditable] { user-select: text; }

/* 16px on text inputs prevents iOS auto-zoom without needing a webkit shim */
input[type="search"], input[type="url"], input[type="text"], input[type="tel"],
input[type="email"], input[type="number"], textarea, select {
  font-size: 16px;
}

/* Tap highlight off — we provide our own feedback via :active */
button, a, [role="button"], .station-card, .genre, .mobile-dock-btn {
  -webkit-tap-highlight-color: transparent;
}

img { max-width: 100%; display: block; }

a { color: var(--c1); text-decoration: none; }
a:hover { text-decoration: underline; }

button {
  appearance: none;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: 0;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Universal focus ring for keyboard users */
:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
  border-radius: 6px;
}


/* ==========================================================================
   3. ATMOSPHERE — animated background
   ========================================================================== */

body {
  background:
    /* Ambient station bloom — two soft radials driven by --ambient-1/2.
       Default to transparent (set in :root via @property), so when no
       ambient color has been extracted these layers paint nothing and only
       the brand gradients below show through. The transition on background
       is what makes the station-to-station fade feel premium. */
    radial-gradient(ellipse 80% 55% at 50% -8%, color-mix(in srgb, var(--ambient-1) 30%, transparent), transparent 65%),
    radial-gradient(ellipse 70% 80% at 110% 110%, color-mix(in srgb, var(--ambient-2) 26%, transparent), transparent 72%),
    /* Brand gradients — VibeDial's permanent cyan/pink/amber signature. */
    radial-gradient(circle at 13% 10%, color-mix(in srgb, var(--c1) 30%, transparent), transparent 32rem),
    radial-gradient(circle at 88% 16%, color-mix(in srgb, var(--c2) 32%, transparent), transparent 28rem),
    radial-gradient(circle at 56% 105%, color-mix(in srgb, var(--c3) 18%, transparent), transparent 34rem),
    linear-gradient(142deg, var(--bg-deep), var(--bg-mid) 48%, var(--bg-high));
  transition: --ambient-1 700ms ease, --ambient-2 700ms ease;
}

body::before, body::after {
  content: "";
  position: fixed;
  inset: -20%;
  pointer-events: none;
  z-index: 0;
}

body::before {
  opacity: .26;
  background-image:
    radial-gradient(circle, rgba(255, 255, 255, .5) 0 1px, transparent 1.4px),
    radial-gradient(circle, rgba(255, 255, 255, .25) 0 1px, transparent 1.4px);
  background-size: 126px 126px, 206px 206px;
  background-position: 0 0, 33px 74px;
  animation: vd-stars 50s linear infinite;
}

body::after {
  opacity: .18;
  background: linear-gradient(115deg, transparent 0 42%, rgba(255, 255, 255, .14) 48%, transparent 55% 100%);
  animation: vd-beam 14s ease-in-out infinite;
  mix-blend-mode: screen;
}

@keyframes vd-stars { to { transform: translate3d(-70px, -38px, 0) rotate(1deg); } }
@keyframes vd-beam {
  0%, 100% { transform: translateX(-42%) rotate(-2deg); opacity: .10; }
  45%      { transform: translateX( 42%) rotate( 1deg); opacity: .26; }
}

@media (prefers-reduced-motion: reduce) {
  body::before, body::after { animation: none; }
}


/* ==========================================================================
   4. APP SHELL — phone-first single column, container-query expansions
   ========================================================================== */

.app {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100dvh;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: minmax(0, 1fr);
  padding: 14px;
  gap: 0;
}

/* TABLET: ≥720 — both panels visible side by side */
@container vd-app (min-width: 720px) {
  .app {
    grid-template-columns: minmax(0, 1fr) 340px;
    grid-template-rows: minmax(0, 1fr);
    gap: 14px;
    padding: 18px;
  }
}

/* DESKTOP: ≥1180 — wider sidebar, more padding */
@container vd-app (min-width: 1180px) {
  .app {
    grid-template-columns: minmax(0, 1fr) 410px;
    gap: 18px;
    padding: 22px;
  }
}


/* ==========================================================================
   5. PANEL CHROME
   ========================================================================== */

.panel {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .04));
  box-shadow: var(--shadow);
  backdrop-filter: blur(22px) saturate(1.15);
  -webkit-backdrop-filter: blur(22px) saturate(1.15);
  overflow: hidden;
  min-width: 0;
  min-height: 0;
}

.panel::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 14% 8%, rgba(255, 255, 255, .14), transparent 14rem),
    radial-gradient(circle at 100% 88%, rgba(255, 255, 255, .06), transparent 18rem);
  mask: linear-gradient(#000, transparent 92%);
  -webkit-mask: linear-gradient(#000, transparent 92%);
}

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


/* ==========================================================================
   6. PANEL VISIBILITY (phone single-panel switching)
   ========================================================================== */

.panel.player  { display: grid; }
.panel.sidebar { display: grid; }

/* PHONE: hide the panel that isn't the active mobile-view.
   These rules are scoped INSIDE the phone container query so they cannot
   affect the tablet/desktop layout. JS adds the body.mobile-view-* classes;
   default phone view = player.

   We also activate this single-column-with-dock layout when the viewport
   is short (≤500px tall) and not very wide — that catches iPhones in
   landscape orientation, where the device width (e.g. 932px) is technically
   over our 720px phone breakpoint but the user clearly still wants the
   single-column phone treatment with a bottom dock. */
/* PHONE / SHORT-LANDSCAPE: hide the panel that isn't the active mobile-view.
   JS adds body.compact-layout when:
     - viewport width ≤ 720px (any phone in portrait, narrow tablets), OR
     - viewport height ≤ 500px AND width ≤ 1100px (phones in landscape,
       where the device's wide axis exceeds 720px but the user clearly still
       wants single-column with bottom dock)
   These rules use that class so the trigger logic stays in JS where it's
   easier to debug, rather than fighting container query OR-syntax.

   ──────────────────────────────────────────────────────────────────────
   Plan #3b cleanup (May 9 2026): the "discover" and "stations" mobile
   views were consolidated into a single "browse" view. The JS was
   updated to emit body.mobile-view-browse, but these CSS rules still
   referenced the orphaned .mobile-view-discover and .mobile-view-stations
   classes. Result: tapping the Browse dock button set the right body
   class but no CSS rule matched it, so the sidebar stayed hidden and
   the player stayed visible — the view never actually switched. Fixed
   here by simplifying to use the canonical .mobile-view-browse class.
   ────────────────────────────────────────────────────────────────────── */
body.compact-layout:not(.mobile-view-browse) .panel.sidebar { display: none; }
body.compact-layout.mobile-view-browse .panel.player { display: none; }
body.compact-layout .mobile-dock { display: grid; }
body:not(.compact-layout) .mobile-dock { display: none; }

@container vd-app (max-width: 719.98px) {
  body:not(.mobile-view-browse) .panel.sidebar { display: none; }
  body.mobile-view-browse .panel.player { display: none; }

  /* Mobile dock visible in this mode (it's hidden on desktop by default) */
  .mobile-dock { display: grid; }
}


/* ==========================================================================
   7. PLAYER PANEL
   ========================================================================== */

.panel.player {
  container: vd-player / inline-size;
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  padding: 18px;
  gap: 14px;
}

@container vd-app (min-width: 1180px) {
  .panel.player { padding: 26px; gap: 18px; }
}


/* ---- Topbar ---- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-width: 0;
}

.brand, .brand.vibedial-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}

/* The custom brand logo image (preserved from production) */
.vd-header-logo {
  height: 36px;
  width: auto;
  max-width: 220px;
  object-fit: contain;
  object-position: left center;
}

@container vd-player (max-width: 480px) {
  .vd-header-logo { height: 30px; max-width: 160px; }
}

.status-strip {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 30px;
  padding: 0 11px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pill .live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 12px var(--good);
  animation: vd-pulse 1.6s ease-in-out infinite;
}

@keyframes vd-pulse {
  0%, 100% { opacity: .55; transform: scale(.85); }
  50%      { opacity: 1;   transform: scale(1.15); }
}

@media (prefers-reduced-motion: reduce) {
  .pill .live-dot { animation: none; }
}

/* Status pill that flips to "live" green when actually playing */
.is-playing .pill:has(.live-dot),
body.is-playing .pill:has(.live-dot) { color: var(--good); }

/* "Source" pill is non-essential on narrow widths */
@container vd-player (max-width: 480px) {
  .pill.hide-mobile { display: none; }
}

/* Mini buttons in the player topbar (compact menu / escape).
   #compactExitBtn is hidden by default; appears in compact mode.
   #compactMenuBtn is hidden on tablet+ where the sidebar's section head has the studio button. */
.mini-escape-btn,
.mini-menu-btn {
  width: 36px;
  height: 36px;
  min-height: 36px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  color: var(--text);
  font-size: 14px;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .15s;
}
.mini-escape-btn:hover,
.mini-menu-btn:hover { background: rgba(255, 255, 255, .08); border-color: var(--line-strong); }
.mini-escape-btn:active,
.mini-menu-btn:active { transform: scale(.95); }

/* Default visibility: */
.mini-escape-btn { display: none; }                  /* only when in compact mode */
body.compact .mini-escape-btn { display: inline-grid; }

.mini-menu-btn { display: inline-grid; }             /* visible on phone */
@container vd-app (min-width: 720px) {
  .mini-menu-btn { display: none; }                  /* hidden when sidebar is visible */
}


/* ---- Player main (turntable + now playing) ---- */

.player-main {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  justify-items: center;
  text-align: center;
  min-width: 0;
  min-height: 0;
  /* Critical: prevents content from painting into the transport row below. */
  overflow: hidden;
}

/* Compact layouts (mobile + mini player): allow the halo to extend
   above the turntable into the panel padding area. The overflow:hidden
   on .player-main was originally a transport-row protection — it
   prevented .now text from overflowing INTO the transport row below.
   On compact layouts that's a non-issue: the topbar above is hidden
   so there's nothing to conflict with halo's upward extension, and the
   .now text is naturally constrained on small viewports.

   With this change the only remaining clip on the halo is at the panel
   border itself, which is a natural visual boundary (the rounded glass
   edge of the panel) rather than a mysterious mid-screen cut line.

   The slight bloom reduction below ensures the halo fits cleanly within
   the panel padding area without bleeding right up to the rounded
   border edge — softer fade with a small buffer.

   Also: align-items: start so .now items don't recenter vertically when
   now-meta appears. Default align-items: center on .player-main causes
   the station title to bump UP when track meta appears (because .now
   grows and stays centered, shifting its top edge upward). Top-aligned
   means the title stays put — only the empty space below grows. */
body.compact-layout .player-main,
body.compact .player-main {
  overflow: visible;
  padding-top: 0;
  align-items: start;
}

body.compact-layout .halo,
body.compact .halo {
  inset: -3%;
  filter: blur(20px);
}

/* Side-by-side once player has room (~480px wide). Below that, stacked. */
@container vd-player (min-width: 480px) {
  .player-main {
    grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
    grid-template-rows: 1fr;
    gap: 22px;
    text-align: start;
    justify-items: stretch;
  }
}


/* ---- Turntable ---- */

.turntable {
  position: relative;
  aspect-ratio: 1 / 1;
  width: clamp(160px, 48cqi, 220px);
  max-width: 100%;
  max-height: 50svh;
  display: grid;
  place-items: center;
  align-self: center;
  justify-self: center;
}

@container vd-player (min-width: 480px) {
  .turntable {
    width: 100%;
    max-width: clamp(220px, 42cqi, 340px);
    max-height: 60svh;
  }
}

.disc-wrap {
  position: relative;
  width: 100%;
  height: 100%;
}

.halo {
  position: absolute;
  inset: -10%;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    color-mix(in srgb, var(--c1) 80%, transparent),
    color-mix(in srgb, var(--c2) 70%, transparent),
    color-mix(in srgb, var(--c3) 70%, transparent),
    color-mix(in srgb, var(--c1) 80%, transparent));
  filter: blur(28px);
  opacity: .55;
  animation: vd-halo 26s linear infinite;
}

@keyframes vd-halo { to { transform: rotate(360deg); } }

.disc {
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 32%, rgba(255, 255, 255, .26) 0 6%, transparent 8%),
    repeating-radial-gradient(circle at 50% 50%,
      #0a0a14 0 2px,
      #14141d 2px 4px,
      #0a0a14 4px 6px),
    radial-gradient(circle at 50% 50%, #1a1a26, #050509);
  box-shadow:
    inset 0 0 0 6px rgba(255, 255, 255, .04),
    inset 0 0 36px rgba(0, 0, 0, .8),
    0 28px 60px rgba(0, 0, 0, .55);
}

.disc::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 26%;
  height: 26%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, #fff 0 8%, transparent 18%),
    conic-gradient(from 220deg, var(--c1), var(--c2), var(--c3), var(--c1));
}

.disc::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 7%;
  height: 7%;
  border-radius: 50%;
  background: #050509;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .15);
  z-index: 2;
}

/* Disc spin — keep going through transient buffering states so the
 * turntable doesn't flicker between spinning/stopped during network
 * blips. The status pill carries the "Reconnecting" label so the user
 * knows what's happening; the visual stays coherent. (rev 12.31) */
body.is-playing .disc,
body.is-buffering .disc,
.is-playing .disc,
.is-buffering .disc { animation: vd-spin 5s linear infinite; }

@keyframes vd-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .disc, body.is-playing .disc, body.is-buffering .disc,
  .is-playing .disc, .is-buffering .disc { animation: none; }
  .halo { animation: none; }
}

/* Tonearm geometry (rev 12.21)
 *
 * Sized so the arm, pivot puck, and headshell always stay visually glued
 * together as a single tonearm at every container size. The trick is
 * binding everything to the arm itself rather than to the player container.
 *
 * The arm's height (clamped 4-9px via cqi) is the reference unit. The puck
 * and headshell are sized as pixel multiples of that base, so when the arm
 * height caps out at 9px on desktop, the puck stops growing too — and they
 * stay in proportion. Their POSITIONS are also expressed in those same
 * units, so there's never a visible gap between arm and puck regardless
 * of container width.
 *
 * Geometric design (unchanged from 12.19/12.20 — that part was correct):
 *   - Pivot anchored just outside the disc (right=4%, top=10%).
 *   - Transform-origin at the right edge of the arm (100% 50%).
 *   - Arm width 34% of disc-wrap.
 *   - Rest rotation: 0deg (parked at outer disc edge).
 *   - Playing rotation: -44deg (counter-clockwise, lands on outer-mid groove).
 */
.arm {
  /* --vd-arm-h is the arm's display height. Drives all child sizing below
   * so puck and headshell scale together with the arm at any container
   * size.
   *
   * Cap raised in rev 12.29: at very large container widths (480px+
   * discs on big desktops), 9px capped the arm too low — the puck and
   * headshell looked dwarfed by the disc. New clamp lets the arm grow
   * to 14px on the biggest displays while staying tight on small ones. */
  --vd-arm-h: clamp(4px, 2cqi, 14px);

  position: absolute;
  z-index: 4;
  top: 10%;
  right: 4%;
  /* Arm length — 38% of disc-wrap. Up from 34% in earlier rev. The
   * extra length makes the headshell land on the outer groove rather
   * than hovering just above the disc edge at large display sizes. */
  width: 38%;
  height: var(--vd-arm-h);
  transform-origin: 100% 50%;
  transform: rotate(0deg);
  background: linear-gradient(90deg,
    rgba(255, 255, 255, .8),
    rgba(255, 255, 255, .35) 18%,
    rgba(255, 255, 255, .92) 60%,
    rgba(255, 255, 255, .35));
  border-radius: calc(var(--vd-arm-h) / 2);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
  transition: transform .8s cubic-bezier(.4, 0, .2, 1);
}

.arm::before {
  /* Pivot puck — circular, sized as a multiple of the arm height so it
   * always looks proportional. Centered on the transform-origin (right
   * edge of arm), so half its width sticks out past the arm end and half
   * extends back over the arm — pivot is invisible behind the puck.
   *
   * Bumped from 3.4× to 3.8× in rev 12.29 to read more clearly as a
   * chunky turntable pivot at large display sizes. */
  content: "";
  position: absolute;
  --vd-puck-size: calc(var(--vd-arm-h) * 3.8);
  width: var(--vd-puck-size);
  height: var(--vd-puck-size);
  right: calc(var(--vd-puck-size) * -0.5);
  top: 50%;
  transform: translateY(-50%);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
    rgba(255, 255, 255, .9),
    rgba(255, 255, 255, .25) 38%,
    rgba(0, 0, 0, .5) 75%);
  border: 1px solid rgba(255, 255, 255, .25);
  box-shadow: 0 calc(var(--vd-arm-h) * 0.3) calc(var(--vd-arm-h) * 0.8) rgba(0, 0, 0, .35);
}

.arm::after {
  /* Cartridge / headshell — at the tip of the arm (left end). Sized as a
   * multiple of the arm height so it stays in proportion. Slightly taller
   * than wide for the classic headshell silhouette.
   *
   * Bumped in rev 12.29 from 2.8×3.5 to 3.2×4.0 to read better at large
   * sizes. The headshell was looking like a tiny square at 500px discs;
   * now it reads as a proper cartridge. */
  content: "";
  position: absolute;
  --vd-head-w: calc(var(--vd-arm-h) * 3.2);
  --vd-head-h: calc(var(--vd-arm-h) * 4.0);
  width: var(--vd-head-w);
  height: var(--vd-head-h);
  left: calc(var(--vd-head-w) * -0.65);
  top: 50%;
  transform: translateY(-50%) rotate(34deg);
  border-radius: calc(var(--vd-arm-h) * 0.7) calc(var(--vd-arm-h) * 0.7) calc(var(--vd-arm-h) * 1.0) calc(var(--vd-arm-h) * 1.0);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .35) 18%, transparent 19%),
    linear-gradient(155deg,
      color-mix(in srgb, var(--c3) 92%, white) 0%,
      color-mix(in srgb, var(--c3) 70%, #111) 65%,
      #14141c 100%);
  border: 1px solid rgba(255, 255, 255, .18);
  clip-path: polygon(12% 0, 100% 0, 92% 72%, 58% 72%, 50% 100%, 42% 72%, 6% 72%);
  box-shadow: 0 0 calc(var(--vd-arm-h) * 1.5) color-mix(in srgb, var(--c3) 40%, transparent);
}

/* Arm playing-state rotation lives below in the consolidated rule
 * (search for "Turntable arm — needle stays on vinyl"). */


/* ---- Now playing column ---- */

.now {
  display: grid;
  gap: 12px;
  min-width: 0;
  min-height: 0;
  /* Default: vertically center children. Important on wide-desktop where
     the disc and .now sit side-by-side and the title-with-now-meta block
     should align vertically with the disc midpoint, not pin to the top
     of the row (which leaves the title floating above the disc). The
     compact override below pins to top in mobile/mini-player contexts
     where now-meta toggling would otherwise shift the title position. */
  align-content: center;
  align-self: stretch;
}

/* Compact contexts (mobile + mini player): pin the .now stack to the top
   of its grid cell so the title position stays invariant when now-meta
   toggles visibility. Default `align-content: center` (above) re-centered
   the whole stack when now-meta appeared, pulling the title up by ~half
   the now-meta height. Top-alignment makes the title position rock-steady;
   now-meta just appears below the status-pill when metadata loads, with
   empty space accumulating at the bottom of .now's box.

   Wide-desktop intentionally KEEPS centered alignment because the disc
   on the left visually anchors the row and a top-aligned title there
   reads as floating. The compact stack has the disc above the .now block
   instead of beside it, so top-alignment of .now reads naturally. */
body.compact-layout .now,
body.compact .now {
  align-content: start;
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  justify-content: center;
}

.eyebrow .meter-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c2);
  box-shadow: 0 0 12px var(--c2);
}

@container vd-player (min-width: 480px) {
  .eyebrow { justify-content: flex-start; }
}

.station-title {
  /* 2-line ellipsis with proper word-handling (rev 12.34):
   *   - hyphens: none prevents browsers from inserting "Phil-adelphia"
   *     style hyphenation when wrapping (default behavior was hyphens:auto)
   *   - overflow-wrap: anywhere lets super-long unbreakable strings wrap
   *     (e.g. some catalog station titles that are 200+ chars with no
   *     spaces); without this they'd overflow the container
   *   - line-clamp: 2 caps the display at 2 lines with "..." truncation
   *   - line-height bumped slightly (was 0.92, now 0.96) to give 2-line
   *     titles a touch of breathing room without pushing them apart */
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-display);
  line-height: 0.96;
  letter-spacing: -0.045em;
  hyphens: none;
  overflow-wrap: anywhere;
  word-break: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  background: linear-gradient(180deg, #fff 0%, #d8dcff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  min-width: 0;
}

.active-title-lockup {
  /* Logo + title side-by-side. align-items changed from center to
   * flex-start in rev 12.34 so the logo aligns with the FIRST line of
   * a multi-line title rather than vertically centering against the
   * whole 2-line block (which left it floating in mid-air). */
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
}

.active-station-logo {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 16px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--line);
  flex: 0 0 auto;
}

.active-station-logo[hidden] { display: none; }

/* Landing-mode wordmark treatment — when the station-title is showing
   "VibeDial" (no station tuned), give it true hero weight: larger, with
   a brand gradient fill across the wordmark. The default station-title
   styling above (white→muted gradient, capped 2-line clamp) handles the
   playing state where the title shows the actual station name. */
body.landing-mode .station-title {
  font-size: clamp(2.4rem, 5.4cqi, 4rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  background: linear-gradient(135deg, var(--c1) 0%, var(--c2) 55%, var(--c3) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* The default style sets max-height for 2-line clamp; on the wordmark
     we want unconstrained height so descenders / ascenders don't clip. */
  max-height: none;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  display: block;
}

.station-sub {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-base);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Landing-mode visibility: the wordmark + tagline + status pill are shown
   when no station is tuned. When a station IS playing, the station name
   takes over (station-title text is replaced by JS) and the active-station
   logo box gets the favicon. The AI logo image (.landing-logo-full) was
   deleted entirely in the flat-Studio fresh-load rev — the HTML wordmark is
   the canonical landing brand mark now. */
body.landing-mode .now-meta,
body.landing-mode .station-facts,
body.landing-mode .active-station-logo { display: none; }

/* Top-right "● live" / "featured" pills + the "tuned to" eyebrow + corner
   brand logo communicate playback state or duplicate the big landing
   wordmark. Hide their content on landing while keeping the .topbar
   itself in the layout — collapsing the whole topbar shifts the rest of
   the page up and looks cramped. */
body.landing-mode #statusText,
body.landing-mode #sourceText,
body.landing-mode .vd-header-logo,
body.landing-mode .eyebrow { display: none; }

/* Transport row (prev / play / next + secondary tools + volume slider)
   should not render until something is queued or playing. Removes the
   "controls for what?" dissonance on a fresh visit. */
body.landing-mode .transport { display: none; }

/* Now-playing strip — track info card */
.now-meta {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 4px;
  padding: 12px 14px;
  border-radius: 14px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--c2) 18%, rgba(0, 0, 0, .35)),
    color-mix(in srgb, var(--c1) 12%, rgba(0, 0, 0, .35)));
  border: 1px solid color-mix(in srgb, var(--c2) 32%, var(--line));
  min-width: 0;
}

.now-meta[hidden] { display: none; }

.now-meta-kicker {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c1);
}

.now-meta strong,
#nowMetaTitle {
  font-weight: 700;
  font-size: var(--text-base);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Per user request: hide the source/route/percentage line. The track info
   itself is enough — that infrastructure detail is for the diagnostics view. */
.now-meta em,
#nowMetaSource { display: none; }

.station-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
}

@container vd-player (min-width: 480px) {
  .station-facts { justify-content: flex-start; }
}

/* On narrow widths the technical pills compete with the now-meta strip;
   they're enhancement, not load-bearing. The now-meta carries what's playing,
   the topbar shows live/source. */
@container vd-player (max-width: 639.98px) {
  .station-facts { display: none; }
}

.fact, .tag-fact, .tag-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  transition: transform .15s, border-color .15s;
}
.fact b, .tag-fact b, .tag-pill b { color: var(--text); font-weight: 800; }
.fact:hover, .tag-fact:hover, .tag-pill:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--c1) 50%, var(--line));
}

/* Landing actions (Start / Surprise) shown only in landing mode */
.landing-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

@container vd-player (min-width: 480px) {
  .landing-actions { justify-content: flex-start; }
}

body:not(.landing-mode) .landing-actions { display: none; }

.landing-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  color: var(--text);
  font: inherit;
  font-weight: 700;
  font-size: var(--text-base);
  padding: 12px 22px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: all .15s;
}
.landing-btn:hover { background: rgba(255, 255, 255, .10); border-color: var(--line-strong); }
.landing-btn.primary-landing {
  background: linear-gradient(135deg, var(--c1), var(--c2) 60%, var(--c3));
  color: #0a0a14;
  border-color: transparent;
  box-shadow: 0 12px 28px color-mix(in srgb, var(--c2) 24%, transparent);
}

/* "or surprise me →" — demoted from a co-equal button to a quiet text link
   beside the primary CTA. Reduces decision friction on landing without
   removing the surprise-me path entirely. */
.landing-btn.landing-link {
  background: transparent;
  border: 0;
  padding: 12px 8px;
  font-weight: 500;
  color: var(--muted);
  box-shadow: none;
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.18);
  text-underline-offset: 4px;
  border-radius: 6px;
  transition: color .15s, text-decoration-color .15s;
}
.landing-btn.landing-link:hover {
  background: transparent;
  border-color: transparent;
  color: var(--text);
  text-decoration-color: var(--c1);
}


/* ---- Transport (controls + quick actions + volume) ---- */

.transport {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  align-items: center;
  justify-items: center;
}

.transport-center {
  display: contents;
}

@container vd-player (min-width: 640px) {
  .transport {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    justify-items: stretch;
  }
  .main-controls   { grid-column: 2; justify-self: center; }
  .quick-actions   { grid-column: 3; justify-self: end; }
}

.main-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

/* Big play button */
.primary, button.primary {
  width: 64px;
  height: 64px;
  font-size: 22px;
  font-weight: 900;
  border-radius: var(--radius-pill);
  border: 0;
  display: inline-grid;
  place-items: center;
  background: linear-gradient(135deg, var(--c1), var(--c2) 60%, var(--c3));
  color: #0a0a14;
  cursor: pointer;
  box-shadow:
    0 18px 40px color-mix(in srgb, var(--c2) 30%, transparent),
    inset 0 0 0 1px rgba(255, 255, 255, .3);
  transition: transform .15s;
}
.primary:hover { transform: scale(1.04); }
.primary:active { transform: scale(.96); }

.icon-btn, .nav-btn {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  color: var(--text);
  font-size: var(--text-base);
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .15s;
}
.icon-btn:hover, .nav-btn:hover {
  background: rgba(255, 255, 255, .08);
  border-color: var(--line-strong);
}
.icon-btn:active, .nav-btn:active { transform: scale(.95); }

.quick-actions {
  display: flex;
  gap: 5px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}

.tool-btn {
  width: 36px;
  height: 36px;
  min-height: 36px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  color: var(--text);
  font-size: 13px;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: color .15s, background .15s, border-color .15s, transform .15s;
}
.tool-btn:hover {
  color: var(--c1);
  background: rgba(255, 255, 255, .08);
  border-color: var(--line-strong);
}
.tool-btn:active { transform: scale(.94); }
.tool-btn.active, .tool-btn.is-active {
  color: var(--c1);
  border-color: color-mix(in srgb, var(--c1) 50%, var(--line));
}

.right-tools {
  width: 100%;
  display: contents;     /* let .volume flow into the transport grid as a row */
}

.volume {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  width: 100%;
}

/* Cap volume slider width on wide players — full-width is too far on desktop */
@container vd-player (min-width: 720px) {
  .volume {
    max-width: 360px;
    margin-inline: auto;
  }
}

.volume input[type="range"] {
  --vol: 65%;  /* Updated by JS on input — see vibedial-controls.js (setupVolumeSlider) */
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(to right,
    var(--c1) 0%,
    var(--c1) var(--vol),
    rgba(255, 255, 255, .14) var(--vol),
    rgba(255, 255, 255, .14) 100%);
  outline: none;
  cursor: pointer;
}

.volume input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .3);
  cursor: pointer;
}
.volume input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .3);
  cursor: pointer;
}

@container vd-player (max-width: 480px) {
  .volume span { display: none; }
}

/* Visualizer was here (54 animated bars in the transport row). Removed in the
   flat-Studio rev — the turntable disc is the hero visual and a second
   simultaneous animation diluted attention. JS scaffolding (buildVisualizer,
   animateBars) and the state.showVisualizer field were deleted at the same
   time. If you ever bring back a visualizer, do it as a single canvas-based
   waveform that responds to the actual audio rather than a fake bar dance. */


/* ==========================================================================
   8. SIDEBAR
   ========================================================================== */

.panel.sidebar {
  container: vd-sidebar / inline-size;
  grid-template-rows: auto auto auto minmax(0, 1fr) auto;
  padding: 16px;
  gap: 12px;
}

@container vd-app (min-width: 1180px) {
  .panel.sidebar { padding: 20px; gap: 14px; }
}


/* ---- Section head ---- */

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
}

.section-head h2 {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.micro-actions {
  display: flex;
  gap: 5px;
  align-items: center;
}

.chip-btn, .icon-chip, .studio-icon-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  color: var(--muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 0 11px;
  min-height: 32px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  transition: color .15s, background .15s, border-color .15s, transform .15s;
}

.chip-btn:hover, .icon-chip:hover, .studio-icon-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, .06);
  border-color: var(--line-strong);
}
.chip-btn:active, .icon-chip:active, .studio-icon-btn:active { transform: scale(.96); }

.chip-btn.active, .icon-chip.active {
  color: var(--c1);
  border-color: color-mix(in srgb, var(--c1) 50%, var(--line));
  background: color-mix(in srgb, var(--c1) 10%, rgba(0, 0, 0, .25));
}

/* Mini-player toggle (#compactBtn) is a DESKTOP-only affordance.
   On phone the dock provides nav; on tablet panels already coexist. */
@container vd-app (max-width: 1179.98px) {
  #compactBtn { display: none; }
}


/* ─────────────────────────────────────────────────────────────────────
   Mobile mode tabs — segmented control + gear, replaces section-head
   and genre-toolbar labels on phone. Hidden on desktop entirely.

   Layout:
     [────── segmented control ──────] [⚙]
   The segmented control fills available width; gear is a fixed-size
   icon button. Each segment is half-width by default. The animated
   pill (.mode-segment-pill) sits underneath and slides via translateX.

   "World-class" details:
     - Springy ease-out-back curve on the pill for a tactile feel
     - Active text color crossfades (color transition independent of pill)
     - Active state uses a subtle frosted-up white, NOT the pink accent
       (pink is reserved for primary CTAs like ★favorites and the dock
       Browse button — using it here would create visual conflict)
     - Container glassy bg + 1px translucent border matches .searchrow
     - Buttons z-index above pill so tap surface is the segment, not pill
   ────────────────────────────────────────────────────────────────────── */

.mobile-mode-tabs {
  display: none;
  align-items: center;
  gap: 10px;
  padding: 4px 0 6px;
  min-width: 0;
}

.mode-segments {
  position: relative;
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  padding: 4px;
  border-radius: 14px;
  background: rgba(0, 0, 0, .28);
  border: 1px solid var(--line);
  min-width: 0;
}

.mode-segment {
  appearance: none;
  position: relative;
  z-index: 1;
  background: transparent;
  border: 0;
  color: var(--dim);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 9px 12px;
  border-radius: 10px;
  cursor: pointer;
  transition: color 200ms ease, transform 120ms ease;
  -webkit-tap-highlight-color: transparent;
  text-align: center;
  white-space: nowrap;
  min-width: 0;
}

.mode-segment.is-active {
  color: var(--text);
}

.mode-segment:active {
  transform: scale(0.97);
}

.mode-segment:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--c1) 60%, transparent);
  outline-offset: 2px;
}

/* Animated pill that slides between segments. Sits under the buttons via
   z-index. Width is calc'd to fill exactly half the container minus the
   inner padding. The springy easing curve is what makes this feel
   "world-class" rather than utilitarian — a small overshoot followed by
   settle, like iOS controls. */
.mode-segment-pill {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  width: calc(50% - 4px);
  border-radius: 10px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .06),
    0 1px 2px rgba(0, 0, 0, .15);
  transform: translateX(0);
  transition: transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
  z-index: 0;
}

.mode-segment-pill[data-mode-target="search"] {
  transform: translateX(100%);
}

/* Gear button matching the mode-segments container height */
.mode-gear-btn {
  appearance: none;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: rgba(0, 0, 0, .28);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 180ms ease, border-color 180ms ease, transform 120ms ease;
  -webkit-tap-highlight-color: transparent;
}

.mode-gear-btn:hover { background: rgba(0, 0, 0, .38); }
.mode-gear-btn:active { transform: scale(0.94); }
.mode-gear-btn:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--c1) 60%, transparent);
  outline-offset: 2px;
}

/* Mobile activation. On mobile-view-browse:
   - Show the new tab strip
   - Hide the section-head ("Dial map" + micro-actions)
   - Hide the genre-toolbar (the "Audio lanes" label + edit pencil)
   - Show searchbox or genre-rail based on body[data-mobile-mode]
   - Match panel heights so swapping modes doesn't shift the station list

   Trigger: body.compact-layout — set by JS based on viewport width/height.
   This catches portrait phones, landscape phones, AND narrow desktop
   windows. Previously this was @container vd-app (max-width: 719.98px),
   which works for portrait but doesn't fire in landscape because the
   .app container is ~824px wide on landscape iPhone.
   ──────────────────────────────────────────────────────────────────── */
body.compact-layout.mobile-view-browse .mobile-mode-tabs {
  display: flex;
}
body.compact-layout.mobile-view-browse .mobile-mode-tabs[hidden] {
  display: flex;  /* override the [hidden] attribute when JS removes it */
}

body.compact-layout.mobile-view-browse .section-head { display: none; }
body.compact-layout.mobile-view-browse .genre-toolbar { display: none; }

/* Default mode: lanes visible, search hidden */
body.compact-layout.mobile-view-browse[data-mobile-mode="lanes"] .searchbox { display: none; }
body.compact-layout.mobile-view-browse[data-mobile-mode="search"] .genre-rail { display: none; }

/* If no data-mobile-mode is set yet (initial render before JS runs),
   default to lanes view */
body.compact-layout.mobile-view-browse:not([data-mobile-mode]) .searchbox { display: none; }

/* Force single-row horizontal scroll for lanes on mobile.
   Desktop default is 2 rows (`repeat(2, auto)`) which would create
   unpredictable panel height when the chip count changes. Single row
   gives lanes a fixed natural height regardless of lane count. */
body.compact-layout.mobile-view-browse .genre-scroll {
  grid-template-rows: auto;
}

/* Lock both mode surfaces to identical 44px height. The previous version
   used min-height: 44, which clamped lanes (natural ~40px) up to 44 but
   let the searchrow render at its natural ~46-49px (driven by chip-btn
   min-height: 32 + searchrow padding: 6 + border: 1). That left a 2-5px
   height delta between the two modes, visible as the station list below
   shifting up/down on mode toggle.
   
   Fix is two-part:
   1. Use explicit `height: 44px` on both surfaces (not just min-height) so
      any natural rendering difference is clamped to the same value.
   2. Tighten the searchrow's internal padding (rules below) so its natural
      content fits in 44px without clipping the chip-btn or input borders.
   
   :not(.filters-open) excludes the searchbox from the lock when the user
   has explicitly expanded filters — that surface needs to grow to show
   the panel below the input row. */
body.compact-layout.mobile-view-browse .searchbox:not(.filters-open),
body.compact-layout.mobile-view-browse .genre-rail {
  height: 44px;
  min-height: 44px;
  box-sizing: border-box;
}

/* Searchrow internal tightening for mobile: padding 6→4 and input padding
   8→6 brings the searchrow's natural height from ~49px down to ~44px so
   it fits the locked surface height without clipping. The chip-btn 
   (🔍, ☰) keeps its default min-height: 32 since it's the dominant 
   height driver — at 32+2 border = 34px effective, plus searchrow padding
   4+4 + border 1+1 = 44px. The input field renders at 31px tall (font 16
   + padding 6+6 + line-height) which is still comfortably tappable since
   the entire 44px row focuses the input on tap. */
body.compact-layout.mobile-view-browse .searchrow {
  padding: 4px 6px;
}
body.compact-layout.mobile-view-browse .search-input-wrap input,
body.compact-layout.mobile-view-browse input[type="search"] {
  padding: 6px 12px;
}

/* Mode switch crossfade. The surfaces still toggle via display:none/grid
   (necessary so the hidden surface doesn't take grid space below), but
   the SHOWING surface gets a 200ms fade-in animation that takes the snap
   off the swap. The 2px translateY adds a soft "settling into place"
   feel without being distracting. The hiding surface still vanishes
   instantly — display can't transition — but visually the new content
   arriving with a fade reads as a smooth handoff. Pairs with the springy
   tab-pill animation above so the whole tab-switch interaction feels
   considered rather than abrupt. */
@keyframes vd-mode-surface-fade-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}
body.compact-layout.mobile-view-browse[data-mobile-mode="search"] .searchbox,
body.compact-layout.mobile-view-browse[data-mobile-mode="lanes"] .genre-rail {
  animation: vd-mode-surface-fade-in 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
  body.compact-layout.mobile-view-browse[data-mobile-mode="search"] .searchbox,
  body.compact-layout.mobile-view-browse[data-mobile-mode="lanes"] .genre-rail {
    animation: none;
  }
}

/* Hide the redundant #listTitle in mobile compact Search mode.
   The search input directly above already shows the query, so the
   title is pure duplication. Hiding it also keeps the count + Dig
   deeper + actions row from wrapping the +/≡ buttons to a second
   line on long search terms. Lanes mode keeps the title — there
   it serves as an anchor for users whose active lane chip may
   have scrolled off-rail. */
body.compact-layout.mobile-view-browse[data-mobile-mode="search"] #listTitle {
  display: none;
}


/* ---- Search box ---- */

.searchbox {
  display: grid;
  gap: 8px;
}

.searchrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 6px;
  align-items: center;
  padding: 6px;
  border-radius: 14px;
  background: rgba(0, 0, 0, .25);
  border: 1px solid var(--line);
}
.searchrow:focus-within { border-color: color-mix(in srgb, var(--c1) 60%, var(--line)); }

.search-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.search-input-wrap input,
input[type="search"] {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 16px;
  padding: 8px 12px;
  width: 100%;
  min-width: 0;
}
.search-input-wrap input::placeholder,
input[type="search"]::placeholder { color: var(--dim); }
.search-input-wrap input:focus,
input[type="search"]:focus { outline: none; }

.input-clear-btn {
  position: absolute;
  right: 4px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 0;
  background: rgba(255, 255, 255, .08);
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.searchrow > button:not(.chip-btn) {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  border: 0;
  background: linear-gradient(135deg, var(--c1), var(--c2));
  color: #0a0a14;
  font: inherit;
  font-size: 14px;
  font-weight: 900;
  cursor: pointer;
}


/* ---- Filters (the dropdowns) ---- */

.filters {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}

@container vd-sidebar (min-width: 360px) {
  .filters { grid-template-columns: 1fr 1fr; }
}

@container vd-app (min-width: 1180px) {
  .filters { grid-template-columns: 1fr 1fr; }
}

select {
  appearance: none;
  -webkit-appearance: none;
  background: rgba(0, 0, 0, .25)
    url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='6' viewBox='0 0 10 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238df7ee' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat right 11px center;
  background-size: 10px 6px;
  border: 1px solid var(--line);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 28px 8px 11px;
  border-radius: 12px;
  cursor: pointer;
  min-height: 36px;
  width: 100%;
  min-width: 0;
}

select:hover { border-color: var(--line-strong); }
select:focus { outline: 2px solid var(--c1); outline-offset: 2px; }

/* Filters panel hidden by default; toggled via state.filtersOpen.
   Filters are OPTIONAL — they should never appear unless the user
   explicitly asks for them via the ☷ toggle. */
.searchbox .filters { display: none; }
.searchbox.filters-open .filters { display: grid; }


/* ---- Genre rail (Audio Lanes) ---- */

.genre-rail {
  display: grid;
  gap: 8px;
  min-height: 0;
}

.genre-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  padding: 0 2px;
}

.genre-toolbar-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

#genreHint, .genre-toolbar em {
  color: var(--c1);
  font-style: normal;
  letter-spacing: 0.08em;
}

/* DEFAULT (all viewports — desktop, tablet, AND mobile): 2 ROWS of chips
   that scroll horizontally. Lanes can never grow into a wall that swallows
   the station list.

   Plan #3b's view consolidation removed a phone-specific override that
   had switched lanes to a vertical 2-column scrolling grid. The
   horizontal default applies on phone too — keeps lanes compact so the
   station list (the primary content) gets maximum vertical space. */
.genre-scroll {
  display: grid;
  grid-template-rows: repeat(2, auto);
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  gap: 5px;
  padding: 2px 2px 8px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.genre {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .04);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  /* Uniformity rules: every chip — default, recents, favorites, saved
     custom lane, genre tag — renders at the SAME height with the SAME
     minimum visual weight, so the rail reads as one consistent strip
     instead of "various round shapes/sizes depending on word length".
     - Fixed height (30px) means icons and no-icon chips align identically.
     - Min-width (88px) prevents short words like "starter" or "lo-fi"
       from rendering as visually weak nubs against longer chips.
     - inline-flex + center alignment keeps icons (★, ↻) and text on a
       common baseline regardless of font metrics quirks.
     - Padding is horizontal-only since height is fixed; box-sizing keeps
       the border accounted for. */
  height: 30px;
  min-width: 88px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, border-color .15s, transform .15s;
}

/* Saved-lane wrap (Labgate, custom dials) — sits in the genre-scroll
   grid alongside direct .genre buttons. Match the chip height exactly
   and let the inner button fill it, so the saved lane reads visually
   identical to default lanes. Without this, the inline-flex wrap
   computed a slightly different vertical alignment than direct chip
   buttons, which gave saved lanes a faintly "off" look against neighbors. */
.saved-lane-wrap {
  height: 30px;
  align-items: stretch;
}
.saved-lane-wrap > .genre {
  height: 100%;
}

.genre:hover {
  background: rgba(255, 255, 255, .08);
  transform: translateY(-1px);
  border-color: var(--line-strong);
}

.genre.active, .genre.is-active {
  background: linear-gradient(135deg, var(--c1), var(--c2));
  color: #0a0a14;
  border-color: transparent;
  font-weight: 800;
  box-shadow: 0 8px 20px color-mix(in srgb, var(--c2) 22%, transparent);
}

/* Group color hints (preserved from production) */
.genre.group-music   { border-color: rgba(255, 255, 255, .14); }
.genre.group-talk    { border-color: rgba(125, 211, 255, .26); }
.genre.group-vibe    { border-color: rgba(141, 247, 238, .26); }
.genre.group-story   { border-color: rgba(249, 223, 106, .28); }
.genre.group-learn   { border-color: rgba(105, 255, 196, .26); }
.genre.group-world   { border-color: rgba(255, 120, 181, .28); }
.genre.group-kids    { border-color: rgba(143, 255, 186, .32); }
.genre.group-mature  { border-color: rgba(255, 100, 127, .32); }

/* Recents lane — populated from state.recent, no API call, no genre tag.
   Subtle clock prefix marks it as a memory-driven lane and visually
   differentiates it from the Starter button (same "starter"-style group-vibe
   border) and from saved Audio Lanes, without changing the rail rhythm. */
.genre.recents-lane::before {
  content: "↻ ";
  opacity: .8;
}
.genre.recents-lane.active::before { opacity: 1; }

/* Favorites lane — populated from state.favoriteStations. Replaces the legacy
   ☆ favorites filter (top-toolbar chip + phone FAB). Same family treatment
   as Recents; gold star prefix to read as "your stuff" at a glance. */
.genre.favorites-lane::before {
  content: "★ ";
  color: var(--c3);
  opacity: .9;
}
.genre.favorites-lane.active::before { opacity: 1; color: rgba(0, 0, 0, .85); }

/* Safety badges (" 18+", " kid") removed in Drop 2. Content filtering is
   now hardcoded to Standard mode (hides mature-tagged content). Per-tile
   visible labels were redundant and visually busy. The .safety-{value}
   class on the element is retained for diagnostics. */

/* Saved/custom lane lockup (preserved from production) */
.saved-lane-wrap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.custom-lane { /* the saved lane chip */ }

.lane-edit-btn, .lane-add-btn {
  min-height: 28px;
  width: 28px;
  padding: 0;
  font-size: 12px;
}


/* ---- Stations list ---- */

/* .stations-wrap is a 3-row grid:
     row 1 (auto)        — .list-meta (title + refine toggle)
     row 2 (auto)        — .discovery-tools (refine panel; auto-collapses to 0 when [hidden])
     row 3 (minmax 0 1fr) — .station-list (fills remaining space, scrolls)
   When the refine panel opens, row 2 expands to its content size and row 3
   automatically shrinks — list keeps scrolling cleanly with no overlap. */
.stations-wrap {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  gap: 10px;
  min-height: 0;
}

.list-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--muted);
  padding: 0 2px;
  flex-wrap: wrap;
}

.list-title-cluster {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

#listTitle { color: var(--text); font-weight: 800; }
#listCount { font-variant-numeric: tabular-nums; }

/* Container for action buttons in the station list header.
 * Holds the +/✓ save-lane button and the refine-toggle (☷) side-by-side.
 * Added in rev 12.36 to host the new save-lane button without shoving
 * the refine toggle around. The wrapper takes margin-left:auto so both
 * buttons sit at the right edge regardless of how many are visible. */
.list-meta-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

.refine-toggle {
  /* Was margin-left:auto; now zero since the parent .list-meta-actions
   * handles the right-alignment. Kept the rule (instead of removing it)
   * so older HTML without .list-meta-actions still right-aligns
   * gracefully. */
  margin-left: auto;
}
.list-meta-actions .refine-toggle {
  margin-left: 0;
}

/* The list-mounted save-lane button uses the same base styling as the
 * genre-rail one (.lane-add-btn), no additional rules needed. The
 * .list-add-lane class is reserved for any list-only differences we
 * decide on later. */

.station-list {
  display: grid;
  /* Hotfix 3: align-content: start so cards never stretch to fill the
     container's remaining space. Without this, when results are filtered
     down to a small set on desktop (where .stations-wrap's 1fr row hands
     the list a fixed, viewport-bound height), the grid's default
     align-content of `normal`/`stretch` distributes the extra height
     across rows and visually grows each card. With `start`, cards keep
     their content/min-height and empty space accumulates at the bottom.
     Mobile is unaffected because the sidebar is content-sized there
     and the list has no excess space to stretch into. */
  align-content: start;
  gap: 3px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 4px 0 0;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  min-height: 0;
}

/* TIGHT SINGLE-ROW CARD DESIGN
   28px logo · name + meta inline · star pinned far right.
   Total height ~38px (was 60+). Fits ~12-14 cards in a phone sidebar. */
.station-card {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  padding: 5px 6px 5px 8px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .03);
  cursor: pointer;
  transition: background .12s, border-color .12s;
  min-width: 0;
  text-align: start;
  font: inherit;
  color: inherit;
}

.station-card:hover {
  background: rgba(255, 255, 255, .07);
  border-color: var(--line-strong);
}

.station-card.active, .station-card.is-active, .station-card[aria-current="true"] {
  background: color-mix(in srgb, var(--c2) 14%, rgba(0, 0, 0, .35));
  border-color: color-mix(in srgb, var(--c2) 40%, var(--line));
}

.station-logo {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--c1) 30%, #1a1a26),
    color-mix(in srgb, var(--c2) 25%, #1a1a26));
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 12px;
  color: rgba(255, 255, 255, .82);
  overflow: hidden;
  flex: 0 0 auto;
}

/* When the production JS injects a station favicon image, it fills the cell
   and takes precedence over the initials fallback. */
.station-logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  display: block;
}
.station-logo:has(img) {
  background: rgba(0, 0, 0, .35);
}

.station-copy {
  min-width: 0;
  display: grid;
  gap: 0;
  line-height: 1.18;
}

.station-name {
  /* 2-line clamp with no hyphenation (rev 12.34). Replaces the
   * previous single-line nowrap+ellipsis behavior. Long station
   * titles like "WHYY 90.9 FM Philadelphia" now wrap cleanly to
   * a second line instead of being forced into "WHYY 90.9 FM Phil-…"
   * with hyphenation and ellipsis. Crazy-long titles get cleanly
   * truncated at the end of line 2. */
  font-weight: 700;
  font-size: 13px;
  letter-spacing: -0.01em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  hyphens: none;
  overflow-wrap: anywhere;
  word-break: normal;
}

.station-detail {
  display: flex;
  gap: 5px;
  flex-wrap: nowrap;
  align-items: center;
  overflow: hidden;
  font-size: 10px;
  color: var(--dim);
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Inline meta — used by JS to compose: "<span class='signal'>99%</span> · SomaFM · ambient" */
.station-detail .signal {
  color: var(--good);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.station-detail .tag {
  color: var(--muted);
}
.station-detail .dot-sep {
  color: var(--dim);
  margin: 0 1px;
}

/* Legacy boxed pills — kept for backward compatibility with existing JS,
   but visually muted to fit the tighter card. */
.tiny-pill {
  display: inline-flex;
  align-items: center;
  padding: 0 6px;
  height: 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tiny-pill.signal-good,
.tiny-pill.signal-excellent {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 36%, var(--line));
}

.tiny-pill.signal-warn {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 36%, var(--line));
}

.score {
  font-size: 10px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

.station-actions {
  display: flex;
  align-items: center;
  gap: 5px;
}

/* Star — pinned far right, tap target is larger than the visual icon */
.star {
  width: 32px;
  height: 32px;
  min-height: 32px;
  border-radius: 50%;
  border: 0;
  background: transparent;
  color: var(--dim);
  font-size: 14px;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: color .12s, background .12s;
  flex: 0 0 auto;
}

.star:hover { color: var(--c3); background: rgba(255, 255, 255, .06); }
.star.is-favorited, .star.active, .star.on { color: var(--c3); }


/* ---- Discovery refine tools (toggleable) ---- */

.discovery-tools {
  display: grid;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
  border-radius: 14px;
}

.discovery-tools[hidden] { display: none; }

.refine-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px;
  align-items: center;
}

.refine-row input {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .3);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  padding: 8px 12px;
  border-radius: 10px;
  width: 100%;
  min-width: 0;
}

.refine-clear {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 13px;
}

.active-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.active-filter-row:empty { display: none; }

.result-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.result-action {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  color: var(--text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 6px 11px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.result-action:hover {
  background: rgba(255, 255, 255, .08);
  border-color: var(--line-strong);
}


/* ---- VibeDock launch card (sidebar bottom) ---- */

.vibedock-launch-card {
  position: relative;
  border-radius: 18px;
}

.vibedock-launch {
  appearance: none;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 11px 13px;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  text-align: start;
  cursor: pointer;
  background:
    radial-gradient(circle at 10% 0%, color-mix(in srgb, var(--c1) 22%, transparent), transparent 60%),
    linear-gradient(180deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .03));
  color: var(--text);
  font: inherit;
  transition: background .15s, border-color .15s;
}
.vibedock-launch:hover {
  background:
    radial-gradient(circle at 10% 0%, color-mix(in srgb, var(--c1) 30%, transparent), transparent 60%),
    linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(255, 255, 255, .05));
  border-color: var(--line-strong);
}

.vibedock-launch-orb {
  width: 38px;
  height: 38px;
  border-radius: 14px;
  background:
    radial-gradient(circle at 30% 25%, #fff 0 9%, transparent 18%),
    conic-gradient(from 220deg, var(--c1), var(--c2), var(--c3), var(--c1));
  display: grid;
  place-items: center;
  color: rgba(0, 0, 0, .8);
  font-weight: 900;
}

.vibedock-launch-copy {
  min-width: 0;
  display: grid;
  gap: 2px;
}
.vibedock-launch-copy strong { font-size: 14px; letter-spacing: -0.02em; }
.vibedock-launch-copy em {
  color: var(--dim);
  font-style: normal;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.vibedock-launch-open { color: var(--c1); font-size: 16px; }

/* On phone the dock is the navigation, and the section-head studio button
   provides Studio access. The launch card crowds the dock and is redundant. */
@container vd-app (max-width: 719.98px) {
  .vibedock-launch-card { display: none; }
}


/* ==========================================================================
   9. MOBILE DOCK (phone-only bottom nav)
   ========================================================================== */

.mobile-dock {
  position: fixed;
  inset-inline: var(--safe-inline);
  bottom: var(--safe-block);
  z-index: 70;
  display: none;
  /* 2 columns since Plan #3b consolidated dock from 3 buttons (Player /
     Discover / Stations) to 2 (Player / Discover). Was repeat(3,...)
     which left a phantom third cell making each button ~33% wide
     instead of ~50%. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 6px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: rgba(7, 8, 18, .82);
  backdrop-filter: blur(22px) saturate(1.2);
  -webkit-backdrop-filter: blur(22px) saturate(1.2);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .42);
}

@container vd-app (max-width: 719.98px) {
  .mobile-dock { display: grid; }
}

/* In compact mode, hide dock entirely (mini player only) */
body.compact .mobile-dock { display: none; }

.mobile-dock-btn {
  appearance: none;
  border: 0;
  background: rgba(255, 255, 255, .04);
  color: var(--muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 9px 6px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  display: grid;
  place-items: center;
  gap: 1px;
  min-height: 44px;
  transition: color .15s, background .15s, transform .15s;
}

.mobile-dock-btn:hover { color: var(--text); }
.mobile-dock-btn:active { transform: scale(.96); }

.mobile-dock-btn.active, .mobile-dock-btn[aria-current="page"] {
  color: rgba(0, 0, 0, .85);
  background: linear-gradient(135deg, var(--c1), var(--c2) 60%, var(--c3));
  font-weight: 900;
  box-shadow: 0 10px 28px color-mix(in srgb, var(--c2) 22%, transparent);
}

.dock-ico { font-size: 15px; line-height: 1; }

/* Reserve bottom padding on the app shell so content never butts the dock.
   Dock is ~56px tall + sits at bottom:6px = top edge at 62px above viewport
   bottom. 64px padding clears it with an 8px buffer above.

   Slice 5.2.3: was `calc(var(--safe-block) + 64px)` which on iOS Safari
   with the bottom URL bar inflated to ~150-175px because env(safe-area-
   inset-bottom) there reports the URL bar + home indicator combined,
   not just the home indicator. Result: huge visible empty band between
   the station list panel and the dock. Flat 64px is correct on all
   mobile contexts because the dock's own positioning (bottom: 6px,
   also flat now) already lives in screen coordinates that don't need
   the safe-area math added on top. */
@container vd-app (max-width: 719.98px) {
  .app {
    padding-bottom: 72px;
  }
}


/* ==========================================================================
   10. COMPACT MODE (mini player)
   ========================================================================== */

body.compact .app {
  grid-template-columns: 1fr;
  max-width: 480px;
  margin-inline: auto;
  padding: 14px;
}

body.compact .panel.sidebar { display: none; }
body.compact .vibedock-launch-card { display: none; }


/* ==========================================================================
   11. THEME MOODS (preserve from production)
   ========================================================================== */

body.theme-neon    { --c1: #50f4f7; --c2: #ff5cb1; --c3: #fffb78; }
body.theme-midnight {
  --bg-deep: #03040c;
  --bg-mid:  #0a0e23;
  --bg-high: #0f1530;
  --c1: #6ad9ff;
}
body.theme-warm    { --c1: #ffc67a; --c2: #ff7a8a; --c3: #f7e08a; }
body.theme-minimal {
  --c1: #e5e9ff;
  --c2: #c8cef2;
  --c3: #a3aacd;
}
body.theme-minimal .halo { opacity: .25; filter: blur(38px); }


/* ==========================================================================
   12. VIBEDOCK MODAL (Studio / Settings)
   ========================================================================== */

.vibedock-modal {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
  padding: max(14px, env(safe-area-inset-top))
           max(14px, env(safe-area-inset-right))
           max(14px, env(safe-area-inset-bottom))
           max(14px, env(safe-area-inset-left));
}

.vibedock-modal[hidden] { display: none; }

.vibedock-scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 30% 10%, color-mix(in srgb, var(--c1) 18%, transparent), transparent 38rem),
    rgba(2, 3, 10, .66);
  backdrop-filter: blur(18px) saturate(1.15);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
}

.vibedock-sheet {
  position: relative;
  width: min(640px, calc(100vw - 28px));
  max-height: min(760px, calc(100dvh - 28px));
  overflow: hidden;
  border-radius: 32px;
  border: 1px solid var(--line-strong);
  background:
    radial-gradient(circle at 12% 0%, color-mix(in srgb, var(--c1) 22%, transparent), transparent 28rem),
    linear-gradient(180deg, rgba(22, 25, 50, .94), rgba(7, 8, 18, .94));
  box-shadow: 0 38px 120px rgba(0, 0, 0, .62);
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
}

@container vd-app (max-width: 719.98px) {
  .vibedock-sheet {
    align-self: end;
    width: 100%;
    max-height: min(82dvh, 760px);
    border-radius: 30px 30px 22px 22px;
  }
  .vibedock-modal { place-items: end center; }
}

.vibedock-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 18px 12px;
}

.vibedock-kicker {
  margin: 0 0 4px;
  color: var(--c1);
  font-size: var(--text-xs);
  font-weight: 1000;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.vibedock-head h2 {
  margin: 0;
  font-size: clamp(1.45rem, 5vw, 2.2rem);
  line-height: 0.92;
  letter-spacing: -0.06em;
  font-family: var(--font-display);
}

.vibedock-close {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .08);
  color: var(--text);
  font-size: 1.35rem;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.vibedock-tabs {
  display: grid;
  /* Equal-width tabs that adapt to however many tab buttons are present.
     Was repeat(5, minmax(0, 1fr)) when the Studio had a 5-tab plan; left
     the strip looking left-clustered after the modal was simplified to 3.
     Auto-flow keeps the row flush regardless of tab count. */
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 6px;
  padding: 0 18px 14px;
}

@container vd-app (max-width: 480px) {
  .vibedock-tabs { gap: 4px; padding: 0 14px 12px; }
}

.vibedock-tab {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  color: var(--muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 0 8px;
  min-height: 38px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color .15s, background .15s, border-color .15s;
}
.vibedock-tab:hover { color: var(--text); border-color: var(--line-strong); }
.vibedock-tab.active, .vibedock-tab[aria-selected="true"] {
  background: linear-gradient(135deg, var(--c1), var(--c2) 60%, var(--c3));
  color: rgba(0, 0, 0, .85);
  border-color: transparent;
  font-weight: 900;
}

.vibedock-body {
  min-height: 0;
  overflow: auto;
  padding: 0 18px 18px;
  -webkit-overflow-scrolling: touch;
}

@container vd-app (max-width: 719.98px) {
  .vibedock-body { padding: 0 14px 14px; }
}

.vibedock-panel { display: none; gap: 12px; }
.vibedock-panel.active { display: grid; }


/* Modal field controls */
.vibe-field {
  display: grid;
  gap: 8px;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .16);
}

.vibe-field label {
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 1000;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vibe-field p {
  margin: 0;
  color: var(--dim);
  font-size: var(--text-xs);
  line-height: 1.4;
}

.vibe-inline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
}

@container vd-app (max-width: 480px) {
  .vibe-inline { grid-template-columns: 1fr; }
}

.vibe-inline input {
  min-height: 44px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .075);
  color: var(--text);
  padding: 0 14px;
  font: inherit;
  font-size: 16px;
}

.vibe-inline button {
  min-height: 44px;
  padding: 0 16px;
  color: rgba(0, 0, 0, .82);
  border: 0;
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, var(--c1), var(--c2));
  font: inherit;
  font-weight: 900;
  cursor: pointer;
}

.vibedock-tip-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 9px;
}

@container vd-app (max-width: 480px) {
  .vibedock-tip-grid { grid-template-columns: 1fr; }
}

.vibedock-tip-grid div,
.vibedock-note-row {
  border-radius: 16px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .18);
}
.vibedock-tip-grid div { padding: 12px; display: grid; gap: 4px; }
.vibedock-tip-grid b { font-size: 13px; }
.vibedock-tip-grid span { color: var(--dim); font-size: var(--text-xs); font-weight: 800; }

.vibe-setting-row {
  appearance: none;
  width: 100%;
  min-height: 64px;
  padding: 12px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  text-align: start;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .18);
  border-radius: 16px;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.vibe-setting-row:hover {
  background: rgba(255, 255, 255, .04);
  border-color: var(--line-strong);
}

.vibe-setting-row span { display: grid; gap: 4px; }
.vibe-setting-row strong { font-size: 14px; }
.vibe-setting-row em {
  color: var(--dim);
  font-size: var(--text-xs);
  font-style: normal;
  font-weight: 700;
}
.vibe-setting-row b {
  color: var(--c1);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.vibe-setting-row.danger-soft b { color: var(--warn); }

.vibedock-memory-list {
  display: grid;
  gap: 6px;
}

.vibedock-memory-list:empty::before {
  content: "No recent stations yet.";
  color: var(--dim);
  font-size: var(--text-sm);
  padding: 14px;
  display: block;
  text-align: center;
  border: 1px dashed var(--line);
  border-radius: 14px;
}

.vibedock-about-card {
  display: grid;
  grid-template-columns: 58px 1fr;
  gap: 13px;
  align-items: center;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .16);
}

.vibedock-about-orb {
  width: 58px;
  height: 58px;
  border-radius: 18px;
  display: grid;
  place-items: center;
  color: rgba(0, 0, 0, .82);
  font-weight: 900;
  background:
    radial-gradient(circle at 30% 20%, #fff 0 9%, transparent 18%),
    conic-gradient(from 220deg, var(--c1), var(--c2), var(--c3), var(--c1));
}

.vibedock-about-card h3 {
  margin: 0 0 5px;
  font-size: 1.25rem;
  letter-spacing: -0.04em;
  font-family: var(--font-display);
}

.vibedock-about-card p {
  margin: 0;
  color: var(--dim);
  font-size: 13px;
  line-height: 1.4;
}

.vibedock-note-row {
  min-height: 44px;
  padding: 0 13px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.vibedock-note-row span {
  color: var(--dim);
  font-size: var(--text-xs);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.11em;
}
.vibedock-note-row b {
  font-size: 13px;
  color: var(--text);
}

.vibedock-thanks-card {
  padding: 16px;
  border-radius: 18px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .16);
}
.vibedock-thanks-card h3 {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 1.4rem;
  letter-spacing: -0.04em;
}
.vibedock-thanks-card p {
  margin: 0;
  color: var(--muted);
  line-height: 1.5;
  font-size: 13px;
}

.venmo-feature {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 14px;
  align-items: center;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid var(--line);
  background:
    radial-gradient(circle at 0 0, color-mix(in srgb, var(--c1) 18%, transparent), transparent 60%),
    rgba(0, 0, 0, .16);
  color: var(--text);
  text-decoration: none;
}
.venmo-feature img {
  width: 96px;
  height: 96px;
  border-radius: 16px;
  background: white;
  padding: 6px;
  opacity: .94;
}
.venmo-feature small {
  color: var(--dim);
  font-weight: 1000;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-size: 10px;
}
.venmo-feature strong {
  display: block;
  font-size: 1.2rem;
  letter-spacing: -0.03em;
  font-family: var(--font-display);
}
.venmo-feature em {
  color: var(--muted);
  font-style: normal;
  font-size: 13px;
  line-height: 1.35;
}

@container vd-app (max-width: 420px) {
  .venmo-feature { grid-template-columns: 72px 1fr; padding: 12px; }
  .venmo-feature img { width: 72px; height: 72px; }
}

body.vibedock-open .app,
body.vibedock-open .mobile-dock {
  filter: saturate(.9);
}


/* ==========================================================================
   13. SHARE SHEET
   ========================================================================== */

.share-sheet {
  position: fixed;
  inset: 0;
  z-index: 130;
  display: grid;
  place-items: end center;
  padding: max(14px, env(safe-area-inset-top))
           max(14px, env(safe-area-inset-right))
           max(14px, env(safe-area-inset-bottom))
           max(14px, env(safe-area-inset-left));
}

.share-sheet[hidden] { display: none; }

.share-scrim {
  position: absolute;
  inset: 0;
  border: 0;
  cursor: pointer;
  background: rgba(2, 3, 10, .66);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.share-card {
  position: relative;
  width: min(560px, 100%);
  max-height: min(760px, calc(100dvh - 28px));
  border-radius: 28px 28px 22px 22px;
  border: 1px solid var(--line-strong);
  background:
    radial-gradient(circle at 12% 0%, color-mix(in srgb, var(--c2) 22%, transparent), transparent 30rem),
    linear-gradient(180deg, rgba(22, 25, 50, .94), rgba(7, 8, 18, .94));
  box-shadow: 0 38px 120px rgba(0, 0, 0, .62);
  padding: 18px;
  display: grid;
  gap: 14px;
  overflow: auto;
}

@container vd-app (min-width: 720px) {
  .share-sheet { place-items: center; }
  .share-card  { border-radius: 28px; }
}

.share-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.share-card-head p {
  margin: 0 0 4px;
  font-size: var(--text-xs);
  font-weight: 1000;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c1);
}

.share-card-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.6rem;
  letter-spacing: -0.045em;
}

.share-card-head em {
  font-style: normal;
  color: var(--muted);
  font-size: 13px;
}

.share-link-preview,
.share-caption-preview {
  padding: 9px 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  font-size: 13px;
  color: var(--muted);
  word-break: break-all;
}
.share-link-preview[hidden],
.share-caption-preview[hidden] { display: none; }

.share-close {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .06);
  color: var(--text);
  font-size: 1.2rem;
  cursor: pointer;
}

.share-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

@container vd-app (min-width: 480px) {
  .share-grid.share-social-grid  { grid-template-columns: repeat(6, 1fr); }
  .share-grid.share-utility-grid { grid-template-columns: repeat(5, 1fr); }
}

.share-grid button {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  color: var(--text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 12px 6px;
  border-radius: 14px;
  cursor: pointer;
  display: grid;
  place-items: center;
  gap: 4px;
  min-height: 64px;
  transition: background .15s, border-color .15s, transform .15s;
}

.share-grid button:hover {
  background: rgba(255, 255, 255, .08);
  border-color: var(--line-strong);
  transform: translateY(-1px);
}
.share-grid button:active { transform: scale(.96); }

.share-grid button span {
  font-size: 18px;
  line-height: 1;
}


/* ==========================================================================
   14. TOAST (notifications)
   ========================================================================== */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-block) + 88px);
  transform: translateX(-50%) translateY(20px);
  z-index: 200;
  padding: 10px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 18, .88);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.01em;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .5);
  opacity: 0;
  pointer-events: none;
  max-width: min(420px, calc(100vw - 28px));
  text-align: center;
  transition: opacity .25s, transform .25s;
}

.toast.show, .toast.is-visible, .toast[data-show="true"] {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

@container vd-app (min-width: 720px) {
  .toast { bottom: calc(var(--safe-block) + 24px); }
}


/* ==========================================================================
   15. METADATA INSPECTOR (dev/diagnostics card — preserved)
   ========================================================================== */

.meta-inspector-card {
  position: fixed;
  inset: 16px;
  z-index: 150;
  padding: 16px;
  border-radius: 22px;
  border: 1px solid var(--line);
  background: rgba(7, 8, 18, .94);
  overflow: auto;
  display: grid;
  gap: 12px;
}

.meta-inspector-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.meta-inspector-grid {
  display: grid;
  gap: 8px;
}

.meta-inspector-row {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(0, 0, 0, .22);
  font-size: var(--text-sm);
}
.meta-inspector-row.full { grid-template-columns: 1fr; }
.meta-inspector-row b { color: var(--muted); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.1em; }
.meta-inspector-row pre {
  margin: 6px 0 0;
  padding: 8px;
  border-radius: 8px;
  background: rgba(0, 0, 0, .35);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  overflow: auto;
  max-height: 180px;
}


/* ==========================================================================
   16. SIGNAL RESCUE TOAST (in-app notification with actions)
   ========================================================================== */

.signal-rescue-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-block) + 100px);
  transform: translateX(-50%);
  z-index: 180;
  display: grid;
  gap: 8px;
  padding: 14px 16px;
  max-width: min(440px, calc(100vw - 28px));
  border-radius: 18px;
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--line));
  background:
    radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--warn) 15%, transparent), transparent 14rem),
    rgba(7, 8, 18, .92);
  backdrop-filter: blur(16px) saturate(1.15);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .5);
  font-size: var(--text-sm);
}

.signal-rescue-toast .rescue-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.signal-rescue-toast .rescue-actions button {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .25);
  color: var(--text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  min-height: 36px;
  cursor: pointer;
}

/* Plan #2b: signal-rescue banner — persistent in-flow surface for the
   Ask decision UI. Lives between #nowMeta and the transport row, so it's
   in the user's visual focus zone. Unlike .signal-rescue-toast (which
   floats and auto-dismisses), the banner stays put until the user
   resolves it via Skip / Auto-skip / Stay, or until rescue ends from
   another path (success, station change, cancel). The visual language
   echoes the toast's warm-warn palette so users recognize it as the
   same system. */
.signal-rescue-banner {
  display: grid;
  gap: 10px;
  margin: 12px 0 4px;
  padding: 14px 16px;
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--line));
  background:
    radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--warn) 18%, transparent), transparent 14rem),
    rgba(15, 18, 32, .82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--warn) 22%, transparent) inset,
    0 12px 28px rgba(0, 0, 0, .35);
  animation: signal-rescue-banner-in 220ms ease-out;
}
.signal-rescue-banner[hidden] { display: none; }

.signal-rescue-banner-message {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  line-height: 1.4;
}

.signal-rescue-banner-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.signal-rescue-banner-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, .28);
  color: var(--text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  min-height: 36px;
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .08s;
}
.signal-rescue-banner-btn:hover {
  background: rgba(0, 0, 0, .42);
  border-color: var(--line-strong);
}
.signal-rescue-banner-btn:active {
  transform: translateY(1px);
}
.signal-rescue-banner-btn-primary {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--warn) 32%, transparent),
    color-mix(in srgb, var(--warn) 20%, transparent));
  border-color: color-mix(in srgb, var(--warn) 55%, var(--line));
  color: var(--text);
}
.signal-rescue-banner-btn-primary:hover {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--warn) 44%, transparent),
    color-mix(in srgb, var(--warn) 28%, transparent));
}
.signal-rescue-banner-btn-quiet {
  background: transparent;
  color: var(--muted);
}
.signal-rescue-banner-btn-quiet:hover {
  color: var(--text);
  background: rgba(255, 255, 255, .04);
}

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


/* ==========================================================================
   17. AUDIO ELEMENT
   ========================================================================== */

audio { display: none; }


/* ==========================================================================
   18. SCROLLBAR STYLING (subtle)
   ========================================================================== */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 999px;
}
*::-webkit-scrollbar-thumb:hover { background: var(--line-strong); }


/* ==========================================================================
   19. ACCESSIBILITY UTILITIES
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

[hidden] { display: none !important; }


/* ==========================================================================
   20. UTILITY: .is-playing class on body (set by JS)
   ========================================================================== */

body.is-playing .pill .live-dot { background: var(--good); box-shadow: 0 0 14px var(--good); }
body:not(.is-playing) .pill .live-dot { background: var(--dim); box-shadow: none; }


/* ==========================================================================
   21. PRODUCTION QUIRKS — preserve ID-targeted behaviors
   These match the JS expectations from the inline script in vibedial_app.html
   without requiring JS changes.
   ========================================================================== */

/* When the lane edit mode is on, show editable indicators on saved lanes */
body.lane-editing .saved-lane-wrap { outline: 1px dashed var(--c3); outline-offset: 2px; }

/* Genre rail spacing in saved-lane configurations */
.lane-add-context { color: var(--c1); }

/* Active-filter row chips */
.active-filter-row > * {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--c1) 30%, var(--line));
  background: color-mix(in srgb, var(--c1) 10%, rgba(0, 0, 0, .25));
  font-size: var(--text-xs);
  font-weight: 700;
}


/* ==========================================================================
   22. FOR JS: prevent layout-thrash class collisions
   The JS toggles body classes. Make sure their CSS is harmless when
   the layout doesn't need them.
   ========================================================================== */

/* body.handheld is set by JS at <=980px viewport. Our layout doesn't need it
   (container queries handle everything), but we keep it harmless. */
body.handheld { /* no-op */ }
body.cramped  { /* no-op */ }
body.big-controls .primary { width: 72px; height: 72px; font-size: 26px; }
body.big-controls .icon-btn,
body.big-controls .nav-btn { width: 50px; height: 50px; }
body.big-controls .tool-btn { width: 42px; height: 42px; min-height: 42px; font-size: 14px; }


/* ==========================================================================
   PWA / PHONE REFINEMENTS
   --------------------------------------------------------------------------
   Single coherent refinement block. Canonical rules for the phone experience,
   the floating cluster (★ + ⚙), the consolidated status pill below the
   turntable, the station card layout, the Studio tabs, and the landscape
   mini player.
   ========================================================================== */


/* ---- Phone landing — hide top chrome ---- */

@container vd-app (max-width: 719.98px) {
  body.landing-mode .topbar { display: none; }
  body.landing-mode .panel.player {
    grid-template-rows: minmax(0, 1fr) auto auto;
  }
  /* Note: previously this block also shrunk the .turntable to
     clamp(120px, 36cqi, 160px) on landing. Removed in this round
     because the size jump when transitioning from landing → playing
     was jarring. The default turntable size now applies to both states. */

  /* The duplicate "TUNE IN. DISCOVER MORE." eyebrow is hidden — the big
     landing wordmark already says that. */
  body.landing-mode .eyebrow { display: none; }
}


/* ---- Phone Radio view — hide topbar AND floating cluster ----
   Radio view IS the music. Status pill below the turntable carries playback
   state. Floating cluster (★ + ⚙) only shows in Discover/Stations where it
   is the primary access path. */

@container vd-app (max-width: 719.98px) {
  body:not(.mobile-view-browse) .panel.player .topbar { display: none; }
  body:not(.mobile-view-browse) .panel.player .fab-cluster { display: none; }
  body:not(.mobile-view-browse) .panel.player {
    grid-template-rows: minmax(0, 1fr) auto;
  }

  body.mobile-view-browse .fab-cluster { display: inline-flex; }

  /* The static topbar Studio button is redundant with the floating cluster on phone. */
  .mini-menu-btn { display: none; }
}


/* ---- Hide the legacy/duplicate playback-state UI ----
   The status pill we add below replaces all of these. Hidden globally rather
   than per-form-factor because the consolidation is universal — same content
   in the same spot on every device. */

/* "● tuned to" / "● featured" / "tune in. discover more." inline tag */
.eyebrow { display: none !important; }

/* "Playing from: Featured" — generated by core/app-readiness.js into #currentSetLine */
#currentSetLine { display: none !important; }

/* "96% signal · MP3 · 128 kbps · Canada" — generated by core/app-readiness.js
   into #playerSignalLine (NOT #stationFacts, which is a separate orphan div).
   Hide both defensively. */
#playerSignalLine,
.station-facts,
#stationFacts { display: none !important; }

/* The Now Playing track text lives in #nowMetaTitle, inside .now-meta (#nowMeta).
   Use !important and ID selector to outrank any leftover external CSS files
   like vibedial-layout5*.css that compete for this element with their own rules.
   The inline app JS sets els.nowMeta.hidden = false when track text is ready
   and = true when not — we honor that via [hidden] selector. */
.now-meta,
#nowMeta {
  display: grid !important;
  grid-template-rows: auto auto !important;
  grid-template-columns: minmax(0, 1fr) !important;
  justify-items: center !important;
  gap: 2px !important;
  padding: 0 !important;
  margin: 6px auto 4px !important;
  max-width: 100% !important;
  text-align: center !important;
  min-height: 0 !important;
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  visibility: visible !important;
  opacity: 1 !important;
}

/* Honor the hidden attribute the inline JS toggles. */
.now-meta[hidden],
#nowMeta[hidden] { display: none !important; }

/* On landing — no station, nothing to play */
body.landing-mode .now-meta,
body.landing-mode #nowMeta { display: none !important; }

.now-meta-kicker,
.now-meta .now-meta-kicker,
#nowMetaKicker {
  font-size: 10px !important;
  font-weight: 800 !important;
  letter-spacing: 0.16em !important;
  text-transform: uppercase !important;
  color: var(--c1) !important;
  opacity: 0.85 !important;
  display: block !important;
  visibility: visible !important;
}

.now-meta strong,
#nowMetaTitle,
#nowMeta strong {
  display: block !important;
  font-family: var(--font-display) !important;
  font-weight: 800 !important;
  font-size: clamp(0.95rem, 2.4cqi, 1.15rem) !important;
  letter-spacing: -0.015em !important;
  color: var(--text) !important;
  line-height: 1.25 !important;
  max-width: 100% !important;
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
  overflow-wrap: anywhere !important;
  visibility: visible !important;
  opacity: 1 !important;
}

/* Hide the source/route line — track info alone is enough. */
.now-meta em,
#nowMetaSource,
#nowMeta em { display: none !important; }


/* ---- Status pill — canonical playback state, below the turntable ----
   Replaces the floating top-center pill, the eyebrow line, and the
   "Playing from" line. Single source of truth for playback state.

   Color-coded via body.is-* classes the controls JS sets from <audio> events.
   Visible on every form factor: phone portrait, phone landscape, tablet,
   desktop, PWA. No container-query gating. */

.status-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 5px 14px;
  height: 28px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(7, 8, 18, .55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 6px auto 4px;
  width: max-content;
  max-width: 100%;
  pointer-events: none;
  transition: color .2s, border-color .2s;
}

.status-pill .dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--muted);
  flex: 0 0 auto;
  transition: background .2s, box-shadow .2s;
}

/* Playback states — only the dot animates; text color shifts subtly. */
body.is-playing .status-pill {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 40%, var(--line));
}
body.is-playing .status-pill .dot {
  background: var(--good);
  box-shadow: 0 0 12px var(--good);
  animation: vd-pulse 1.6s ease-in-out infinite;
}

body.is-buffering .status-pill {
  color: var(--c3);
  border-color: color-mix(in srgb, var(--c3) 40%, var(--line));
}
body.is-buffering .status-pill .dot {
  background: var(--c3);
  box-shadow: 0 0 12px var(--c3);
  animation: vd-pulse 1.0s ease-in-out infinite;
}

body.is-paused .status-pill {
  color: var(--muted);
}

/* Hide the pill on landing — the big logo + CTAs are the focus there. */
body.landing-mode .status-pill { display: none; }


/* ---- Centered station title on radio view ---- */

.panel.player .now {
  text-align: center;
  align-items: center;
  justify-items: center;
}
.panel.player .station-title,
.panel.player .station-sub,
.panel.player .eyebrow,
.panel.player .landing-actions { text-align: center; }
.panel.player .landing-actions { justify-content: center; }


/* ---- Clickable hashtag links in station-sub ---- */

.tag-link {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--c1);
  font: inherit;
  font-size: inherit;
  font-weight: 700;
  padding: 0 2px;
  margin: 0 1px;
  cursor: pointer;
  border-radius: 4px;
  transition: background .12s, color .12s;
  text-decoration: none;
}
.tag-link:hover {
  background: color-mix(in srgb, var(--c1) 14%, transparent);
  color: var(--text);
}
.tag-link:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 2px;
}


/* ---- Floating cluster (★ + ⚙) — phone only, Discover/Stations only ----
   PWA / iOS safe-area: keep buttons clear of the iOS status bar in standalone
   mode. env(safe-area-inset-top) reports the status bar height. */

.fab-cluster {
  position: absolute;
  top: max(14px, calc(env(safe-area-inset-top, 0px) + 10px));
  right: max(14px, calc(env(safe-area-inset-right, 0px) + 4px));
  z-index: 50;
  display: none;
  gap: 8px;
}
.fab-cluster .fab {
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 18, .82);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  color: var(--text);
  font-size: 16px;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .35);
  transition: transform .12s, border-color .12s, background .12s;
}
.fab-cluster .fab:hover {
  background: rgba(7, 8, 18, .94);
  border-color: var(--c1);
  transform: translateY(-1px);
}
.fab-cluster .fab:active { transform: scale(.95); }
.fab-cluster .fab.is-active,
.fab-cluster .fab[aria-pressed="true"] {
  color: var(--c3);
  border-color: color-mix(in srgb, var(--c3) 50%, var(--line));
}
.fab-cluster .fab-studio {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--c1) 22%, rgba(7, 8, 18, .82)),
    color-mix(in srgb, var(--c2) 18%, rgba(7, 8, 18, .82)));
  color: var(--c1);
}
.fab-cluster .fab-studio:hover { color: var(--text); }

@container vd-app (max-width: 719.98px) {
  .fab-cluster { display: inline-flex; }
}


/* ---- Mobile dock + app shell safe-area ---- */

.mobile-dock {
  /* Slice 5.2.2: was `max(14px, calc(env(safe-area-inset-bottom, 0px) + 4px))`
   * which on iOS Safari with bottom URL bar yields ~80-110px (env-bottom
   * there includes the URL bar height). That produced a huge gap below
   * the dock. Flat 6px keeps the dock anchored near the viewport bottom
   * regardless of context. The container-query override at the mobile
   * breakpoint (further down) also lands on 6px — both rules now agree
   * so cascade order doesn't matter. */
  bottom: 6px;
}
@container vd-app (max-width: 719.98px) {
  .app {
    padding-top: max(14px, env(safe-area-inset-top, 0px));
  }
}


/* ---- Filter dropdown — pushes content down (NOT an overlay) ----
   Filters are explicitly opt-in. They show only when state.filtersOpen
   is true (the user tapped the ☷ button). No focus-within auto-open,
   no desktop-always-visible override — the user controls when filters
   are visible everywhere. */

.searchbox { position: relative; }

.searchbox.filters-open .filters {
  position: static;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 12px;
  margin-top: 8px;
  border-radius: 14px;
  border: 1px solid var(--line-strong);
  background: rgba(7, 8, 18, .96);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .35);
  isolation: isolate;
  z-index: 2;
}

.filter-hint {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
  text-align: center;
  opacity: 0.85;
}

/* Filter-toggle indicator dot removed in Drop 2 hotfix — the Reset filters
   link inside the panel is enough cue that filters are active, and the dot
   read as visual noise once filter persistence meant it routinely showed up.
   The `.has-filters` class is still applied by JS for any other styling
   that wants to key off it; the visual indicator just no longer renders. */
.filter-toggle { position: relative; }

/* Drop 2, slice 5.7: small text link inside the filter panel for clearing
   persisted filter state. Only shown when sort/language are non-default. */
.filter-reset {
  appearance: none;
  grid-column: 1 / -1;
  justify-self: end;
  border: 0;
  background: transparent;
  color: var(--dim);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 2px 0 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .15s ease;
}
.filter-reset:hover { color: var(--c1); }
.filter-reset:focus-visible { outline: 2px solid var(--c1); outline-offset: 2px; border-radius: 4px; }
.filter-reset[hidden] { display: none; }

@container vd-app (min-width: 720px) {
  .searchbox.filters-open .filters {
    box-shadow: none;
    background: rgba(0, 0, 0, .25);
    border-color: var(--line);
  }
}


/* ==========================================================================
   STATION CARD — VibeDial-style, top-aligned, lively
   --------------------------------------------------------------------------
   Layout:
     [logo]  STATION NAME              [★]
             tag · tag · tag           99%

   - Top-aligned content with adequate top padding (12px)
   - Active card uses the VibeDial gradient as a left accent border
   - Names use the display font subtly for personality
   - Tags get a distinct color and italic accent
   - Signal % uses tabular numerics with a glow when excellent
   ========================================================================== */

.station-card {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: start;
  /* Row gap bumped from 8px to 14px in rev 12.37 — 8px wasn't enough
   * visual separation between 2-line titles and the detail row. With
   * 14px, the second line of a long station title has clear breathing
   * room before the country/codec/bitrate line below. */
  gap: 14px 12px;
  padding: 12px 12px 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg,
    rgba(255, 255, 255, .04) 0%,
    rgba(255, 255, 255, .02) 100%);
  cursor: pointer;
  text-align: start;
  font: inherit;
  color: inherit;
  min-width: 0;
  min-height: 60px;
  transition: background .15s, border-color .15s, transform .15s;
  overflow: hidden;
  position: relative;
}

.station-card:hover {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, .08) 0%,
    rgba(255, 255, 255, .04) 100%);
  border-color: var(--line-strong);
  transform: translateX(1px);
}

/* Active station — left-edge gradient bar in the VibeDial palette */
.station-card.active,
.station-card.is-active,
.station-card[aria-current="true"] {
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--c1) 22%, transparent) 0%,
      color-mix(in srgb, var(--c2) 14%, transparent) 35%,
      rgba(0, 0, 0, .25) 100%);
  border-color: color-mix(in srgb, var(--c2) 50%, var(--line));
}
.station-card.active::before,
.station-card.is-active::before,
.station-card[aria-current="true"]::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--c1), var(--c2), var(--c3));
}

/* Logo cell — spans both rows on the left */
.station-card .station-logo {
  grid-row: 1 / 3;
  grid-column: 1;
  width: 38px; height: 38px;
  border-radius: 10px;
  /* Logo vertically centered across both rows (rev 12.40). User spec:
   * "On the far left: a centered logo". Logo span is rows 1-2, so
   * align-self: center vertically centers the 38x38 logo in the
   * total content height (title + gap + detail). */
  align-self: center;
  margin-top: 0;       /* tiny visual nudge so it aligns with text baseline */
  font-size: 14px;
  flex: 0 0 auto;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--c1) 28%, #1a1a26),
    color-mix(in srgb, var(--c2) 24%, #1a1a26));
  font-family: var(--font-display);
  font-weight: 900;
  color: rgba(255, 255, 255, .82);
  display: grid;
  place-items: center;
  overflow: hidden;
}

.station-card .station-logo img {
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: inherit;
  display: block;
}
.station-card .station-logo:has(img) {
  background: rgba(0, 0, 0, .35);
}

/* Production renderList wraps name+detail in .station-copy.
   display: contents promotes the children into the parent grid as cells. */
.station-card .station-copy {
  display: contents;
}

/* Row 1, col 2: name */
.station-card .station-name {
  /* 2-line clamp + no hyphenation (rev 12.34). Same pattern as the
   * base .station-name rule — long titles wrap to 2 lines with
   * ellipsis on overflow. */
  grid-row: 1;
  grid-column: 2;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: -0.01em;
  line-height: 1.2;
  align-self: start;
  min-width: 0;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  hyphens: none;
  overflow-wrap: anywhere;
  word-break: normal;
}

/* Row 2, col 2: tags / detail line */
.station-card .station-detail {
  grid-row: 2;
  grid-column: 2;
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  align-self: start;
  display: block;
  line-height: 1.3;
  min-width: 0;
  letter-spacing: 0.01em;
}

/* When the production code emits .tag-pill or .tiny-pill chips inside the
   detail line, give them a clean inline look — no boxed badges, just colored
   text with separators. Lively without being noisy.
   Rev 12.40: explicit `display: inline` so pills participate in the parent
   detail row's text flow. With the parent `white-space: nowrap; overflow:
   hidden; text-overflow: ellipsis`, inline content ellipsizes cleanly when
   it doesn't fit. inline-flex (from the base .tiny-pill rule) would create
   atomic units that don't ellipsize. */
.station-card .station-detail .tiny-pill,
.station-card .station-detail .tag-pill {
  display: inline;
  background: transparent;
  border: 0;
  padding: 0;
  height: auto;
  font-size: inherit;
  font-weight: 600;
  color: var(--muted);
  margin-right: 4px;
}
.station-card .station-detail .tiny-pill::after,
.station-card .station-detail .tag-pill::after {
  content: "·";
  margin-left: 4px;
  color: var(--dim);
  opacity: 0.6;
}
.station-card .station-detail .tiny-pill:last-child::after,
.station-card .station-detail .tag-pill:last-child::after {
  content: "";
}
.station-card .station-detail .tiny-pill.signal-good,
.station-card .station-detail .tiny-pill.signal-excellent {
  color: var(--good);
  font-weight: 700;
}

/* Production wraps star + score in .station-actions; promote children into grid */
.station-card .station-actions {
  display: contents;
}

/* Row 1, col 3: star */
.station-card .star {
  grid-row: 1;
  grid-column: 3;
  width: 28px; height: 28px;
  align-self: start;
  margin-top: -2px;     /* tiny pull-up so it sits flush with the name baseline */
  font-size: 14px;
  flex: 0 0 auto;
}

/* Row 2, col 3: signal % */
.station-card .score,
.station-card .station-card-signal {
  grid-row: 2;
  grid-column: 3;
  font-size: 10.5px;
  font-weight: 800;
  color: var(--good);
  font-variant-numeric: tabular-nums;
  text-align: end;
  white-space: nowrap;
  align-self: start;
  line-height: 1.3;
  flex: 0 0 auto;
  letter-spacing: 0.02em;
}
.station-card .station-card-signal.low { color: var(--warn); }

.station-list { gap: 6px; }

/* Strip any "tap to play" hint ever produced by older render code */
.station-card .tap-hint,
.station-card .tap-to-play,
.station-card [data-hint="tap"] {
  display: none !important;
}

/* Star colors — production JS toggles `on` class on toggleFavorite */
.star.is-favorited,
.star.active,
.star.on { color: var(--c3); }


/* ---- Turntable arm — needle stays on vinyl during transient buffering ----
 *
 * The .arm rotation rules used to be defined in two places — once near the
 * disc rules (~line 689) and once here. The version here added a
 * `.arm:not(.is-playing) { transform: rotate(0deg); }` reset that fired
 * whenever the .arm element itself didn't have .is-playing class — which
 * is ALWAYS, since no code ever sets that class on the arm directly.
 *
 * That stray reset was higher in the cascade and had the same specificity
 * as the body.is-playing .arm rule, so it won. Result: the arm popped back
 * to rest position even while audio was clearly playing and the disc was
 * spinning. Bug fixed in rev 12.31.
 *
 * Bonus: also rotate the arm to playing position during is-buffering
 * states. A 2-second network blip used to slam the arm back to rest, then
 * back to playing — visually jarring. Now the arm stays on the disc as
 * long as the app is in playing OR buffering state. Status pill still
 * shows "Reconnecting" so the user knows what's happening, but the
 * turntable looks coherent. */

body.is-playing .arm,
body.is-buffering .arm,
.is-playing .arm,
.is-buffering .arm { transform: rotate(-44deg); }


/* ==========================================================================
   STUDIO MODAL — uniform tab sizing
   --------------------------------------------------------------------------
   Tabs were resizing on click because:
     1. Each label has different length (Settings vs Tune vs Saved)
     2. Active state was bumping font-weight, shifting layout
     3. No flex/grid distribution on the tab strip

   Fix: equal-width grid columns that adapt to however many tabs exist,
   identical box dimensions for active and inactive (state distinguished
   by border-bottom color and text color, not by font-weight or padding).
   ========================================================================== */

.vibedock-tabs {
  display: grid;
  /* Auto-flow + auto-columns means the row stays flush regardless of how
     many .vibedock-tab buttons we ship. Originally hard-coded to 5 cols
     (legacy 5-tab plan); the modal is currently 3 tabs (Now / Tune /
     Settings). This avoids the empty 2/5 gap on the right and won't need
     touching if we add or remove tabs. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 0;
  padding: 0 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 12px;
}

.vibedock-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 700;             /* same weight inactive AND active — no shift */
  letter-spacing: 0.04em;
  padding: 10px 4px 10px;        /* same padding either state */
  margin: 0;
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  min-width: 0;
  border-radius: 0;
  transition: color .15s, border-color .15s;
}

.vibedock-tab:hover {
  color: var(--text);
}

.vibedock-tab.active,
.vibedock-tab[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--c1);
  /* NO font-weight bump, NO padding change, NO transform — box stays identical */
}

.vibedock-tab:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: -4px;
  border-radius: 4px;
}


/* ---- Studio modal — Venmo card in the Thanks panel ---- */

.vd-venmo-card {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 14px;
  align-items: center;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid color-mix(in srgb, var(--c1) 40%, var(--line));
  background:
    radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--c1) 14%, transparent), transparent 60%),
    rgba(0, 0, 0, .25);
  text-decoration: none;
  color: var(--text);
  margin-top: 10px;
  transition: border-color .15s, transform .15s;
}
.vd-venmo-card:hover {
  border-color: var(--c1);
  transform: translateY(-1px);
  text-decoration: none;
}
.vd-venmo-card .vd-venmo-qr {
  width: 100px; height: 100px;
  border-radius: 14px;
  background: white;
  padding: 6px;
  display: grid;
  place-items: center;
}
.vd-venmo-card .vd-venmo-qr img,
.vd-venmo-card .vd-venmo-qr svg {
  width: 100%; height: 100%;
  display: block;
}
.vd-venmo-card .vd-venmo-copy small {
  display: block;
  color: var(--c1);
  font-size: 10px;
  font-weight: 1000;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.vd-venmo-card .vd-venmo-copy strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: -0.025em;
  margin-bottom: 4px;
}
.vd-venmo-card .vd-venmo-copy em {
  display: block;
  color: var(--muted);
  font-style: normal;
  font-size: 12px;
  line-height: 1.4;
}

@container vd-app (max-width: 460px) {
  .vd-venmo-card { grid-template-columns: 80px 1fr; padding: 12px; gap: 12px; }
  .vd-venmo-card .vd-venmo-qr { width: 80px; height: 80px; }
}


/* ---- Mature content toggle — visual state ---- */

#matureContentBtn[aria-pressed="true"] .setting-state { color: var(--c3); }
#matureContentBtn[aria-pressed="false"] .setting-state { color: var(--dim); }

body:not(.mature-allowed) .genre.safety-mature,
body:not(.mature-allowed) .genre.group-mature {
  display: none;
}


/* ---- Hide volume control when device doesn't support programmatic volume ---- */

body.volume-locked .volume {
  display: none;
}


/* ---- Tablet landscape low-height — height-aware shrinks ----
   Converted from a dead @container query in rev 12.12: the vd-app container
   only tracks inline-size (see line 111), so any (max-height) condition
   inside @container vd-app never matched. Using a viewport @media query
   instead, which works against actual viewport dimensions. */

@media (max-height: 600px) and (min-width: 720px) and (orientation: landscape) {
  .app { padding: 12px; gap: 12px; }
  .panel.player { padding: 14px; gap: 10px; }
  .panel.sidebar { padding: 12px; gap: 10px; }
  .turntable {
    max-width: clamp(180px, 28cqi, 240px);
    max-height: 78%;
  }
  .station-title { font-size: clamp(1.3rem, 3.4cqi, 2rem); }
}


/* ============================================================================
   PHONE LANDSCAPE / DESKTOP-SHORT MINI PLAYER
   ----------------------------------------------------------------------------
   Any landscape viewport that's short — phone rotated sideways, AND a desktop
   browser window made short (rev 12.12 broadened from phones-only).

   Was an @container vd-app rule before rev 12.12, but vd-app only tracks
   inline-size (line 111) so the (orientation) and (max-height) parts never
   matched and these rules were silently dead. Converted to a viewport
   @media query, which works against actual viewport dimensions and fires
   for any short landscape window regardless of width.

   Layout: collapse to single-column with the station info, controls, and
   transport bar getting full width. Status pill stays visible (just below
   the title in the center column). Sidebar hidden but reachable via the
   dock at the bottom (which JS pairs by setting body.compact-layout for
   the same h≤500 condition).
   ========================================================================== */

@media (orientation: landscape) and (max-height: 500px) {
  /* Phone in landscape OR desktop window made short. body.compact-layout from
     JS already gives us single-column-with-dock. These rules tune the PLAYER
     for the short height: HIDE the turntable (it gets squashed into an
     oval and blocks the info anyway), give all the space to the info
     column so the user sees station, tags, status pill AND now-playing
     track text without any cutoff.
     The transport (controls + volume) sits in a tight single row below. */
  .panel.sidebar { display: none; }

  .app {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr);
    /* Reserve bottom space for the fixed-position .mobile-dock so it doesn't
       overlap the transport controls in this layout (rev 12.13). The
       previous rule that did this was gated to `(max-width: 719.98px)` only,
       so wide-but-short windows had the dock sitting on top of the play
       button. Top/sides keep the original 8px padding; bottom is dock-cleared. */
    padding: 8px 8px calc(var(--safe-block) + 80px);
  }

  .panel.player {
    container-type: size;
    container-name: vd-player;
    grid-template-rows: minmax(0, 1fr) auto;
    padding: 8px 14px;
    gap: 6px;
  }

  .panel.player .topbar { display: none; }

  /* Single column — no turntable. All horizontal space goes to info. */
  .player-main {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    gap: 0;
    padding-top: 0;
    align-items: center;
    text-align: start;
    justify-items: stretch;
  }

  /* Hide the turntable — it gets squashed in this aspect ratio and was
     blocking the info column. Users in landscape want the info, not the
     decoration. (Turntable still shows in portrait — unchanged.) */
  .turntable { display: none !important; }

  .now {
    text-align: start;
    align-content: center;
    justify-items: stretch;
    gap: 4px;
    padding: 0;
    max-width: 100%;
  }

  .panel.player .now,
  .panel.player .station-title,
  .panel.player .station-sub {
    text-align: start;
  }

  .status-pill {
    margin-left: 0;
    margin-right: auto;
  }

  .station-title {
    font-size: clamp(1.3rem, 4.5cqi, 1.8rem);
    line-height: 1.1;
    margin: 0;
  }

  .station-sub {
    -webkit-line-clamp: 1;
    font-size: 12px;
    margin: 0;
  }

  /* Show the now-meta block prominently — this was getting blocked
     before. Tight kicker, single-line track text with ellipsis. */
  #nowMeta:not([hidden]),
  .now-meta:not([hidden]) {
    text-align: start !important;
    margin: 0 !important;
    align-self: start;
    justify-items: start !important;
  }
  #nowMetaTitle,
  .now-meta strong {
    font-size: clamp(0.92rem, 2.4cqi, 1.05rem) !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    text-align: start !important;
  }

  body.landing-mode .landing-actions { justify-content: flex-start; }

  /* Controls + volume on one tight row */
  .transport {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: auto;
    gap: 10px;
    align-items: center;
    justify-items: stretch;
    padding: 0;
  }
  .main-controls { grid-column: 1; gap: 6px; }
  .quick-actions { display: none; }
  .volume {
    grid-column: 2;
    grid-row: 1;
    max-width: 100%;
    margin-inline: 0;
  }
  .btn-play, .primary { width: 44px; height: 44px; font-size: 15px; }
  .btn-nav, .icon-btn, .nav-btn { width: 34px; height: 34px; font-size: 11px; }

  .mobile-dock {
    /* Slice 5.2.2: dock sits flush near the screen bottom edge. Removed
     * safe-area-inset-bottom math entirely — previous iterations (full
     * inset, then halved) left visible empty space below the buttons.
     * 6px flat clearance keeps the rounded pill corners from touching
     * the screen edge but removes the gap users were noticing. */
    bottom: 6px;
  }

  body.mobile-view-browse .panel.player { display: none; }
  body.mobile-view-browse .panel.sidebar { display: grid; }
}


/* ==========================================================================
   13. REV 11 ADDITIONS — station card polish, favorites star, track-sources
       sheet, permanent tap-hint kill, etc.
   ========================================================================== */

/* ---- Permanent kill for "tap to play" hints from deployed phaseXX modules
   The actual injected class is .vd-card-action-label (from
   /assets/vibedial/js/ui/product-refinement.js — appends a span with
   "Tap to play" text into .station-actions). Earlier rev only caught
   .tap-hint and .tap-to-play which weren't the right names. This rule
   adds the real one. JS MutationObserver also strips at insertion. ---- */
.station-card .tap-hint,
.station-card .tap-to-play,
.station-card .vd-card-action-label,
.station-card [data-hint="tap"],
.station-card .station-hint,
.station-card .play-hint,
.station-card .tap-cue,
.station-actions .vd-card-action-label,
.vd-card-action-label {
  display: none !important;
  visibility: hidden !important;
  height: 0 !important;
  width: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  pointer-events: none !important;
}

/* ---- STAR FAVORITE — dramatic clear states using ::before
   Problem with the previous rev: deployed render outputs ★ as text content
   for both states, only differing by class. Even with a color flip, the
   visual difference (gold ★ vs grey ★) was too subtle for the user to
   spot at a glance.
   Fix: hide the actual text content, render ☆/★ ourselves via ::before
   pseudo-element. Now the CHARACTER changes too, not just the color.
   Combined with stronger color/glow on .on, the favorited state is
   unmistakable.
   Added !important on the visual properties so any leftover deployed CSS
   (vibedial-layout5*.css with their own .star rules) can't override. ---- */
.station-card .star,
.station-actions .star,
.now-playing-actions .star,
button.star {
  appearance: none;
  background: transparent !important;
  border: 0 !important;
  padding: 4px 6px !important;
  margin: 0 !important;
  font-size: 0 !important;            /* hide the text content (the ★ char) */
  color: transparent !important;
  line-height: 1 !important;
  cursor: pointer;
  border-radius: 8px !important;
  position: relative;
  display: inline-grid !important;
  place-items: center !important;
  width: auto !important;             /* override layout53.css width: 44px !important */
  height: auto !important;
  min-width: 32px !important;
  min-height: 32px !important;
  transition:
    transform 220ms cubic-bezier(.34, 1.56, .64, 1),
    background 120ms ease;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}
.station-card .star::before,
.station-actions .star::before,
button.star::before {
  content: "☆";                       /* outline star — NOT favorited */
  font-size: 22px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.42);
  font-weight: 400;
  text-shadow: none;
  transition: color 160ms ease, text-shadow 160ms ease, transform 160ms ease;
}
.station-card .star.on::before,
.station-card .star.is-favorited::before,
.station-card .star.active::before,
.station-card .star.vd-fav-active::before,
.station-actions .star.on::before,
.station-actions .star.is-favorited::before,
.station-actions .star.active::before,
.station-actions .star.vd-fav-active::before,
button.star.on::before,
button.star.is-favorited::before,
button.star.active::before,
button.star.vd-fav-active::before {
  content: "★";                       /* SOLID star — FAVORITED */
  color: var(--c3, #f8c861);
  text-shadow:
    0 0 4px color-mix(in srgb, var(--c3, #f8c861) 90%, transparent),
    0 0 14px color-mix(in srgb, var(--c3, #f8c861) 60%, transparent);
}

.station-card .star:hover::before,
.station-actions .star:hover::before,
button.star:hover::before {
  color: rgba(255, 215, 100, 0.9);
}
.station-card .star.on:hover::before,
.station-card .star.is-favorited:hover::before,
.station-card .star.active:hover::before,
.station-card .star.vd-fav-active:hover::before,
button.star.on:hover::before,
button.star.is-favorited:hover::before,
button.star.active:hover::before,
button.star.vd-fav-active:hover::before {
  color: var(--c3, #f8c861);
  transform: scale(1.08);
}

@keyframes vd-star-pop {
  0%   { transform: scale(1.0); }
  40%  { transform: scale(1.4); }
  100% { transform: scale(1.0); }
}
.station-card .star.vd-just-toggled::before,
.station-actions .star.vd-just-toggled::before,
button.star.vd-just-toggled::before {
  animation: vd-star-pop 360ms cubic-bezier(.34, 1.56, .64, 1);
}

/* Hide any heart icon that deployed phaseXX modules might inject — we use
   stars only. Belt+braces; vibedial-controls.js also strips them. */
.station-card .heart,
.station-card .heart-btn,
.station-card .fav-heart,
.station-actions .heart {
  display: none !important;
}

/* ---- "Add as Lane" button hidden when viewing All Favorites.
   JS adds body.viewing-favorites when favOnly mode is active. */
body.viewing-favorites #addLaneBtn { display: none !important; }

/* ---- TRACK SOURCES SHEET — opened by tapping Now Playing. Mirrors the
   share-sheet styles for visual consistency. The track sources have
   the same look as social/share buttons. ---- */
.track-sources-sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  pointer-events: none;
}
.track-sources-sheet:not([hidden]) { pointer-events: auto; }
.track-sources-sheet .share-card {
  position: relative;
  width: min(560px, calc(100% - 16px));
  margin: 8px;
  z-index: 1;
  animation: vd-sheet-rise 220ms cubic-bezier(.2, .8, .2, 1);
}
@keyframes vd-sheet-rise {
  from { transform: translateY(20px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
.track-sources-sheet[hidden] { display: none; }

.track-sources-sheet .share-kicker {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.18em;
  margin: 0 0 4px;
  color: var(--c1, #ec4899);
  text-transform: uppercase;
  opacity: 0.85;
}
.track-sources-sheet .track-sources-station {
  font-size: 12px;
  margin: 4px 0 0;
  color: var(--muted, #9aa0aa);
}
.track-sources-grid,
.track-sources-utility {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  padding: 12px;
}
.track-sources-utility { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@container vd-app (max-width: 480px) {
  .track-sources-grid    { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .track-sources-utility { grid-template-columns: 1fr 1fr; }
}
.track-sources-grid a,
.track-sources-grid button,
.track-sources-utility a,
.track-sources-utility button {
  display: grid;
  grid-template-columns: 28px 1fr;
  align-items: center;
  gap: 8px;
  padding: 12px 10px;
  border-radius: 12px;
  border: 1px solid var(--line, rgba(255,255,255,.08));
  background: rgba(255, 255, 255, 0.04);
  color: inherit;
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.track-sources-grid a:hover,
.track-sources-grid button:hover,
.track-sources-utility a:hover,
.track-sources-utility button:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--line-strong, rgba(255,255,255,.15));
  transform: translateY(-1px);
}
.track-sources-grid a span,
.track-sources-grid button span {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  font-size: 14px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--c1, #ec4899) 25%, #1a1a26),
    color-mix(in srgb, var(--c2, #6366f1) 20%, #1a1a26));
  color: #fff;
}

/* Make the now-meta and station-title areas obviously tappable. */
#nowMeta:not([hidden]),
.panel.player .station-title,
.panel.player .station-sub {
  cursor: pointer;
  transition: opacity 120ms ease;
}
#nowMeta:not([hidden]):hover,
.panel.player .station-title:hover,
.panel.player .station-sub:hover {
  opacity: 0.85;
}

/* ---- DESKTOP STATION CARD: two-line minimal
   Goal: very dense, scannable list. Logo + name top, single tight subtitle
   below, star on right. Reduce noise: hide secondary pills/actions on
   desktop in favor of the cleaner two-line layout. The score percentage
   hides entirely; star is the only persistent action. ---- */
@container vd-app (min-width: 720px) {
  .station-card {
    grid-template-columns: 42px minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    /* Row gap bumped to 14px in rev 12.37 — was 8px, then 2px before
     * 12.35. Multi-line titles need this much vertical space so the
     * second line doesn't visually crash into the detail row below. */
    gap: 14px 14px;
    /* Padding, min-height, and border-radius bumped to match phone's
     * spacious feel (rev 12.41). User feedback: desktop cards were
     * too tight — fitting 8 stations vertically while mobile fits 7.
     * Mobile values: padding 14px, min-height 80px, border-radius 16px.
     * Desktop now: padding 14px, min-height 72px, border-radius 14px.
     * Slightly tighter than mobile because the desktop sidebar column
     * is narrower than the mobile full-width view, but close enough
     * that the cards feel uniform across breakpoints. */
    padding: 14px;
    min-height: 72px;
    border-radius: 14px;
  }
  .station-list {
    /* Bump list gap on desktop so cards have a touch more breathing
     * room between them, matching the spacious mobile feel (rev 12.41). */
    gap: 8px;
  }
  .station-card .station-logo {
    grid-row: 1 / 3;
    width: 42px; height: 42px;
    /* Vertically centered (rev 12.40). User spec: centered logo on
     * far left. Same as canonical — align-self: center within the
     * 2-row span (title + gap + detail).
     * Logo bumped from 36px to 42px in rev 12.41 to match the more
     * spacious card sizing. Still smaller than mobile's 56px because
     * the desktop column is narrower, but more substantial than
     * before — looks balanced with the 14px card padding. */
    margin-top: 0;
    align-self: center;
  }
  .station-card .station-copy {
    grid-row: 1 / 3;
    grid-column: 2;
    align-self: center;
    min-width: 0;
  }
  .station-card .station-name {
    /* Desktop: same 2-line behavior as canonical (rev 12.34). */
    font-size: 14px;
    font-weight: 700;
    line-height: 1.2;
    margin: 0 0 2px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    hyphens: none;
    overflow-wrap: anywhere;
    word-break: normal;
  }
  /* On desktop, show only ONE compressed subtitle line: country + first tag */
  .station-card .station-detail {
    font-size: 11px;
    line-height: 1.2;
    color: var(--muted, #9aa0aa);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;          /* override flex-wrap if any */
    margin: 0;
  }
  /* Hide all detail pills on desktop except keep the country & first tag.
     The detail row falls back to a plain text combine via JS handling. */
  .station-card .station-detail .tiny-pill,
  .station-card .station-detail .tag-pill {
    display: inline;
    padding: 0;
    margin: 6px 0px 0px 0px;
    background: transparent !important;
    border: 0 !important;
    color: inherit !important;
    font-size: inherit;
    font-weight: 500;
  }
  .station-card .station-detail .tiny-pill::before,
  .station-card .station-detail .tag-pill::before { content: ""; }
  .station-card .station-detail .tiny-pill::after,
  .station-card .station-detail .tag-pill:not(:last-child)::after {
    content: " · ";
    color: rgba(255, 255, 255, 0.25);
  }
  /* Hide all but the first 2 detail items on desktop for tight list */
  .station-card .station-detail .tiny-pill:nth-of-type(n+3),
  .station-card .station-detail .tag-pill:nth-of-type(n+3) {
    display: none;
  }
  /* Quality score hidden on desktop — the star is the only persistent action */
  .station-card .station-actions .score { display: none; }
  .station-card .station-actions {
    grid-row: 1 / 3;
    grid-column: 3;
    align-self: center;
    display: flex;
    align-items: center;
    gap: 4px;
  }
}

/* ---- PHONE STATION CARD: card-style with prominent logo
   Goal: bigger, more inviting. Logo prominent, name big, tagline,
   star top-right. Tap-friendly, generous padding. ---- */
@container vd-app (max-width: 719.98px) {
  .station-card {
    grid-template-columns: 56px minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    /* Row gap bumped to 14px in rev 12.37 — 8px wasn't enough visual
     * separation between 2-line titles and the detail/tags row. */
    gap: 14px 14px;
    padding: 14px;
    min-height: 80px;
    border-radius: 16px;
  }
  .station-card .station-logo {
    grid-row: 1 / 3;
    width: 56px; height: 56px;
    /* Vertically centered (rev 12.40). Matches desktop and canonical —
     * user spec is unified card setup across breakpoints. */
    margin-top: 0;
    align-self: center;
    font-size: 20px;
  }
  .station-card .station-name {
    /* Mobile: same 2-line behavior, slightly bigger font (rev 12.34). */
    font-size: 15px;
    font-weight: 700;
    line-height: 1.25;
    margin: 0 0 4px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    hyphens: none;
    overflow-wrap: anywhere;
    word-break: normal;
  }
  .station-card .station-detail {
    /* Single-line attributes row (rev 12.40). User spec: "3rd line
     * for attributes". Single line, ellipsize on overflow. Matches
     * the desktop behavior so the layout setup is identical across
     * breakpoints — only the font sizes differ. */
    font-size: 11.5px;
    line-height: 1.3;
    color: var(--muted, #9aa0aa);
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin: 0;
  }
  /* Hide score on phone too — clutter */
  .station-card .station-actions .score { display: none; }
  .station-card .station-actions {
    grid-row: 1 / 3;
    grid-column: 3;
    align-self: center;
    display: flex;
    align-items: center;
  }
  .station-card .star { font-size: 22px; padding: 6px 8px; }
}


/* ==========================================================================
   14. STUDIO MODAL REDESIGN (rev 12) — 3 tabs, expanded color mood picker
   ========================================================================== */

/* ---- Section structure for grouped settings/content ---- */
.vibedock-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.vibedock-section:last-child { border-bottom: 0; }
.vibedock-section-head {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted, #9aa0aa);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.vibedock-section-hint,
.vibedock-section-count {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--muted, #9aa0aa);
  opacity: 0.8;
}
.vibedock-section-danger { border-bottom: 0; }
.vibedock-section-about {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  border-bottom: 0;
  padding-top: 16px;
  margin-top: 4px;
}
.vibedock-about-line {
  margin: 0 0 4px;
  font-size: 12px;
  color: var(--muted, #9aa0aa);
}
.vibedock-about-credit a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.25);
}
.vibedock-about-credit a:hover { text-decoration-color: var(--c1); }

/* ---- About → Privacy · Contact links ---- */
.vibedock-about-links {
  margin: 14px 0 0;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--muted);
}
.vibedock-about-links a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.18);
  text-underline-offset: 3px;
}
.vibedock-about-links a:hover { text-decoration-color: var(--c1); }
.vibedock-about-sep { opacity: 0.4; }

/* ---- Custom Stream collapsible (HTML <details>) ---- */
.vibedock-collapsible { padding: 0; }
.vibedock-collapsible > .vibedock-section-summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 0;
  user-select: none;
}
.vibedock-collapsible > .vibedock-section-summary::-webkit-details-marker { display: none; }
.vibedock-collapsible > .vibedock-section-summary > .vibedock-section-head {
  /* Match the standard section-head typography while sitting inside a summary. */
  margin: 0;
}
.vibedock-summary-chevron {
  display: inline-block;
  font-size: 12px;
  color: var(--muted);
  transition: transform 220ms ease;
}
.vibedock-collapsible[open] > .vibedock-section-summary > .vibedock-summary-chevron {
  transform: rotate(180deg);
}
.vibedock-collapsible > :not(.vibedock-section-summary) {
  padding-bottom: 14px;
}

/* Subsection inside a collapsible (e.g., "Recently tuned" list) */
.vibedock-subsection {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.vibedock-subsection-head {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.8;
}

/* ---- Reset section (was "Memory") — now at the bottom of Studio ----
   Plan #2a: visually quarantine the destructive controls. The Reset
   section sits below playful Studio sections (color mood orbs, theme
   pickers, etc.) — without strong separation the eye groups the danger
   buttons with the harmless ones above. Larger top margin + stronger
   border-top color creates a clear "below this line is destructive"
   boundary. Confirm dialogs in JS provide the second layer of protection. */
.vibedock-section-reset {
  margin-top: 24px;
  border-top: 1px solid rgba(255, 100, 127, 0.42);
  border-bottom: 0;
  padding-top: 18px;
  position: relative;
}
.vibedock-section-reset::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255, 100, 127, 0.55) 35%,
    rgba(255, 100, 127, 0.55) 65%,
    transparent 100%);
  pointer-events: none;
}
.vibedock-section-line {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}
.vibedock-section-line-muted { opacity: 0.85; }
.vibedock-reset-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

/* ---- "Now playing" card in Studio's Now tab ---- */
.vibedock-now-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
  border: 1px solid var(--line, rgba(255,255,255,0.08));
  min-height: 64px;
}
.vibedock-now-card .vd-now-logo {
  width: 44px; height: 44px;
  border-radius: 10px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--c1) 30%, #1a1a26), color-mix(in srgb, var(--c2) 25%, #1a1a26));
  display: grid; place-items: center;
  flex: 0 0 auto;
  overflow: hidden;
}
.vibedock-now-card .vd-now-logo img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 10px;
}
.vibedock-now-card .vd-now-copy { min-width: 0; flex: 1; }
.vibedock-now-card .vd-now-name {
  /* Studio Now tab: 2-line ellipsis matching the rest of the app (rev 12.34). */
  font-weight: 700;
  font-size: 14px;
  margin: 0 0 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  hyphens: none;
  overflow-wrap: anywhere;
  word-break: normal;
}
.vibedock-now-card .vd-now-track {
  font-size: 12px;
  color: var(--muted);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.vibedock-now-empty {
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
}

/* ---- Pill button row (Now tab actions) ---- */
.vibedock-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.vibedock-pill {
  appearance: none;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line, rgba(255,255,255,0.1));
  color: inherit;
  padding: 7px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.vibedock-pill:hover {
  background: rgba(255, 255, 255, 0.09);
  border-color: var(--line-strong, rgba(255,255,255,0.18));
  transform: translateY(-1px);
}
.vibedock-pill-soft {
  opacity: 0.75;
  font-weight: 500;
}
.vibedock-pill-danger {
  color: #ff8b8b;
  border-color: rgba(255, 100, 100, 0.2);
  background: rgba(255, 100, 100, 0.06);
}
.vibedock-pill-danger:hover {
  background: rgba(255, 100, 100, 0.12);
  border-color: rgba(255, 100, 100, 0.35);
}

/* ---- Mini list (favorites preview, recent preview) ---- */
.vibedock-mini-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.vibedock-mini-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 10px;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  transition: background 120ms ease, border-color 120ms ease;
}
.vibedock-mini-item:hover {
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--line, rgba(255,255,255,0.06));
}
.vibedock-mini-item .vd-mi-logo {
  width: 24px; height: 24px;
  border-radius: 6px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--c1) 25%, #1a1a26), color-mix(in srgb, var(--c2) 20%, #1a1a26));
  flex: 0 0 auto;
}
.vibedock-mini-item .vd-mi-name {
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
.vibedock-mini-item .vd-mi-meta {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.vibedock-empty {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  margin: 0;
  padding: 8px 0;
}
.vibedock-link {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 4px 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--c1, #8df7ee);
  cursor: pointer;
  align-self: flex-start;
}
.vibedock-link:hover { text-decoration: underline; }

/* ---- COLOR MOOD PICKER (visual swatch grid) ---- */
.color-mood-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: 8px;
  margin-top: 4px;
}
.color-mood {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 6px 4px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  border-radius: 10px;
  transition: background 120ms ease, transform 120ms ease;
  color: inherit;
  font: inherit;
}
.color-mood:hover {
  background: rgba(255, 255, 255, 0.04);
  transform: translateY(-1px);
}
.color-mood-orb {
  display: block;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid transparent;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    0 2px 6px rgba(0, 0, 0, 0.25);
  transition: border-color 200ms ease, transform 200ms ease, box-shadow 200ms ease;
}
.color-mood-name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted, #9aa0aa);
  transition: color 160ms ease;
}
.color-mood.active .color-mood-orb {
  border-color: rgba(255, 255, 255, 0.7);
  transform: scale(1.08);
  box-shadow:
    inset 0 0 0 2px rgba(0, 0, 0, 0.2),
    0 0 0 3px color-mix(in srgb, var(--c1, #8df7ee) 35%, transparent),
    0 4px 14px rgba(0, 0, 0, 0.35);
}
.color-mood.active .color-mood-name {
  color: var(--text, #fff);
}

/* ---- NEW THEME MOODS (rev 12) ---- */
body.theme-sunset {
  --c1: #ff8b3d;
  --c2: #ff5470;
  --c3: #ffd166;
}
body.theme-forest {
  --c1: #71d28e;
  --c2: #2c7a4b;
  --c3: #d6c188;
  --bg-deep: #08120c;
  --bg-mid:  #0d1f15;
}
body.theme-ocean {
  --c1: #6dd5ed;
  --c2: #2193b0;
  --c3: #88e0c1;
  --bg-deep: #051221;
  --bg-mid:  #0a1c30;
}
body.theme-vapor {
  --c1: #ff71ce;
  --c2: #b967ff;
  --c3: #01cdfe;
  --bg-deep: #0d0820;
  --bg-mid:  #1a0e35;
}
body.theme-ember {
  --c1: #ff4f4f;
  --c2: #c81d25;
  --c3: #f0a04b;
  --bg-deep: #150404;
  --bg-mid:  #220909;
}
body.theme-mono {
  --c1: #d8d8d8;
  --c2: #888888;
  --c3: #b8b8b8;
}
body.theme-mono .halo { opacity: 0.18; filter: blur(40px) saturate(0); }
body.theme-vinyl {
  --c1: #f4ce86;
  --c2: #b9743a;
  --c3: #4d2a18;
  --bg-deep: #0d0805;
  --bg-mid:  #160d08;
}


/* ==========================================================================
   15. STUDIO MODAL — UNIFORM TAB SIZING + POLISH (rev 12.1)
   ========================================================================== */

/* The modal body is now a fixed-size scroll container so all three tabs
   render in the same dimensions. The longest tab (Settings, with the
   color mood grid) sets the floor; shorter tabs scroll within the same
   bounds. Eliminates the "modal jumps size when I switch tabs" feeling. */
.vibedock-body {
  min-height: min(60vh, 540px);
  max-height: min(78vh, 680px);
}
@container vd-app (max-width: 719.98px) {
  .vibedock-body {
    min-height: min(70vh, 520px);
    max-height: 80vh;
  }
}

/* Each panel fills the body so they all open to the same size. */
.vibedock-panel.active {
  display: grid;
  align-content: start;
  gap: 8px;
}

/* ---- Recently played card polish ---- */
.memory-card,
.vibedock-memory-list .memory-card {
  appearance: none;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line, rgba(255, 255, 255, 0.08));
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.memory-card:hover,
.vibedock-memory-list .memory-card:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--line-strong, rgba(255, 255, 255, 0.15));
  transform: translateY(-1px);
}
.memory-card span {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.memory-card strong {
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.memory-card em {
  font-size: 11px;
  font-style: normal;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.memory-card b {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c1, #8df7ee);
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--c1, #8df7ee) 12%, transparent);
}
.vibedock-memory-list { display: grid; gap: 6px; }
.vibedock-memory-list .note {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  margin: 0;
  padding: 8px 0;
}

/* ---- Mature content danger section ---- */
.vibedock-section-mature {
  border-top: 1px solid rgba(255, 100, 100, 0.18);
  padding-top: 14px;
  margin-top: 8px;
}
.vibedock-mature-head {
  color: rgba(255, 130, 130, 0.85) !important;
}
.vibe-setting-mature {
  border: 1px solid rgba(255, 100, 100, 0.18);
  background: rgba(255, 80, 80, 0.04);
}
.vibedock-warn-inline {
  color: rgba(255, 130, 130, 0.9);
  font-weight: 700;
}

/* ---- About section tagline + Venmo card sizing ---- */
.vibedock-about-tag {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
#vibedockVenmoSlot { display: block; margin-top: 12px; }
#vibedockVenmoSlot .vd-venmo-card {
  /* Keep the Venmo card compact within the About section */
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-radius: 14px;
  border: 1px solid var(--line, rgba(255, 255, 255, 0.08));
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
  color: inherit;
  text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
#vibedockVenmoSlot .vd-venmo-card:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: var(--line-strong, rgba(255, 255, 255, 0.18));
  transform: translateY(-1px);
}
#vibedockVenmoSlot .vd-venmo-qr {
  width: 64px; height: 64px;
  background: #fff;
  border-radius: 8px;
  padding: 4px;
  display: grid; place-items: center;
  flex: 0 0 auto;
}
#vibedockVenmoSlot .vd-venmo-qr img { width: 100%; height: 100%; object-fit: contain; }
#vibedockVenmoSlot .vd-venmo-copy { display: grid; gap: 2px; }
#vibedockVenmoSlot .vd-venmo-copy small {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c1, #8df7ee);
}
#vibedockVenmoSlot .vd-venmo-copy strong {
  font-size: 14px;
  font-weight: 700;
}
#vibedockVenmoSlot .vd-venmo-copy em {
  font-size: 11px;
  font-style: normal;
  color: var(--muted);
  line-height: 1.4;
}

/* ==========================================================================
   16. THEME MOODS — make ALL of them visibly distinct (rev 12.1)
   --------------------------------------------------------------------------
   Earlier rev: only some themes changed background variables, others only
   touched accent colors. Result: half the moods looked nearly identical to
   Aurora. Fix: every theme now sets BOTH accents AND a backdrop tint, so
   each one is unmistakably different at a glance.
   ========================================================================== */

/* Aurora — the default. Already correct, but explicit for clarity. */
body { /* default — the :root values define this, overridden below */ }

body.theme-default,
body:not([class*="theme-"]) {
  --bg-deep: #050614;
  --bg-mid:  #0c0f24;
  --bg-high: #131836;
}

body.theme-neon {
  --c1: #50f4f7; --c2: #ff5cb1; --c3: #fffb78;
  --bg-deep: #04081a;
  --bg-mid:  #0a0f30;
  --bg-high: #11183f;
}
body.theme-neon .halo { opacity: 0.55; }

body.theme-warm {
  --c1: #ffc67a; --c2: #ff7a8a; --c3: #f7e08a;
  --bg-deep: #170a05;
  --bg-mid:  #2a1209;
  --bg-high: #38190d;
}

body.theme-minimal {
  --c1: #e5e9ff; --c2: #c8cef2; --c3: #a3aacd;
  --bg-deep: #0d0e16;
  --bg-mid:  #14152a;
  --bg-high: #1c1d35;
}
body.theme-minimal .halo { opacity: 0.18; filter: blur(40px); }

body.theme-sunset {
  --c1: #ff8b3d; --c2: #ff5470; --c3: #ffd166;
  --bg-deep: #150708;
  --bg-mid:  #2a0e10;
  --bg-high: #3a1614;
}

body.theme-mono {
  --c1: #d8d8d8; --c2: #888888; --c3: #b8b8b8;
  --bg-deep: #07070a;
  --bg-mid:  #101012;
  --bg-high: #18181b;
}
body.theme-mono .halo { opacity: 0.15; filter: blur(40px) saturate(0); }

/* Apply the bg variables to the actual page background.
   The base CSS uses --bg gradient, --bg-deep, --bg-mid, --bg-high in halos.
   Force a refresh for these on theme change. */
body[class*="theme-"] {
  background:
    radial-gradient(circle at 20% 0%, var(--bg-mid, #0c0f24), var(--bg-deep, #050614) 60%),
    var(--bg-deep, #050614);
}

/* ============================================================================
 * STUDIO POLISH (rev 12.18)
 *
 * Quiet visual hierarchy for VibeDial Studio. No new structures, no JS — pure
 * CSS layered on top of the existing .vibedock-* selectors.
 *
 * Goals (from user brief: sleek + premium + fun, a pleasure to use):
 *   1. Modal opens with the same 220ms rise the share/track sheets use,
 *      so studio feels intentional rather than flat.
 *   2. Section heads get a thin gradient underline that picks up the
 *      current color mood — small touch, makes the studio feel personal
 *      without shouting.
 *   3. Section spacing tightens slightly so the eye scans faster.
 *   4. Settings rows get a subtle hover/active feedback so taps feel alive.
 *   5. Action pills (Share/Link/Stream) get a quiet ::before glow that
 *      lights up on hover. The destructive pills (Hide/Broken/Clear) stay
 *      flat — they should feel different.
 *   6. The Venmo card gets a touch more presence so it reads as
 *      thoughtful gratitude, not buried marketing.
 * ============================================================================ */

/* Studio sheet rises with the same easing as track-sources/share sheets */
.vibedock-sheet {
  animation: vd-sheet-rise 220ms cubic-bezier(.2, .8, .2, 1);
}

/* Tighter, more confident kicker */
.vibedock-kicker strong {
  letter-spacing: -0.015em;
}
.vibedock-kicker span {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  letter-spacing: 0.04em;
  opacity: 0.78;
}

/* Section head: thin gradient underline using the current color mood.
 * Doesn't compete with the heading text — sits 4px below as a quiet accent. */
.vibedock-section-head {
  position: relative;
  padding-bottom: 6px;
}
.vibedock-section-head::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 28px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--c1) 80%, transparent),
    color-mix(in srgb, var(--c2) 60%, transparent));
  opacity: 0.85;
  transition: width 240ms cubic-bezier(.2, .8, .2, 1);
}
/* Section heads with a count or hint — extend the underline a bit so it
 * doesn't look truncated next to the inline text */
.vibedock-section-head:has(.vibedock-section-count)::after,
.vibedock-section-head:has(.vibedock-section-hint)::after {
  width: 36px;
}

/* Hint text inside section heads — softer, paired more loosely with the head */
.vibedock-section-hint {
  margin-left: 6px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: none;
  color: color-mix(in srgb, var(--text) 55%, transparent);
}

/* Setting rows feel alive on touch */
.vibe-setting-row {
  transition: transform 140ms cubic-bezier(.2, .8, .2, 1),
              border-color 140ms ease,
              background-color 140ms ease;
}
.vibe-setting-row:hover {
  border-color: color-mix(in srgb, var(--c1) 35%, var(--line-strong));
}
.vibe-setting-row:active {
  transform: scale(0.985);
}

/* Action pills (Share/Link/Stream) — quiet glow on hover */
.vibedock-pill:not(.vibedock-pill-soft):not(.vibedock-pill-danger) {
  position: relative;
  overflow: hidden;
  transition: transform 140ms cubic-bezier(.2, .8, .2, 1),
              border-color 140ms ease,
              color 140ms ease;
}
.vibedock-pill:not(.vibedock-pill-soft):not(.vibedock-pill-danger)::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg,
    color-mix(in srgb, var(--c1) 18%, transparent),
    color-mix(in srgb, var(--c2) 14%, transparent));
  opacity: 0;
  transition: opacity 200ms ease;
  pointer-events: none;
  z-index: -1;
}
.vibedock-pill:not(.vibedock-pill-soft):not(.vibedock-pill-danger):hover::before {
  opacity: 1;
}
.vibedock-pill:not(.vibedock-pill-soft):not(.vibedock-pill-danger):hover {
  border-color: color-mix(in srgb, var(--c1) 45%, var(--line-strong));
  color: var(--text);
}
.vibedock-pill:active { transform: scale(0.97); }

/* Color mood orbs spin gently on hover — small reward for noticing */
.color-mood {
  transition: transform 180ms cubic-bezier(.2, .8, .2, 1),
              border-color 180ms ease;
}
.color-mood:hover .color-mood-orb {
  transform: rotate(8deg) scale(1.06);
}
.color-mood-orb {
  transition: transform 240ms cubic-bezier(.2, .8, .2, 1);
}

/* Venmo card — slightly more present, not louder */
.vd-venmo-card {
  transition: transform 180ms cubic-bezier(.2, .8, .2, 1),
              border-color 180ms ease;
}
.vd-venmo-card:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--c1) 35%, var(--line-strong));
}
.vd-venmo-copy small {
  letter-spacing: 0.05em;
  opacity: 0.78;
}
.vd-venmo-copy strong {
  letter-spacing: -0.015em;
}
.vd-venmo-copy em {
  font-style: normal;
  opacity: 0.82;
}

/* Empty states — slightly warmer typography */
.vibedock-now-empty,
.vibedock-empty {
  font-style: normal;
  letter-spacing: 0.005em;
  opacity: 0.78;
}


/* ============================================================================
 * RESULT LIST POLISH (rev 12.22)
 *
 * User feedback: search results "feel okay but a little bland and flat."
 * Diagnosis: cards are visually monotone, the tags (most useful info) get
 * the least visual weight, and there's no reveal animation when results
 * land. Quality score is present but reads like an afterthought.
 *
 * Goals (consistent with rev 12.18 brief — sleek, premium, fun, not wry):
 *   1. Cards reveal with a quick stagger when a new result set lands.
 *   2. Quality score earns more visual weight on high-quality matches
 *      (90%+ gets a soft pill); stays muted at lower scores.
 *   3. Tags read as primary detail; country/language sit quieter.
 *   4. Hover gets a soft mood-tinted glow (consistent with Studio polish).
 *   5. Active card edge bar pulses subtly to signal "this is playing."
 *   6. Subtle quality edge — high-confidence matches get a barely-visible
 *      brighter left edge tint than low-confidence ones, so scanning the
 *      list feels intelligent without shouting.
 *
 * No JS changes. No new DOM. Pure CSS layered on existing selectors.
 * ============================================================================ */

/* --- 1. Card reveal stagger --- */
/* When the .station-list re-renders, cards fade up one after another.
 * 30ms stagger × ~6 visible cards = ~180ms total — fast enough to feel
 * snappy, slow enough to register as intentional motion. */
@keyframes vd-card-reveal {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.station-list .station-card {
  animation: vd-card-reveal 200ms cubic-bezier(.2, .8, .2, 1) backwards;
}
/* Stagger by index — 30ms per card. Past 12 cards the stagger caps so
 * long lists don't feel like a slow waterfall. */
.station-list .station-card:nth-child(1)  { animation-delay: 0ms; }
.station-list .station-card:nth-child(2)  { animation-delay: 30ms; }
.station-list .station-card:nth-child(3)  { animation-delay: 60ms; }
.station-list .station-card:nth-child(4)  { animation-delay: 90ms; }
.station-list .station-card:nth-child(5)  { animation-delay: 120ms; }
.station-list .station-card:nth-child(6)  { animation-delay: 150ms; }
.station-list .station-card:nth-child(7)  { animation-delay: 180ms; }
.station-list .station-card:nth-child(8)  { animation-delay: 210ms; }
.station-list .station-card:nth-child(9)  { animation-delay: 240ms; }
.station-list .station-card:nth-child(10) { animation-delay: 270ms; }
.station-list .station-card:nth-child(11) { animation-delay: 300ms; }
.station-list .station-card:nth-child(12) { animation-delay: 330ms; }
.station-list .station-card:nth-child(n+13) { animation-delay: 360ms; }

/* Honor reduced-motion preferences */
@media (prefers-reduced-motion: reduce) {
  .station-list .station-card {
    animation: none;
  }
}

/* --- 2. Detail line hierarchy --- */
/* Quality score is intentionally hidden in production by existing
 * breakpoint rules (see line ~3869). The star is the only persistent
 * action. So result-list polish focuses on the detail line and
 * card-level hierarchy instead. */

/* --- 3. Tag emphasis in the detail line --- */
/* Tags are the most useful detail info. Today they share visual weight
 * with country/language. Goal: tags slightly more present, others
 * quieter. The .tiny-pill class is shared so we use position-based
 * selectors. */

/* The first two tiny-pills are typically country and language (utility).
 * Make them fade slightly. */
.station-card .station-detail .tiny-pill:nth-child(1),
.station-card .station-detail .tiny-pill:nth-child(2) {
  color: color-mix(in srgb, var(--muted) 80%, transparent);
  font-weight: 500;
}
/* Third+ tiny-pills are typically the tag list — give them a touch of
 * the color mood and slightly more weight. */
.station-card .station-detail .tiny-pill:nth-child(n+3) {
  color: color-mix(in srgb, var(--c2) 50%, var(--text));
  font-weight: 600;
}

/* --- 4. Soft hover glow consistent with Studio --- */
/* Existing .station-card:hover already exists with translateX(1px) and
 * background bump. Layer in a quiet glow on the left edge that picks up
 * the color mood, similar to what Studio pills got in 12.18. */
.station-card {
  position: relative;
  overflow: hidden;
}
.station-card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--c1) 0%, transparent) 0%,
    transparent 30%);
  opacity: 0;
  transition: opacity 220ms ease;
}
.station-card:hover::after {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--c1) 18%, transparent) 0%,
    transparent 30%);
  opacity: 1;
}
/* Active card already has its own gradient — don't double up */
.station-card.active::after,
.station-card.is-active::after,
.station-card[aria-current="true"]::after {
  display: none;
}

/* --- 5. Active card edge bar pulses subtly --- */
/* Existing rule paints a 3px gradient bar on the left edge of the active
 * card. Make it breathe — slow alpha pulse, 3.2s cycle. Not intrusive,
 * just signals "live." */
@keyframes vd-active-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.station-card.active::before,
.station-card.is-active::before,
.station-card[aria-current="true"]::before {
  animation: vd-active-pulse 3.2s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .station-card.active::before,
  .station-card.is-active::before,
  .station-card[aria-current="true"]::before {
    animation: none;
  }
}

/* --- 6. List-meta header earns a touch of warmth --- */
/* The "Search: post rock / 4 stations" header above the list is plain
 * muted text. Give the count a small accent so the user sees their
 * search returned a real number. */
#listCount {
  color: color-mix(in srgb, var(--c1) 50%, var(--text));
  font-weight: 800;
}
