/* ═══════════════════════════════════════════════════════════════════════
   streamingmusic.io — the Studio + the pizazz layer
   Loaded after style.css. Every token here comes from that file; nothing
   in it is restated. Spec: docs/STUDIO-SPEC.md §5.1, §5.2, §6.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═════════════════════════════════════════════════ animatable tokens ═══ */
/* Registering these makes the *shell* recolour smoothly. §6 "Legibility of
   the precognition": the room takes the cover's colour 800 ms after the
   brief lands, four minutes before the PNG exists, so the eventual match
   reads as a prediction rather than a cross-fade. Unregistered custom
   properties snap; registered ones interpolate. If an engine rejects the
   at-rule it is ignored wholesale and the colour simply snaps. */
@property --a1{syntax:"<color>";inherits:true;initial-value:#D96F33}
@property --a2{syntax:"<color>";inherits:true;initial-value:#E4AE6E}
@property --glow{syntax:"<number>#";inherits:true;initial-value:217,111,51}
@property --pal-top{syntax:"<number>#";inherits:true;initial-value:16,24,52}
@property --pal-mid{syntax:"<number>#";inherits:true;initial-value:58,44,110}
@property --pal-accent{syntax:"<number>#";inherits:true;initial-value:232,150,110}

:root{
  /* tempo lock — JS sets --bpm and --phase only; the rest derives.
     --phase carries a unit (e.g. `1.47s`) because animation-delay needs one. */
  --bpm:120;
  --beat:calc(60s / var(--bpm));
  --bar:calc(var(--beat) * 4);
  --phase:0s;

  /* the one genuine spring in the app — a physical meter, nothing else */
  --spring:cubic-bezier(.34,1.56,.64,1);

  --good:#7BCFA5;
  --bad:#E8867B;

  /* the room recolours over 800ms; see the @property block above */
  transition:--a1 .8s var(--ease),--a2 .8s var(--ease),--glow .8s var(--ease);
}

/* ══════════════════════════════════════════════ the genre backdrop ═══ */
/* The world the selected genre is made in, sitting behind the whole Studio.

   Fixed, not absolute: the studio column scrolls and a backdrop that scrolls
   with it stops reading as a room and starts reading as a very tall picture.

   Two stacked layers cross-fade so a swap never shows a gap. The scrim above
   them is what makes the type legible — the images are already dark (centre
   luma ~11-14 of 255) and this takes the rest, heaviest in the middle where the
   interface actually sits, lightest at the edges where the artwork lives. */
/* absolute inside #app, which is exactly one viewport tall and does not scroll —
   the main column scrolls inside it. Same visual result as fixed, without
   depending on no ancestor ever gaining a transform. Only shown in the Studio. */
.gen-backdrop{position:absolute;inset:0;z-index:0;pointer-events:none;
  opacity:0;transition:opacity .5s var(--ease)}
.gen-backdrop.ready{opacity:1}
/* The views must stack above it; #app's own background would otherwise cover it. */
#view-studio,#view-library{position:relative;z-index:1;background:transparent}
/* The Studio's artwork can be more present than the Library's — the Library is a
   dense list of rows and needs a calmer ground behind the type. */
body:not(.in-studio) .gen-backdrop.ready{opacity:.72}
.gb-layer{position:absolute;inset:0;background-size:cover;background-position:center;
  opacity:0;transition:opacity .55s var(--ease)}
.gb-layer.on{opacity:1}
/* Tuned by measurement, not by eye. The first pass sat at .94/.86 and erased the
   artwork entirely — pointless, since the images are already ~11-14 luma in the
   centre and do not need much help. This keeps the room visible at the edges and
   only leans on the middle, where the cards sit; the cards carry their own
   translucent backing on top of it. */
.gb-scrim{position:absolute;inset:0;
  background:
    radial-gradient(130% 100% at 50% 42%,rgba(12,10,9,.72) 0%,
                    rgba(12,10,9,.50) 46%,rgba(12,10,9,.10) 100%),
    linear-gradient(180deg,rgba(12,10,9,.72) 0%,rgba(12,10,9,.30) 24%,
                    rgba(12,10,9,.42) 70%,rgba(12,10,9,.86) 100%)}

/* The studio's own panels sit on top and need to stay readable over artwork
   rather than over flat navy, so they gain a little more body. */
.studio .style{background:rgba(14,12,10,.72);backdrop-filter:blur(6px)}
.studio .style.sel{background:rgba(19,16,13,.86)}
.studio .brief,.studio .plate{backdrop-filter:blur(18px) saturate(140%)}

@media(prefers-reduced-motion:reduce){
  .gen-backdrop,.gb-layer{transition:none}
}

/* ═════════════════════════════════════════════════════════ the shell ═══ */
.studio{display:grid;gap:24px;align-items:start;
  grid-template-columns:minmax(0,1fr) 380px}
.studio-main,.studio-side{min-width:0;display:flex;flex-direction:column;gap:16px}

/* The busy sweep from the old job row is explicitly cut (§6 "The small
   ones") — the only permitted loops here are the running conic sweep and
   the heartbeat dot. */
.studio .job.busy::after{content:none}

/* ══════════════════════════════════════════════════════ the style rail ═══ */
/* .style keeps its geometry from style.css:845-861. Only --c1/--c2 change,
   and they now come from the style's real first palette entry so the card
   and the eventual cover agree. */
/* Forty-one genres in thirteen families. The grid moved down a level onto
   .style-row so each family can carry its own heading; .gen-grid is now the
   stack of those sections. */
.studio .gen-grid{display:block;gap:0;margin-bottom:0}
.studio .style-row{display:grid;
  grid-template-columns:repeat(auto-fill,minmax(196px,1fr));gap:11px}
.studio .style-fam{margin:18px 0 9px;font-size:10px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.studio .style-fam:first-child{margin-top:0}
.studio .style{padding:15px 16px;position:relative}
.studio .style b{font-size:14.5px}
.studio .style span{font-size:12px;line-height:1.45}
/* A genre's vocal policy is a real constraint — an orchestral cue never gets a
   voice, a gospel record always does — so it is worth showing before you pick.

   In normal flow, not absolutely positioned. Floating it in the corner collided
   with the reference line on 20 of 41 cards, because the reference wraps to two
   lines whenever the artist names are long and there is no way to know which
   from CSS. Letting it take its own row costs 14px and can never collide.

   No opacity: it multiplies against the background and would drag this 9.5px
   label back under AA, which is what --ink-3 was just fixed for. */
.studio .style{display:flex;flex-direction:column;align-items:flex-start}
.studio .style-voc{align-self:flex-end;margin-top:6px;font-style:normal;
  font-size:9.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-3)}

/* Collapse upward into a 28px chip over 480ms when a run starts (§5.1
   "running"). grid-template-rows 1fr→0fr is the only collapse that lets the
   ladder reflow into the vacated space; the inner layer carries the motion
   on transform/opacity so nothing paints during the reflow. */
.rail{display:grid;grid-template-rows:1fr;
  transition:grid-template-rows .48s var(--expo)}
.rail > *{min-height:0;overflow:hidden;
  transition:opacity .28s var(--ease),transform .48s var(--expo)}
.rail.collapsed{grid-template-rows:0fr}
.rail.collapsed > *{opacity:0;transform:translateY(-12px)}

.rail-chip{display:none;align-items:center;gap:9px;height:28px;padding:0 13px;
  max-width:100%;border-radius:var(--pill);border:1px solid var(--line-2);
  background:var(--surface);box-shadow:var(--hi);
  font-size:12px;color:var(--ink-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rail-chip.on{display:inline-flex;animation:chipin .42s var(--expo) both}
.rail-chip i{width:8px;height:8px;flex:none;border-radius:50%;
  background:linear-gradient(135deg,var(--c1,var(--flame-1)),var(--c2,var(--flame-3)))}
.rail-chip em{font-style:normal;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis}
@keyframes chipin{from{opacity:0;transform:translateY(-8px) scale(.94)}to{opacity:1;transform:none}}

/* ═══════════════════════════════════════════════════════════ the brief ═══ */
.brief{padding:18px 20px 14px;border-radius:var(--r-xl);
  background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015));
  border:1px solid var(--line);box-shadow:var(--hi),0 24px 50px -44px rgba(0,0,0,1);
  backdrop-filter:blur(14px) saturate(140%)}
.brief-head{font-size:10.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3)}
.brief-note{margin:7px 0 12px;font-size:13px;font-weight:500;letter-spacing:.05em;
  font-variant-caps:all-small-caps;color:var(--ink-2)}
.brief-row{display:grid;gap:8px 18px;align-items:baseline;padding:7px 0;
  grid-template-columns:minmax(0,auto) minmax(0,1fr);border-top:1px solid var(--line)}
.brief-row:first-of-type{border-top:0;padding-top:0}
.brief-k{font-size:10.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3);white-space:nowrap}
/* Cross-fade on style change: 140ms out, 200ms in, 24ms per-row stagger.
   JS sets --i:0..7 on each row and toggles .out for one frame-set. */
.brief-v{font-size:13.5px;font-weight:350;line-height:1.5;color:var(--ink-1);text-align:right;
  transition:opacity .2s var(--ease) calc(var(--i,0) * 24ms),
             transform .2s var(--ease) calc(var(--i,0) * 24ms)}
.brief.out .brief-v{opacity:0;transform:translateY(-4px);
  transition-duration:.14s,.14s;transition-delay:calc(var(--i,0) * 24ms)}

/* ═══════════════════════════════════════════════════════════ the Plate ═══ */
/* A 1:1 deterministic cover preview, painted entirely in CSS. Direct port of
   flowstate/artwork.py:47-92 — the browser computes sha256(title)[:8] % 5
   and hands us the palette, so this is byte-faithful to the PNG the run will
   render four minutes later. --pal-* are `r,g,b` triplets (no rgb() wrapper)
   because the horizon glow needs them inside rgba(). */
.plate-wrap{position:relative;isolation:isolate}
.plate{position:relative;z-index:1;width:100%;aspect-ratio:1;overflow:hidden;
  container-type:inline-size;
  border-radius:var(--r-lg);border:1px solid var(--line-2);background:#0F0D0B;
  box-shadow:var(--hi),0 40px 90px -46px rgba(0,0,0,1);
  /* PALETTES[0] — dusk violet -> amber */
  --pal-top:16,24,52;--pal-mid:58,44,110;--pal-accent:232,150,110;
  /* §6 "The small ones": hovering a style card morphs the gradient in 420ms */
  transition:--pal-top .42s var(--ease),--pal-mid .42s var(--ease),
             --pal-accent .42s var(--ease)}

.plate-css{position:absolute;inset:0;z-index:2}

.plate-wash{position:absolute;inset:0;
  background:linear-gradient(180deg,rgb(var(--pal-top)) 0%,rgb(var(--pal-mid)) 62%,
    rgb(var(--pal-accent)) 100%)}
/* artwork.py:59 eases the lower band with u**1.6 before blending toward the
   accent. A two-stop gradient is linear there and lands visibly lighter, so
   the eased curve is sampled at u = .25/.5/.75 → 10.9/33.0/63.1 %. */
@supports (background:color-mix(in srgb,red,blue)){
  .plate-wash{background:linear-gradient(180deg,
    rgb(var(--pal-top)) 0%,
    rgb(var(--pal-mid)) 62%,
    color-mix(in srgb,rgb(var(--pal-accent)) 10.9%,rgb(var(--pal-mid))) 71.5%,
    color-mix(in srgb,rgb(var(--pal-accent)) 33%,rgb(var(--pal-mid))) 81%,
    color-mix(in srgb,rgb(var(--pal-accent)) 63.1%,rgb(var(--pal-mid))) 90.5%,
    rgb(var(--pal-accent)) 100%)}
}
/* artwork.py:66-75 — 28 stacked ellipses at (50%, 78%), 1.9:1, gaussian
   blurred, blended at .55. One radial gradient reproduces it exactly. */
.plate-glow{position:absolute;inset:0;
  background:radial-gradient(190% 100% at 50% 78%,rgba(var(--pal-accent),.55),transparent 70%)}
/* artwork.py:79 — rectangle at .780→.782 height, fill (255,255,255,40) */
.plate-rule{position:absolute;left:0;right:0;top:78%;height:1px;background:rgba(255,255,255,.16)}

/* artwork.py:83-93. Sizes are 132/104/78 px on the 1500² square render →
   8.8 / 6.933 / 5.2 % of the side. The engine never wraps and lets long
   titles run off the canvas, so neither do we. */
.plate-title{position:absolute;left:5%;right:5%;top:34%;text-align:center;
  font-family:"DejaVu Sans",var(--sans);font-weight:700;line-height:1;
  letter-spacing:.005em;text-transform:uppercase;white-space:nowrap;
  color:rgba(255,255,255,.94);text-shadow:.023em .03em 0 rgba(0,0,0,.47);
  font-size:2.2rem;font-size:8.8cqw}
.plate[data-len="m"] .plate-title{font-size:1.75rem;font-size:6.933cqw}
.plate[data-len="l"] .plate-title{font-size:1.3rem;font-size:5.2cqw}

.plate-sub{position:absolute;left:5%;right:5%;top:44.8%;text-align:center;
  font-family:"DejaVu Sans",var(--sans);letter-spacing:.02em;line-height:1;
  color:rgba(255,255,255,.67);
  font-size:.68rem;font-size:2.533cqw}
.plate[data-len="m"] .plate-sub{top:42.93%}
.plate[data-len="l"] .plate-sub{top:41.2%}

/* the real PNG, underneath the replica so the CSS layer can dissolve off it */
.plate-img{position:absolute;inset:0;z-index:1;width:100%;height:100%;
  object-fit:cover;opacity:0}

/* §6 "The cover develop", phase 1 — 1.6s var(--ease) at stage.done brief */
.plate.developing .plate-css{animation:develop1 1.6s var(--ease) both}
@keyframes develop1{
  from{opacity:0;filter:blur(28px) saturate(.4)}
  to{opacity:1;filter:blur(10px) saturate(1)}}

/* phase 2 — 1.4s var(--expo) at stage.done artwork. The contrast/brightness
   pair is the photographic part: a print coming up in a tray. */
.plate.resolved .plate-img{opacity:1;animation:develop2 1.4s var(--expo) both}
.plate.resolved .plate-css{animation:developout 1.4s var(--expo) both}
@keyframes develop2{
  from{filter:blur(10px) contrast(.75) brightness(1.15);transform:scale(1.04)}
  to{filter:blur(0) contrast(1) brightness(1);transform:scale(1)}}
@keyframes developout{from{opacity:1}to{opacity:0}}

/* §6 "The reveal" — job.done. 1→1.06→1 over 900ms. */
.plate.reveal{animation:coverpop .9s var(--expo)}
@keyframes coverpop{0%{transform:scale(1)}42%{transform:scale(1.06)}100%{transform:scale(1)}}

/* One pulse. Never a loop. */
.plate-bloom{position:absolute;left:50%;top:50%;z-index:0;width:140%;aspect-ratio:1;
  border-radius:50%;pointer-events:none;opacity:0;
  transform:translate(-50%,-50%) scale(.4);
  background:radial-gradient(circle,rgba(var(--glow),.5),transparent 62%)}
.plate-bloom.fire{animation:bloom 1.2s var(--ease) 1 both}
@keyframes bloom{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.4)}
  22%{opacity:.9}
  100%{opacity:0;transform:translate(-50%,-50%) scale(2.2)}}

/* ═══════════════════════════════════════════════════════════ composer ═══ */
.composer{display:flex;flex-direction:column;gap:11px;margin-top:16px}
.studio .gen-input{width:100%;min-width:0;height:48px}
.studio-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:3px}
.studio-actions .btn{width:100%;padding-inline:14px}
.fine{font-size:12.5px;font-weight:350;line-height:1.5;color:var(--ink-3)}

/* ═══════════════════════════════════════════════════ state: drafting ═══ */
.draft-wait{display:flex;flex-direction:column;gap:14px;padding:18px 20px;
  border-radius:var(--r-xl);border:1px solid var(--line);background:rgba(255,255,255,.022)}
.sk-lines{display:flex;flex-direction:column;gap:9px}
/* reuses .sk-row's shimmer from style.css:816 — markup is
   <i class="sk-row sk-line" style="--w:82%"> */
.sk-line{height:13px;width:var(--w,100%);border-radius:99px}
.draft-copy{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  flex-wrap:wrap;font-size:12.5px;color:var(--ink-3);font-weight:350}
.elapsed{font-size:13px;color:var(--ink-1);font-variant-numeric:tabular-nums;
  letter-spacing:.02em}

/* ════════════════════════════════════════════════════ state: drafted ═══ */
.drafted{display:flex;flex-direction:column;gap:14px;padding:20px;
  border-radius:var(--r-xl);border:1px solid var(--line);
  background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015));
  box-shadow:var(--hi),0 30px 64px -46px rgba(0,0,0,1);
  animation:rowin .5s var(--expo) both}
.drafted-head{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap}
.drafted-title{flex:1;min-width:0;font-family:var(--serif);font-weight:550;
  letter-spacing:-.018em;line-height:1.06;text-transform:uppercase;
  font-size:clamp(24px,3.2vw,34px);font-optical-sizing:auto;
  font-variation-settings:"opsz" 40}
.dh-meta{display:flex;align-items:baseline;gap:14px;font-size:12.5px;color:var(--ink-2);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.dh-meta b{font-weight:600;color:var(--ink-1)}
/* 70–95 words is M3's own rule (brain.py:29), so the counter is honest, not a
   style opinion. Outside it, amber. */
.wc{transition:color .25s var(--ease)}
.wc.warn{color:var(--ember)}

.prompt-edit{width:100%;min-height:136px;padding:14px 16px;resize:vertical;
  border-radius:var(--r);background:rgba(0,0,0,.3);border:1px solid var(--line-2);
  outline:none;font-family:var(--sans);font-size:14px;line-height:1.62;color:var(--ink-0);
  /* the focus ring snaps rather than easing — a box-shadow spread transition
     repaints the whole textarea every frame */
  transition:border-color .22s var(--ease),background .22s var(--ease)}
.prompt-edit:focus{border-color:rgba(224,143,76,.5);background:rgba(0,0,0,.42);
  box-shadow:0 0 0 4px rgba(217,111,51,.1)}
.prompt-edit::placeholder{color:var(--ink-3)}

.tags{display:flex;flex-wrap:wrap;gap:7px}
/* 40ms stagger, 12px rise (§6 "The small ones"). JS sets --i per chip. */
.tag{display:inline-flex;align-items:center;height:26px;padding:0 11px;border-radius:var(--pill);
  border:1px solid var(--line-2);background:var(--surface);
  font-size:12px;font-weight:400;color:var(--ink-2);white-space:nowrap;
  animation:tagin .34s var(--expo) both;animation-delay:calc(var(--i,0) * 40ms)}
.tag.more{color:var(--ink-3);border-style:dashed}
@keyframes tagin{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

.drafted-note{font-size:12.5px;color:var(--ink-3);font-weight:350}
.drafted-actions{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.drafted-actions .spacer{flex:1;min-width:0}

/* ═════════════════════════════════════════════════════ state: queued ═══ */
.queue-note{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--ink-2)}
.queue-note::before{content:"";width:7px;height:7px;flex:none;border-radius:50%;
  background:var(--ink-2);animation:heartbeat 2.4s var(--ease-io) infinite}

/* ════════════════════════════════════════════════════ state: running ═══ */
/* Elapsed only. No ETA anywhere — n=6 with a ±50 % spread. */
.runstrip{position:relative;display:flex;align-items:center;gap:14px;height:56px;
  padding:0 16px;flex-wrap:nowrap;overflow:hidden;
  border:1px solid var(--line);border-bottom:0;border-radius:var(--r) var(--r) 0 0;
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.02));
  box-shadow:var(--hi)}

/* Overall progress. scaleX from a left origin, so the paint is one composited
   transform per update rather than a layout-triggering width change — the same
   trick the player's own position line uses. `--p` is 0..1 and is set by the
   identical setP() the ring used, so nothing about the data path changed. */
.rs-prog{position:absolute;left:0;right:0;bottom:0;height:2px;transform-origin:0 50%;
  transform:scaleX(var(--p,0));
  background:linear-gradient(90deg,var(--a1,var(--flame-1)),var(--a2,var(--flame-3)));
  transition:transform 1s linear}
.rs-prog.snap{transition:transform .42s var(--expo)}
.rs-dot{width:9px;height:9px;flex:none;border-radius:50%;background:var(--flame-3);
  box-shadow:0 0 12px rgba(var(--glow),.9);
  animation:heartbeat 2.4s var(--ease-io) infinite}
@keyframes heartbeat{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.42;transform:scale(.8)}}
.rs-time{font-size:16px;font-weight:550;letter-spacing:.02em;font-variant-numeric:tabular-nums}
.rs-style{min-width:0;font-size:13px;color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rs-actions{margin-left:auto;display:flex;align-items:center;gap:10px;flex:none}
/* Per-stage cancel copy (§2.5) — sits beside the button, never in a tooltip. */
.stop-copy{font-size:11.5px;color:var(--ink-3);text-align:right;max-width:26ch;line-height:1.35}

/* ─────────────────────────────────────────────── the progress ring ─── */
/* Asymptotic by construction (§6): p = lo + (hi-lo)(1-exp(-(t-t0)/45)), driven
   at 1 Hz, so the resting transition is 1s linear and only a real stage.done
   snaps it — .snap, 420ms expo. Both shapes must carry pathLength="1" so the
   dash maths is geometry-independent. */
.stop-wrap{position:relative;display:inline-grid;place-items:center;flex:none}

/* ─────────────────────────────────────────────────── the ladder ─── */
.lad{display:grid;grid-template-columns:minmax(0,236px) minmax(0,1fr);
  border:1px solid var(--line);background:rgba(255,255,255,.022);
  transition:opacity .6s var(--ease)}
.lad-left{display:flex;flex-direction:column;gap:16px;padding:16px;min-width:0;
  border-right:1px solid var(--line)}
.lad-rows{display:flex;flex-direction:column;gap:2px;padding:8px;min-width:0}

.lad-row{display:grid;gap:5px 12px;padding:10px 12px;border-radius:12px;
  grid-template-columns:24px minmax(0,1fr) auto;
  grid-template-areas:"ring label time" ". body body";
  border:1px solid transparent;
  transition:background .3s var(--ease),border-color .3s var(--ease),opacity .3s var(--ease)}
.lad-row.waiting{opacity:.45}
.lad-row.waiting .lad-body{display:none}
.lad-row.running{background:var(--surface-2);border-color:var(--line-2)}
.lad-row.failed{background:rgba(224,110,95,.08);border-color:rgba(224,110,95,.3)}

.lad-ring{grid-area:ring;position:relative;display:grid;place-items:center;
  width:22px;height:22px;border-radius:50%;
  border:1px solid var(--ink-3);color:var(--ink-2);
  font-size:10px;font-weight:600;font-variant-numeric:tabular-nums;
  transition:border-color .3s var(--ease),background .3s var(--ease),color .3s var(--ease)}
.lad-row.running .lad-ring{border-color:var(--line-2);color:var(--ink-0)}
/* rotating conic sweep — one of exactly two permitted loops on this screen.
   The ring is carved out of the disc with a mask so only the rim animates,
   and the motion itself is a transform. */
.lad-row.running .lad-ring::after{content:"";position:absolute;inset:-2px;border-radius:50%;
  background:conic-gradient(from 0turn,transparent 0 60%,rgba(224,143,76,.18) 74%,
    var(--flame-3) 96%,transparent 100%);
  -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));
  mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));
  animation:sweepring 1.4s linear infinite}
@keyframes sweepring{to{transform:rotate(1turn)}}
.lad-row.done .lad-ring{border-color:transparent;color:#fff;
  background:var(--flame-2)}
.lad-row.failed .lad-ring{border-color:var(--bad);color:var(--bad)}

.lad-n{transition:opacity .2s var(--ease)}
.lad-check{position:absolute;inset:0;display:none;width:100%;height:100%;
  fill:none;stroke:#fff;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.lad-row.done .lad-n{display:none}
.lad-row.done .lad-check{display:block}
/* The geometry must carry pathLength="1" — then the dash maths is independent
   of the viewBox and of how the tick is drawn. The animation goes on the path
   itself, not the <svg>: inheriting an animated stroke-dashoffset works but is
   a trick, and this survives someone swapping the markup for a <polyline>. */
.lad-check path,.lad-check polyline{stroke-dasharray:1;stroke-dashoffset:1}
.lad-row.done .lad-check path,.lad-row.done .lad-check polyline{
  animation:checkdraw .42s var(--ease) .08s forwards}
@keyframes checkdraw{to{stroke-dashoffset:0}}

.lad-label{grid-area:label;font-size:11.5px;font-weight:600;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink-2);align-self:center}
.lad-row.done .lad-label,.lad-row.running .lad-label{color:var(--ink-0)}
.lad-time{grid-area:time;align-self:center;font-size:12px;color:var(--ink-3);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.lad-row.running .lad-time{color:var(--ember)}
.lad-row.done .lad-time{color:var(--good);animation:flipin .38s var(--expo) both}
@keyframes flipin{from{opacity:0;transform:translateY(80%)}to{opacity:1;transform:none}}
.lad-body{grid-area:body;display:flex;flex-direction:column;gap:9px;min-width:0;
  padding-top:2px}
.lad-copy{font-size:12.5px;font-weight:350;line-height:1.55;color:var(--ink-2);max-width:56ch}
.lad-copy b{font-weight:500;color:var(--ink-1)}

/* stage progress inside a row — scaleX, never width */
.stage-bar{position:relative;height:4px;border-radius:99px;overflow:hidden;
  background:rgba(255,255,255,.09)}
.stage-bar i{position:absolute;inset:0;border-radius:99px;transform-origin:left center;
  transform:scaleX(var(--p,0));
  background:linear-gradient(90deg,var(--flame-1),var(--ember));
  transition:transform 1s linear}
.stage-bar.snap i{transition:transform .42s var(--expo)}

/* ── the drafted brief, rendered inside row ① ── */
.job-title{font-family:var(--serif);font-weight:550;letter-spacing:-.024em;line-height:1.04;
  font-size:clamp(30px,4.6vw,52px);font-optical-sizing:auto;
  font-variation-settings:"opsz" 56}
/* §6 "The title slam" — 720ms expo. The letter-spacing collapse is the whole
   move: it reads as focus pulling in. It costs a re-layout of one line of
   text once per run, which is the trade the spec makes deliberately. */
.job-title.slam{animation:slam .72s var(--expo) both;will-change:filter}
@keyframes slam{
  from{opacity:0;letter-spacing:.34em;filter:blur(14px)}
  to{opacity:1;letter-spacing:-.024em;filter:blur(0)}}

/* BPM odometer support: JS counts 60→122 over 900ms with an overshoot to 124.
   Tabular figures and a reserved box mean the settle never reflows the row. */
.odo{display:inline-block;min-width:3ch;text-align:right;
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

/* §6 "The prompt reveal" — not a typewriter. The full text is in the DOM and
   selectable; each word span is revealed by animation-delay alone. */
.prompt-words{font-size:13.5px;font-weight:350;line-height:1.6;color:var(--ink-1);
  white-space:pre-wrap;max-width:60ch}
.pw{display:inline-block;
  animation:promptword .26s var(--ease) both;animation-delay:calc(var(--i,0) * 14ms)}
@keyframes promptword{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}

/* ── made with ── */
.madewith{margin-top:auto;font-size:11.5px}
.mw-head{display:flex;align-items:center;gap:10px;margin-bottom:9px;
  font-size:10px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.mw-head::before,.mw-head::after{content:"";flex:1;height:1px;background:var(--line)}
.mw-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding:3px 0;color:var(--ink-3)}
.mw-row b{font-weight:500;color:var(--ink-1)}

/* ═══════════════════════════════════════════════ the mastering bench ═══ */
/* -24 … -6 LUFS, hairline ticks every 3 LU, ghost tick at the target.
   JS sets --lufs as a bare number (e.g. --lufs:-11.8); the position is
   derived here so the spring transition fires on one property write. */
.bench{display:flex;flex-direction:column;gap:11px;padding:16px;
  border-radius:var(--r);background:rgba(0,0,0,.24);border:1px solid var(--line)}
.bench-grid{display:grid;gap:6px 12px;align-items:center;
  grid-template-columns:56px minmax(0,1fr) 58px}
.bench-axis{grid-column:2;position:relative;height:13px;
  font-size:10px;color:var(--ink-3);font-variant-numeric:tabular-nums}
.bench-axis b{position:absolute;top:0;left:calc(var(--i,0) * 100% / 6);
  transform:translateX(-50%);font-weight:500}
.bench-axis .unit{left:auto;right:0;transform:none;letter-spacing:.12em}
.bench-name{font-size:10.5px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3)}
.bench-val{font-size:13px;text-align:right;color:var(--ink-1);
  font-variant-numeric:tabular-nums}

.bench-scale{position:relative;height:22px}
.bench-scale::before{content:"";position:absolute;left:0;right:0;top:50%;height:1px;
  background:var(--line-2);transform:translateY(-50%)}
/* six equal cells, one hairline at the head of each + a cap on the right = the
   seven ticks at -24 -21 -18 -15 -12 -9 -6 */
.bench-scale::after{content:"";position:absolute;inset:5px 0;
  background-image:linear-gradient(90deg,var(--line-2) 0 1px,transparent 1px);
  background-size:calc(100% / 6) 100%;border-right:1px solid var(--line-2)}

.needle,.bench-target{position:absolute;left:0;top:0;bottom:0;width:100%;
  pointer-events:none;
  transform:translateX(calc(clamp(0,(var(--lufs,-24) + 24) / 18,1) * 100%))}
.needle{z-index:2;transition:transform 1100ms var(--spring)}
.bench-target{z-index:1}
.needle::after{content:"";position:absolute;left:0;top:50%;width:11px;height:11px;
  border-radius:50%;transform:translate(-50%,-50%);
  background:linear-gradient(135deg,var(--ember),var(--flame-1));
  box-shadow:0 0 14px -2px rgba(var(--glow),.9),0 1px 3px rgba(0,0,0,.7)}
.needle.ghosted::after{background:none;border:1.5px solid var(--ink-2);
  box-shadow:none;width:9px;height:9px}
/* ▲ target -14.0 */
.bench-target::after{content:"";position:absolute;left:0;bottom:1px;width:0;height:0;
  transform:translateX(-50%);
  border-left:5px solid transparent;border-right:5px solid transparent;
  border-bottom:7px solid rgba(255,255,255,.34)}
.bench-target b{position:absolute;left:0;bottom:-15px;transform:translateX(-50%);
  font-size:10px;color:var(--ink-3);white-space:nowrap;font-variant-numeric:tabular-nums}
/* ▽ ghost on the axis row */
.bench-ghost{position:absolute;top:1px;left:calc(clamp(0,(var(--lufs,-14) + 24) / 18,1) * 100%);
  transform:translateX(-50%);width:0;height:0;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:6px solid var(--ink-2)}

.bench-peak{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  font-size:13px;color:var(--ink-2);font-variant-numeric:tabular-nums}
.peak-val{font-weight:600;transition:color .6s var(--ease)}
.peak-val.hot{color:var(--bad)}
.peak-val.safe{color:var(--good)}
.bench-peak em{font-style:normal;color:var(--ink-3);font-size:12.5px}
/* every number here was measured; the sentence is a reading, not a claim */
.bench-note{font-family:var(--serif);font-style:italic;font-size:16px;font-weight:400;
  line-height:1.5;color:var(--ink-1);font-optical-sizing:auto}

/* ═══════════════════════════════════════════════════ console drawer ═══ */
.console{overflow:hidden;border:1px solid var(--line);border-top:0;
  border-radius:0 0 var(--r) var(--r);background:rgba(0,0,0,.3)}
.console-head{display:flex;align-items:center;gap:10px;width:100%;padding:10px 16px;
  font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);transition:color .2s var(--ease)}
.console-head:hover{color:var(--ink-1)}
.console-head .count{margin-left:auto;letter-spacing:.02em;text-transform:none;
  font-weight:400;font-variant-numeric:tabular-nums}
.console-head .caret{transition:transform .3s var(--ease)}
.console.open .console-head .caret{transform:rotate(180deg)}
.console-body{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .34s var(--expo)}
.console.open .console-body{grid-template-rows:1fr}
.console-scroll{min-height:0;max-height:220px;overflow:hidden auto;scroll-behavior:smooth;
  padding:0 16px;
  font-family:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size:12px;line-height:1.7;color:var(--ink-2)}
.console.open .console-scroll{padding:2px 16px 14px}
.cline{white-space:pre-wrap;word-break:break-word;
  animation:clinein .16s var(--ease) both}
.cline.err{color:var(--bad)}
@keyframes clinein{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* ═══════════════════════════════════════════════════════ state: done ═══ */
.studio[data-state="done"] .lad{opacity:.3}
.finale{position:relative;display:grid;gap:22px;align-items:center;padding:22px;
  grid-template-columns:minmax(0,320px) minmax(0,1fr);
  border-radius:var(--r-xl);border:1px solid var(--line);
  background:linear-gradient(180deg,rgba(255,255,255,.048),rgba(255,255,255,.014));
  box-shadow:var(--hi),0 34px 70px -44px rgba(0,0,0,1);
  animation:rowin .5s var(--expo) both}
.finale-copy{min-width:0;display:flex;flex-direction:column;gap:12px;align-items:flex-start}
.saved-pill{display:inline-flex;align-items:center;gap:9px;height:38px;padding:0 17px;
  border-radius:var(--pill);color:#171209;
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;background:var(--ink-0);
  box-shadow:0 12px 28px -16px rgba(0,0,0,.9);
  animation:pillin .5s var(--expo) both}
@keyframes pillin{from{opacity:0;transform:translateX(28px)}to{opacity:1;transform:none}}
.finale-sub{font-size:13.5px;color:var(--ink-2);font-variant-numeric:tabular-nums}
.finale-actions{display:flex;gap:10px;flex-wrap:wrap}
.cover-recovery{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr);
  gap:7px 10px;align-items:start;padding:11px 12px;border-radius:14px;
  color:var(--ink-2);border:1px solid rgba(228,174,110,.22);
  background:linear-gradient(145deg,rgba(228,174,110,.085),rgba(255,255,255,.025));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045),0 14px 34px -28px rgba(0,0,0,.95)}
.cover-recovery-mark{width:7px;height:7px;margin-top:4px;border-radius:50%;
  background:#E0A468;box-shadow:0 0 0 4px rgba(224,164,104,.1),0 0 16px rgba(224,143,76,.45)}
.cover-recovery-copy{min-width:0;display:flex;flex-direction:column;gap:3px;
  font-size:10.5px;font-weight:350;line-height:1.42}
.cover-recovery-copy b{color:var(--ink-1);font-size:11px;font-weight:550;letter-spacing:.015em}
.cover-retry{grid-column:1/-1;display:inline-flex;align-items:center;justify-content:center;gap:7px;
  min-height:34px;padding:7px 11px;border:1px solid rgba(228,174,110,.2);border-radius:10px;
  color:rgba(237,220,203,.9);background:rgba(255,255,255,.035);
  font:500 10.5px/1 var(--sans);letter-spacing:.025em;cursor:pointer;
  transition:color .2s var(--ease),border-color .2s var(--ease),background .2s var(--ease),transform .2s var(--ease)}
.cover-retry .ic{width:14px;height:14px}
.cover-retry:hover:not(:disabled){color:#fff;border-color:rgba(228,174,110,.42);
  background:rgba(224,143,76,.1);transform:translateY(-1px)}
.cover-retry:focus-visible{outline:2px solid var(--ember);outline-offset:2px}
.cover-retry:disabled{cursor:wait;opacity:.72}
.cover-retry-spinner{width:12px;height:12px;border:1.5px solid rgba(255,255,255,.22);
  border-top-color:#E0A468;border-radius:50%;animation:cover-retry-spin .75s linear infinite}
@keyframes cover-retry-spin{to{transform:rotate(1turn)}}
.cover-recovery[data-state="working"]{border-color:rgba(228,174,110,.32)}
.cover-recovery[data-state="done"]{display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:48px;color:#a7e6c5;border-color:rgba(116,196,156,.24);
  background:linear-gradient(145deg,rgba(74,158,112,.1),rgba(255,255,255,.025));
  font-size:11px;font-weight:550;letter-spacing:.025em}
.cover-recovery[data-state="done"] .ic{width:14px;height:14px}

/* §6 "The reveal" — the waveform paints left-to-right on the same frame the
   audio starts, so the paint reads as the track playing itself into being. */
.wave-paint{position:relative;width:100%;height:74px;clip-path:inset(0 100% 0 0)}
.wave-paint.painting{animation:wavepaint 1.4s var(--ease-io) forwards}
.wave-paint.painted{clip-path:inset(0 0 0 0)}
@keyframes wavepaint{to{clip-path:inset(0 0 0 0)}}
.wave-paint canvas{display:block;width:100%;height:100%}

/* Four minutes after the click, autoplay activation is usually gone. The
   finale degrades to an obvious affordance, never to unexplained silence.
   This loop exists only in that degraded branch. */
/* ═════════════════════════════════════════════════════ state: failed ═══ */
.fail{display:flex;flex-direction:column;gap:12px;align-items:flex-start;padding:18px 20px;
  border-radius:var(--r);background:rgba(224,110,95,.08);border:1px solid rgba(224,110,95,.28);
  animation:rowin .45s var(--expo) both}
.fail-kind{font-size:10.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--bad)}
.fail-copy{max-width:58ch;font-size:14px;font-weight:350;line-height:1.6;color:var(--ink-1)}
.fail-actions{display:flex;gap:9px;flex-wrap:wrap}
.lad-row.failed .fail{background:none;border:0;padding:0}

/* ══════════════════════════════════════════════════ state: cancelled ═══ */
.cancelled{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:14px 18px;
  border-radius:var(--r);background:var(--surface);border:1px solid var(--line);
  font-size:13.5px;color:var(--ink-2)}
.cancelled b{font-weight:500;color:var(--ink-1)}
.cancelled .spacer{flex:1;min-width:0}

/* ════════════════════════════════════════════════════ state: offline ═══ */
.offline{display:flex;flex-direction:column;gap:14px;align-items:flex-start;padding:24px;
  border-radius:var(--r-xl);border:1px dashed var(--line-3);background:rgba(255,255,255,.022)}
.offline p{max-width:52ch;font-size:14.5px;font-weight:350;line-height:1.6;color:var(--ink-1)}
/* the screen is never empty — static-manifest cards stay, visibly inert */
.studio[data-state="offline"] .style{opacity:.42;pointer-events:none;filter:saturate(.35)}

/* ═══════════════════════════════════════════════════════ Music Geek ═══ */
/* The class remains `.receipt` for the Studio's stable internal API. Visibly it
   is the Music Geek drawer: authored intent, measurements and estimates never
   share an unlabeled row. */
.music-geek-layer{position:fixed;inset:0;z-index:210;visibility:hidden;pointer-events:none;
  isolation:isolate;transition:visibility 0s linear .42s}
body.has-music-geek{overscroll-behavior:none}
.music-geek-layer.open{visibility:visible;pointer-events:auto;transition-delay:0s}
.music-geek-scrim{position:absolute;inset:0;background:rgba(4,3,2,.5);
  opacity:0;backdrop-filter:blur(0) saturate(100%);cursor:default;
  transition:opacity .22s var(--ease),backdrop-filter .3s var(--ease)}
.music-geek-layer.open .music-geek-scrim{opacity:1;backdrop-filter:blur(3px) saturate(82%)}
.music-geek-layer .receipt{position:absolute;top:10px;right:10px;bottom:10px;z-index:1;
  display:flex;flex-direction:column;width:clamp(440px,36vw,540px);max-width:calc(100vw - 48px);
  overflow:hidden;border:1px solid rgba(255,255,255,.13);border-radius:24px;
  background:
    radial-gradient(90% 42% at 105% -4%,rgba(217,111,51,.09),transparent 62%),
    linear-gradient(180deg,rgba(21,17,14,.985),rgba(12,10,8,.985));
  backdrop-filter:blur(28px) saturate(145%);
  box-shadow:inset 1px 0 rgba(255,255,255,.055),-18px 0 50px rgba(0,0,0,.38),
             -60px 0 140px rgba(0,0,0,.34);
  opacity:0;transform:translate3d(calc(100% + 24px),0,0);pointer-events:none;
  will-change:transform,opacity;
  transition:opacity .28s var(--ease),transform .42s var(--expo)}
.music-geek-layer .receipt::before{content:"";position:absolute;z-index:3;left:24px;right:24px;top:0;
  height:1px;background:linear-gradient(90deg,transparent,rgba(228,174,110,.7),transparent);
  pointer-events:none}
.music-geek-layer.open .receipt,.music-geek-layer .receipt.open{
  opacity:1;transform:none;pointer-events:auto}
.receipt-head{position:relative;z-index:2;display:flex;align-items:center;gap:14px;
  min-height:70px;padding:14px 20px 13px 24px;border-bottom:1px solid rgba(255,255,255,.075);
  background:linear-gradient(180deg,rgba(26,21,17,.93),rgba(18,15,12,.82));
  backdrop-filter:blur(18px) saturate(140%)}
.receipt-heading{display:flex;flex-direction:column;gap:4px;min-width:0}
.receipt-head .kicker{margin:0;font-size:10.5px;color:var(--flame-3)}
.receipt-heading p{margin:0;font-size:11.5px;font-weight:350;color:var(--ink-3)}
.receipt-head .icon-btn{margin-left:auto;width:40px;height:40px;border-radius:13px;
  border:1px solid var(--line-2);background:rgba(255,255,255,.035)}
.receipt-head .icon-btn:hover{border-color:rgba(224,143,76,.28);background:rgba(217,111,51,.1)}
.receipt-body{position:relative;z-index:1;flex:1;min-height:0;overflow-y:auto;
  overscroll-behavior:contain;scrollbar-gutter:stable;padding:20px 22px 24px;
  display:flex;flex-direction:column;gap:18px}
.geek-track-card{display:grid;grid-template-columns:116px minmax(0,1fr);align-items:center;gap:18px;
  padding:10px;border:1px solid rgba(255,255,255,.09);border-radius:18px;
  background:linear-gradient(145deg,rgba(255,255,255,.055),rgba(255,255,255,.018));
  box-shadow:inset 0 1px rgba(255,255,255,.04),0 24px 50px -38px rgba(0,0,0,.9)}
.receipt-body .geek-track-card .plate-wrap{width:116px;border-radius:13px;overflow:hidden}
.geek-track-copy{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:8px}
.receipt-body img.rc-cover{width:100%;border-radius:var(--r)}
.rc-title{font-family:var(--serif);font-size:26px;font-weight:500;letter-spacing:-.026em;
  line-height:1.12;font-optical-sizing:auto;font-variation-settings:"opsz" 36}
.rc-sub{font-size:12px;line-height:1.45;color:var(--ink-2);font-variant-numeric:tabular-nums}
.rc-sec{display:flex;flex-direction:column;gap:8px}
.rc-h{font-size:10px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.rc-row{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  font-size:12.5px;color:var(--ink-3)}
.rc-row b{font-weight:450;color:var(--ink-1);text-align:right;
  font-variant-numeric:tabular-nums}
.rc-prompt{font-size:13px;font-weight:350;line-height:1.62;color:var(--ink-1)}
.rc-id{display:flex;align-items:center;gap:8px;padding:7px 8px 7px 12px;border-radius:10px;
  background:rgba(0,0,0,.34);border:1px solid var(--line-2)}
.rc-id code{flex:1;min-width:0;font-size:11px;color:var(--ember);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rc-note{font-size:11.5px;color:var(--ink-3);font-weight:350}
.receipt-foot{position:relative;z-index:2;display:flex;gap:8px;flex-wrap:wrap;padding:14px 20px 18px;
  border-top:1px solid rgba(255,255,255,.075);background:rgba(11,9,8,.9);
  backdrop-filter:blur(18px) saturate(135%)}
.receipt-foot .btn{flex:1;min-width:110px}

.music-geek-open{width:max-content;margin-top:16px}
.geek-legend{display:flex;gap:6px;flex-wrap:wrap;margin-top:2px}
.geek-origin{display:inline-flex;align-items:center;min-height:22px;padding:3px 8px;
  border-radius:var(--pill);border:1px solid var(--line-2);background:var(--surface);
  font-size:9px;font-weight:650;line-height:1;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-2);white-space:nowrap}
.geek-origin.authored{color:var(--ember);border-color:rgba(228,174,110,.28);
  background:rgba(228,174,110,.08)}
.geek-origin.measured{color:var(--good);border-color:rgba(111,227,180,.25);
  background:rgba(111,227,180,.07)}
.geek-origin.inferred{color:#AFC9F5;border-color:rgba(175,201,245,.25);
  background:rgba(125,160,220,.08)}
.geek-origin.listening{color:var(--ink-1)}

.geek-disclosures{display:flex;flex-direction:column;gap:6px;padding:11px 12px;
  border:1px solid rgba(228,174,110,.18);border-radius:11px;background:rgba(228,174,110,.045)}
.geek-disclosures p{margin:0;font-size:11.5px;font-weight:350;line-height:1.5;color:var(--ink-2)}
.geek-group{display:flex;flex-direction:column;gap:11px;padding-top:16px;
  border-top:1px solid var(--line)}
.geek-group-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.geek-group-copy{min-width:0}
.geek-group-title{margin:0;font-size:13px;font-weight:570;color:var(--ink-0)}
.geek-group-description{margin:4px 0 0;font-size:11.5px;font-weight:350;
  line-height:1.45;color:var(--ink-3)}
.geek-items{display:flex;flex-direction:column;margin:0}
.geek-item{display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);
  gap:8px 16px;padding:10px 0;border-top:1px solid rgba(255,255,255,.045)}
.geek-item:first-child{padding-top:0;border-top:0}
.geek-item-label{font-size:11.5px;line-height:1.4;color:var(--ink-3)}
.geek-item-answer{display:flex;flex-direction:column;align-items:flex-end;gap:3px;margin:0;
  min-width:0;text-align:right}
.geek-item-value{max-width:100%;font-size:12.5px;font-weight:480;line-height:1.42;
  color:var(--ink-1);font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.geek-item-note{max-width:32ch;font-size:10.5px;font-weight:350;line-height:1.4;color:var(--ink-3)}
.geek-item.wide{grid-template-columns:1fr;gap:5px}
.geek-item.wide .geek-item-answer{align-items:flex-start;text-align:left}
.geek-item.wide .geek-item-note{max-width:none}
.geek-empty{margin:0;padding:12px;border:1px dashed var(--line-2);border-radius:10px;
  font-size:11.5px;font-weight:350;line-height:1.5;color:var(--ink-3)}
.geek-error{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:16px;
  border:1px solid var(--line-2);border-radius:var(--r);background:var(--surface)}
.geek-error strong{font-size:13px;font-weight:560;color:var(--ink-0)}
.geek-error p{margin:0;font-size:11.5px;font-weight:350;line-height:1.5;color:var(--ink-2)}
.geek-error .btn{margin-top:4px}

/* ══════════════════════════════════════════════════════ tempo lock ═══ */
/* html.tempo is set by play() only when the track has a BPM. Uploads and any
   prompt_override render (pipeline.py:75-81 builds meta with no bpm key) keep
   the static look — we never fake a tempo we do not have.
   Honest note: --bpm is what M3 was asked for, never beat-detected. The 16s
   resync corrects phase, not drift. */
html.tempo .eqbars i{animation-duration:calc(var(--beat) / 2);
  animation-delay:calc(-1 * var(--phase))}
html.tempo .eqbars i:nth-child(2){animation-delay:calc(-1 * var(--phase) - var(--beat) * .34)}
html.tempo .eqbars i:nth-child(3){animation-delay:calc(-1 * var(--phase) - var(--beat) * .67)}
/* The wordmark's note breathes once per bar, phase-locked like everything
   else on this list — a breath per measure, never per beat: the mark sits in
   the corner of every view and a per-beat pulse there is a metronome. */
html.tempo .sidebar .mark .note-head{--note-beat:var(--bar);
  --note-phase:calc(-1 * var(--phase))}
html.tempo .ea-eq i{animation-duration:var(--bar);animation-delay:calc(-1 * var(--phase))}

/* ── the sidechain duck, deliberately scoped ── */
/* Two of the six presets are tagged "deep focus" and "study music". A room
   that ducks 122 times a minute for four hours is the first thing a returning
   user switches off, so this is applied to exactly two elements that already
   mean "this is playing" — and never to .ambient. */
@keyframes pump{0%{opacity:.95}6%{opacity:.48}55%,100%{opacity:.95}}

html.tempo .p-left .now{position:relative;isolation:isolate}
html.tempo .p-left .now::before{content:"";position:absolute;z-index:-1;
  left:-4px;top:50%;width:64px;height:64px;transform:translateY(-50%);
  border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(var(--glow),.7),transparent 66%);
  filter:blur(9px);
  animation:pump var(--beat) var(--ease-io) infinite;
  animation-delay:calc(-1 * var(--phase))}

/* .tr.playing paints its tint on the row itself, and the row carries the text
   — so the gradient moves onto a pseudo-element and only that pseudo ducks. */
html.tempo .tr.playing{background:none}
html.tempo .tr.playing::before{content:"";position:absolute;inset:0;z-index:-1;
  border-radius:inherit;pointer-events:none;
  background:linear-gradient(90deg,rgba(var(--glow),.16),rgba(var(--glow),.03));
  animation:pump var(--beat) var(--ease-io) infinite;
  animation-delay:calc(-1 * var(--phase))}

/* The tempo layer is literally beat-synced — `--beat` is derived from the
   track's BPM. Pumping to a tempo while the audio is stopped is the most
   obviously wrong version of this, so it stops with the sound. */
html.tempo .p-left .now::before,
html.tempo .tr.playing::before{animation-play-state:paused}
html.tempo.is-playing .p-left .now::before,
html.tempo.is-playing .tr.playing::before{animation-play-state:running}

/* ═══════════════════════════════════════════════ motion: calm / off ═══ */
/* prefers-reduced-motion is an OS setting most people have never seen, so the
   sidebar carries its own three-way control. calm keeps every state change
   and drops the ornament; off makes everything instant. */
.motion-calm .lad-row.running .lad-ring::after,
.motion-calm .plate-bloom,
.motion-calm .rs-dot,
.motion-calm .queue-note::before,
.motion-calm .p-left .now::before,
.motion-calm .tr.playing::before{animation:none}
.motion-calm .rs-dot{opacity:.85}
/* iteration-count, not play-state: the is-playing gate owns play-state and
   would outrank this. Zero iterations retires the breathe loop outright while
   the one-shot entrance (first list entry) keeps its single pass. */
.motion-calm .mark .note-head{animation-iteration-count:1,0}
/* no overshoot when the user has asked for calm */
.motion-calm .needle{transition-timing-function:var(--ease);transition-duration:.6s}
.motion-calm .job-title.slam{animation-duration:.36s}
.motion-calm .pw{animation-duration:.18s;animation-delay:calc(var(--i,0) * 6ms)}
.motion-calm .tag{animation-duration:.2s;animation-delay:calc(var(--i,0) * 16ms)}
.motion-calm .plate.developing .plate-css{animation-duration:.6s}
.motion-calm .plate.resolved .plate-img,
.motion-calm .plate.resolved .plate-css{animation-duration:.5s}
.motion-calm .plate.reveal{animation:none}
.motion-calm .wave-paint.painting{animation-duration:.6s}

.motion-off *,.motion-off *::before,.motion-off *::after{
  animation-duration:1ms !important;animation-iteration-count:1 !important;
  animation-delay:0ms !important;transition-duration:1ms !important;
  transition-delay:0ms !important;scroll-behavior:auto !important}

@media(prefers-reduced-motion:reduce){
  /* style.css already flattens every duration; these restore the two states
     the spec keeps deliberate — the prompt block still arrives as a block. */
  .pw{animation:none !important;opacity:1;transform:none}
  .prompt-words{animation:fade .2s var(--ease) both !important}
  .plate.developing .plate-css{opacity:1;filter:none}
  .plate.resolved .plate-img{opacity:1;filter:none;transform:none}
  .plate.resolved .plate-css{opacity:0}
  .wave-paint{clip-path:inset(0 0 0 0)}
  .plate-bloom.fire{display:none}
  html.tempo .tr.playing::before,
  html.tempo .p-left .now::before{animation:none !important}
}

/* ═════════════════════════════════════════════════════════ responsive ═══ */
@media(max-width:1180px){
  .studio{grid-template-columns:minmax(0,1fr) 330px}
  .finale{grid-template-columns:minmax(0,260px) minmax(0,1fr)}
}
@media(max-width:1080px){
  .studio{grid-template-columns:1fr}
  /* the Plate stops being a full column and becomes a bounded card */
  .studio-side .plate-wrap{max-width:420px}
}
@media(max-width:900px){
  .lad{grid-template-columns:1fr}
  .lad-left{border-right:0;border-bottom:1px solid var(--line);
    flex-direction:row;align-items:center;gap:20px}
  .lad-left .plate-wrap{width:min(180px,38vw);flex:none}
  .madewith{margin-top:0;flex:1;min-width:0}
}
@media(max-width:720px){
  .finale{grid-template-columns:1fr}
  .bench-grid{grid-template-columns:44px minmax(0,1fr) 54px;gap:5px 8px}
  .bench-name{font-size:9.5px;letter-spacing:.1em}
  .bench-val{font-size:12px}
  .music-geek-layer .receipt{inset:0;width:100%;max-width:none;height:100dvh;
    border:0;border-radius:0;transform:translate3d(0,18px,0)}
  .receipt-head{min-height:68px;padding-top:max(12px,env(safe-area-inset-top));
    padding-right:max(16px,env(safe-area-inset-right));padding-left:max(18px,env(safe-area-inset-left))}
  .receipt-head .icon-btn{width:44px;height:44px}
  .receipt-body{padding-right:max(18px,env(safe-area-inset-right));
    padding-left:max(18px,env(safe-area-inset-left))}
  .receipt-foot{padding-right:max(16px,env(safe-area-inset-right));
    padding-bottom:max(16px,env(safe-area-inset-bottom));
    padding-left:max(16px,env(safe-area-inset-left))}
  .geek-track-card{grid-template-columns:104px minmax(0,1fr);gap:14px}
  .receipt-body .geek-track-card .plate-wrap{width:104px}
}
@media(max-width:560px){
  .studio{gap:16px}
  .studio .gen-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:9px}
  .studio-actions{grid-template-columns:1fr}
  .runstrip{height:auto;padding:12px 14px;flex-wrap:wrap;row-gap:8px}
  .rs-actions{width:100%;margin-left:0;justify-content:space-between}
  .stop-copy{text-align:left;max-width:none}
  .lad-left{flex-direction:column;align-items:stretch}
  .lad-left .plate-wrap{width:100%}
  .lad-rows{padding:6px}
  .lad-row{padding:9px 8px;gap:5px 9px;grid-template-columns:22px minmax(0,1fr) auto}
  .drafted-head{gap:8px}
  .dh-meta{width:100%}
  /* shrink rather than thin out — dropping every other label would put the
     ghost target between two unlabelled ticks */
  .bench-axis{font-size:9px}
  .bench-axis .unit{display:none}
}

@media(prefers-reduced-motion:reduce){
  .music-geek-layer,.music-geek-scrim,.music-geek-layer .receipt{transition:none!important}
  .music-geek-layer .receipt{transform:none}
}
@media(max-width:402px){
  /* the narrowest phone we support; nothing may overflow the viewport */
  .studio .gen-grid{grid-template-columns:1fr}
  .brief-row{grid-template-columns:1fr;gap:2px}
  .brief-v{text-align:left}
  .bench-grid{grid-template-columns:1fr;gap:4px}
  .bench-axis{grid-column:1}
  .bench-name,.bench-val{text-align:left}
  .drafted,.fail,.finale,.offline{padding:16px 14px}
  .drafted-actions .spacer,.cancelled .spacer{display:none}
  .finale-actions .btn,.fail-actions .btn,.drafted-actions .btn{flex:1;min-width:0}
}

/* The measured voice, studio register — mirrors the rule at the foot of
   style.css for the labels this sheet owns. */
.brief-head,.brief-k,.style-fam,.style-voc,.lad-label,.bench-name,.mw-head,
.rc-h,.console-head,.fail-kind,.geek-origin,.rail-chip em,
.bench-axis,.bench-target b{font-family:var(--mono);font-weight:500}
