/* Crates — mock UI: a crate of records */

* {
  box-sizing: border-box;
}

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

/* soft, light — not stark white: a gentle wash of blue + peach like
   an Apple wallpaper, kept airy and bright. The whole wash is a
   theme token, so a skin re-tints the room. */
body {
  background: var(--app-bg);
  color: var(--app-ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, sans-serif;
}

/* ---- Centered stage: player on top, covers below. Height is locked
   to the viewport and overflow is hidden, so the page never scrolls —
   the composition centres on any screen from medium up. ---- */
.stage {
  position: relative;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 20px 22px;
  overflow: hidden;
}

.stage::before,
.stage::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  filter: blur(72px);
  opacity: 0.55;
  pointer-events: none;
}

.stage::before {
  width: 420px;
  height: 420px;
  top: 8%;
  left: 6%;
  background: var(--blob-a);
}

.stage::after {
  width: 460px;
  height: 460px;
  bottom: 6%;
  right: 8%;
  background: var(--blob-b);
}

/* ---- Crate of records ---- */
.covers {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  /* wider crate: fills most of the stage — only ~25-30% total stays
     as side margins */
  width: min(72%, 880px);
}

/* Arrow chevrons — no circle background. margin-top centers them on
   the cover art, tracking the lift headroom above the row. */
.arrow {
  width: 38px;
  height: 38px;
  margin-top: calc(75px + 10vh);
  padding: 0;
  border: none;
  background: none;
  color: var(--arrow-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color 0.15s ease, opacity 0.15s ease;
}

.arrow svg {
  width: 22px;
  height: 22px;
}

.arrow:hover:not(:disabled) {
  color: var(--arrow-ink-strong);
}

.arrow:disabled {
  opacity: 0.25;
  cursor: default;
}

/* Clips the sliding row; bottom padding leaves room for the
   stack's contact shadow and reflection; top padding leaves room for
   the picked record to lift itself up ~15% toward the top */
.covers-viewport {
  position: relative;
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
  height: calc(200px + 10vh);
  padding-top: calc(10vh + 14px);
  padding-bottom: 50px;
}

/* the crate is still being pulled together — a quiet spinner and a
   line, so the empty stretch doesn't look broken while it loads */
.crate-loading {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--arrow-ink);
  font-size: 14px;
  letter-spacing: 0.02em;
  transition: opacity 0.35s ease;
}

.crate-loading.hide {
  opacity: 0;
  pointer-events: none;
}

.crate-spinner {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid var(--display-track);
  border-top-color: var(--display-on);
  animation: crate-spin 0.8s linear infinite;
}

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

/* faint ambient/contact shadow cast by the whole stack onto the
   matte-white surface — soft, wide, no hard edge */
.covers-viewport::after {
  content: "";
  position: absolute;
  left: 5%;
  right: 5%;
  bottom: 22px;
  height: 40px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(28, 30, 35, 0.15), rgba(28, 30, 35, 0) 72%);
  filter: blur(16px);
  pointer-events: none;
  z-index: 0;
}

/* positions the sliding row and its mirror above the shadow */
.track-wrap {
  position: relative;
  z-index: 1;
  height: 100%;
}

.track {
  display: flex;
  align-items: flex-end;
  height: 100%;
  /* end margins of the crate; the whole row slides left/right */
  padding-left: 68px;
  padding-right: 68px;
  will-change: transform;
  /* no default transition — the wheel moves it directly, arrows
     animate via an inline transition from JS */
  transition: none;
}

/* each record's own floor reflection — a full flipped copy of the
   record (same art, same border, same inset edge), masked to fade from
   the seam — so the reflection flows straight out of the record with
   no gap. It's a child, so it always stays with its record.
   Hidden during the entrance cascade (a blurred layer per record would
   make the slide repaint) — it fades in once the crate settles. */
.record-mirror {
  position: absolute;
  top: 100%;
  left: 0;
  width: 112px;
  height: 112px;
  background-size: cover;
  background-position: center;
  transform: scaleY(-1);
  border-radius: 5px;
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.14),
    10px 0 14px -10px rgba(0, 0, 0, 0.25),
    0 5px 10px rgba(24, 27, 33, 0.08);
  filter: blur(1px);
  opacity: 0;
  -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0) 45%);
  mask-image: linear-gradient(to top, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0) 45%);
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.entered .record-mirror {
  opacity: 0.2;
  transition: opacity 0.4s ease 0.3s;
}

/* while a record is away at the top, its reflection is gone */
.record.picked .record-mirror {
  display: none;
}

/* hovering a record fades its reflection away */
.record:hover .record-mirror,
.record:focus .record-mirror {
  opacity: 0;
}

/* A record: sleeve standing in the row, straight and level.
   The LEFT record overlaps the one to its right (front of the deck).
   Only the on-screen window gets a composited layer (inline will-
   change, set from JS during the build) — promoting every record at
   once makes Safari choke on large libraries. */
.record {
  position: relative;
  width: 112px;
  height: 112px;
  flex: 0 0 112px;
  border-radius: 5px;
  transition: transform 0.25s cubic-bezier(0.22, 0.7, 0.22, 1);
  box-shadow:
    /* soft shadow each record casts onto the one behind (to the right) */
    10px 0 14px -10px rgba(0, 0, 0, 0.25);
}

.record + .record {
  margin-left: -16px;
}

/* a blank slot — opens up where a record is about to land, so the
   record can sit into the space instead of pushing its way in */
.record-slot {
  width: 112px;
  height: 112px;
  flex: 0 0 112px;
}

.record + .record-slot,
.record-slot + .record,
.record-slot + .record-slot {
  margin-left: -16px;
}

.record-slot:first-child {
  margin-left: 0;
}

/* hover/focus: slide or pop up from the same slot — no jumping
   in front of the others. Driven by the JS hover (wheel + mousemove)
   or focus — not native :hover, which lags during scrolling and can
   leave the previous record highlighted. */
.record:focus,
.record.hovered {
  transform: translateY(-12px);
  outline: none;
}

/* clicked: the record stays a clean cover as it travels (no thumb
   space — keeps the transition smooth); the vinyl only appears once
   it lands at the top */

/* the vinyl disc tucked inside the sleeve — hidden until the record
   lands at the top */
.record-disc {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 104px;
  height: 104px;
  border-radius: 50%;
  /* one fixed light — a soft studio reflection on the pressed body;
     it never moves, only the grooves turn beneath it */
  background:
    radial-gradient(90% 90% at 30% 16%, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0) 55%),
    linear-gradient(165deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 38%),
    radial-gradient(circle at 50% 44%, #2b2d31 0%, #1c1e22 48%, #15171b 78%, #0b0c0e 100%);
  -webkit-mask-image: radial-gradient(circle 3.5px at 50% 50%, transparent 2.5px, #000 3.5px);
  mask-image: radial-gradient(circle 3.5px at 50% 50%, transparent 2.5px, #000 3.5px);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 3px 10px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

/* the rotor carries the vinyl grooves + gloss and spins while
   playing — the disc itself stays put so its slide-out still works */
.record-disc-rotor {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  /* the pressed grooves — a quiet texture that catches the light as
     it turns, never louder than the vinyl itself */
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(0, 0, 0, 0.18) 0 0.6px,
      rgba(255, 255, 255, 0.035) 0.6px 1.1px,
      rgba(0, 0, 0, 0) 1.1px 2px);
  animation: disc-spin 5s linear infinite;
  animation-play-state: paused;
  /* own layer so the spin composites instead of repainting the
     repeating gradient every frame (Safari) */
  will-change: transform;
}

/* the vinyl only turns once the needle is down on the groove — the
   spin class is added when the audio actually starts */
body.playing.spin .record-disc-rotor {
  animation-play-state: running;
}

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

.record-disc-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  /* a picture label: the album art fills the centre of the vinyl */
  background-color: #e4e7ec;
  background-size: cover;
  background-position: center;
  -webkit-mask-image: radial-gradient(circle 3.5px at 50% 50%, transparent 2.5px, #000 3.5px);
  mask-image: radial-gradient(circle 3.5px at 50% 50%, transparent 2.5px, #000 3.5px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}

/* once a record lands at the top it becomes the big vinyl scene: the
   disc slides out of the sleeve smoothly. One studio light from the
   top-left — the sleeve and disc cast soft shadows down-right onto the
   player surface. */
.record.landed {
  box-shadow:
    /* the sleeve's own ground shadow */
    10px 16px 22px -10px rgba(24, 27, 33, 0.24),
    20px 30px 44px -18px rgba(24, 27, 33, 0.18),
    inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}

.record.landed .record-disc {
  left: 8px;
  top: 8px;
  transform: translateX(0);
  width: 235px;
  height: 235px;
  opacity: 1;
  /* the vinyl is clickable once landed — clicking it resets the record */
  pointer-events: auto;
  -webkit-mask-image: radial-gradient(circle 6px at 50% 50%, transparent 5px, #000 6px);
  mask-image: radial-gradient(circle 6px at 50% 50%, transparent 5px, #000 6px);
  box-shadow:
    /* the disc's own ground shadow, falling down-right */
    13px 18px 26px -8px rgba(24, 27, 33, 0.3),
    26px 36px 52px -18px rgba(24, 27, 33, 0.2),
    inset 0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 3px 10px rgba(0, 0, 0, 0.5);
  transition: opacity 0.4s ease, transform 0.6s cubic-bezier(0.22, 0.7, 0.22, 1);
}

.record.landed .record-disc-label {
  width: 76px;
  height: 76px;
  -webkit-mask-image: radial-gradient(circle 6px at 50% 50%, transparent 5px, #000 6px);
  mask-image: radial-gradient(circle 6px at 50% 50%, transparent 5px, #000 6px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
}

/* the vinyl slides a little more than half out of the sleeve */
.record.landed.is-out .record-disc {
  transform: translateX(var(--out-x));
}

/* clicking the vinyl resets it: a quick lift-and-settle on the disc
   alone — the sleeve stays put */
.record.landed .record-disc {
  --out-x: 140px; /* how far the vinyl sits out — shared with the bounce */
}

.record.landed .record-disc.reset-bounce {
  /* driven from script.js via WAAPI (Safari-safe) */
  animation: none;
}

@keyframes reset-bounce {
  0%,
  100% {
    transform: translateX(var(--out-x));
  }
  32% {
    transform: translateX(var(--out-x)) translateY(-16px);
  }
  72% {
    transform: translateX(var(--out-x)) translateY(3px);
  }
}

.record.landed:hover,
.record.landed:focus {
  transform: none;
}

.record.landed .record-tip {
  display: none;
}

/* the one continuous journey: pop up, then fly into the top slot.
   NOTE: the @keyframes below (fly-up / fly-in / return-off / reset-
   bounce) are intentionally dead in production — they reference var()
   inside keyframes, which Safari only supports from 16.2 (with quirks).
   script.js drives these flights with the Web Animations API instead;
   the classes here only manage layer promotion. */
.record.flying {
  will-change: transform;
  animation: none;
}

/* on load the crate spills out: each record slides in from the right
   and settles into its slot, left to right — like biscuits slipping
   out of the box to the left */
@keyframes records-in {
  from {
    opacity: 0;
    transform: translateX(360px) translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
}

.record {
  /* the stagger is set inline from JS (animationDelay) so Safari never
     has to parse var()/min() inside animation-delay */
  animation: records-in 0.6s cubic-bezier(0.25, 0.6, 0.3, 1) backwards;
}

/* once the load entrance has played, never re-run it — otherwise any
   record that gets moved (landing at the top, gliding back to its
   slot) would replay the cascade. The will-change promotion is also
   released — hundreds of promoted layers would waste memory. */
.entered .record:not(.flying):not(.flying-in):not(.returning),
#playerVinyl .record {
  animation: none;
  will-change: auto;
}

@keyframes fly-up {
  0% {
    transform: translate(0, 0) scale(1);
  }
  28% {
    /* pop straight up out of the stack */
    transform: translate(0, -15vh) scale(1.02);
  }
  100% {
    /* glide into the top slot */
    transform: translate(var(--dx), var(--dy)) scale(var(--s));
  }
}

/* a record whose slot is off the visible list: it slides in from past
   the arrow (the reverse of how it disappears on the way back), then
   rises into the deck */
.record.flying-in {
  will-change: transform, opacity;
  animation: none;
}

@keyframes fly-in {
  0% {
    transform: translate(0, 0) scale(1);
    opacity: 0;
  }
  16% {
    opacity: 1;
  }
  42% {
    /* slid in to the visible list edge */
    transform: translate(var(--mx), var(--my)) scale(1);
    opacity: 1;
  }
  100% {
    /* up into the deck */
    transform: translate(var(--dx), var(--dy)) scale(var(--s));
    opacity: 1;
  }
}

/* an off-page record returning home: it comes down into the visible
   list, then slides off past the arrow and fades out */
.record.returning {
  will-change: transform, opacity;
  animation: none;
}

@keyframes return-off {
  0% {
    transform: translate(0, 0) scale(1);
    opacity: 1;
  }
  40% {
    /* dipped down into the list at the edge */
    transform: translate(var(--mx), var(--my)) scale(var(--ms));
    opacity: 1;
  }
  100% {
    /* slid off past the arrow, faded out */
    transform: translate(var(--dx), var(--dy)) scale(var(--es));
    opacity: 0;
  }
}

.record:focus-visible {
  outline: 2px solid rgba(28, 29, 34, 0.45);
  outline-offset: 2px;
}

/* Sleeve face: picsum art + spine shading */
.record-art {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  background-color: #e4e7ec;
  background-size: cover;
  background-position: center;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.14);
}

.record-art::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 5px;
  background:
    /* spine, left */
    linear-gradient(90deg, rgba(0, 0, 0, 0.16) 0 8%, transparent 8%);
}

/* Tooltip: song name + artist above the hovered record. The frosted
   blur is only applied when the tip is actually visible — Safari keeps
   a blur context per element even at opacity 0, and with a tooltip on
   every record that would repaint the crate whenever one is shown. */
.record-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  transform: translateX(-50%) translateY(6px);
  min-width: 118px;
  padding: 7px 12px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid rgba(0, 0, 0, 0.06);
  box-shadow: 0 10px 24px rgba(24, 27, 33, 0.14);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.08s ease, transform 0.08s ease;
  z-index: 20;
}

.record-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: rgba(255, 255, 255, 0.88);
}

.record:focus .record-tip,
.record.hovered .record-tip {
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.tip-title {
  font-size: 13px;
  font-weight: 600;
  color: #1c1d22;
}

.tip-artist {
  font-size: 12px;
  color: rgba(28, 29, 34, 0.62);
}

@media (prefers-reduced-motion: reduce) {
  .track,
  .record,
  .record-disc,
  .record-tip {
    transition: none;
  }
  .record {
    animation: none;
  }
  .record-disc-rotor {
    animation: none;
  }
}

/* ---- Player (top section): the selected record's vinyl + info ---- */

/* ==== theme tokens — the whole site is driven by these. data-theme on
   <html> swaps the entire set (see the espresso / midnight blocks). ==== */
:root {
  /* the room: a light mint-green wash flowing left to right — it
     follows the display's emerald readout, the same way espresso's
     amber and midnight's blue rooms follow their displays */
  --app-bg:
    radial-gradient(1100px 700px at 12% 8%, rgba(168, 234, 202, 0.55), transparent 60%),
    radial-gradient(1000px 700px at 88% 90%, rgba(188, 240, 214, 0.45), transparent 55%),
    linear-gradient(165deg, #fbfcf8 0%, #f3f6f0 55%, #e9eee5 100%);
  --app-ink: #1c1d22;
  --blob-a: radial-gradient(circle, #d3f0e0 0%, rgba(211, 240, 224, 0) 70%);
  --blob-b: radial-gradient(circle, #d9f2e5 0%, rgba(217, 242, 229, 0) 70%);

  /* the deck: plinth, platter, brand — a warm pearl top plate */
  --plinth-bg: linear-gradient(155deg, #fdf9f3 0%, #f3ede3 55%, #e9e1d3 100%);
  --plinth-border: rgba(28, 29, 34, 0.08);
  /* the plinth's side wall — the thickness you'd see looking slightly
     down at the deck, like a Minimoog's wooden body. The visible band
     below the top plate is this material. */
  --plinth-side: linear-gradient(180deg, #ece4d3 0%, #e6dcc6 58%, #d8cab0 100%);
  /* chamfer: the top edge catches the light, the bottom edge falls
     into shade — the bevel that reads as a machined top plate */
  --plinth-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    inset 1px 0 0 rgba(255, 255, 255, 0.4),
    inset 0 -1px 0 rgba(28, 29, 34, 0.07),
    inset -1px 0 0 rgba(28, 29, 34, 0.05);
  /* the shadow the deck casts onto the surface beneath it (top-down) */
  --plinth-drop:
    0 18px 32px -12px rgba(24, 27, 33, 0.22),
    0 6px 12px -6px rgba(24, 27, 33, 0.1);
  --platter-bg: radial-gradient(circle at 36% 30%, #35363c 0%, #212226 52%, #14151a 100%);
  --platter-rim: rgba(255, 255, 255, 0.07);
  --brand-color: rgba(28, 29, 34, 0.5);
  --brand-shadow: 0 1px 0 rgba(255, 255, 255, 0.65);

  /* the info column is a digital display — a dark screen behind glass
     with glowing readout text and controls. A skin swaps the
     display-* tokens (amber VFD, blue LCD, …) while the physical deck
     keeps its own tokens, so a skin can mix any deck with any
     display. */
  --display-screen: linear-gradient(180deg, #141920 0%, #0b0e13 100%);
  --display-bezel: rgba(255, 255, 255, 0.09);
  --display-frame: #171b21; /* the bezel frame around the glass */
  --display-on: #63e6b0;
  --display-dim: rgba(99, 230, 176, 0.55);
  --display-faint: rgba(99, 230, 176, 0.16);
  --display-track: rgba(99, 230, 176, 0.14);
  --display-glow: rgba(99, 230, 176, 0.4);
  --display-ink: #08120c;
  --display-press: #3ec792; /* a control pressed — deeper, calmer */

  --arrow-ink: rgba(28, 29, 34, 0.55);
  --arrow-ink-strong: rgba(28, 29, 34, 0.9);
}

/* espresso — a warm walnut deck with an amber VFD readout */
:root[data-theme='espresso'] {
  --app-bg:
    radial-gradient(1100px 700px at 12% 8%, rgba(255, 176, 96, 0.16), transparent 60%),
    radial-gradient(1000px 700px at 88% 90%, rgba(255, 132, 64, 0.12), transparent 55%),
    linear-gradient(165deg, #271c10 0%, #1d1509 55%, #161008 100%);
  --app-ink: #f0e5d4;
  --blob-a: radial-gradient(circle, rgba(255, 176, 96, 0.18), rgba(255, 176, 96, 0) 70%);
  --blob-b: radial-gradient(circle, rgba(255, 132, 64, 0.12), rgba(255, 132, 64, 0) 70%);

  --plinth-bg: linear-gradient(155deg, #423324 0%, #362a1c 55%, #2a2014 100%);
  --plinth-border: rgba(255, 214, 160, 0.14);
  --plinth-side: linear-gradient(180deg, #5b452c 0%, #4d3a25 58%, #3c2d1b 100%);
  --plinth-shadow:
    inset 0 1px 0 rgba(255, 214, 160, 0.22),
    inset 1px 0 0 rgba(255, 214, 160, 0.08),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4),
    inset -1px 0 0 rgba(0, 0, 0, 0.28);
  --plinth-drop:
    0 18px 32px -12px rgba(0, 0, 0, 0.45),
    0 6px 12px -6px rgba(0, 0, 0, 0.25);
  --platter-bg: radial-gradient(circle at 36% 30%, #2e2b26 0%, #1c1a16 52%, #100e0b 100%);
  --platter-rim: rgba(255, 190, 110, 0.09);
  --brand-color: rgba(255, 224, 178, 0.5);
  --brand-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);

  --display-screen: linear-gradient(180deg, #1d1205 0%, #140c02 100%);
  --display-bezel: rgba(255, 196, 110, 0.12);
  --display-frame: #241705;
  --display-on: #ffb84d;
  --display-dim: rgba(255, 184, 77, 0.55);
  --display-faint: rgba(255, 184, 77, 0.16);
  --display-track: rgba(255, 184, 77, 0.14);
  --display-glow: rgba(255, 184, 77, 0.4);
  --display-ink: #170d02;
  --display-press: #e2922a;

  --arrow-ink: rgba(255, 224, 178, 0.55);
  --arrow-ink-strong: rgba(255, 224, 178, 0.9);
}

/* midnight — a graphite deck with a cyan-blue LCD readout */
:root[data-theme='midnight'] {
  --app-bg:
    radial-gradient(1100px 700px at 12% 8%, rgba(96, 166, 255, 0.13), transparent 60%),
    radial-gradient(1000px 700px at 88% 90%, rgba(128, 142, 255, 0.1), transparent 55%),
    linear-gradient(165deg, #11161e 0%, #0c1017 55%, #0a0d13 100%);
  --app-ink: #dde7f1;
  --blob-a: radial-gradient(circle, rgba(96, 166, 255, 0.16), rgba(96, 166, 255, 0) 70%);
  --blob-b: radial-gradient(circle, rgba(128, 142, 255, 0.1), rgba(128, 142, 255, 0) 70%);

  --plinth-bg: linear-gradient(155deg, #2d3038 0%, #24272e 55%, #1b1e24 100%);
  --plinth-border: rgba(255, 255, 255, 0.09);
  --plinth-side: linear-gradient(180deg, #3c4048 0%, #33373f 58%, #292c33 100%);
  --plinth-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    inset 1px 0 0 rgba(255, 255, 255, 0.05),
    inset 0 -1px 0 rgba(0, 0, 0, 0.45),
    inset -1px 0 0 rgba(0, 0, 0, 0.3);
  --plinth-drop:
    0 18px 32px -12px rgba(0, 0, 0, 0.5),
    0 6px 12px -6px rgba(0, 0, 0, 0.3);
  --platter-bg: radial-gradient(circle at 36% 30%, #272b32 0%, #171a20 52%, #0d1015 100%);
  --platter-rim: rgba(140, 200, 255, 0.09);
  --brand-color: rgba(186, 224, 255, 0.5);
  --brand-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);

  --display-screen: linear-gradient(180deg, #0a141c 0%, #060d13 100%);
  --display-bezel: rgba(140, 210, 255, 0.12);
  --display-frame: #0e1722;
  --display-on: #6fd8ff;
  --display-dim: rgba(111, 216, 255, 0.55);
  --display-faint: rgba(111, 216, 255, 0.16);
  --display-track: rgba(111, 216, 255, 0.14);
  --display-glow: rgba(111, 216, 255, 0.4);
  --display-ink: #051018;
  --display-press: #39aede;

  --arrow-ink: rgba(186, 224, 255, 0.55);
  --arrow-ink-strong: rgba(186, 224, 255, 0.9);
}

.player {
  position: relative;
}

/* the plinth: the deck the scene sits on — pure backdrop, no animation.
   It wraps the whole player, so the empty state is a real deck. Seen
   from directly above: the inset shadow is the top plate's chamfer
   (light above, shade below) and the drop shadow is what the whole
   deck casts on the surface under it. */
.deck {
  --pl: 12px; /* left padding */
  --pt: 28px;
  --pr: 20px;
  --pb: 38px;
  position: relative;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: var(--pt) var(--pr) var(--pb) var(--pl);
  border-radius: 22px;
  background: var(--plinth-bg);
  border: 1px solid var(--plinth-border);
  box-shadow: var(--plinth-shadow), var(--plinth-drop);
}

/* the brand — a signature maker's mark engraved on the plinth's
   bottom-right */
.deck-brand {
  position: absolute;
  right: calc(var(--pr) + 6px);
  bottom: 18px;
  margin: 0;
  z-index: 2;
  pointer-events: none;
  user-select: none;
}

.brand-name {
  font-family: 'Monsieur La Doulaise', 'Snell Roundhand', 'Segoe Script', cursive;
  font-size: 32px;
  line-height: 1;
  color: var(--brand-color);
  text-shadow: var(--brand-shadow);
}

/* the theme switches — three physical square keys on the plinth,
   top-right. The active key stays pressed in; the others pop out.
   Each key is its theme's accent. */
.deck-themes {
  position: absolute;
  right: calc(var(--pr) + 6px);
  top: 12px;
  display: flex;
  gap: 8px;
  z-index: 2;
}

.theme-btn {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 6px;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  /* a glossy keycap lit from inside: the accent face carries a soft
     radial reflection near the top and a faint light pooling at the
     bottom — like light shining through the key */
  background:
    radial-gradient(75% 60% at 32% 25%, rgba(255, 255, 255, 0.65), rgba(255, 255, 255, 0) 55%),
    radial-gradient(120% 100% at 50% 115%, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0) 55%),
    var(--acc);
  box-shadow:
    inset 0 1px 1px rgba(255, 255, 255, 0.55),
    inset 0 -1px 2px rgba(0, 0, 0, 0.25),
    0 2px 3px rgba(0, 0, 0, 0.35),
    0 1px 1px rgba(0, 0, 0, 0.25);
  transition: box-shadow 0.15s ease, transform 0.1s ease, filter 0.15s ease;
}

.theme-btn:hover {
  filter: brightness(1.1);
}

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

/* the chosen key stays in — the face sinks into the plinth */
.theme-btn.is-pressed {
  transform: translateY(1px);
  filter: brightness(0.8);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.45),
    inset 0 -1px 0 rgba(255, 255, 255, 0.12),
    0 1px 1px rgba(0, 0, 0, 0.3);
}

/* empty state — just the connect button, centred in the same
   compact, recessed control well the info column occupies, so the two
   swap without any size jump. The deck itself (platter, parked arm,
   branded base) carries the empty look, so there's nothing else to
   say. */
.player-empty {
  position: absolute;
  left: calc(var(--pl) + 480px + 18px - 40px);
  right: var(--pr);
  top: calc(var(--pt) + (100% - var(--pt) - var(--pb)) / 2);
  height: 203px;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  /* the same device — bezel frame + glass screen — so the two swap
     seamlessly */
  border: 6px solid var(--display-frame);
  border-radius: 16px;
  background: var(--display-screen);
  box-shadow:
    inset 0 0 0 1px var(--display-bezel),
    inset 0 2px 10px rgba(0, 0, 0, 0.55),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06);
  opacity: 1;
  transition: opacity 0.45s ease;
  pointer-events: none;
}

.player-empty::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 40%);
  pointer-events: none;
  z-index: 1;
}

.player-empty.hide {
  opacity: 0;
}

/* connect button — shown in the placeholder when not signed in */
.login-btn {
  margin-top: 4px;
  padding: 12px 26px;
  border: none;
  border-radius: 999px;
  background: var(--display-on);
  color: var(--display-ink);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 0 16px var(--display-glow), 0 8px 20px rgba(0, 0, 0, 0.35);
  pointer-events: auto; /* the placeholder itself is click-through */
  transition: transform 0.12s ease, background 0.15s ease;
}

.login-btn:hover {
  filter: brightness(1.1);
}

.login-btn:active {
  transform: scale(0.97);
}

.player-vinyl {
  position: relative;
  flex: 0 0 auto;
  width: 480px;
  height: 270px;
}

/* ---- Tonearm: pivot top-right, needle tracks the groove ---- */
.tonearm {
  position: absolute;
  inset: 0;
  z-index: 30; /* above the landed record, below nothing interactive */
  width: 100%;
  height: 100%;
  overflow: visible; /* the bearing housing may peek past the corner */
  pointer-events: none;
}

.player-info {
  /* fixed width so the player doesn't re-center when the details
     appear — otherwise the landed record would jump. The panel is a
     device: a dark bezel frame around the glass screen. The size never
     changes (titles are single-line, so the height is constant);
     overflow is clipped so a long name can never flow outside the
     deck, and the bottom padding keeps the play button's shadow inside
     that clip. */
  width: 340px;
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 16px 20px 24px;
  /* the bezel frame + the glass screen inside it */
  border: 6px solid var(--display-frame);
  border-radius: 16px;
  background: var(--display-screen);
  box-shadow:
    inset 0 0 0 1px var(--display-bezel),
    inset 0 2px 10px rgba(0, 0, 0, 0.55),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06);
  opacity: 0;
  transition: opacity 0.4s ease;
}

/* the glass sheen — a subtle diagonal reflection across the screen */
.player-info::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 40%);
  pointer-events: none;
  z-index: 1;
}

.player-info.show {
  opacity: 1;
}

/* ---- Play controls (bottom of the artist info): prev / play / next ---- */
.player-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin-top: 14px;
}

.ctl {
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  border-radius: 50%;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  color: var(--display-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.12s ease, box-shadow 0.15s ease;
}

/* a clean circular focus ring instead of the default square outline */
.ctl:focus-visible {
  outline: 2px solid var(--display-on);
  outline-offset: 3px;
}

.ctl:hover {
  background: var(--display-faint);
  color: var(--display-on);
}

.ctl:active {
  transform: scale(0.92);
}

.ctl svg {
  width: 20px;
  height: 20px;
}

.ctl-play {
  width: 56px;
  height: 56px;
  background: var(--display-on);
  color: var(--display-ink);
  /* a lit key: a soft glow, never a square edge */
  box-shadow:
    0 0 14px var(--display-glow),
    0 4px 14px rgba(0, 0, 0, 0.4),
    0 1px 3px rgba(0, 0, 0, 0.3);
}

.ctl-play:hover {
  background: var(--display-press);
  transform: translateY(-1px);
  box-shadow:
    0 0 14px var(--display-glow),
    0 4px 14px rgba(0, 0, 0, 0.42),
    0 1px 3px rgba(0, 0, 0, 0.3);
}

.ctl-play svg {
  width: 22px;
  height: 22px;
}

/* play <-> pause icon swap while playing */
.player-controls .icon-pause {
  display: none;
}

body.playing .player-controls .icon-play {
  display: none;
}

body.playing .player-controls .icon-pause {
  display: block;
}

/* ---- Seeker: thin progress bar above the play controls ---- */
.seek-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 16px;
}

.seeker {
  position: relative;
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: var(--display-track);
  cursor: pointer;
  touch-action: none;
}

.seeker-fill {
  position: relative;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--display-on);
  box-shadow: 0 0 6px var(--display-glow);
}

.seeker-fill::after {
  content: "";
  position: absolute;
  right: -6px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--display-on);
  box-shadow: 0 0 8px var(--display-glow);
}

.seek-time {
  min-width: 34px;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--display-dim);
}

.seek-time:first-child {
  text-align: left;
}

.seek-time:last-child {
  text-align: right;
}

/* the readout: a big mono line for the title, glowing like lit
   segments; the artist is the dimmer sub-line of the same display */
.detail-title {
  margin: 0 0 6px;
  font-size: 26px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.15;
  width: 100%;
  max-width: 100%;
  white-space: nowrap;
  font-family: 'Bitcount Prop Single', 'Space Grotesk', ui-monospace, 'SF Mono', 'Cascadia Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
  color: var(--display-on);
  text-shadow: 0 0 10px var(--display-glow);
}

.detail-artist {
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  width: 100%;
  max-width: 100%;
  white-space: nowrap;
  font-family: 'Bitcount Prop Single', 'Space Grotesk', ui-monospace, 'SF Mono', 'Cascadia Mono', 'SFMono-Regular', Menlo, Consolas, monospace;
  color: var(--display-dim);
  text-shadow: 0 0 8px var(--display-glow);
}

/* a name too long for its slot becomes a gentle marquee: it slides to
   reveal the tail, holds, then slides back — never wraps, and the info
   column clips it so it can't leave the deck. The scroll itself is
   driven from script.js via WAAPI (var() inside @keyframes is
   unsupported in Safari < 16.2, so the CSS keyframes below are dead). */
.detail-title.mq,
.detail-artist.mq {
  animation: none;
  will-change: transform;
}

@keyframes mq-scroll {
  0%,
  10% {
    transform: translateX(0);
  }
  55% {
    transform: translateX(calc(-1 * var(--mq-d)));
  }
  80% {
    transform: translateX(calc(-1 * var(--mq-d)));
  }
  100% {
    transform: translateX(0);
  }
}

/* ---- Album-art lightbox: frosted-glass zoom (Apple-style) ---- */
/* the lightbox is hidden by the hidden attribute unless opened —
   display:flex below must never override it, or the invisible layer
   would sit over the page and swallow every click */
.art-lightbox[hidden] {
  display: none;
}

.art-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  /* frosted but see-through — a light glass, not a wall */
  background: rgba(246, 248, 251, 0.35);
  -webkit-backdrop-filter: blur(12px) saturate(1.05);
  backdrop-filter: blur(12px) saturate(1.05);
  opacity: 0;
  transition: opacity 0.32s ease;
  cursor: zoom-out;
}

.art-lightbox.open {
  opacity: 1;
}

.art-lightbox-img {
  width: min(76vw, 56vh);
  height: min(76vw, 56vh);
  object-fit: cover;
  border-radius: 18px;
  transform: scale(0.94);
  opacity: 0;
  transition: transform 0.4s cubic-bezier(0.22, 0.7, 0.22, 1), opacity 0.28s ease;
  pointer-events: none;
}

.art-lightbox.open .art-lightbox-img {
  transform: scale(1);
  opacity: 1;
}
