/* Cassette-deck page (cassettes#show) — the rail · main · dock layout
   (ADR-016), restyled flat for the washi/sumi system (ADR-021). The tape
   visualization itself lives in cassette-shell.css. Colours are theme tokens
   (themes.css); the language is quiet: hairlines, flat fills, no gradients. */

.page--deck {
  max-width: 1560px;
  margin: 0 auto;
  padding: 1.25rem 1.5rem 2.5rem;
}

.deck {
  /* Neutrals derived from tokens so light + dark themes both read well. */
  --line: color-mix(in srgb, var(--text) 12%, transparent);
  --soft: color-mix(in srgb, var(--text) 5%, transparent);
  --soft-2: color-mix(in srgb, var(--text) 8%, transparent);

  display: grid;
  grid-template-columns: 210px minmax(0, 1fr) 340px;
  gap: 1.25rem;
  align-items: start;
}

/* Shared panel surface — soft chrome: flat fill, no border. */
.panel {
  background: var(--bg-raised);
  border-radius: 16px;
  padding: 1.15rem 1.25rem;
}

.panel-label,
.rail-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ============ LEFT RAIL ============ */
.deck-rail {
  background: var(--bg-raised);
  border-radius: 16px;
  padding: 1rem 0.7rem;
  position: sticky;
  top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}
.rail-group { display: flex; flex-direction: column; gap: 2px; }
.rail-group .rail-label { padding: 0 0.6rem; margin-bottom: 0.4rem; }

.rail-link {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.55rem 0.65rem;
  border-radius: 10px;
  color: var(--text-dim);
  font-weight: 700;
  font-size: 0.92rem;
  transition: background 0.12s ease, color 0.12s ease;
}
.rail-link:hover { background: var(--soft); color: var(--text); text-decoration: none; }
.rail-link.is-active {
  color: var(--text);
  background: var(--wash-accent);
  box-shadow: inset 3px 0 0 var(--accent-strong);
}
.rail-ico { width: 20px; text-align: center; opacity: 0.85; font-size: 0.95rem; }

/* Hamburger for the rail's nav groups — mobile-only chrome, so hidden here
   and switched on in the ≤820px block below. */
.rail-menu-toggle {
  display: none;
  align-items: center;
  gap: 0.55rem;
  height: 42px;
  padding: 0 0.95rem;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--soft);
  color: var(--text);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.rail-menu-toggle .rail-ico { width: auto; font-size: 1.05rem; opacity: 1; }
.deck-rail.is-open .rail-menu-toggle {
  background: var(--wash-accent);
  border-color: var(--accent);
}

/* ============ MAIN COLUMN ============ */
.deck-main {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
  /* The column can get narrow independently of the viewport (the >=1860px
     five-column grid squeezes it on ~1920px screens), so the now-card
     stacks by container width, not media width. */
  container-type: inline-size;
}

.now-card {
  position: relative; /* anchors the vertical kana wordmark */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.75rem;
  align-items: center;
  background: var(--bg-raised);
  border-radius: 20px;
  padding: 1rem 2.5rem 1.1rem 1.75rem;
}

/* Vertical katakana wordmark — tategaki, riding the card's right edge.
   position:absolute also removes it from the card's grid flow. */
.now-kana {
  position: absolute;
  top: 1.1rem;
  right: 0.8rem;
  writing-mode: vertical-rl;
  font-size: 0.7rem;
  letter-spacing: 0.5em;
  color: var(--text-dim);
  user-select: none;
}

/* The tape shrinks into a "now playing" hero. The layout box of a scaled
   element keeps its pre-transform size, so negative margins collapse the
   empty space down to the *visual* footprint (534x335 → ~332x209 at 0.62). */
.now-cassette { display: grid; place-items: center; }
.now-cassette .cassette-wrap { margin: 0; }
.now-cassette .cassette {
  transform: scale(0.62);
  transform-origin: center;
  margin: -63px -101px;
}

/* Narrow main column (e.g. 1920x1080 with flanking speakers): the info
   block moves under the cassette and the vertical kana yields its lane. */
@container (max-width: 700px) {
  .now-card {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    padding: 1.5rem 1.75rem;
  }
  .now-kana { display: none; }
  .panel-head { flex-wrap: wrap; }
  .cassette-filter { flex: 1 1 100%; }
}

/* Fill the grid cell even in the stacked layout (where the now-card uses
   justify-items: center, which would otherwise shrink-wrap this to its content
   and defeat the one-row clip below). */
.deck .now-info { min-width: 0; justify-self: stretch; }
.now-info { min-width: 0; }
.now-track {
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
  margin-top: 0.35rem;
}
.now-album { color: var(--text-dim); margin-top: 0.35rem; font-size: 0.95rem; }

/* Title and album each stay on one row; overflow shows an ellipsis by default.
   When the text is too long, cassette_controller measures it and adds
   .is-scrolling, which turns the inner span into a marquee that slides left to
   reveal the end, then returns. Shift distance + duration come from the
   controller — the public deck sets no marquee, so it stops at the ellipsis. */
.deck .now-track,
.deck .now-album {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.deck .now-track.is-scrolling,
.deck .now-album.is-scrolling { text-overflow: clip; text-align: left; }
.deck .now-track.is-scrolling > span,
.deck .now-album.is-scrolling > span {
  display: inline-block;
  will-change: transform;
  animation: now-marquee var(--marquee-duration, 8s) ease-in-out infinite;
}
@keyframes now-marquee {
  0%, 14% { transform: translateX(0); }
  50%, 64% { transform: translateX(var(--marquee-shift, 0)); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .deck .now-track.is-scrolling > span,
  .deck .now-album.is-scrolling > span { animation: none; }
}

.now-seek { margin-top: 0.85rem; }
.deck .progress-bar {
  position: relative;
  height: 8px;
  background: var(--soft-2);
  border-radius: 999px;
  cursor: pointer;
  overflow: hidden;
}
.deck .progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.1s linear;
}
.progress-time {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  margin-top: 0.5rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* --- source picker (tabs + lists) --- */
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
.panel-tabs { display: flex; gap: 0.3rem; }
.panel-tab {
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--soft);
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: all 0.12s ease;
}
.panel-tab:hover { color: var(--text); }
.panel-tab.active { background: var(--accent-2); border-color: var(--accent-2); color: var(--bg); }

.cassette-filter {
  flex: 0 1 200px;
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--text);
  outline: none;
  transition: border-color 0.15s ease;
}
.cassette-filter::placeholder { color: var(--text-dim); }
.cassette-filter:focus { border-color: var(--accent); }

.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* Albums render as a cover grid; artists / favorites as tidy rows. */
.album-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  align-content: start; /* rows keep natural height when the list is stretched */
  gap: 1rem;
  max-height: 340px;
  overflow-y: auto;
  padding: 0.15rem;
}
.album-item {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.6rem;
  border: 1px solid transparent;
  border-radius: 12px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.album-item:hover { background: var(--soft); border-color: var(--line); transform: translateY(-2px); }
.album-item.active { border-color: var(--accent); background: var(--wash-accent); }
.album-thumb {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 10px;
  object-fit: cover;
  background: var(--accent-3);
}
.album-thumb--empty {
  display: grid;
  place-items: center;
  font-size: 2rem;
  color: var(--text-dim);
  background: var(--accent-3);
}

.artist-list,
.favorite-list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-height: 340px;
  overflow-y: auto;
  padding: 0.15rem;
}
.artist-item,
.favorite-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid transparent;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.artist-item:hover,
.favorite-item:hover { background: var(--soft); border-color: var(--line); }
.artist-item.active,
.favorite-item.active { border-color: var(--accent); background: var(--wash-accent); }

/* Keyboard hit area inside each source-panel item: an unstyled button whose
   click bubbles to the item's cue action. Focus ring on the item itself. */
.item-hit {
  display: flex;
  min-width: 0;
  flex: 1;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.album-item .item-hit { flex-direction: column; gap: 0.55rem; width: 100%; }
.item-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 8px; }

/* Per-item queue actions (⤵ play next / ＋ add to queue), revealed on
   hover/keyboard focus; always faintly visible on touch. */
.item-actions { display: flex; gap: 0.25rem; }
.album-item { position: relative; }
.album-item .item-actions { position: absolute; top: 0.85rem; right: 0.85rem; }
.item-action {
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--text-dim);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 999px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.album-item:hover .item-action,
.artist-item:hover .item-action,
.favorite-item:hover .item-action,
.album-item:focus-within .item-action,
.artist-item:focus-within .item-action,
.favorite-item:focus-within .item-action { opacity: 1; }
.item-action:hover { background: var(--soft-2); color: var(--text); border-color: var(--accent); }
@media (hover: none) { .item-action { opacity: 0.55; } }

.album-info { min-width: 0; }
.album-title {
  font-weight: 700;
  font-size: 0.88rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.album-artist { font-size: 0.8rem; color: var(--text-dim); }

/* ============ RIGHT DOCK ============ */
.deck-dock {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  position: sticky;
  top: 1rem;
}

/* One transport row, in the now-card under the cassette; spans both grid
   columns so the pill sits centred beneath the whole hero. */
.transport-row--now { grid-column: 1 / -1; margin-top: 0.85rem; }
/* Same "one live set of controls" rule as the player bar (player.css) while
   the pop-out window is open. */
.player--piped .transport-row--now { display: none; }

.transport-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.8rem;
}
.transport-btn[hidden] { display: none; }
.transport-btn {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--soft-2);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, transform 0.1s ease, color 0.15s ease;
}
.transport-btn:hover { background: var(--soft); }
.transport-btn:active { transform: scale(0.94); }
.transport-btn svg { width: 19px; height: 19px; fill: var(--text); }
/* Shuffle lights up in the accent when engaged (mirrors the player bar). */
.transport-btn--shuffle.active { background: var(--wash-accent); border-color: var(--accent); }
.transport-btn--shuffle.active svg { fill: var(--accent-strong); }
/* The play control is a labelled pill (▶ Play / ⏸ Pause) rather than a bare
   circle — prev/next stay as the flanking icon buttons. */
.transport-btn.play {
  width: auto;
  height: 40px;
  padding: 0 1.4rem 0 1.2rem;
  gap: 0.45rem;
  border-radius: 999px;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
  font-weight: 700;
  font-size: 0.98rem;
  letter-spacing: 0.01em;
}
.transport-btn.play:hover { background: var(--accent-strong); }
.transport-btn.play svg { fill: var(--paper); width: 19px; height: 19px; }
.transport-label { line-height: 1; }
/* The icon toggles via JS display; the label follows the same play state
   purely in CSS off the .playing class the controller sets on the button. */
.transport-btn.play .transport-label--pause { display: none; }
.transport-btn.play.playing .transport-label--play { display: none; }
.transport-btn.play.playing .transport-label--pause { display: inline; }

.fx-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1.25rem;
}
.knob-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
}
.knob-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  white-space: nowrap;
}
.knob-label b { color: var(--text); }
.knob {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--bg-raised);
  border: 2px solid var(--text);
  position: relative;
  cursor: ns-resize;
}
/* Initial states the controller later overrides via CSSOM — inline style
   attributes are blocked by the CSP (style-src 'self', no unsafe-inline). */
.knob--fx { transform: rotate(-20deg); }
.transport-btn svg[data-cassette-target="pauseIcon"] { display: none; }

.knob::before {
  content: "";
  position: absolute;
  top: 6px;
  left: 50%;
  width: 3px;
  height: 15px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--text);
  transform-origin: 50% 20px;
}

.visualizer {
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  height: 52px;
  padding: 0.45rem 0.6rem;
  border-radius: 8px;
  background: var(--soft);
  border: 1px solid var(--line);
}
.visualizer-bar {
  width: 5px;
  height: 8px; /* idle height; the controller drives it per frame while playing */
  background: var(--text);
  border-radius: 2px;
  transition: height 0.1s ease;
}

/* --- playlist --- */
.playlist-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.75rem;
}
.playlist-header { gap: 0.6rem; }
.playlist-count {
  font-size: 0.72rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-left: auto;
}
.playlist-clear {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: color 0.12s ease;
}
.playlist-clear:hover { color: var(--accent); }
.playlist-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.deck .playlist {
  max-height: 360px;
  overflow-y: auto;
  margin: 0 -0.3rem;
  padding: 0 0.3rem;
}
.playlist-track {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.6rem;
  border-radius: 9px;
  cursor: pointer;
  font-size: 0.86rem;
  transition: background 0.12s ease;
}
.playlist-track:hover { background: var(--soft); }
.playlist-track.active {
  background: var(--wash-accent);
  box-shadow: inset 3px 0 0 var(--accent-strong);
}
.playlist-track-no {
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--text-dim);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.playlist-track.active .playlist-track-no { color: var(--accent); }
/* A button (keyboard playback), styled as inline text. */
.playlist-track-title {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.playlist-track-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* The per-row ＋ "add to playlist" menu — a native <select> styled down to a
   quiet glyph, revealed on row hover/focus (always shown on touch). */
.pl-add {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  text-align: center;
  border-radius: 7px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.playlist-track:hover .pl-add,
.playlist-track:focus-within .pl-add { opacity: 1; }
.pl-add:hover { background: var(--soft-2); color: var(--text); }
@media (hover: none) { .pl-add { opacity: 0.55; } }

/* Rail playlists */
button.rail-link {
  width: 100%;
  border: none;
  background: none;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}
.rail-group--playlists .rail-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.rail-label__link {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--text-dim);
  opacity: 0.75;
}
.rail-label__link:hover { opacity: 1; color: var(--text); text-decoration: none; }
.rail-link--playlist .rail-pl-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-link--playlist .rail-pl-count {
  flex: 0 0 auto;
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  background: var(--soft-2);
  border-radius: 999px;
  padding: 0.05rem 0.42rem;
}
.rail-link--newpl { color: var(--text-dim); font-weight: 700; opacity: 0.85; }
.rail-link--newpl:hover { opacity: 1; }

/* Transient deck confirmation: add-to-playlist signed in, "that's the free
   15 seconds" on the public deck. */
.deck-toast {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%) translateY(0.5rem);
  background: var(--text);
  color: var(--bg);
  padding: 0.6rem 1.15rem;
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 600;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
  z-index: 60;
}
.deck-toast.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Custom scrollbars for the scroll regions */
.deck .album-list::-webkit-scrollbar,
.deck .artist-list::-webkit-scrollbar,
.deck .favorite-list::-webkit-scrollbar,
.deck .playlist::-webkit-scrollbar { width: 8px; }
.deck ::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 999px;
}
.deck ::-webkit-scrollbar-track { background: transparent; }

/* --- empty states --- */
.deck .empty-state {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--text-dim);
}
.deck .now-empty { padding: 3rem 1rem; grid-column: 1 / -1; }
.deck .empty-state p { margin: 0 0 1rem; }
.empty-cta {
  display: inline-block;
  padding: 0.6rem 1.3rem;
  background: var(--accent);
  color: var(--paper);
  border-radius: 999px;
  font-weight: 700;
  font-style: normal;
  transition: background 0.15s ease;
}
.empty-cta:hover { background: var(--accent-strong); text-decoration: none; }

/* ============ APPLIANCE HEIGHT (desktop) ============ */
/* Rail, main, dock (and the flanking speakers when present) share one
   height — the viewport below the titlebar. Inner lists scroll; the page
   itself doesn't. Only where the three-column grid exists (>1180px). */
@media (min-width: 1181px) {
  .deck {
    align-items: stretch;
    height: calc(100dvh - var(--titlebar-height) - 3.75rem);
    min-height: 560px;
  }
  /* Public landing (logged out): a fixed appliance, exactly like the signed-in
     deck — the page itself never scrolls, only the middle column does. No
     titlebar to reserve, so it fills the viewport with an even 0.75rem inset
     (side padding kept from .page--deck so the speakers clear the edges). */
  .page--landing {
    height: 100dvh;
    overflow: hidden; /* the frame is pinned; scrolling lives inside a column */
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
  }
  .deck--public {
    height: calc(100dvh - 1.5rem);
    /* Override .deck's 560px floor: on a short viewport the deck must shrink to
       fit rather than push the whole page into a scroll (the "floating" bug).
       The columns below take the overflow internally instead. */
    min-height: 0;
  }
  /* If the rail or dock outgrow a short viewport, they scroll in place rather
     than growing the page — mirrors .deck-main's own overflow. */
  .deck--public .deck-rail,
  .deck--public .deck-dock { overflow-y: auto; }
  .deck-rail,
  .deck-dock { position: static; }
  .deck-main,
  .deck-dock { min-height: 0; }
  /* The now-card carries the transport now, so on short viewports the hero +
     source panel can exceed the fixed deck height; let the column scroll
     rather than clip the source panel to nothing. */
  .deck-main { overflow-y: auto; }

  .source-panel {
    flex: 1 1 260px;
    min-height: 260px;
    display: flex;
    flex-direction: column;
  }
  .tab-panel.active { flex: 1; min-height: 0; overflow: hidden; }
  .deck .album-list,
  .deck .artist-list,
  .deck .favorite-list { height: 100%; max-height: none; }

  .transport-panel { flex: 0 0 auto; }
  .playlist-panel {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  .deck .playlist { flex: 1; min-height: 0; max-height: none; }
}

/* ============ FLANKING SPEAKERS (ADR-020) ============ */
/* The ADR-015 boombox hardware back on the deck — only where there is real
   room beside the rail·main·dock shell. The page cap is lifted and the
   speaker tracks flex, so the two cabinets absorb ALL remaining width on
   big screens (no dead margins); their height comes from the shared
   appliance-height grid row above. The chassis inherits --speaker-pump
   from .deck (published per frame by cassette_controller). */
.deck-speaker { display: none; }

@media (min-width: 1860px) {
  .page--deck { max-width: none; }
  .deck {
    grid-template-columns:
      minmax(200px, 1fr) 210px minmax(0, 1fr) 340px minmax(200px, 1fr);
  }
  /* Stretched by the appliance-height grid row — same height as the
     rail/main/dock columns, full remaining width each side. */
  .deck-speaker {
    display: block;
    min-height: 520px;
  }
}

/* ============ RESPONSIVE ============ */
@media (max-width: 1180px) {
  .deck { grid-template-columns: 190px minmax(0, 1fr); }
  .deck-dock {
    grid-column: 1 / -1;
    flex-direction: row;
    flex-wrap: wrap;
    position: static;
  }
  .deck-dock .panel { flex: 1 1 300px; }
}

@media (max-width: 820px) {
  .deck { grid-template-columns: 1fr; }
  .deck-rail { position: static; }
  .now-card { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .now-info { text-align: center; }

  /* The dock's knobs and visualizer are pointer-driven desktop chrome — hide
     the whole panel on mobile. The transport already lives in the now-card. */
  .deck-dock .transport-panel { display: none; }

  /* Compact top bar: a ☰ Library hamburger on the left, then an icon-only
     Upload button and the theme pill tucked to the right — one 42px row.
     The nav groups hide until the hamburger opens them (rail_controller
     toggles .is-open); open, they stack below the bar inside the same card. */
  .deck-rail { flex-flow: row wrap; align-items: center; gap: 0.6rem; padding: 0.6rem 0.7rem; }
  .rail-menu-toggle { display: inline-flex; }
  .deck-rail .rail-group:not(.rail-group--manage) { display: none; }
  .deck-rail.is-open .rail-group:not(.rail-group--manage) {
    display: flex;
    flex-direction: column;
    flex: 1 1 100%;
    order: 10; /* after every bar item, so each group wraps to its own row */
    margin-top: 0.3rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--line);
  }
  .rail-group--manage { flex: 0 0 auto; flex-direction: row; align-items: center; margin-left: auto; }
  .rail-group--manage .rail-label { display: none; }
  .rail-link--upload {
    width: 42px;
    height: 42px;
    padding: 0;
    justify-content: center;
    border: 1px solid var(--line);
    border-radius: 11px;
  }
  .rail-link--upload .rail-text { display: none; }
  .rail-link--upload .rail-ico { width: auto; font-size: 1.2rem; opacity: 1; }
}

@media (max-width: 480px) {
  .now-track { font-size: 1.35rem; }
  .deck-dock { flex-direction: column; }
  .panel-head { flex-wrap: wrap; }
  .cassette-filter { flex: 1 1 100%; }
  /* Keep the 534px tape inside a phone viewport: shrink the hero scale and
     recompute the negative margins that collapse the pre-transform box. */
  .now-cassette .cassette {
    transform: scale(0.52);
    margin: -80px -128px;
  }
}
