/* live-banner.css — Phase 1 styles.
   Colors reference the existing v1 palette (values mirrored for now; Phase 2
   extracts them into design tokens). When active the banner is a STICKY bar pinned
   above the nav header: it stays visible as you scroll (z-index 1100, above the
   header's 1000). The header sticks just below it — its `top` is offset by the
   live `--crab-banner-h` custom property that live-banner.js measures each render
   (0 when the banner is idle/hidden, so the header pins to the very top as before). */

:root {
  --crab-banner-bg: #2b2721;
  --crab-banner-fg: #f6ecd3;
  --crab-banner-muted: #c8bfa7;
  --crab-banner-border: #3d3830;
  --crab-banner-live: #d4603a;
  --crab-banner-live-glow: rgba(212, 96, 58, 0.45);
  --crab-banner-studio: #e08a4f;
  --crab-banner-accent: #a3ad7e;
  --crab-next-bg: #211e19;
}

.crab-live-banner[hidden] { display: none !important; }

/* Enter/leave is a CSS `grid-template-rows: 0fr ↔ 1fr` transition on __reveal,
   driven by animateBanner() toggling [data-reveal] when the show flips On Air ↔
   Dormant. This pushes the page content below smoothly without the per-frame
   full-document reflow that scripted `height` caused (the old choppiness). Duration =
   --dur-med, so prefers-reduced-motion (token → 0ms) makes it an instant toggle. */
.crab-live-banner {
  position: sticky;
  top: 0;
  z-index: 1100;              /* above the nav header (1000) so it pins on top while scrolling */
  background: linear-gradient(to bottom, var(--crab-banner-bg), #23201a);
  border-bottom: 1px solid var(--crab-banner-border);
  color: var(--crab-banner-fg);
  font-size: 0.95rem;
}

/* The collapsing row track. At rest it's 1fr (fully open) so overflow can stay
   visible and the live dot's pulsing box-shadow glow is never clipped; the clipping
   .is-revealing class is applied by JS only for the duration of a slide. */
.crab-live-banner__reveal {
  display: grid;
  grid-template-rows: 1fr;
  /* A single full-width column so __inner spans the bar (max-width:1200px, centered)
     and its flex layout can spread — lamp far left, CTA far right. Without this the
     implicit `auto` column sizes to content and the whole bar collapses to the middle.
     minmax(0,1fr) lets a long now-playing title shrink instead of overflowing. */
  grid-template-columns: minmax(0, 1fr);
  transition: grid-template-rows var(--dur-med) var(--ease-in-out);
}
.crab-live-banner__reveal > * {
  min-height: 0;
  opacity: 1;
  transition: opacity var(--dur-med) var(--ease-in-out);
}
.crab-live-banner.is-revealing .crab-live-banner__reveal > * { overflow: hidden; }
.crab-live-banner[data-reveal="closed"] .crab-live-banner__reveal { grid-template-rows: 0fr; }
.crab-live-banner[data-reveal="closed"] .crab-live-banner__reveal > * { opacity: 0; }

/* Now-playing module cards (homepage "We're live!" card + the /music module) fade and
   slide in/out when a track appears/clears, driven by the .is-shown class toggled in
   live-banner.js (revealCard). `hidden` is the removed-from-flow end state; these cards
   start at opacity:0 so a no-JS visit simply shows nothing (progressive enhancement).
   --dur-med → 0ms under prefers-reduced-motion makes it an instant swap. */
[data-now-playing],
[data-crab-nowplaying-music] {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-med) var(--ease-in-out),
              transform var(--dur-med) var(--ease-in-out);
}
[data-now-playing].is-shown,
[data-crab-nowplaying-music].is-shown {
  opacity: 1;
  transform: translateY(0);
}

.crab-live-banner__inner {
  /* width:100% is REQUIRED now that __inner is a grid item of __reveal: its
     `margin: 0 auto` makes auto inline margins, which on a grid item suppress the
     default stretch and shrink it to content width (the "squished to the middle"
     bug). An explicit width fills the full-width column; max-width + auto margins
     still cap it at 1200 and center it. (box-sizing: border-box is global.) */
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0.65rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

/* Only the state matching [data-state] is visible. */
.crab-live-banner__state { display: none; flex: 1; align-items: center; gap: 0.75rem; }
.crab-live-banner[data-state="pre"]            .crab-live-banner__state[data-for-state="pre"],
.crab-live-banner[data-state="live-rotation"]  .crab-live-banner__state[data-for-state="live-rotation"],
.crab-live-banner[data-state="live-studio"]    .crab-live-banner__state[data-for-state="live-studio"],
.crab-live-banner[data-state="post"]           .crab-live-banner__state[data-for-state="post"],
.crab-live-banner[data-state="new-episode"]    .crab-live-banner__state[data-for-state="new-episode"] {
  display: flex;
}

.crab-live-banner__dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--crab-banner-muted);
  flex-shrink: 0;
}
.crab-live-banner__dot--live {
  background: var(--crab-banner-live);
  box-shadow: 0 0 0 0 var(--crab-banner-live-glow);
  animation: crab-pulse 2.2s ease-out infinite;
}
.crab-live-banner__dot--studio {
  background: var(--crab-banner-studio);
  box-shadow: 0 0 0 0 var(--crab-banner-live-glow);
  animation: crab-pulse 1.8s ease-out infinite;
}
.crab-live-banner__dot--new {
  background: var(--crab-banner-accent);
}

@keyframes crab-pulse {
  0%   { box-shadow: 0 0 0 0 var(--crab-banner-live-glow); }
  70%  { box-shadow: 0 0 0 14px rgba(212, 96, 58, 0); }
  100% { box-shadow: 0 0 0 0 rgba(212, 96, 58, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .crab-live-banner__dot--live,
  .crab-live-banner__dot--studio { animation: none; }
}

.crab-live-banner__text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  flex: 1;
  min-width: 0;
}

.crab-live-banner__label {
  font-weight: 700;
  letter-spacing: 0.01em;
  overflow-wrap: anywhere;
}

.crab-live-banner__meta {
  color: var(--crab-banner-muted);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.crab-live-banner__cta {
  background: var(--crab-banner-accent);
  color: #1a1713;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
  transition: background 140ms ease;
}
/* The CTA carries two labels: the full stream label (desktop) and a compact
   "Listen Live" (mobile stack, where the button shares a row with the song info).
   Only one shows at a time — see the media queries below. */
.crab-live-banner__cta-short { display: none; }
.crab-live-banner__cta:hover,
.crab-live-banner__cta:focus-visible {
  background: #b6bf8d;
  outline: none;
}

/* Mobile: stack the CTA onto its own full-width row. The CTA lives *inside*
   .crab-live-banner__state (the flex:1 child), so the wrap that drops it to a new
   line has to happen on __state — not just on __inner. */
@media (max-width: 640px) {
  .crab-live-banner__inner {
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.5rem 0.6rem;
  }
  .crab-live-banner__state { flex-wrap: wrap; }
  .crab-live-banner__text { flex: 1 1 100%; }
  /* Re-assert full width over the desktop "flex: 0 1 auto" (higher-specificity)
     live-state rule, and drop the mini now-playing module onto its own row. */
  .crab-live-banner[data-state="live-rotation"] .crab-live-banner__text,
  .crab-live-banner[data-state="live-studio"]   .crab-live-banner__text { flex: 1 1 100%; }
  /* Mobile stack: the Listen-Live button and the now-playing info SHARE one row
     under the status text — button hugged left, song info pushed to the right edge.
     `order` flips the DOM (the now-playing card precedes the CTA in markup) so the
     compact "Listen Live" button sits to the LEFT; margin-left:auto on the card eats
     the gap between them. The body stays shrunk-to-content + flush-right for a clean
     margin. (On wider screens the card sits inline in the single banner row,
     untouched.) */
  /* Scoped with .crab-live-banner so margin-left:auto out-specifies the later
     `.crab-now-playing--banner { margin: 0 }` base block (equal specificity would
     otherwise let that later rule win and kill the push-right). */
  .crab-live-banner .crab-now-playing--banner {
    flex: 0 1 auto;
    order: 2;
    margin-left: auto;
    min-width: 0;
    /* Hard cap the card so a long song title can never grow it wide enough to wrap the
       "Listen Live" button onto its own row. 130px reserves the compact button + gap;
       clamping the card's hypothetical size keeps it on the button's line, and the song
       fields marquee-scroll whatever then overflows (see .crab-np-marquee-inner). */
    max-width: calc(100% - 130px);
  }
  .crab-now-playing--banner .crab-now-playing__body { flex: 0 1 auto; text-align: right; }
  /* Let the fields fill the (capped) body so the marquee measures overflow against the
     real available width, not a fixed 220px. */
  .crab-now-playing--banner .crab-now-playing__song,
  .crab-now-playing--banner .crab-now-playing__artist,
  .crab-now-playing--banner .crab-now-playing__album { max-width: none; }
  .crab-live-banner__cta {
    order: 1;
    flex: 0 0 auto;
    align-self: center;
    padding: 0.5rem 0.9rem;
    font-size: 0.8rem;
  }
  /* Swap to the compact label on the shared row. */
  .crab-live-banner__cta-full { display: none; }
  .crab-live-banner__cta-short { display: inline; }
  .crab-live-banner__meta { font-size: 0.8rem; }
}

/* Small phones: trim padding and type so the longest state (live-studio) fits
   without horizontal scroll down to ~280px. */
@media (max-width: 400px) {
  .crab-live-banner__inner { padding: 0.55rem 0.7rem; gap: 0.4rem 0.5rem; }
  /* Tighter gap so the compact "Listen Live" button + song info still share one row
     down here — the song text ellipsises to fit. */
  .crab-live-banner__state { gap: 0.45rem; }
  .crab-live-banner__label { font-size: 0.88rem; }
  .crab-live-banner__meta { font-size: 0.78rem; }
}

/* ---------- Next-show card ---------- */

.crab-next-show[hidden] { display: none !important; }

/* Idle shows the next-date block; live swaps to the "We're live!" block. */
.crab-next-show [data-next-live] { display: none; }
.crab-next-show[data-state="live-rotation"] [data-next-default],
.crab-next-show[data-state="live-studio"]   [data-next-default] { display: none; }
.crab-next-show[data-state="live-rotation"] [data-next-live],
.crab-next-show[data-state="live-studio"]   [data-next-live] { display: block; }

.crab-next-show__live-dot {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--crab-banner-live);
  box-shadow: 0 0 0 0 var(--crab-banner-live-glow);
  animation: crab-pulse 2.2s ease-out infinite;
  vertical-align: baseline;
  margin-right: 0.15rem;
}
@media (prefers-reduced-motion: reduce) {
  .crab-next-show__live-dot { animation: none; }
}

.crab-next-show {
  position: relative;
  background: var(--crab-next-bg);
  border: 1px solid var(--crab-banner-border);
  border-radius: 14px;
  padding: 1.5rem 1.75rem;
  margin: 2rem auto;
  max-width: 680px;
  color: var(--crab-banner-fg);
}

.crab-next-show__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  color: var(--crab-banner-accent);
  margin: 0 0 0.35rem;
  font-weight: 600;
}

.crab-next-show__date {
  margin: 0 0 0.5rem;
  font-size: 1.75rem;
  line-height: 1.15;
}
.crab-next-show__time {
  color: var(--crab-banner-muted);
  font-size: 1rem;
  font-weight: 400;
}

.crab-next-show__tags {
  position: absolute;
  top: 1rem;
  right: 1.25rem;
  display: flex;
  gap: 0.4rem;
  z-index: 1;
}
.crab-next-show__tags [hidden] { display: none; }

.crab-next-show__studio {
  color: var(--crab-banner-muted);
  font-size: 0.95rem;
  margin: 0 0 1rem;
}
.crab-next-show__studio[hidden] { display: none; }

.crab-next-show__badge {
  display: inline-block;
  background: var(--crab-banner-studio);
  color: #1a1713;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  margin-right: 0.5rem;
  vertical-align: 2px;
}

.crab-next-show__actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: nowrap;
}

.crab-next-show__btn {
  background: var(--crab-banner-accent);
  color: #1a1713;
  padding: 0.55rem 1.1rem;
  white-space: nowrap;
  flex-shrink: 1;
  min-width: 0;
  text-align: center;
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
  transition: background 140ms ease;
  font-size: 0.9rem;
}
.crab-next-show__btn:hover,
.crab-next-show__btn:focus-visible {
  background: #b6bf8d;
  outline: none;
}
.crab-next-show__btn--ghost {
  background: transparent;
  color: var(--crab-banner-accent);
  border: 1px solid var(--crab-banner-accent);
}
.crab-next-show__btn--ghost:hover,
.crab-next-show__btn--ghost:focus-visible {
  background: rgba(163, 173, 126, 0.1);
  color: var(--crab-banner-fg);
}

/* Mobile — shrink card and buttons to keep actions side-by-side */
@media (max-width: 640px) {
  .crab-next-show {
    padding: 1.25rem 1rem;
  }
  .crab-next-show__actions {
    gap: 0.5rem;
  }
  .crab-next-show__btn {
    display: block;
    padding: 0.55rem 0.6rem;
    font-size: 0.75rem;
    letter-spacing: 0.02em;
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
  }
}
@media (max-width: 360px) {
  .crab-next-show__btn {
    font-size: 0.7rem;
    padding: 0.5rem 0.4rem;
  }
}
@media (max-width: 300px) {
  /* Extreme narrow: allow wrapping as a last resort */
  .crab-next-show__actions { flex-wrap: wrap; }
}

/* ---------- Schedule-page "We're live!" section ---------- */

.schedule-live[hidden] { display: none !important; }

.schedule-live {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  background: linear-gradient(to bottom, var(--crab-banner-bg), #23201a);
  border: 1px solid var(--crab-banner-border);
  border-radius: 12px;
  padding: 0.85rem 1.1rem;
  margin: 0 0 1.5rem;
  color: var(--crab-banner-fg);
}

.schedule-live__text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  flex: 1;
  min-width: 0;
}
.schedule-live__label { font-weight: 700; }
.schedule-live__meta {
  color: var(--crab-banner-muted);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}
.schedule-live [data-studio][hidden] { display: none; }

.schedule-live .btn { white-space: nowrap; }

@media (max-width: 640px) {
  .schedule-live { flex-wrap: wrap; align-items: flex-start; gap: 0.6rem; }
  .schedule-live .btn { width: 100%; text-align: center; }
}
@media (max-width: 400px) {
  .schedule-live { padding: 0.7rem 0.85rem; }
  .schedule-live__meta { font-size: 0.8rem; }
}

/* ---------- Now playing (homepage card, /music/ module, /studio/ preview) ---------- */

.crab-now-playing[hidden] { display: none !important; }

.crab-now-playing {
  display: flex;
  gap: 0.9rem;
  align-items: center;
  margin: 1rem 0;
  padding: 0.75rem;
  background: rgba(0, 0, 0, 0.18);
  border: 1px solid var(--crab-banner-border);
  border-radius: 12px;
  text-align: left;
}

.crab-now-playing__art {
  width: 72px;
  height: 72px;
  flex-shrink: 0;
  border-radius: 6px;
  object-fit: cover;
  background: #36322c;
}

.crab-now-playing__body { min-width: 0; flex: 1; }

.crab-now-playing__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.65rem;
  color: var(--crab-banner-live);
  font-weight: 700;
  margin: 0 0 0.2rem;
}
.crab-now-playing__song {
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1.2;
  margin: 0;
  overflow-wrap: anywhere;
}
.crab-now-playing__artist {
  color: var(--crab-banner-fg);
  margin: 0.1rem 0 0;
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}
.crab-now-playing__album {
  color: var(--crab-banner-muted);
  font-size: 0.85rem;
  font-style: italic;
  margin: 0.1rem 0 0;
  overflow-wrap: anywhere;
}
.crab-now-playing__album:empty { display: none; }

.crab-now-playing__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.55rem;
}
.crab-np-chip {
  display: inline-block;
  background: rgba(163, 173, 126, 0.14);
  border: 1px solid var(--crab-banner-accent);
  color: var(--crab-banner-accent);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background 140ms ease, color 140ms ease;
}
.crab-np-chip:hover,
.crab-np-chip:focus-visible {
  background: var(--crab-banner-accent);
  color: #1a1713;
  outline: none;
}

/* Full-width variant: the /music/ page module + /studio/ preview */
.crab-nowplaying-module[hidden] { display: none !important; }
.crab-nowplaying-module {
  /* Match the body content width (page-header / table) instead of spanning the full
     viewport: same max-width + centered + 1.5rem side padding as .page-header. */
  max-width: var(--max-width, 1200px);
  margin: 0 auto;
  padding: 1.5rem 1.5rem 0;
}
.crab-nowplaying-module__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--crab-banner-live);
  margin: 0 0 0.5rem;
}
.crab-now-playing--music {
  margin: 0;
  padding: 0.85rem 1rem;
  gap: 1rem;
  background: linear-gradient(to bottom, var(--crab-banner-bg), #23201a);
  border-radius: 14px;
}
.crab-now-playing--music .crab-now-playing__art { width: 84px; height: 84px; border-radius: 8px; }
.crab-now-playing--music .crab-now-playing__song { font-size: 1.1rem; }

/* The homepage "We're live!" card, the public /music/ module, and the /studio/ live
   preview share one layout: the streaming-link chips sit on their OWN full-width row,
   below the art + song info. Their links div is a direct child of .crab-now-playing
   (moved out of __body) so flex-wrap + a 100% basis can drop it to the next line — at
   every width. (The top banner carries no links and is excluded.) */
.crab-next-show .crab-now-playing,
.crab-nowplaying-module .crab-now-playing,
.crab-studio__preview .crab-now-playing { flex-wrap: wrap; }
.crab-next-show .crab-now-playing__links,
.crab-nowplaying-module .crab-now-playing__links,
.crab-studio__preview .crab-now-playing__links { flex-basis: 100%; }

/* Public "recently played" — a quiet, read-only disclosure under the /music/ module. It
   inherits the live-module identity (dark card, --crab-banner-* tokens, .crab-np-chip pills);
   the timestamp column is the structural device — a live set unfolding in real time. */
.crab-np-recent[hidden] { display: none !important; }
.crab-np-recent { margin: 0.6rem 0 0; }

/* Homepage "We're live!" panel: "Recently played" belongs to the now-playing
   card, so snug it up under the dark box; the space it gives up moves below it
   so the Listen Live button stays put and reads as standing on its own.
   Scoped to [data-next-live] so the /music/ live module is unchanged. */
.crab-next-show [data-next-live] .crab-now-playing { margin-bottom: 0.3rem; }
.crab-next-show [data-next-live] .crab-np-recent { margin-top: 0; }
.crab-next-show [data-next-live] .crab-np-recent + .crab-next-show__actions { margin-top: 0.7rem; }

.crab-np-recent__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  background: none;
  border: 0;
  padding: 0.3rem 0.1rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--crab-banner-muted);
  transition: color 140ms ease;
}
.crab-np-recent__toggle:hover { color: var(--crab-banner-fg); }
.crab-np-recent__toggle:focus-visible {
  outline: 2px solid var(--crab-banner-accent);
  outline-offset: 2px;
  border-radius: 4px;
}
.crab-np-recent__chevron {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform var(--dur-fast, 140ms) var(--ease-out, ease);
}
.crab-np-recent__toggle[aria-expanded="true"] .crab-np-recent__chevron { transform: rotate(-180deg); }

/* Instant show/hide — deliberately NO height animation. Animating grid-template-rows
   here forced a full re-layout of the long /music/ song table on every frame of the
   expand (multi-second cursor lag on that page); a plain display toggle is free. */
.crab-np-recent__reveal[data-reveal="closed"] { display: none; }

.crab-np-recent__list {
  list-style: none;
  margin: 0.35rem 0 0;
  padding: 0;
}
.crab-np-recent__row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--crab-banner-border);
}
.crab-np-recent__row:first-child { border-top: 0; }
.crab-np-recent__time {
  flex: none;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.72rem;
  color: var(--crab-banner-muted);
  padding-top: 0.15rem;
  min-width: 2.6em;
}
.crab-np-recent__art {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 4px;
  object-fit: cover;
  background: #36322c;
}
.crab-np-recent__body { min-width: 0; flex: 1; }
.crab-np-recent__main {
  font-size: 0.9rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.crab-np-recent__seg {
  font-style: italic;
  color: var(--crab-banner-accent);
}
.crab-np-recent__sub {
  font-size: 0.78rem;
  font-style: italic;
  color: var(--crab-banner-muted);
  margin-top: 0.1rem;
  overflow-wrap: anywhere;
}
.crab-np-recent__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.4rem;
}
/* Smaller chips than the now-playing card — the recent rows are supporting, not the hero. */
.crab-np-recent__links .crab-np-chip {
  padding: 0.12rem 0.5rem;
  font-size: 0.7rem;
}

/* Compact variant: the top live banner. Chrome stripped; height bounded to the art,
   with song/artist/album pinned to the art's top/center/bottom edges. */
.crab-now-playing--banner {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  gap: 0.6rem;
  align-items: stretch;
}
.crab-now-playing--banner .crab-now-playing__art {
  width: 60px;
  height: 60px;
  border-radius: 4px;
}
.crab-now-playing--banner .crab-now-playing__body {
  display: flex;
  flex-direction: column;
  justify-content: space-between; /* song → top, artist → center, album → bottom */
  min-width: 0;
}
/* Single-line rows with ellipsis so a long title can't blow out the banner
   (overrides the base modules' overflow-wrap: anywhere). Zero the inherited
   per-line margins and use one line-height so space-between spaces the three
   rows evenly within the (taller) art height. */
.crab-now-playing--banner .crab-now-playing__song,
.crab-now-playing--banner .crab-now-playing__artist,
.crab-now-playing--banner .crab-now-playing__album {
  margin: 0;
  line-height: 1.3;
  max-width: 220px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  overflow-wrap: normal;
}
.crab-now-playing--banner .crab-now-playing__song { font-size: 0.82rem; font-weight: 700; }
.crab-now-playing--banner .crab-now-playing__artist { font-size: 0.78rem; }
.crab-now-playing--banner .crab-now-playing__album { font-size: 0.72rem; }

/* Marquee for banner song-info fields that overflow their (width-capped) column on
   narrow screens. live-banner.js wraps the text in .crab-np-marquee-inner, measures the
   overflow, and sets --marquee-shift (negative px) + --marquee-dur, then adds .is-marquee.
   The field clips (overflow:hidden, above); the inner span ping-pongs left↔right with a
   brief hold at each end (the 6%/47–53%/94% keyframe stops), ease-in-out, slowly. */
.crab-np-marquee-inner { display: inline-block; white-space: nowrap; }
.is-marquee > .crab-np-marquee-inner {
  animation: crab-marquee var(--marquee-dur, 6s) ease-in-out infinite;
  will-change: transform;
}
@keyframes crab-marquee {
  0%, 6%    { transform: translateX(0); }
  47%, 53%  { transform: translateX(var(--marquee-shift, 0)); }
  94%, 100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .is-marquee > .crab-np-marquee-inner { animation: none; transform: none; }
}

/* Push everything after the meta text (the now-playing module + the CTA) into the
   right-hand void. The text stops growing and takes an auto right margin that eats the
   free space — so the CTA hugs the right edge whether or not the now-playing module is
   present. Scoped to the live states. */
.crab-live-banner[data-state="live-rotation"] .crab-live-banner__text,
.crab-live-banner[data-state="live-studio"]   .crab-live-banner__text {
  flex: 0 1 auto;
  margin-right: auto;
}

@media (max-width: 480px) {
  .crab-now-playing { flex-direction: column; align-items: flex-start; }
  .crab-now-playing--music .crab-now-playing__art { width: 90px; height: 90px; }
  /* …but the top banner's mini card stays INLINE — art beside the track text, never
     stacked. The generic column rule above is for the homepage/ /music/ cards; the
     banner is a compact horizontal strip and the space-between body layout needs the
     art's height to spread song/artist/album across. */
  .crab-now-playing--banner { flex-direction: row; align-items: stretch; }
  /* …and the homepage "We're live!" card + the /music/ module ALSO stay inline: art on
     the left, song/artist/album to its right (art size unchanged). The links still wrap
     to their own full-width row below, via the flex-wrap rules above. */
  .crab-next-show .crab-now-playing,
  .crab-nowplaying-module .crab-now-playing,
  .crab-studio__preview .crab-now-playing { flex-direction: row; align-items: center; }
}
@media (max-width: 360px) {
  .crab-now-playing { padding: 0.6rem; gap: 0.6rem; }
  .crab-now-playing__art { width: 64px; height: 64px; }
  .crab-now-playing--music { padding: 0.85rem 0.9rem; }
  .crab-now-playing--music .crab-now-playing__art { width: 72px; height: 72px; }
  .crab-now-playing__song { font-size: 1rem; }
  .crab-now-playing--music .crab-now-playing__song { font-size: 1.1rem; }
  .crab-now-playing__artist { font-size: 0.88rem; }
  /* Keep recent rows horizontal but tighter so a long title can't blow out the viewport. */
  .crab-np-recent__row { gap: 0.45rem; }
  .crab-np-recent__art { width: 34px; height: 34px; }
  .crab-np-recent__time { min-width: 0; }
}

/* ---------- Schedule-page special-broadcasts list ---------- */

.schedule-exceptions[hidden] { display: none !important; }
.schedule-exceptions {
  background: var(--crab-next-bg);
  border: 1px solid var(--crab-banner-studio);
  border-left-width: 4px;
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin: 0 0 1.5rem;
  color: var(--crab-banner-fg);
}
.schedule-exceptions h2 {
  margin: 0 0 0.35rem;
  font-size: 1.15rem;
  color: var(--crab-banner-studio);
}
.schedule-exceptions__intro {
  margin: 0 0 0.6rem;
  color: var(--crab-banner-muted);
  font-size: 0.9rem;
}
.schedule-exceptions__list {
  margin: 0;
  padding-left: 1.1rem;
  line-height: 1.5;
}
.schedule-exceptions__list li { margin: 0.15rem 0; }
