/* ═══════════════════════════════════════════════════════════════
   site.css — shared tokens + the cross-page nav.
   Loaded by every page. Deliberately small: each room (crate, mixes)
   keeps its own styling, this only defines the things that must
   agree across them.
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* Palette sampled from the two apps this site links together:
     the vinyl label red is the crate's default labelColor,
     the blues are the rekordbox POC's --bg-selected tokens. */
  --ink:        #0e0d0c;
  --ink-soft:   #17151300;
  --dust:       #8f8a80;
  --paper:      #e9e5dc;
  --label-red:  #cc3333;
  --rb-blue:    #1a4d8f;
  --rb-blue-hot:#2f7be6;

  /* ── Den grade (every room) ─────────────────────────────────────
     Late-night-den regrade: amber owns the AMBIENT light, red owns
     the ACCENT (selection edge, REC dot, PLAY/RANDOM fills). These
     mirror the literals in crate.js / player.js — the canvases keep
     their own copies since they can't read CSS vars per-frame —
     so change both together. Documented here as the source of truth.

     The crate, the deck and the PC are corners of ONE room, so they
     share one ramp: --room-lit where the lamp reaches, falling off
     through --room-air and --room to --room-deep in the far corners.
     A page that invents its own browns stops looking like this room. */
  --room-lit:   #2b2318;   /* wall the lamp actually reaches */
  --room-air:   #191308;   /* mid wall, between lamp and corner */
  --room:       #120d08;   /* unlit air of the room (canvas background) */
  --room-deep:  #0b0805;   /* darkest corner, far edge of the shot */
  --wood-lit:   #4d3820;   /* furniture surface under the lamp */
  --wood:       #2b2116;   /* dark chrome / furniture shadow */
  --wood-deep:  #241708;   /* furniture edge falling into shadow */
  --lamp:       #e8a550;   /* tungsten pool over the crate (drawGlow) */
  --paper-warm: #e6ddc9;   /* stickers, chips, ink-on-dark text */
  --ember:      #e05238;   /* red, warmed: selection edge, REC, stripe */
  --brick:      #a83622;   /* red, warmed + dimmed: PLAY / RANDOM fills */

  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --nav-h: 30px;
}

/* ─── Camera grade ────────────────────────────────────────────────
   One camera films every room, so the vignette and grain live here
   instead of being reinvented per page. The distinction that keeps
   this honest: scene LIGHT belongs to the scene (the crate's lamp
   pool, the CRT's own glow, the lava lamp), while everything the
   lens and film stock add on top belongs here.

   Sits above the page but below the nav, and never eats a click.
   Usage:  <div class="camera-fx" aria-hidden="true"></div>          */
.camera-fx {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  background-image:
    radial-gradient(118% 118% at 50% 44%, transparent 58%, rgba(8, 5, 2, 0.46) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/><feComponentTransfer><feFuncA type='linear' slope='0.06'/></feComponentTransfer></filter><rect width='240' height='240' filter='url(%23n)'/></svg>");
  animation: cam-grain 0.8s steps(8) infinite;
}
@keyframes cam-grain {
  0%   { background-position: 0 0, 0 0; }
  25%  { background-position: 0 0, -70px 45px; }
  50%  { background-position: 0 0, 55px -60px; }
  75%  { background-position: 0 0, -40px -35px; }
  100% { background-position: 0 0, 30px 70px; }
}

/* ─── Turning between corners ─────────────────────────────────────
   The crate, the deck and the PC are corners of one room, so moving
   between them should read as a camera whip-pan rather than a page
   swap. A panel of room-colour sweeps across in the direction you're
   turning, then keeps sweeping the same way on the far side — one
   continuous move split across the page load. nav.js drives it.

   Sits above everything, including the nav, the way a real pan would.

   Driven by CSS animations rather than JS-toggled transitions on
   purpose: an animation starts the moment its class lands, whereas a
   transition needs a frame to commit the start state first. Around a
   page load that frame isn't guaranteed, and a dropped one would leave
   this panel parked over the whole viewport. */
.room-wipe {
  position: fixed;
  inset: 0;
  z-index: 10000;
  pointer-events: none;
  background: var(--room-deep);
  transform: translateX(100%);   /* parked offscreen until a turn runs */
  will-change: transform;
}

/* Outgoing half: sweeps in from the side you're turning toward. */
.room-wipe.from-right { animation: wipe-from-right 380ms cubic-bezier(.4, 0, .2, 1) forwards; }
.room-wipe.from-left  { animation: wipe-from-left  380ms cubic-bezier(.4, 0, .2, 1) forwards; }
/* Incoming half: keeps going the same way and exits the far side. */
.room-wipe.to-left    { animation: wipe-to-left    380ms cubic-bezier(.4, 0, .2, 1) forwards; }
.room-wipe.to-right   { animation: wipe-to-right   380ms cubic-bezier(.4, 0, .2, 1) forwards; }

@keyframes wipe-from-right { from { transform: translateX(100%); }  to { transform: translateX(0); } }
@keyframes wipe-from-left  { from { transform: translateX(-100%); } to { transform: translateX(0); } }
@keyframes wipe-to-left    { from { transform: translateX(0); }     to { transform: translateX(-100%); } }
@keyframes wipe-to-right   { from { transform: translateX(0); }     to { transform: translateX(100%); } }

/* ─── Cross-page nav ──────────────────────────────────────────────
   Injected by shared/nav.js. Fixed to the top-left, small enough to
   sit over a full-bleed canvas without eating the artwork. */
.xnav {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 9999;
  display: flex;
  align-items: stretch;
  height: var(--nav-h);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: rgba(10, 10, 11, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.11);
  border-radius: 2px;
  overflow: hidden;
  transition: opacity 220ms ease;
}

/* Inline variant: the mixes page already has a toolbar, so the nav sits
   inside it instead of floating over the table. */
.xnav.is-inline {
  position: static;
  height: auto;
  background: none;
  border: none;
  border-radius: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  margin-right: 4px;
}
.xnav.is-inline a { padding: 0 9px; height: 20px; }
.xnav.is-inline a + a { border-left-color: rgba(255, 255, 255, 0.14); }

.xnav a {
  display: flex;
  align-items: center;
  padding: 0 11px;
  color: var(--dust);
  text-decoration: none;
  white-space: nowrap;
  transition: color 140ms ease, background 140ms ease;
}

.xnav a + a { border-left: 1px solid rgba(255, 255, 255, 0.09); }

.xnav a:hover,
.xnav a:focus-visible { color: var(--paper); background: rgba(255, 255, 255, 0.06); }

.xnav a[aria-current="page"] { color: var(--paper); }
.xnav a[aria-current="page"]::before {
  content: "";
  width: 5px; height: 5px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--label-red);
}

/* Which way you'd have to turn to face each of the others. The nav is
   already ordered along the room's axis, so left/right in the bar is
   left/right in the room. nav.js sets these; the current room gets
   neither (it has the red dot instead). */
.xnav a.is-left::before,
.xnav a.is-right::after {
  opacity: 0.45;
  font-size: 1.1em;
  line-height: 1;
}
.xnav a.is-left::before  { content: "‹"; margin-right: 6px; }
.xnav a.is-right::after  { content: "›"; margin-left: 6px; }
.xnav a:hover::before,
.xnav a:hover::after { opacity: 0.9; }

.xnav a:focus-visible { outline: 1px solid var(--rb-blue-hot); outline-offset: -1px; }

/* The crate page fades the nav out while a record is being lifted onto
   the deck, so it never sits on top of the transition. crate.js toggles
   this class on <body>. */
body.is-transitioning .xnav { opacity: 0; pointer-events: none; }

/* ─── Now playing ─────────────────────────────────────────────────
   Built by shared/nowplaying.js, and only ever in the SHELL — it has
   to survive the room swaps, same as the record it's reporting on.

   Same chip as the nav, in the far corner from it: the nav is where
   you're going, this is what you left running. Below .room-wipe so a
   turn passes over it, above the room so it's never buried by one. */
.now-playing {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 9998;
  display: flex;
  align-items: stretch;
  height: var(--nav-h);
  /* Stops short of the middle, because the crate parks its ‹ PLAY ›
     row along the bottom centre — that row reaches 86px either side of
     centre, so this may claim half the width less that plus a margin.
     Get this wrong and the strip sits on the next-record button.

     Floored at the width of the buttons themselves: below about 560px
     that subtraction goes to nothing, and a chip narrower than its own
     controls is worse than one that overlaps. */
  max-width: max(76px, min(calc(50vw - 110px), 340px));
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  background: rgba(10, 10, 11, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.11);
  border-radius: 2px;
  overflow: hidden;
}
.now-playing[hidden] { display: none; }

.now-playing button {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;                     /* so the title, not the chip, truncates */
  padding: 0 10px;
  background: none;
  border: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--dust);
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease;
}
.now-playing button + button { border-left: 1px solid rgba(255, 255, 255, 0.09); }
.now-playing button[hidden] { display: none; }
.now-playing button:hover,
.now-playing button:focus-visible { color: var(--paper); background: rgba(255, 255, 255, 0.06); }
.now-playing button:focus-visible { outline: 1px solid var(--rb-blue-hot); outline-offset: -1px; }

.np-toggle { font-size: 9px; color: var(--ember); }
.np-lift   { font-size: 14px; padding: 0 9px; }

.np-open  { flex: 1 1 auto; }
.np-text  {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The record itself, turning. Stops when the record does, which is the
   whole tell — the strip reads as a thing still running, not a label. */
.np-disc {
  flex: 0 0 auto;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, var(--label-red) 0 26%, transparent 27%),
    repeating-radial-gradient(circle at 50% 50%,
      rgba(255, 255, 255, 0.16) 0 1px, transparent 1px 2px),
    #14110e;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.now-playing.is-playing .np-disc { animation: np-spin 1.8s linear infinite; }

/* Off-centre so the spin is legible on an 11px disc — a perfectly
   concentric label would look motionless. */
@keyframes np-spin {
  from { transform: rotate(0deg)   translateX(0.35px) rotate(0deg); }
  to   { transform: rotate(360deg) translateX(0.35px) rotate(-360deg); }
}

/* Below this there isn't room for a title beside the crate's controls,
   and the two buttons are the part worth keeping — the strip becomes
   just the record, turning, with a way to stop it. */
@media (max-width: 620px) {
  .np-text { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .now-playing button { transition: none; }
  .now-playing.is-playing .np-disc { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .xnav, .xnav a { transition: none; }
  /* The vignette stays; only the grain's shimmer goes. */
  .camera-fx { animation: none; }
}
