/* ========================================================
   SILVERFACE // 1970 — 1970s stereo receiver
   Every surface is CSS. No image assets anywhere.
   ======================================================== */

/* ========== Faceplate themes ==========
   Each theme is a complete material set: the anodised metal of the
   faceplate, the lamp behind the dial glass, and the meter face.
   Canvas can't read CSS vars, so readFaceColors() mirrors the
   meter-* values into JS globals — keep the two in sync. */
:root {
  /* CHAMPAGNE — Marantz 2270: gold-anodised face, blue dial lamp */
  --face:        #c3b092;
  --face-hi:     #dbcbb0;
  --face-lo:     #a3906f;
  --face-edge:   #8d7a5c;
  --ink:         #241d12;
  --ink-dim:     rgba(36, 29, 18, 0.55);
  --ink-etch:    rgba(255, 250, 240, 0.35);
  --silk-ink:    rgba(36, 29, 18, 0.78);

  --wood:        #5b3d26;
  --wood-hi:     #795234;
  --wood-lo:     #2f1e13;

  --collar-hi:   #e8e8e8;
  --collar-lo:   #6d6d6d;
  --knob-cap:    #191919;
  --knob-hi:     #4c4c4c;
  --knob-lo:     #101010;
  --knob-mark:   #d8d8d8;

  --glass:       #070b12;
  --glass-edge:  #1a2130;
  --lamp:        #5aa8de;
  --lamp-soft:   rgba(90, 168, 222, 0.30);
  --dial-ink:    #cfe4f5;
  --dial-ink-dim: rgba(207, 228, 245, 0.45);
  --pointer:     #ff5f2e;

  --meter-face:  #ddd2b6;
  --meter-ink:   #1d1a12;
  --meter-red:   #b3321f;
  --meter-needle: #14110b;
  --meter-lamp:  #ffdca8;
  /* Segment modes render on a dark smoked window, the way a real LED
     bargraph does — cream meter card is for needles only. */
  --display-bg:  #16130d;
  --led-lo:      #6fe08a;
  --led-mid:     #ffc23d;
  --led-hi:      #ff4b2b;

  --led-on:      #ff5f2e;
  --led-off:     rgba(0, 0, 0, 0.45);

  --fl-bg:       #06100c;
  --fl-ink:      #4be0a8;

  --font-panel: "Barlow Semi Condensed", "Helvetica Neue", Arial, sans-serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, monospace;

  --lamp-gain: 1;   /* audio-reactive, set from JS */
}

/* SILVER — Pioneer SX: brushed silver face, amber dial lamp */
[data-theme="silver"] {
  --face:        #b3b7ba;
  --face-hi:     #d9dcde;
  --face-lo:     #8f9497;
  --face-edge:   #74797c;
  --ink:         #17191b;
  --ink-dim:     rgba(23, 25, 27, 0.55);
  --ink-etch:    rgba(255, 255, 255, 0.42);
  --silk-ink:    rgba(23, 25, 27, 0.78);

  --wood:        #4a3323;
  --wood-hi:     #63452e;
  --wood-lo:     #241810;

  --glass:       #0a0705;
  --glass-edge:  #2a1e14;
  --lamp:        #ffab3d;
  --lamp-soft:   rgba(255, 171, 61, 0.28);
  --dial-ink:    #ffe0b8;
  --dial-ink-dim: rgba(255, 224, 184, 0.42);
  --pointer:     #ff3b1f;

  --meter-face:  #17181a;
  --meter-ink:   #dfe3e6;
  --meter-red:   #ff4a2a;
  --meter-needle: #f4f6f8;
  --meter-lamp:  #ffb757;
  --display-bg:  #0d0906;
  --led-lo:      #7ae0a0;
  --led-mid:     #ffb757;
  --led-hi:      #ff4a2a;

  --led-on:      #ffab3d;
  --fl-ink:      #ffab3d;
}

/* BLACKFACE — late-70s black anodised, green fluorescent lamp */
[data-theme="black"] {
  --face:        #212426;
  --face-hi:     #33373a;
  --face-lo:     #16181a;
  --face-edge:   #0c0e0f;
  --ink:         #c6cbce;
  --ink-dim:     rgba(198, 203, 206, 0.45);
  --ink-etch:    rgba(0, 0, 0, 0.5);
  --silk-ink:    rgba(198, 203, 206, 0.74);

  --wood:        #2a2c2e;
  --wood-hi:     #3a3d40;
  --wood-lo:     #121415;

  --collar-hi:   #b8bcc0;
  --collar-lo:   #4a4d50;
  --knob-cap:    #0d0e0f;
  --knob-hi:     #3d4144;
  --knob-lo:     #0a0b0c;

  --glass:       #04070a;
  --glass-edge:  #10171c;
  --lamp:        #4be0a8;
  --lamp-soft:   rgba(75, 224, 168, 0.24);
  --dial-ink:    #c8f5e2;
  --dial-ink-dim: rgba(200, 245, 226, 0.40);
  --pointer:     #ff3b30;

  --meter-face:  #0e1113;
  --meter-ink:   #cfe6dd;
  --meter-red:   #ff3b30;
  --meter-needle: #eafaf3;
  --meter-lamp:  #4be0a8;
  --display-bg:  #080b0a;
  --led-lo:      #4be0a8;
  --led-mid:     #ffd166;
  --led-hi:      #ff3b30;

  --led-on:      #4be0a8;
  --fl-ink:      #4be0a8;
}

/* GLASS — McIntosh: black glass fascia, blue meters, chrome trim */
[data-theme="glass"] {
  --face:        #0b0c0e;
  --face-hi:     #1e2126;
  --face-lo:     #060708;
  --face-edge:   #000;
  --ink:         #d6dade;
  --ink-dim:     rgba(214, 218, 222, 0.45);
  --ink-etch:    rgba(0, 0, 0, 0.6);
  --silk-ink:    rgba(214, 218, 222, 0.74);

  --wood:        #17191c;
  --wood-hi:     #2b2f34;
  --wood-lo:     #08090a;

  --collar-hi:   #f2f4f6;
  --collar-lo:   #5c6166;
  --knob-cap:    #0a0b0c;
  --knob-hi:     #43484d;
  --knob-lo:     #08090a;

  --glass:       #03050a;
  --glass-edge:  #0d1420;
  --lamp:        #2e6fd6;
  --lamp-soft:   rgba(46, 111, 214, 0.30);
  --dial-ink:    #cfe0ff;
  --dial-ink-dim: rgba(207, 224, 255, 0.42);
  --pointer:     #7fb6ff;

  --meter-face:  #0c3d8e;
  --meter-ink:   #e6f0ff;
  --meter-red:   #ff5a3c;
  --meter-needle: #ffffff;
  --meter-lamp:  #6fa8ff;
  --display-bg:  #04070e;
  --led-lo:      #6fa8ff;
  --led-mid:     #9fd0ff;
  --led-hi:      #ff5a3c;

  --led-on:      #2e6fd6;
  --fl-ink:      #8ab4ff;
}

/* ========== Base ========== */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  background: #17140f;
  font-family: var(--font-panel);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: clamp(12px, 3vw, 40px);
  gap: 14px;
  overflow-x: hidden;
}

/* The room behind the rack: a dim wall with a pool of light on it. */
.room {
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 90% 60% at 50% 8%, rgba(255, 226, 180, 0.10), transparent 70%),
    radial-gradient(ellipse 120% 90% at 50% 120%, rgba(0, 0, 0, 0.65), transparent 60%),
    linear-gradient(180deg, #241f18 0%, #16130f 55%, #0c0a08 100%);
}

.hidden { display: none !important; }

/* body is a column flex container, so this is a flex item sized to its own
   content on the cross axis. Without the cap, a long track name in .now-strip
   sets the min-content width of the whole subtree and the cabinet grows past
   the viewport — dramatically so on a phone. `min-width: 0` alone does not fix
   it; the cap is what actually binds. (body has overflow-x:hidden, which hid
   the scrollbar but not the oversized cabinet.) */
.app {
  width: 100%;
  max-width: 100%;
  min-width: 0;
}

/* ========== Cabinet: walnut veneer wrapping the faceplate ========== */
/* `margin-inline: auto` is load-bearing, not tidiness. body centres its flex
   items, but .app is `width: 100%` so it stretches the full viewport — and
   .receiver is a plain block inside it, which means max-width alone parks the
   cabinet hard against the left edge on any screen wider than 1160px. */
.receiver {
  width: 100%;
  max-width: 1160px;
  margin-inline: auto;
  perspective: 1800px;
}

.cabinet {
  position: relative;
  padding: clamp(9px, 1.1vw, 15px);
  border-radius: 5px;
  /* Walnut: two out-of-phase grain gradients plus a couple of skewed
     knots, so the repeat never reads as a stripe pattern. */
  background:
    radial-gradient(ellipse 30px 190px at 14% 40%, rgba(0, 0, 0, 0.30), transparent 72%),
    radial-gradient(ellipse 22px 150px at 82% 62%, rgba(0, 0, 0, 0.24), transparent 70%),
    repeating-linear-gradient(91deg,
      rgba(0, 0, 0, 0.16) 0 2px,
      rgba(255, 255, 255, 0.035) 2px 4px,
      rgba(0, 0, 0, 0.05) 4px 9px,
      rgba(255, 255, 255, 0.02) 9px 11px),
    repeating-linear-gradient(89deg,
      rgba(0, 0, 0, 0.12) 0 5px,
      transparent 5px 17px),
    linear-gradient(180deg, var(--wood-hi) 0%, var(--wood) 40%, var(--wood-lo) 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.10) inset,
    0 -1px 0 rgba(0, 0, 0, 0.5) inset,
    0 28px 60px -18px rgba(0, 0, 0, 0.85),
    0 4px 14px rgba(0, 0, 0, 0.6);
}

/* ========== Faceplate: brushed anodised aluminium ========== */
.faceplate {
  position: relative;
  padding: clamp(12px, 1.5vw, 20px);
  border-radius: 3px;
  /* Brushed metal: two fine repeating gradients at coprime pitches
     (3px / 7px) so the grain doesn't moiré, over a broad vertical
     sheen and a wide off-centre specular. */
  background:
    linear-gradient(105deg, rgba(255, 255, 255, 0.10) 0%, transparent 32%, transparent 68%, rgba(255, 255, 255, 0.06) 100%),
    repeating-linear-gradient(0deg,
      rgba(255, 255, 255, 0.05) 0 1px,
      rgba(0, 0, 0, 0.04) 1px 3px),
    repeating-linear-gradient(0deg,
      rgba(255, 255, 255, 0.025) 0 2px,
      rgba(0, 0, 0, 0.02) 2px 7px),
    linear-gradient(180deg,
      var(--face-hi) 0%,
      var(--face) 14%,
      var(--face-lo) 52%,
      var(--face) 84%,
      var(--face-hi) 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.45) inset,
    0 -1px 0 rgba(0, 0, 0, 0.35) inset,
    0 0 0 1px var(--face-edge),
    0 6px 18px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.3vw, 16px);
}

/* Silkscreened panel lettering — tiny, tracked-out, slightly etched */
/* Panel silkscreen. --ink-dim was ~3:1 against every faceplate, which is under
   the 4.5:1 WCAG asks for at this size — these are 8.5px labels naming controls,
   not decoration, so they get their own higher-contrast ink. */
.silk {
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--silk-ink);
  text-shadow: 0 1px 0 var(--ink-etch);
  white-space: nowrap;
  line-height: 1;
}

/* ========== Dial window ========== */
.dial-row {
  display: flex;
  gap: clamp(8px, 1.1vw, 14px);
  align-items: stretch;
}

/* The tuning dial and the cassette door are the same window with different
   mechanism behind it. Both children occupy grid cell 1/1, so the row keeps
   its height whichever source is selected and switching costs no reflow. */
.aperture {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
}
.aperture > * { grid-area: 1 / 1; }

.dial-window, .tape-window {
  min-width: 0;
  padding: 3px;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--face-lo), var(--face-hi));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* Shared so the two apertures can never drift to different heights. */
.dial-glass, .tape-glass {
  position: relative;
  height: clamp(104px, 11vw, 122px);
  border-radius: 2px;
  overflow: hidden;
  background: linear-gradient(180deg, var(--glass-edge) 0%, var(--glass) 30%, var(--glass) 70%, var(--glass-edge) 100%);
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.8) inset,
    0 0 0 1px rgba(0, 0, 0, 0.6);
}

/* The lamp behind the dial. Gain is driven by audio level from JS. */
.dial-lamp {
  position: absolute;
  inset: 0;
  opacity: calc(0.85 * var(--lamp-gain));
  background:
    radial-gradient(ellipse 70% 120% at 22% 55%, var(--lamp-soft), transparent 72%),
    radial-gradient(ellipse 70% 120% at 78% 55%, var(--lamp-soft), transparent 72%),
    radial-gradient(ellipse 120% 70% at 50% 100%, var(--lamp-soft), transparent 65%);
  transition: opacity 0.25s ease;
}

/* The 18px bottom inset is a reserved lane for .dial-legend. Without it the
   callsigns run to the bottom of the glass and the last three on the band sit
   underneath the FM / MHz readout — invisible on desktop, where the glass is
   tall enough to keep them clear, but a collision at every phone width. */
.dial-band {
  position: absolute;
  inset: 6px 22px 18px 22px;
}

.dial-scale, .dial-marks { position: absolute; left: 0; right: 0; }
.dial-scale { top: 0; height: 46%; }
.dial-marks { top: 50%; bottom: 0; }

/* Scale numerals + ticks, absolutely placed by frequency (JS-generated) */
.tick {
  position: absolute;
  bottom: 0;
  width: 1px;
  background: var(--dial-ink-dim);
  transform: translateX(-0.5px);
}
.tick.major { height: 62%; background: var(--dial-ink); }
.tick.minor { height: 34%; }

.tick-num {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: clamp(10px, 1.15vw, 13px);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--dial-ink);
  text-shadow: 0 0 6px var(--lamp-soft);
}

/* Station callsigns printed on the glass, staggered across two rows */
.dial-mark {
  position: absolute;
  transform: translateX(-50%);
  font-size: 8px;
  font-weight: 500;
  letter-spacing: 0.09em;
  white-space: nowrap;
  color: var(--dial-ink-dim);
  padding-top: 7px;
  transition: color 0.2s ease;
}
.dial-mark::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 1px;
  height: 5px;
  background: var(--dial-ink-dim);
}
.dial-mark.row2 { padding-top: 22px; }
.dial-mark.row2::before { height: 20px; }
.dial-mark.tuned { color: var(--pointer); font-weight: 600; }
.dial-mark.tuned::before { background: var(--pointer); }

/* The sliding pointer */
.dial-pointer {
  position: absolute;
  top: -6px;
  bottom: -4px;
  width: 2px;
  margin-left: -1px;
  background: var(--pointer);
  box-shadow: 0 0 7px var(--pointer), 0 0 16px var(--pointer);
  border-radius: 1px;
  /* Positioned by transform from paintDial(), not by `left` — see there. */
  left: 0;
  will-change: transform;
}
.dial-pointer::after {
  content: "";
  position: absolute;
  top: 0;
  left: -3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--pointer);
  box-shadow: 0 0 8px var(--pointer);
}

.dial-legend {
  position: absolute;
  right: 6px;
  bottom: 3px;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 8px;
  letter-spacing: 0.12em;
}
.band-tag {
  padding: 1px 4px;
  border: 1px solid var(--dial-ink-dim);
  border-radius: 2px;
  color: var(--dial-ink-dim);
}
.band-tag.on {
  color: var(--glass);
  background: var(--dial-ink);
  border-color: var(--dial-ink);
}
.dial-mhz { color: var(--dial-ink-dim); }
/* Tabular figures keep digit *widths* equal, but the digit *count* still
   changes (88.5 is four characters, 106.1 is five) — so the box is also sized
   for the widest reading and right-aligned. Without this the legend resizes
   every time you cross 100MHz and nudges the dial glass. */
.dial-freq {
  display: inline-block;
  min-width: 3.4em;
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--dial-ink);
  text-shadow: 0 0 6px var(--dial-glow);
}

/* Glass reflection — tracks the pointer position of the mouse */
.glass-glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  background:
    linear-gradient(103deg, transparent 30%, rgba(255, 255, 255, 0.055) 44%, rgba(255, 255, 255, 0.02) 50%, transparent 62%);
}

/* ========== Cassette door ==========
   The other thing behind the aperture glass. A deck's door is a smoked window
   onto an unlit chamber: the cassette sits inside with barely any light on it,
   and what you actually see through the shell's front window is the two hubs
   turning and the outer edge of the tape packs catching a little light.
   Everything else falls to black, so that is all this draws. */

/* Not a lamp so much as the light that gets past the door. Kept faint and
   coming from the front-top, which is where a deck's chamber light comes from
   whether it has a bulb or only the room. */
.tape-lamp {
  position: absolute;
  inset: 0;
  opacity: calc(0.4 * var(--lamp-gain));
  background: radial-gradient(ellipse 80% 130% at 50% -15%, var(--lamp-soft), transparent 70%);
  transition: opacity 0.25s ease;
}

/* Dark on every faceplate. This is the inside of a machine, not backlit glass,
   so it does not take the dial's --glass colour. */
.tape-glass {
  background:
    radial-gradient(ellipse 62% 150% at 50% 44%, #13120f 0%, #080807 58%, #030302 100%);
}

.tape-shell {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
}
.tape-window.loaded .tape-shell { display: flex; }

/* Openings about one diameter apart, which is where they are on the shell —
   hub centres 42mm apart, windows about 19mm across. */
.tape-hubs {
  display: flex;
  align-items: center;
  gap: clamp(44px, 7vw, 86px);
}

.hub {
  position: relative;
  width: clamp(56px, 7.2vw, 86px);
  aspect-ratio: 1;
  border-radius: 50%;
}

/* The opening in the shell's front window. Its rim is the one hard edge in
   here — a thin catch of light on top, black everywhere else. */
.hub-bore {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #050403;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.9),
    0 -1px 2px rgba(255, 240, 215, 0.07),
    0 3px 7px rgba(0, 0, 0, 0.95) inset;
}

/* Wound tape. Only the top of it is lit, and only just — the pack reads as a
   dark disc whose edge you can find, which is what it looks like in there. */
.pack {
  position: absolute;
  inset: 1px;
  border-radius: 50%;
  overflow: hidden;
  transform: scale(var(--pack, 1));
  transition: transform 0.5s linear;
  background:
    radial-gradient(circle at 50% 24%,
      rgba(112, 86, 60, 0.42) 0%,
      rgba(52, 39, 27, 0.44) 44%,
      rgba(10, 7, 5, 0.92) 100%);
  /* The lit top edge of the pack. Without it the pack has no findable rim in
     the dark, and its radius is the whole position readout. */
  box-shadow:
    0 0 5px rgba(0, 0, 0, 0.9),
    inset 0 2px 3px -1px rgba(255, 234, 202, 0.22);
}
.hub-supply .pack { transform: scale(var(--pack-supply, 1)); }
.hub-take   .pack { transform: scale(var(--pack-take, 0.34)); }

/* The wound edge. Fine and very low contrast on purpose: wound tape is a
   smooth disc, and anything you can see standing still reads as a pinwheel
   rather than a pack. This only has to be enough to show it turning. */
.pack-spin {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
    rgba(255, 232, 200, 0.03) 0deg 6deg,
    rgba(0, 0, 0, 0.09) 6deg 12deg);
}

/* The splined hub — the white part, and the only genuinely bright thing in the
   chamber. Six teeth, so at this speed you can count them. */
.hub-spline {
  position: absolute;
  inset: 32%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, #100d0b 0 34%, transparent 34%),
    repeating-conic-gradient(from 0deg,
      #e4ddcd 0deg 11deg,
      #322f29 11deg 60deg);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.85),
    0 0 7px rgba(255, 240, 215, 0.13);
}

/* Reflection off the door glass. Static — it belongs to the glass, not to
   anything behind it, so it must not turn with the hub. */
.hub-gloss {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(158deg,
    rgba(255, 248, 232, 0.14) 0%,
    rgba(255, 248, 232, 0.03) 34%,
    transparent 52%);
}

/* Slowly, and at two rates that drift apart, so the pair reads as a mechanism
   rather than one decal drawn twice. (The physical rates depend on pack radius
   and so change through a side, but animation-duration cannot be retimed
   mid-run without an arbitrary phase jump — see CLAUDE.md.)

   The rate is a variable rather than two selectors so this rule stays at three
   classes, which leaves room for runtime-static to outrank it without a tie. */
.hub-supply { --spin: 3.4s; }
.hub-take   { --spin: 3.9s; }
.tape-window.spinning .hub-spline,
.tape-window.spinning .pack-spin { animation: hub-spin var(--spin) linear infinite; }
@keyframes hub-spin { to { transform: rotate(360deg); } }

/* Etched on the door rather than printed on a paper label — a bright label
   would be the brightest thing behind unlit glass, which it never is. */
.tape-title {
  position: absolute;
  left: 8px;
  bottom: 4px;
  max-width: 42%;
  font-size: 8px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dial-ink-dim);
  opacity: 0.85;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Empty deck. A door with nothing in it should say what to do about it. */
.tape-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-panel);
}
.tape-window.loaded .tape-empty { display: none; }
.tape-empty-title {
  font-size: 15px;
  font-weight: 300;
  letter-spacing: 0.3em;
  color: var(--dial-ink-dim);
}
.tape-empty-hint {
  font-size: 8px;
  letter-spacing: 0.18em;
  color: var(--dial-ink-dim);
  opacity: 0.75;
}
.tape-empty:hover .tape-empty-title,
.tape-empty:focus-visible .tape-empty-title { color: var(--dial-ink); }

.tape-legend {
  position: absolute;
  right: 6px;
  bottom: 3px;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 8px;
  letter-spacing: 0.12em;
}
/* Mechanical four-digit counter, recessed into the door */
.tape-counter {
  padding: 1px 4px;
  border-radius: 1px;
  background: #0a0806;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.8) inset;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  letter-spacing: 0.14em;
  color: var(--dial-ink);
}

/* ========== Source select ==========
   Two detented positions. The paddle slides; the legends are printed on the
   track, so the selected one is the one under the metal. */
.selector {
  position: relative;
  display: flex;
  padding: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, #191919, #2c2c2c);
  box-shadow:
    0 0 0 1px var(--face-edge),
    0 2px 4px rgba(0, 0, 0, 0.5) inset;
}
.selector-slide {
  position: absolute;
  top: 2px;
  bottom: 2px;
  left: 2px;
  width: calc(50% - 2px);
  border-radius: 1px;
  background: linear-gradient(180deg, #cfcfcf, #8a8a8a);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  transition: transform 0.12s ease;
}
[data-source="tape"] .selector-slide { transform: translateX(100%); }
.selector-opt {
  position: relative;
  z-index: 1;
  width: 40px;
  padding: 5px 0;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-panel);
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: #8e8e8e;
  transition: color 0.12s ease;
}
.selector-opt[aria-checked="true"] { color: #14120e; }
.selector:focus-within { outline: 2px solid var(--led-on); outline-offset: 3px; }

/* ========== Source gating ==========
   One rule drives the whole reconfiguration. Everything swapped this way is
   either inside the aperture (whose two children share a grid cell) or a
   button in the wrapping strip, so nothing here changes the cabinet height. */
[data-source="tuner"] [data-src="tape"],
[data-source="tape"] [data-src="tuner"] { display: none; }

/* The skip keys serve both sources, so REPEAT — the one key in that group
   that does not — hides without giving up its slot. Removing it outright
   narrowed the group and slid the counter and seek bar beside it sideways
   on every throw of the selector. */
[data-source="tuner"] .transport [data-src="tape"] {
  display: flex;
  visibility: hidden;
  pointer-events: none;
}

/* ========== Fluorescent display panel ========== */
.fl-display {
  flex: 0 0 clamp(180px, 21vw, 250px);
  padding: 8px 10px;
  border-radius: 2px;
  background: linear-gradient(180deg, #0a1712 0%, var(--fl-bg) 40%, #030806 100%);
  box-shadow:
    0 2px 8px rgba(0, 0, 0, 0.8) inset,
    0 0 0 3px var(--face-lo),
    0 0 0 4px var(--face-edge);
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--fl-ink);
  overflow: hidden;
  display: flex;
  align-items: center;
}
.fl-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 8px;
  width: 100%;
  letter-spacing: 0.06em;
}
.fl-grid > span:nth-child(odd) { opacity: 0.45; }
.fl-val {
  color: var(--fl-ink);
  text-shadow: 0 0 7px currentColor;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ========== Meter bridge ========== */
.meter-row {
  display: flex;
  gap: clamp(8px, 1.1vw, 14px);
  align-items: stretch;
}

.meter-bridge {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: clamp(104px, 13vw, 150px);
  border-radius: 2px;
  overflow: hidden;
  background: var(--meter-face);
  box-shadow:
    0 0 0 3px var(--face-lo),
    0 0 0 4px var(--face-edge),
    0 3px 10px rgba(0, 0, 0, 0.5) inset;
}
.meter-canvas { display: block; width: 100%; height: 100%; }

/* Curved cover glass over the meter face */
.meter-glass {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(178deg, rgba(255, 255, 255, 0.13) 0%, rgba(255, 255, 255, 0.03) 18%, transparent 34%),
    radial-gradient(ellipse 130% 100% at 50% 0%, rgba(255, 255, 255, 0.07), transparent 55%);
}

.mini-meters {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
}
.mini-meter {
  flex: 0 0 auto;
  width: clamp(54px, 7.4vw, 88px);
  padding: 6px 4px 3px;
  border-radius: 2px;
  background: var(--meter-face);
  box-shadow:
    0 0 0 3px var(--face-lo),
    0 0 0 4px var(--face-edge),
    0 2px 8px rgba(0, 0, 0, 0.45) inset;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
}
.mini-face { width: 100%; display: block; }
.mini-arc {
  fill: none;
  stroke: var(--meter-ink);
  stroke-width: 1.5;
  opacity: 0.5;
}
.mini-center { stroke: var(--meter-red); stroke-width: 2; }
.mini-needle {
  stroke: var(--meter-needle);
  stroke-width: 1.6;
  stroke-linecap: round;
  transform-origin: 50px 58px;
  transform: rotate(-40deg);
}
.mini-pivot { fill: var(--meter-needle); }
.mini-label {
  font-size: 7px;
  letter-spacing: 0.13em;
  color: var(--meter-ink);
  opacity: 0.65;
}

/* ========== Brand strip ========== */
.brand-strip {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 0 2px;
}
.brand-name {
  font-size: clamp(17px, 2vw, 23px);
  font-weight: 300;
  letter-spacing: 0.06em;
  color: var(--ink);
  text-shadow: 0 1px 0 var(--ink-etch);
}
.brand-model {
  flex: 1;
  font-size: 8.5px;
  letter-spacing: 0.2em;
  color: var(--ink-dim);
  text-shadow: 0 1px 0 var(--ink-etch);
}
.status-cluster {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  /* Reserved so STANDBY -> LIVE -> TUNING does not drag the lamp sideways.
     Anchored left, because the lamp is the fixed point a user's eye returns to;
     the text grows rightward into space that is already allocated. */
  min-width: 96px;
  font-size: 9px;
  letter-spacing: 0.16em;
  white-space: nowrap;
  color: var(--ink-dim);
}
.led {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--led-off);
  box-shadow: 0 1px 1px rgba(255, 255, 255, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.4) inset;
}
.led.on {
  background: var(--led-on);
  box-shadow: 0 0 7px var(--led-on), 0 0 14px var(--led-on);
}

/* ========== Control deck ========== */
.deck {
  display: flex;
  align-items: flex-end;
  gap: clamp(10px, 1.8vw, 26px);
  padding: clamp(8px, 1vw, 12px) 4px 4px;
  border-top: 1px solid var(--ink-etch);
  box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.14);
}
.deck-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Must exceed the shadow reach of the control above the legend, including
     the 2px a pressed button travels toward it. See .power. */
  gap: 9px;
}
.presets-cell, .tone-cell, .switch-cell { flex: 0 1 auto; }
.tuning-cell { margin-left: auto; }

/* ---------- Knobs ---------- */
.knob-bank { display: flex; gap: clamp(8px, 1.2vw, 16px); }
.knob-unit {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Clears the knob's 0 2px 5px shadow — see .deck-cell. */
  gap: 8px;
}

.knob {
  position: relative;
  border-radius: 50%;
  padding: 3px;
  /* Chrome collar. Static — only the body inside rotates. */
  background: linear-gradient(155deg, var(--collar-hi, #e8e8e8) 0%, var(--collar-lo, #6d6d6d) 46%, var(--collar-hi, #e8e8e8) 100%);
  box-shadow:
    0 2px 5px rgba(0, 0, 0, 0.5),
    0 1px 0 rgba(255, 255, 255, 0.3) inset;
  cursor: grab;
  touch-action: none;
}
.knob:active { cursor: grabbing; }
.knob-sm { width: 44px; height: 44px; }
.knob-md { width: 60px; height: 60px; }
.knob-lg { width: 82px; height: 82px; padding: 4px; }

.knob-body {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  /* Knurled rim (repeating conic) with a smooth machined cap punched
     out of the middle by a hard-stop radial. */
  background:
    radial-gradient(circle at 50% 50%,
      var(--knob-cap) 0 61%,
      rgba(255, 255, 255, 0.06) 61% 62%,
      transparent 62%),
    repeating-conic-gradient(from 0deg,
      var(--knob-hi) 0deg 1.7deg,
      var(--knob-lo) 1.7deg 3.4deg);
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6) inset;
  will-change: transform;
}

/* Fixed specular — sits on the collar, so it does NOT rotate with the
   knob. A highlight that spins with the body reads as plastic. */
.knob::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  pointer-events: none;
  background:
    radial-gradient(circle at 34% 24%, rgba(255, 255, 255, 0.34) 0%, rgba(255, 255, 255, 0.07) 26%, transparent 52%),
    linear-gradient(180deg, transparent 55%, rgba(0, 0, 0, 0.28) 100%);
}

.knob-mark {
  position: absolute;
  top: 8%;
  left: 50%;
  width: 2px;
  height: 26%;
  margin-left: -1px;
  border-radius: 1px;
  background: var(--knob-mark, #d8d8d8);
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.7);
}
.knob-lg .knob-mark { top: 7%; height: 20%; }

/* Finger dimple on the flywheel tuning knob */
.knob-dimple {
  position: absolute;
  top: 50%;
  left: 22%;
  width: 13px;
  height: 13px;
  margin-top: -6.5px;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #000 0%, #2a2a2a 70%, #4a4a4a 100%);
  box-shadow: 0 1px 1px rgba(255, 255, 255, 0.18), 0 -1px 2px rgba(0, 0, 0, 0.7) inset;
}

/* The real control lives underneath: keyboard, screen readers, and
   form semantics all keep working; the knob is just its skin.
   Clipped rather than opacity:0 at 1px — a zero-opacity control gets
   dropped from some accessibility trees, a clipped one does not. */
.knob-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.knob-input:focus-visible + .silk,
.knob-unit:focus-within .silk { color: var(--ink); }
.knob-unit:focus-within .knob {
  outline: 2px solid var(--led-on);
  outline-offset: 3px;
}

/* ---------- Power button ---------- */
.power {
  position: relative;
  width: 46px;
  height: 46px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.16), transparent 45%),
    linear-gradient(180deg, #2c2c2c, #131313);
  /* Shadow reach (offset + blur) is kept under the .deck-cell gap. Any deeper
     and it falls across the silkscreen legend below, which costs that label
     about two thirds of its contrast — worst on .power.on, where the button
     also translates 2px down toward it. */
  box-shadow:
    0 3px 0 var(--face-edge),
    0 3px 5px rgba(0, 0, 0, 0.42),
    0 1px 0 rgba(255, 255, 255, 0.16) inset;
  transition: transform 0.05s ease, box-shadow 0.05s ease;
}
.power:active, .power.on {
  transform: translateY(2px);
  box-shadow:
    0 1px 0 var(--face-edge),
    0 2px 4px rgba(0, 0, 0, 0.5),
    0 2px 5px rgba(0, 0, 0, 0.6) inset;
}
.power-glyph {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 15px;
  height: 15px;
  border: 2px solid #8b8b8b;
  border-radius: 50%;
  border-top-color: transparent;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.power-glyph::after {
  content: "";
  position: absolute;
  top: -5px;
  left: 50%;
  width: 2px;
  height: 9px;
  margin-left: -1px;
  background: #8b8b8b;
  transition: background 0.2s ease;
}
.power.on .power-glyph {
  border-color: var(--led-on);
  border-top-color: transparent;
  box-shadow: 0 0 8px var(--led-on);
}
.power.on .power-glyph::after { background: var(--led-on); }

/* ---------- Preset pushbuttons ---------- */
.preset-row { display: flex; gap: 4px; }
.preset {
  position: relative;
  width: 26px;
  height: 32px;
  border: none;
  border-radius: 2px;
  cursor: pointer;
  font-family: var(--font-panel);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: #9a9a9a;
  background: linear-gradient(180deg, #333 0%, #1a1a1a 60%, #232323 100%);
  box-shadow:
    0 3px 0 var(--face-edge),
    0 3px 4px rgba(0, 0, 0, 0.4),
    0 1px 0 rgba(255, 255, 255, 0.14) inset;
  transition: transform 0.05s ease, box-shadow 0.05s ease, color 0.15s ease;
}
.preset:hover { color: #ccc; }
.preset.empty { color: #5a5a5a; }
.preset.empty:hover { color: #8f8f8f; }
/* Hold-to-store: the slot fills so the gesture is visible before it commits.
   The duration must track PRESET_HOLD_MS in app.js. */
.preset::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  border-radius: 0 0 2px 2px;
  background: var(--led-on);
  opacity: 0.5;
  pointer-events: none;
}
.preset.holding::after { height: 100%; transition: height 0.6s linear; }
.preset:active, .preset.latched {
  transform: translateY(2px);
  box-shadow:
    0 1px 0 var(--face-edge),
    0 2px 3px rgba(0, 0, 0, 0.45),
    0 2px 5px rgba(0, 0, 0, 0.6) inset;
}
.preset.latched { color: var(--led-on); text-shadow: 0 0 6px var(--led-on); }
.preset.empty { color: rgba(154, 154, 154, 0.3); }

/* ---------- Rocker switches ---------- */
.switch-bank { display: flex; gap: clamp(5px, 0.8vw, 9px); }
.rocker {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.rocker-body {
  position: relative;
  display: block;
  width: 30px;
  height: 17px;
  border-radius: 2px;
  background: linear-gradient(180deg, #1a1a1a, #2e2e2e);
  box-shadow:
    0 0 0 1px var(--face-edge),
    0 2px 4px rgba(0, 0, 0, 0.5) inset;
  overflow: hidden;
}
/* The paddle rests on the lower half; flipping raises it to the top. */
.rocker-body::after {
  content: "";
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 1px;
  height: 9px;
  border-radius: 1px;
  background: linear-gradient(180deg, #7d7d7d, #3d3d3d);
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.6);
  transition: transform 0.09s ease, background 0.15s ease;
}
.rocker[aria-checked="true"] .rocker-body::after {
  transform: translateY(-6px);
  background: linear-gradient(180deg, #cfcfcf, #8a8a8a);
}
.rocker[aria-checked="true"] .silk { color: var(--ink); }

/* ========== Utility strip (recessed sub-panel) ==========
   Two declared rows — transport above, panel + setup below. One wrapping row
   re-flowed differently at every width, so the same button landed in a
   different place on a laptop than on a phone and nothing could be found
   twice in the same spot. */
.strip {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 9px clamp(8px, 1vw, 12px);
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.10));
  box-shadow:
    0 2px 5px rgba(0, 0, 0, 0.35) inset,
    0 1px 0 var(--ink-etch);
}
.strip-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.4vw, 18px);
  flex-wrap: wrap;
}
.strip-group { display: flex; align-items: center; gap: 5px; }
.counter-group { flex: 1 1 190px; min-width: 150px; }
/* Preferences, not operation — pushed to the far end so the row reads
   left-to-right as "what do you want to open" then "how should it look". */
.setup-group { margin-left: auto; }

/* Piano-key transport buttons */
.key {
  width: 34px;
  height: 26px;
  border: none;
  border-radius: 2px;
  cursor: pointer;
  color: #b4b4b4;
  background: linear-gradient(180deg, #3a3a3a 0%, #1c1c1c 55%, #262626 100%);
  box-shadow:
    0 3px 0 rgba(0, 0, 0, 0.55),
    0 4px 5px rgba(0, 0, 0, 0.4),
    0 1px 0 rgba(255, 255, 255, 0.13) inset;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.05s ease, box-shadow 0.05s ease, color 0.15s ease;
}
.key:hover { color: #e6e6e6; }
.key:active, .key.active-btn {
  transform: translateY(2px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.55), 0 2px 5px rgba(0, 0, 0, 0.6) inset;
}
.key.active-btn { color: var(--led-on); }
.key.repeat-one { color: var(--led-on); text-shadow: 0 0 6px var(--led-on); }

.ico { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* Small labelled panel buttons */
.tab-btn, .micro-btn {
  padding: 5px 8px;
  border: none;
  border-radius: 2px;
  cursor: pointer;
  font-family: var(--font-panel);
  font-size: 8.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: #a8a8a8;
  background: linear-gradient(180deg, #343434, #1d1d1d);
  box-shadow:
    0 2px 0 rgba(0, 0, 0, 0.5),
    0 1px 0 rgba(255, 255, 255, 0.12) inset;
  transition: transform 0.05s ease, box-shadow 0.05s ease, color 0.15s ease;
  white-space: nowrap;
}
.tab-btn.wide { min-width: 62px; }
.tab-btn:hover, .micro-btn:hover { color: #e2e2e2; }
.tab-btn:active, .micro-btn:active,
.tab-btn.active-btn, .micro-btn.active-btn {
  transform: translateY(1px);
  box-shadow: 0 0 0 rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.6) inset;
}
.tab-btn.active-btn, .micro-btn.active-btn {
  color: var(--led-on);
  text-shadow: 0 0 6px var(--led-on);
}

/* Mechanical tape counter. Width is reserved: these read "0:00" on tape and
   "LIVE" on the tuner, and letting them size to their text slid the seek bar
   between them sideways on every source change. */
.counter {
  min-width: 4.2em;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  padding: 2px 5px;
  border-radius: 2px;
  color: #d8d2c2;
  background: #14120e;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7) inset;
  letter-spacing: 0.04em;
}

/* Seek slider */
.seek { position: relative; flex: 1; height: 18px; display: flex; align-items: center; }
.seek-track {
  position: absolute;
  left: 0; right: 0;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(180deg, #141414, #2c2c2c);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.7) inset;
  pointer-events: none;
}
#seek {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 18px;
  margin: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
#seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 11px;
  height: 16px;
  border-radius: 2px;
  background: linear-gradient(180deg, #e4e4e4, #8f8f8f);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
  border: none;
}
#seek::-moz-range-thumb {
  width: 11px;
  height: 16px;
  border: none;
  border-radius: 2px;
  background: linear-gradient(180deg, #e4e4e4, #8f8f8f);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
#seek:disabled { opacity: 0.35; cursor: default; }

/* ========== Now-playing strip (on the cabinet lip, below the face) ========== */
/* This is the one line that answers "what am I listening to", and it sits on
   dark walnut rather than the faceplate — the inherited alphas measured under
   3:1 there. Raised, and given a lip of its own so it reads as part of the
   cabinet instead of floating text. */
.now-strip {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding: 7px 10px;
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.12));
  box-shadow:
    0 1px 4px rgba(0, 0, 0, 0.4) inset,
    0 1px 0 rgba(255, 255, 255, 0.06);
  font-size: 12px;
}
.now-label {
  flex: 0 0 auto;
  font-size: 8px;
  letter-spacing: 0.2em;
  color: rgba(255, 245, 230, 0.55);
}
.now-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: 0.05em;
  color: rgba(255, 246, 232, 0.95);
  /* Duration is mirrored by NOW_FADE in app.js — change both together. */
  transition: opacity 0.34s ease;
}
.now-title.now-fading { opacity: 0; }
.now-count {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9px;
  color: rgba(255, 245, 230, 0.6);
}
.like-btn { padding: 4px 6px; }
/* Not `.hidden` — see updateLikeBtn(). The box stays so the strip keeps one
   height; only the ink goes. visibility:hidden also drops it from the
   accessibility tree and from tab order, same as display:none would. */
.like-btn.like-off { visibility: hidden; pointer-events: none; }
.like-btn.active-btn .ico { fill: var(--led-on); }

/* ========== Drop overlay ========== */
.drop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 8, 6, 0.82);
  backdrop-filter: blur(2px);
}
.drop-inner {
  padding: 32px 52px;
  border: 2px dashed var(--led-on);
  border-radius: 4px;
  text-align: center;
  background: rgba(0, 0, 0, 0.5);
}
.drop-title {
  font-size: 26px;
  font-weight: 300;
  letter-spacing: 0.28em;
  color: var(--led-on);
  text-shadow: 0 0 14px var(--led-on);
}
.drop-text {
  margin-top: 8px;
  font-size: 9px;
  letter-spacing: 0.22em;
  color: rgba(255, 245, 230, 0.5);
}

/* ========== Drawers ========== */
.drawer {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 40;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  display: flex;
  flex-direction: column;
  padding: 14px;
  border-radius: 4px;
  /* A printed service card sitting on top of the receiver */
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.02) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, #ece3d0, #d8cdb6);
  color: #241d12;
  box-shadow:
    0 24px 60px -14px rgba(0, 0, 0, 0.8),
    0 0 0 1px rgba(0, 0, 0, 0.35);
}
.drawer-head, .drawer-foot {
  font-size: 8.5px;
  letter-spacing: 0.2em;
  color: rgba(36, 29, 18, 0.6);
}
.drawer-head, .drawer-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.drawer-head { padding-bottom: 9px; border-bottom: 1px solid rgba(36, 29, 18, 0.22); }
.drawer-foot { padding-top: 9px; border-top: 1px solid rgba(36, 29, 18, 0.22); }

/* Small printed-legend button used in drawer heads and feet */
.drawer-action {
  flex: 0 0 auto;
  padding: 3px 8px;
  border: 1px solid rgba(36, 29, 18, 0.32);
  border-radius: 2px;
  background: rgba(36, 29, 18, 0.05);
  cursor: pointer;
  font-family: var(--font-panel);
  font-size: 8px;
  letter-spacing: 0.16em;
  color: rgba(36, 29, 18, 0.72);
}
.drawer-action:hover { background: rgba(36, 29, 18, 0.12); }
.drawer-action.active-btn { background: #241d12; border-color: #241d12; color: #ece3d0; }

/* ---- Band editor ---- */
.band-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-bottom: 1px solid rgba(36, 29, 18, 0.10);
}
.band-row:last-child { border-bottom: none; }
.band-name {
  flex: 1;
  min-width: 0;
  padding: 3px 6px;
  border: 1px solid rgba(36, 29, 18, 0.20);
  border-radius: 2px;
  background: rgba(255, 253, 246, 0.55);
  font-family: var(--font-panel);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: #241d12;
}
.band-name:focus { outline: none; border-color: rgba(36, 29, 18, 0.55); background: #fffdf6; }
/* The callsign is derived, never typed — showing it read-only here is what
   makes the name/dial relationship legible while editing. */
.band-call {
  flex: 0 0 auto;
  min-width: 58px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.1em;
  text-align: right;
  color: rgba(36, 29, 18, 0.45);
}
.band-btn {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid rgba(36, 29, 18, 0.22);
  border-radius: 2px;
  background: rgba(36, 29, 18, 0.04);
  cursor: pointer;
  font-size: 9px;
  line-height: 1;
  color: rgba(36, 29, 18, 0.65);
}
.band-btn:hover { background: rgba(36, 29, 18, 0.14); }
.band-del:hover { background: rgba(179, 50, 31, 0.16); border-color: rgba(179, 50, 31, 0.5); color: #b3321f; }

.band-add { display: flex; gap: 6px; padding-top: 9px; border-top: 1px solid rgba(36, 29, 18, 0.22); }
.band-add .search-input { flex: 1; min-width: 0; }
.band-add .drawer-action { padding: 3px 12px; }

/* While editing, the search box stays (it is how you find a stream URL) but
   its results give up their space to the band list. */
.search-wrap.band-editing .search-results { max-height: 92px; }

.st-add {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid rgba(36, 29, 18, 0.28);
  border-radius: 2px;
  background: rgba(36, 29, 18, 0.04);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  color: rgba(36, 29, 18, 0.7);
}
.st-add:hover { background: rgba(36, 29, 18, 0.15); }
.st-add.on { color: #1f5c2f; border-color: rgba(31, 92, 47, 0.45); background: rgba(31, 92, 47, 0.08); cursor: default; }
.drawer-list { flex: 1; overflow-y: auto; padding: 6px 0; }

.drawer-tabs { display: flex; gap: 6px; padding-bottom: 9px; border-bottom: 1px solid rgba(36, 29, 18, 0.22); }
.drawer-tab {
  padding: 4px 10px;
  border: 1px solid rgba(36, 29, 18, 0.28);
  border-radius: 2px;
  background: none;
  cursor: pointer;
  font-family: var(--font-panel);
  font-size: 8.5px;
  letter-spacing: 0.16em;
  color: rgba(36, 29, 18, 0.6);
}
.drawer-tab.active { background: #241d12; color: #ece3d0; border-color: #241d12; }

.st-item, .q-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: none;
  border-radius: 2px;
  background: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-panel);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: #241d12;
}
.st-item:hover, .q-item:hover { background: rgba(36, 29, 18, 0.08); }
.st-item.active, .q-item.active { background: rgba(36, 29, 18, 0.14); font-weight: 600; }
.st-freq {
  font-family: var(--font-mono);
  font-size: 10px;
  opacity: 0.55;
  min-width: 40px;
}
.st-name, .q-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-fav { padding: 2px 5px; border: none; background: none; cursor: pointer; color: rgba(36, 29, 18, 0.35); }
.st-fav.on { color: #b3321f; }
/* ---- Playback log: entries read as lines on a service ledger ---- */
.hist-entry {
  padding: 7px 8px 8px;
  border-bottom: 1px solid rgba(36, 29, 18, 0.14);
}
.hist-entry:last-child { border-bottom: none; }
.hist-entry-title {
  font-family: var(--font-panel);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: #241d12;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hist-entry-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-top: 5px;
}
/* Chips, not running text — without a border these tags concatenate into
   unreadable runs like "4m ago4m03sSKIPRADIO". */
.tag {
  padding: 1px 5px;
  border: 1px solid rgba(36, 29, 18, 0.22);
  border-radius: 2px;
  background: rgba(36, 29, 18, 0.04);
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(36, 29, 18, 0.62);
  white-space: nowrap;
}
.tag-completed { border-color: rgba(31, 92, 47, 0.45); color: #1f5c2f; background: rgba(31, 92, 47, 0.07); }
.tag-skipped   { border-color: rgba(146, 94, 12, 0.45); color: #925e0c; background: rgba(146, 94, 12, 0.07); }
.tag-stopped   { border-color: rgba(36, 29, 18, 0.30); color: rgba(36, 29, 18, 0.62); }
.tag-error     { border-color: rgba(179, 50, 31, 0.50); color: #b3321f; background: rgba(179, 50, 31, 0.07); }

.hist-empty, .search-empty {
  padding: 14px 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: rgba(36, 29, 18, 0.45);
}

.search-wrap { padding-top: 9px; }
.search-input {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid rgba(36, 29, 18, 0.3);
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.35);
  font-family: var(--font-panel);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: #241d12;
}
.search-input:focus { outline: 2px solid rgba(36, 29, 18, 0.4); outline-offset: -1px; }
.search-results { max-height: 150px; overflow-y: auto; }

/* ========== Footer hint ========== */
.hint {
  font-size: 8.5px;
  line-height: 1.7;
  letter-spacing: 0.16em;
  text-align: center;
  color: rgba(255, 240, 220, 0.42);
  max-width: 1160px;
}
/* Keyboard shortcuts on a touch device are a wall of text describing keys
   that are not there. */
@media (pointer: coarse) { .hint { display: none; } }

/* ========== Perf: kill continuous animation when not foreground-active.
   Same lever as Phosphor's runtime-static — this is a CPU control, not
   a cosmetic one. ========== */
.runtime-static .dial-lamp,
.runtime-static .tape-lamp,
.runtime-static .led.on { transition: none; }
/* Must outrank `.tape-window.spinning .hub-spline`, whose `animation` shorthand
   resets play-state to running. The leading `html` buys the extra specificity;
   without it this rule silently lost and the hubs kept turning off-screen —
   which is what the old .reel-spin version of it did. */
html.runtime-static .tape-window.spinning .hub-spline,
html.runtime-static .tape-window.spinning .pack-spin { animation-play-state: paused; }
.runtime-static .glass-glare { display: none; }
/* With the transition gone the swap must be a hard cut, not 340ms of blank —
   the JS still waits out NOW_FADE either way. */
.runtime-static .now-title { transition: none; }
.runtime-static .now-title.now-fading { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  .now-title.now-fading { opacity: 1; }
}

/* ========== Responsive ========== */
@media (max-width: 900px) {
  .dial-row { flex-direction: column; }
  /* Stacked, the readout becomes a wide strip — reflow the label/value
     pairs across three columns so it stays two rows tall, not six. */
  .fl-display { flex: 0 0 auto; padding: 6px 9px; }
  .fl-grid { grid-template-columns: repeat(3, auto 1fr); gap: 2px 8px; }
  /* Three pairs per line gives each value ~70px, which ellipsises every
     station name worth reading. Two pairs costs one extra 13px line and is
     the readout actually doing its job. */
  @media (max-width: 620px) {
    .fl-grid { grid-template-columns: repeat(2, auto 1fr); }
  }

  /* Six cells need ~679px of the 682 a 768px tablet gives, so flex-wrap tipped
     over and dropped the tuning knob alone onto a centred second row with
     300px of empty faceplate either side. Two declared rows instead:
     operating controls above, tone and switches below. */
  .deck {
    display: grid;
    grid-template-columns: auto auto auto 1fr auto;
    grid-template-areas:
      "power source presets vol    tuning"
      "tone  tone   tone    switch switch";
    align-items: center;
    justify-items: center;
    row-gap: 16px;
    column-gap: clamp(10px, 1.8vw, 26px);
  }
  .power-cell   { grid-area: power; }
  .source-cell  { grid-area: source; }
  .presets-cell { grid-area: presets; }
  .vol-cell     { grid-area: vol; }
  .tuning-cell  { grid-area: tuning; margin-left: 0; }
  .tone-cell    { grid-area: tone; justify-self: start; }
  .switch-cell  { grid-area: switch; justify-self: end; }

  .knob-lg { width: 68px; height: 68px; }
  .knob-md { width: 52px; height: 52px; }
  .knob-sm { width: 38px; height: 38px; }
}

@media (max-width: 620px) {
  body { padding: 8px; justify-content: flex-start; }

  /* Beside the small pair the VU face drops to ~110px a channel, at which the
     printed scale sheds all but four numerals — and the VU face is the thing
     this project is for. It gets the full width; SIGNAL and TUNING go under it
     at a fixed 88px. They used to take `flex: 1` here and came out 116px tall
     against the VU's 104, which read as the more important pair. */
  .meter-row { flex-direction: column; }
  .mini-meters { justify-content: center; gap: 8px; }
  .mini-meter { width: 88px; }

  /* Declared rows instead of flex-wrap. The three controls actually reached
     for — power, volume, tuning — take the top one; tone and switches are
     set once and left, so they go last. */
  .deck {
    display: grid;
    grid-template-columns: auto auto 1fr auto;
    grid-template-areas:
      "power   source  vol     tuning"
      "presets presets presets presets"
      "tone    tone    switch  switch";
    justify-items: center;
    /* Grid inherits the deck's flex-end, which bottom-aligns three cells of
       very different heights and leaves POWER sitting 40px below VOLUME. */
    align-items: center;
    row-gap: 14px;
    column-gap: 12px;
  }
  .power-cell   { grid-area: power; }
  .source-cell  { grid-area: source; }
  .vol-cell     { grid-area: vol; }
  .tuning-cell  { grid-area: tuning; margin-left: 0; }
  .presets-cell { grid-area: presets; }
  /* The tablet grid pins these to the outer edges; centred is right once the
     rows are full-width. */
  .tone-cell    { grid-area: tone; justify-self: center; }
  .switch-cell  { grid-area: switch; justify-self: center; }

  /* Wraps to two lines at this width and collides with the status lamp.
     It is the model number on a fascia — nothing is lost by dropping it. */
  .brand-model { display: none; }
  /* Keeps the base min-width — dropping it here put STANDBY -> LIVE back to
     dragging the lamp sideways. */
  .status-cluster { margin-left: auto; }

  .strip-row { justify-content: center; }
  .counter-group { order: -1; flex: 1 1 100%; }
  .setup-group { margin-left: 0; }
}

/* Below ~380px the three tone knobs and the four rockers stop fitting on one
   line — at touch sizes they need 309px of the 332 a 390px phone gives. */
@media (max-width: 380px) {
  .deck {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "power   vol     tuning"
      "source  source  source"
      "presets presets presets"
      "tone    tone    tone"
      "switch  switch  switch";
  }
}

/* The callsigns are printed at a fixed 8px, so the only way they fit twelve
   stations on a 320px dial is a shorter pitch: claw back the glass margins
   and tighten the type. */
@media (max-width: 430px) {
  .dial-band { inset: 6px 12px 18px 12px; }
  .dial-mark { font-size: 7px; letter-spacing: 0.04em; }
}

/* ========== Touch: controls sized for fingers ==========
   The panel buttons were 17–21px tall — under the 24px WCAG 2.2 floor and
   well under what a thumb lands on reliably. Real receiver buttons are about
   10mm, so growing them here is also closer to the object. Coarse pointers
   only: the faceplate keeps its proportions on a desktop. */
@media (pointer: coarse) {
  .tab-btn, .micro-btn { padding: 9px 11px; font-size: 9px; }
  .key { width: 46px; height: 34px; }
  .ico { width: 15px; height: 15px; }
  .preset { width: 38px; height: 40px; font-size: 11px; }
  .preset-row { gap: 6px; }
  .rocker-body { width: 38px; height: 22px; }
  .rocker-body::after { height: 12px; }
  .rocker[aria-checked="true"] .rocker-body::after { transform: translateY(-8px); }
  .silk { font-size: 9px; }

  .seek, #seek { height: 30px; }
  #seek::-webkit-slider-thumb { width: 15px; height: 24px; }
  #seek::-moz-range-thumb { width: 15px; height: 24px; }

  /* Drawer rows are lists you scroll and tap — the most touch-hostile part of
     the app at the inherited 23px row height. */
  .st-item, .q-item { padding: 11px 8px; font-size: 12px; }
  .drawer-action, .drawer-tab { padding: 8px 12px; }
  .st-fav { padding: 8px 10px; }
  .band-btn { width: 30px; height: 30px; }
  .st-add { width: 28px; height: 28px; }
  .search-input { padding: 10px 8px; font-size: 11px; }
}
