/* ============================================================
   JAM BOX player — a 2x2 rack of machined panels: playlist (TL),
   LED equalizer (TR), lyrics (BL), vote (BR).

   The chassis IS the design. Every panel is a screwed-down metal
   face with a recessed screen sunk into it, so the page reads as
   hardware rather than as four divs with borders. Two rules keep
   that illusion honest everywhere:
     - metal catches light on its top edge, screens are lit from
       inside and sink on their top edge (inset shadow, top-heavy);
     - nothing that glows sits flush with the panel face.
   ============================================================ */

@font-face {
  font-family: 'VT323';
  src: url('jam/vt323.woff2') format('woff2');
  font-display: swap;
}

html, body { margin: 0; padding: 0; height: 100%; background: #07060a; }
body { overflow: hidden; }
@media (max-width: 900px) { body { overflow: auto; } }

:root {
  --jam-amber: #ffa32b;
  --jam-amber-hot: #ffdca8;
  --jam-amber-dim: rgba(255, 163, 43, 0.32);
  --jam-green: #57e06a;
  --jam-green-hot: #c8ffd2;
  --jam-red: #ff4b3a;
  --jam-label: #cec4b1;

  --jam-face-hi: #2e271f;
  --jam-face-lo: #100d0a;
  --jam-edge: #070503;

  /* One screw, reused at four corners of every panel. Drawn rather than
     shipped: it has to stay crisp at any DPR and tint with the chassis. */
  --jam-screw: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cdefs%3E%3CradialGradient id='s' cx='.34' cy='.28' r='.78'%3E%3Cstop offset='0' stop-color='%237d715e'/%3E%3Cstop offset='.55' stop-color='%23342d24'/%3E%3Cstop offset='1' stop-color='%23110e0b'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='8' cy='8' r='6.3' fill='url(%23s)' stroke='%23080605' stroke-width='1.1'/%3E%3Cpath d='M4.7 10.3 11.3 5.7' stroke='%230a0806' stroke-width='1.8' stroke-linecap='round'/%3E%3Cpath d='M4.9 9.6 11.5 5' stroke='%23968a76' stroke-width='.55' stroke-linecap='round' opacity='.4'/%3E%3C/svg%3E");

  /* Cast grain. Without it the faces read as flat brown fills next to the
     texture of the screens, and the whole rack looks like CSS again. */
  --jam-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23n)' opacity='.15'/%3E%3C/svg%3E");
}

.jam-grid-page {
  height: 100vh;
  height: 100dvh;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 16px;
  padding: 16px;
  background:
    radial-gradient(ellipse 90% 70% at 50% 0%, #17130f 0%, transparent 70%),
    #07060a;
  color: var(--jam-label);
  font-family: 'VT323', ui-monospace, monospace;
  overflow: hidden;
}

/* ---------- the panel: brushed face, bevelled rim, four screws ---------- */
.jam-cell {
  position: relative;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  background:
    var(--jam-screw), var(--jam-screw), var(--jam-screw), var(--jam-screw),
    var(--jam-noise),
    repeating-linear-gradient(90deg,
      rgba(255, 240, 214, 0.016) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, var(--jam-face-hi) 0%, #1b1611 42%, var(--jam-face-lo) 100%);
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, repeat, no-repeat, no-repeat;
  background-size: 13px 13px, 13px 13px, 13px 13px, 13px 13px, 150px 150px, auto, auto;
  background-position:
    11px 11px, calc(100% - 11px) 11px,
    11px calc(100% - 11px), calc(100% - 11px) calc(100% - 11px),
    0 0, 0 0, 0 0;
  box-shadow:
    inset 0 1px 0 rgba(255, 234, 200, 0.13),
    inset 0 0 0 1px rgba(0, 0, 0, 0.6),
    inset 0 -2px 6px rgba(0, 0, 0, 0.55),
    0 16px 34px rgba(0, 0, 0, 0.62);
  overflow: hidden;
}

.jam-cell-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 9px 30px 7px;
  flex-shrink: 0;
}

.jam-cell-title {
  flex: 1;
  font-size: 1.05rem;
  letter-spacing: 0.3em;
  color: var(--jam-label);
  text-align: center;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
}

/* ---------- machined keys (back link, transport, vote) ---------- */
.jam-back,
.jam-transport button,
.jam-votebtn {
  font-family: inherit;
  color: var(--jam-amber);
  background: linear-gradient(180deg, #352d22 0%, #1d1811 55%, #120e0a 100%);
  border: 1px solid var(--jam-edge);
  border-radius: 7px;
  box-shadow:
    inset 0 1px 0 rgba(255, 220, 170, 0.16),
    inset 0 -2px 4px rgba(0, 0, 0, 0.6),
    0 2px 4px rgba(0, 0, 0, 0.55);
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.12s, transform 0.06s, box-shadow 0.12s;
}
.jam-back:hover,
.jam-transport button:hover,
.jam-votebtn:hover { filter: brightness(1.28); }
.jam-back:active,
.jam-transport button:active,
.jam-votebtn:active {
  transform: translateY(2px);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.75);
}
.jam-back:focus-visible,
.jam-transport button:focus-visible,
.jam-votebtn:focus-visible,
.jam-row:focus-visible {
  outline: 2px solid var(--jam-amber);
  outline-offset: 2px;
}

.jam-back {
  font-size: 1rem;
  letter-spacing: 0.1em;
  padding: 3px 12px;
  white-space: nowrap;
}

.jam-transport {
  display: flex;
  gap: 5px;
  padding: 4px;
  border-radius: 9px;
  background: linear-gradient(180deg, #0d0b08, #191410);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.8), 0 1px 0 rgba(255, 225, 180, 0.07);
}
.jam-transport button {
  font-size: 0.95rem;
  line-height: 1;
  padding: 4px 11px;
  text-shadow: 0 0 7px rgba(255, 163, 43, 0.55);
}

/* ---------- the recessed screen every panel is built around ---------- */
.jam-screen {
  position: relative;
  flex: 1;
  min-height: 0;
  margin: 0 16px 16px;
  display: flex;
  flex-direction: column;
  border-radius: 9px;
  overflow: hidden;
  box-shadow:
    inset 0 4px 16px rgba(0, 0, 0, 0.95),
    inset 0 0 0 1px rgba(0, 0, 0, 0.9),
    0 1px 0 rgba(255, 230, 190, 0.1);
}
/* Phosphor bloom + scanlines + the corner falloff of a curved tube. Pointer
   events off so it never eats a click on the playlist underneath. */
.jam-screen::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.34) 0 1px, transparent 1px 3px),
    radial-gradient(ellipse 78% 74% at 50% 45%, transparent 55%, rgba(0, 0, 0, 0.55) 100%);
}

.jam-screen--green { background: #041a0b; }
.jam-screen--amber { background: #0b0703; }
.jam-screen--eq    { background: #060806; }
.jam-screen--heart { background: #0a0605; }

/* ---------- playlist ---------- */
.jam-playlist {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 14px;
  color: var(--jam-green);
  scrollbar-width: thin;
  scrollbar-color: rgba(87, 224, 106, 0.28) transparent;
}
.jam-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  background: transparent;
  border: 0;
  border-radius: 5px;
  padding: 5px 10px;
  font: inherit;
  font-size: 1.3rem;
  line-height: 1.45;
  color: var(--jam-green);
  text-align: left;
  cursor: pointer;
  text-shadow: 0 0 7px rgba(87, 224, 106, 0.4);
}
.jam-row:hover { background: rgba(87, 224, 106, 0.09); }
.jam-row[data-active] {
  background: linear-gradient(90deg, rgba(87, 224, 106, 0.32), rgba(87, 224, 106, 0.13));
  box-shadow:
    inset 0 0 0 1px rgba(150, 255, 175, 0.45),
    0 0 20px rgba(87, 224, 106, 0.22);
  color: var(--jam-green-hot);
  text-shadow: 0 0 10px rgba(150, 255, 175, 0.8);
}
.jam-row-num { flex-shrink: 0; font-variant-numeric: tabular-nums; }
.jam-row-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-row-time { flex-shrink: 0; font-variant-numeric: tabular-nums; opacity: 0.75; }

/* The cue flag and the reel are the row's state, not ornament: the flag marks
   which track is loaded, the reel only turns while audio is actually running. */
.jam-row-flag { flex-shrink: 0; width: 1ch; opacity: 0; }
.jam-row[data-active] .jam-row-flag { opacity: 1; }
.jam-row-reel { flex-shrink: 0; width: 19px; height: 19px; display: none; }
.jam-row[data-active] .jam-row-reel { display: block; }
.jam-row-reel circle, .jam-row-reel path { stroke: var(--jam-green-hot); fill: none; }
@keyframes jam-reel-spin { to { transform: rotate(360deg); } }
.jam-grid-page[data-playing] .jam-row[data-active] .jam-row-reel {
  animation: jam-reel-spin 2.6s linear infinite;
}

.jam-nowbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: rgba(0, 0, 0, 0.55);
  border-top: 1px solid rgba(87, 224, 106, 0.22);
  color: var(--jam-amber);
  text-shadow: 0 0 8px currentColor;
  flex-shrink: 0;
}
.jam-dot {
  width: 9px; height: 9px; flex-shrink: 0; border-radius: 2px;
  background: #3a2408;
  box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.8);
}
.jam-dot[data-on] { background: var(--jam-amber); box-shadow: 0 0 9px var(--jam-amber); }
.jam-now { flex: 1; font-size: 1.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jam-timer { font-size: 1.1rem; font-variant-numeric: tabular-nums; }

/* ---------- LED equalizer ---------- */
.jam-eq-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: 1fr auto;
  gap: 7px 9px;
  padding: 14px 14px 11px;
}
#eqCanvas { width: 100%; height: 100%; display: block; min-width: 0; min-height: 0; }
.jam-eq-db,
.jam-eq-hz,
.jam-eq-unit {
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  color: rgba(206, 196, 177, 0.55);
  font-variant-numeric: tabular-nums;
}
.jam-eq-db {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
  line-height: 1;
}
.jam-eq-hz { display: flex; justify-content: space-between; }
.jam-eq-unit { align-self: end; }

/* ---------- lyrics ---------- */
.jam-lyrics-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 18px 30vh;
  text-align: left;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 163, 43, 0.25) transparent;
}
/* Exactly one line is bright at any moment -- the one being sung. Sung lines
   were at 0.75 alpha, close enough to the active line's full amber that the
   whole block above read as "current" and the highlight didn't stand out. */
.jam-lyric-line {
  display: grid;
  grid-template-columns: 4.4em 1.1em 1fr;
  align-items: baseline;
  gap: 0 0.5em;
  font-size: 1.35rem;
  line-height: 1.52;
  color: rgba(255, 163, 43, 0.38);
  transition: color 0.18s, text-shadow 0.18s;
}
/* Caret, rule and colour all move on the same clock. When only colour was
   eased the marks snapped ahead of it, and for a quarter second the line
   above the caret was still the brightest thing on the sheet. */
.jam-lyric-t {
  font-variant-numeric: tabular-nums;
  color: rgba(255, 163, 43, 0.3);
  font-size: 0.92em;
  transition: color 0.18s;
}
.jam-lyric-caret { opacity: 0; transition: opacity 0.18s; }
.jam-lyric-x {
  justify-self: start;
  border-bottom: 1px solid transparent;
  transition: border-color 0.18s;
}
.jam-lyric-line.sung { color: rgba(255, 163, 43, 0.5); }
.jam-lyric-line.active {
  color: var(--jam-amber-hot);
  text-shadow: 0 0 12px rgba(255, 163, 43, 0.75);
}
.jam-lyric-line.active .jam-lyric-t { color: var(--jam-amber); }
.jam-lyric-line.active .jam-lyric-caret { opacity: 1; color: var(--jam-amber); }
.jam-lyric-line.active .jam-lyric-x { border-bottom-color: rgba(255, 163, 43, 0.55); }
.jam-lyrics-empty { font-size: 1.1rem; color: rgba(206, 196, 177, 0.4); }

/* ---------- vote ---------- */
.jam-vote-body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr minmax(140px, 0.4fr);
  gap: 14px;
  padding: 0 16px 16px;
}
.jam-vote-body .jam-screen { margin: 0; }

#heartCanvas { flex: 1; width: 100%; min-height: 0; display: block; }

.jam-vote-scale {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  padding: 6px 12px 8px;
  border-top: 1px solid rgba(255, 163, 43, 0.14);
  font-size: 0.9rem;
  color: rgba(206, 196, 177, 0.5);
  font-variant-numeric: tabular-nums;
}
/* The meter spans the whole 0..100 track -- an earlier version put the fill in
   the 0..50 half only, so every reading rendered at half its real value. */
.jam-scale-track {
  position: relative;
  flex: 1;
  height: 7px;
  background: repeating-linear-gradient(90deg,
    rgba(206, 196, 177, 0.32) 0 1px, transparent 1px 11px);
}
.jam-scale-track::before {
  content: '';
  position: absolute;
  left: 50%; top: -3px; bottom: -3px;
  width: 1px;
  background: rgba(206, 196, 177, 0.65);
}
/* Real data, kept quiet: how this track's votes compare with the week's leader. */
.jam-scale-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0;
  background: rgba(255, 75, 58, 0.5);
  box-shadow: 0 0 8px rgba(255, 75, 58, 0.5);
  transition: width 0.6s ease;
}

/* The key and the readout stretch to fill the column: left on `auto` they
   huddled at the top with a dead third of the panel under them. */
.jam-vote-side {
  display: grid;
  grid-template-rows: 1.15fr 1fr auto auto;
  gap: 11px;
  min-height: 0;
}
.jam-votebtn {
  font-size: 1.3rem;
  letter-spacing: 0.16em;
  padding: 14px 6px;
  border-radius: 9px;
  text-shadow: 0 0 9px rgba(255, 163, 43, 0.55);
}
.jam-votebtn[data-voted] { color: rgba(255, 163, 43, 0.4); cursor: default; text-shadow: none; }
@keyframes jam-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}
.jam-votebtn.shake { animation: jam-shake 0.28s ease 2; }

.jam-vote-readout {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 9px 8px;
  border-radius: 7px;
  background: #041208;
  box-shadow:
    inset 0 3px 10px rgba(0, 0, 0, 0.9),
    inset 0 0 0 1px rgba(0, 0, 0, 0.9),
    0 1px 0 rgba(255, 230, 190, 0.08);
  font-size: 1.5rem;
  letter-spacing: 0.06em;
  color: var(--jam-green);
  text-shadow: 0 0 11px rgba(87, 224, 106, 0.7);
  font-variant-numeric: tabular-nums;
}
.jam-vote-readout[data-idle] { color: rgba(87, 224, 106, 0.35); text-shadow: none; }

.jam-lamps { display: flex; justify-content: space-around; gap: 8px; }
.jam-lamp {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  color: rgba(206, 196, 177, 0.5);
}
.jam-lamp i {
  width: 13px; height: 13px; border-radius: 50%;
  background: #2b1c07;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.9), 0 1px 0 rgba(255, 225, 180, 0.09);
  transition: background 0.2s, box-shadow 0.2s;
}
.jam-lamp i[data-on] {
  background: var(--jam-amber);
  box-shadow: 0 0 11px var(--jam-amber), inset 0 -1px 2px rgba(120, 60, 0, 0.6);
}

.jam-logo {
  align-self: end;
  justify-self: end;
  width: min(100%, 130px);
  padding: 5px 8px;
  box-sizing: border-box;
  background: #f2efe8;
  border-radius: 5px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

/* ---------- responsive ---------- */
/* Small screens: stack the four cells; scrolling is fine there. */
@media (max-width: 900px) {
  .jam-grid-page {
    height: auto;
    grid-template-columns: 1fr;
    grid-template-rows: none;
    overflow: auto;
    gap: 14px;
    padding: 14px;
  }
  .jam-cell { min-height: 45vh; }
  /* The lyrics cell must stay BOUNDED. With only a min-height it grows to fit all
     21 lines, .jam-lyrics-body has nothing to scroll (scrollHeight == clientHeight),
     and the follow-along silently does nothing -- the sung line just sits wherever
     it falls in a long page. This hits phones and any desktop window under 900px. */
  .jam-cell-lyrics { height: 55vh; }
  /* The equalizer is decoration and shouldn't cost as much scrolling as the
     playlist and the lyrics. (min-height from .jam-cell would win over height.) */
  .jam-cell-eq { min-height: 0; height: 32vh; }
}

/* Narrow phones: the playlist head (back link + title + transport) does not fit
   on one row at 320px -- the transport buttons were pushed clean off-screen.
   Give the transport its own row, which also makes it properly tappable. */
@media (max-width: 520px) {
  .jam-cell-head { padding: 9px 22px 7px; gap: 10px; }
  .jam-cell-playlist .jam-cell-head { flex-wrap: wrap; }
  .jam-cell-playlist .jam-transport { flex-basis: 100%; justify-content: center; }
  .jam-transport button { flex: 1; padding: 11px 0; font-size: 1.05rem; }
  .jam-screen { margin: 0 12px 12px; }
  .jam-vote-body { padding: 0 12px 12px; }
  /* Below this the dB column steals width the LED grid needs to stay readable;
     the frequency axis alone still tells you what you're looking at. */
  .jam-eq-db, .jam-eq-unit { display: none; }
  .jam-eq-grid { grid-template-columns: 1fr; }
  .jam-vote-body { grid-template-columns: 1fr; grid-template-rows: 1fr auto; }
  .jam-vote-side { grid-template-rows: auto auto; grid-template-columns: 1fr 1fr; align-items: center; }
  .jam-lamps { grid-column: 1 / -1; }
  .jam-logo { display: none; }
  .jam-lyric-line { grid-template-columns: 3.9em 1em 1fr; font-size: 1.2rem; }
}

@media (hover: none) {
  .jam-transport button { min-height: 44px; }
  .jam-back { display: inline-flex; align-items: center; min-height: 44px; }
  .jam-row { padding: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .jam-grid-page[data-playing] .jam-row[data-active] .jam-row-reel { animation: none; }
  .jam-votebtn.shake { animation: none; }
  .jam-scale-fill { transition: none; }
}
