/* =============================================================================
   studio-live.css — the Studio-Live polish layer ("Ink & Ivory · Studio Cut").
   Loaded by /studio + /studio-demo AFTER senova-ui.css + senova-ds.css and
   BEFORE the shell's inline <style>; overrides therefore carry deliberate
   specificity (`.composer .bar.cmp-bar`, `figure.card`, `.wrap .grid`, ids).

   SPACING SCALE (8px base, from --sn-1..8):
     4  micro (label→control)         16 card gaps, grid gap, group gaps
     8  intra-group control gaps      24 view-head→content
     12 control-strip row gap         32 section→section
     14 hairline breathing (pad around a border-top)
   Rules: solid hairlines only, no dashed borders, no sliders, no toggles.
   Price appears ONLY on the accent button + confirm/quote. Both themes.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §0 MOTION PRIMITIVES — every view eases in; everything else is micro.
   -------------------------------------------------------------------------- */
@keyframes sl-view-in { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
@keyframes sl-shimmer { from { background-position:200% 0; } to { background-position:-200% 0; } }
@keyframes sl-note-in { from { opacity:0; transform:translateY(-2px); } to { opacity:1; transform:none; } }

#stage > .gen, #stage > .wrap { animation:sl-view-in 260ms var(--sn-ease-out) both; }
#stage > .gen + .wrap { animation-delay:50ms; }

/* ---------------------------------------------------------------------------
   §1 PAGE RHYTHM — the two page shapes (create hero / manage wrap).
   -------------------------------------------------------------------------- */
.main .wrap { padding:30px 32px 88px; }
.wrap .view-head { margin:4px 0 28px; }
.wrap .view-head h1 { letter-spacing:-.015em; }
.wrap .view-head p { max-width:56ch; }

/* Section head — replaces the ad-hoc inline `row-between` heads. */
.sec-head { display:flex; align-items:center; gap:12px; margin:0 0 14px; min-height:32px; }
.sec-head h2 { font:600 1.0625rem/1.2 var(--sn-sans); letter-spacing:-.01em; margin:0; color:var(--sn-ink); }
.sec-head .count { font:.75rem var(--sn-mono); color:var(--sn-ink-3); }
.sec-head .grow { flex:1; }
.sec-head.lib-tools { margin-bottom:16px; }

/* Create hero — quiet ember glow behind the composer, both themes. */
.gen { position:relative; overflow:clip; }
.gen::before { content:""; position:absolute; left:50%; top:-60px; width:min(720px,84%); height:300px;
  transform:translateX(-50%); background:radial-gradient(closest-side, var(--sn-accent-glow), transparent 72%);
  pointer-events:none; }
.gen .gen-inner { position:relative; padding:56px 28px 40px; }
.gen .gen-inner h1 { letter-spacing:-.015em; }
.gen .gen-inner .sub { margin:0 0 28px; }

/* ---------------------------------------------------------------------------
   §2 THE COMPOSER — one card, three tiers: prompt / control strip / note.
   Markup contract (see spec): .composer#sl-composer > .cmp-prompt(+.cmp-media)
   > .bar.cmp-bar > .cmp-controls(.cmp-group×n, .cmp-div) + .cmp-actions
   (.model-pick #cm-model, .grow, .cmp-hint, .cmp-gen #cm-gen) > .cmp-note.
   -------------------------------------------------------------------------- */
.composer.composer { padding:16px 16px 14px; border-radius:var(--sn-r-card);
  transition:border-color var(--sn-t-micro) var(--sn-ease), box-shadow var(--sn-t-micro) var(--sn-ease); }
/* Neutral focus (Rob: no orange on the prompt box) — keep the hairline, lift the shadow, no accent ring. */
#sl-composer:focus-within { border-color:var(--sn-hairline-strong);
  box-shadow:var(--sn-elev-2); }
[data-theme="studio"] .composer.composer { box-shadow:var(--sn-elev-2), inset 0 1px 0 rgba(245,244,239,.04); }

/* tier 1 — the prompt */
.composer .cmp-prompt { padding:2px 4px 0; }
.composer .cmp-prompt .cmp-media { margin:0 0 14px; }
.composer .cmp-prompt textarea, #cm-ta {
  width:100%; border:none; background:none; outline:none; resize:none;
  color:var(--sn-ink); font:1rem/1.6 var(--sn-sans); min-height:72px;
  padding:4px 2px 10px; caret-color:var(--sn-ink);
}
.composer .cmp-prompt textarea::placeholder { color:var(--sn-ink-3); font-style:italic; }

/* tier 2 — the control strip: one hairline, then controls row + actions row */
.composer .bar.cmp-bar { display:flex; flex-direction:column; align-items:stretch; gap:12px;
  margin-top:10px; padding-top:14px; border-top:1px solid var(--sn-hairline); flex-wrap:nowrap; }
.cmp-bar .cmp-controls { display:flex; align-items:stretch; flex-wrap:wrap; gap:10px 12px; }
.cmp-bar .cmp-group { display:flex; flex-direction:column; align-items:flex-start; gap:5px; }
.cmp-bar .cmp-group .seg-lbl { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.07em; color:var(--sn-ink-3); margin:0; padding-left:2px; line-height:1; }
.cmp-bar .cmp-div { width:1px; align-self:stretch; background:var(--sn-hairline); flex:none; }

/* segmented controls — numeric values read technical (mono), states are crisp */
.cmp-bar .sn-seg { background:var(--sn-bg-raised); }
.cmp-bar .sn-seg button { min-width:34px; padding:0 10px;
  transition:color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
.cmp-bar .sn-seg button:hover:not(.is-active) { color:var(--sn-ink); background:var(--sn-hover); }
.cmp-bar .sn-seg button:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); position:relative; z-index:1; }
.cmp-bar .sn-seg [data-asp], .cmp-bar .sn-seg [data-count], .cmp-bar .sn-seg [data-sec],
.cmp-bar .sn-seg [data-len], .cmp-bar .sn-seg [data-int] { font:500 .75rem var(--sn-mono); letter-spacing:.01em; }

/* sound — a pressed state, not a toggle switch */
.cmp-bar #cm-sound, .cmp-bar #an-sound { height:32px; border-radius:var(--sn-r-input); }
.cmp-bar #cm-sound[aria-pressed="true"], .cmp-bar #an-sound[aria-pressed="true"] {
  background:var(--sn-accent-soft); border-color:transparent; color:var(--sn-accent-ink); box-shadow:none; }
.cmp-bar #cm-sound[disabled] { opacity:.45; }

/* tier 2b — the action row: model field on the left, Generate on the right */
.cmp-bar .cmp-actions { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.model-pick { transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
.model-pick:hover { border-color:var(--sn-ink-3); background:var(--sn-bg-raised); }
.model-pick:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.cmp-actions .model-pick { height:40px; padding:0 12px; border-radius:var(--sn-r-input); gap:9px; max-width:280px; }
.cmp-actions .model-pick .sn-ic { color:var(--sn-accent); }
.model-pick .mp-tx { display:flex; flex-direction:column; align-items:flex-start; gap:2px; min-width:0; }
.model-pick .mp-k { font:600 .5625rem var(--sn-mono); text-transform:uppercase; letter-spacing:.07em;
  color:var(--sn-ink-3); line-height:1; }
.model-pick .mp-v { font:500 .8125rem/1 var(--sn-sans); color:var(--sn-ink);
  max-width:190px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.model-pick .mp-chev { color:var(--sn-ink-3); }

.cmp-hint { display:inline-flex; align-items:center; gap:6px; font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }

/* Generate — the one loud thing; price rides in a quiet chip inside it */
.cmp-bar .cmp-gen { height:40px; padding:0 16px; border-radius:var(--sn-r-input); gap:9px; font-weight:600; }
.cmp-bar .cmp-gen:focus-visible { box-shadow:var(--sn-ring); }
.cmp-gen .price { font:500 .75rem/1 var(--sn-mono); letter-spacing:.01em; padding:3px 7px;
  border-radius:var(--sn-r-xs); background:rgba(255,255,255,.16); color:inherit; white-space:nowrap; }
.cmp-gen.is-busy { pointer-events:none; }
.cmp-gen.is-busy::before { content:""; width:14px; height:14px; border-radius:50%; flex:none;
  border:2px solid rgba(255,255,255,.35); border-top-color:#FFF8F5; animation:sn-spin .8s linear infinite; }

/* THE MODEL ROW — one component across 3D, Product Shots, Characters and Scroll Scene, so "which model
   runs this" looks and behaves the same wherever a tool has the choice. Options wrap; a gated one reads
   as unavailable rather than disappearing; a pinned or absent model is a sentence, not a control. */
.model-row { display:flex; flex-direction:column; gap:7px; margin-top:11px; padding-top:11px;
  border-top:1px solid var(--sn-hairline); }
.model-opts { display:flex; gap:8px; flex-wrap:wrap; }
.model-opt { flex:1 1 200px; min-width:0; text-align:left; padding:9px 11px; border-radius:var(--sn-r-input);
  border:1px solid var(--sn-hairline); background:var(--sn-bg-raised); cursor:pointer;
  display:flex; flex-direction:column; gap:2px; transition:border-color .12s, background .12s; }
.model-opt:hover:not(:disabled) { border-color:var(--sn-accent); }
.model-opt.is-on { border-color:var(--sn-accent); background:var(--sn-selected); }
.model-opt:disabled { opacity:.55; cursor:not-allowed; }
.model-opt .mo-h { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
.model-opt .mo-h b { font:600 .8125rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.model-opt .mo-h i { font:500 .6875rem var(--sn-mono); font-style:normal; color:var(--sn-ink-3); }
.model-opt .mo-b { font:.6875rem/1.4 var(--sn-sans); color:var(--sn-ink-3);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
/* A model we chose FOR them, named with the reason — and a tool that runs no model at all. */
.model-pinned, .model-none { margin:0; font:.6875rem/1.5 var(--sn-sans); color:var(--sn-ink-3); }
.model-pinned b { color:var(--sn-ink-2); font-weight:600; }
.model-none { margin-top:9px; padding-top:9px; border-top:1px solid var(--sn-hairline); }

/* 3D: the subject cut-out option. It carries a price, so it reads as a decision the customer is making —
   the label states the trade in plain words rather than leaving "isolate" to be guessed at. */
.m3d-iso { display:flex; align-items:flex-start; gap:9px; margin-top:11px; padding-top:11px;
  border-top:1px solid var(--sn-hairline); font:.8125rem/1.45 var(--sn-sans); color:var(--sn-ink-2);
  cursor:pointer; }
.m3d-iso input { accent-color:var(--sn-accent); margin-top:2px; flex:none; }
.m3d-iso b { font-weight:600; color:var(--sn-ink); }
.m3d-iso i { display:block; margin-top:3px; font-style:normal; font-size:.75rem; line-height:1.45; }

/* tier 3 — the note: a real message row, never a bare line of text */
.composer .cmp-note, .sn-card .cmp-note { margin-top:12px; padding:9px 12px; border-radius:var(--sn-r-input);
  background:var(--sn-bg-raised); border:1px solid var(--sn-hairline);
  font:.8125rem/1.5 var(--sn-sans); color:var(--sn-ink-2); animation:sl-note-in 180ms var(--sn-ease-out); }
.composer .cmp-note.err, .sn-card .cmp-note.err { background:var(--sn-error-soft);
  border-color:color-mix(in srgb, var(--sn-error) 28%, var(--sn-hairline)); color:var(--sn-error); }

/* narrow composer — dividers drop, Generate takes the row */
@media (max-width:640px) {
  .cmp-bar .cmp-div, .cmp-hint { display:none; }
  .cmp-bar .cmp-actions .grow { display:none; }
  .cmp-actions .model-pick { flex:1; max-width:none; }
  .cmp-bar .cmp-gen { width:100%; order:9; }
}

/* ---------------------------------------------------------------------------
   §3 MODEL MENU + pickers — roomier rows, a visible selection.
   -------------------------------------------------------------------------- */
.sn-menu.model-menu { padding:6px; min-width:300px; max-width:340px; }
.model-menu .sn-menu-item { height:auto; align-items:flex-start; gap:10px; padding:9px 10px; border-radius:var(--sn-r-input); }
.model-menu .sn-menu-item.is-active { background:var(--sn-selected); }
/* The upload row sits above the Library list and reads as the other kind of answer to "which file?",
   so it is separated by a rule rather than looking like the first asset in the list. */
.model-menu .sn-menu-item.is-upload { border-bottom:1px solid var(--sn-hairline); border-radius:var(--sn-r-input) 8px 0 0;
  margin-bottom:4px; padding-bottom:11px; }
.model-menu .sn-menu-item.is-upload b { color:var(--sn-accent-ink); }
.model-menu .sn-menu-item.is-upload:disabled { opacity:.6; pointer-events:none; }
.model-menu .sn-menu-ic { width:16px; height:16px; flex:none; margin-top:1px; color:var(--sn-accent);
  display:inline-flex; align-items:center; justify-content:center; }
.model-menu .sn-menu-ic .sn-ic { width:14px; height:14px; }
.model-menu .sn-menu-tx { display:flex; flex-direction:column; gap:2px; min-width:0; }
.model-menu .sn-menu-tx b { font:500 .875rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.model-menu .sn-menu-tx > span { font:.75rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }
/* Camera-preset library — a family header above each group of moves. Theme-aware via existing tokens. */
.cam-lib-menu { max-height:min(72vh,560px); overflow-y:auto; }
.cam-lib-menu .cam-lib-head { padding:8px 10px 4px; font:600 .68rem/1 var(--sn-sans); letter-spacing:.06em;
  text-transform:uppercase; color:var(--sn-ink-3); }
.cam-lib-menu .cam-lib-head:first-child { padding-top:4px; }
.model-menu::-webkit-scrollbar { width:8px; }
.model-menu::-webkit-scrollbar-thumb { background:var(--sn-hairline-strong); border-radius:var(--sn-r-input);
  border:2px solid var(--sn-surface); }

/* ---------------------------------------------------------------------------
   §4 RESULT CARDS + GRID — calm at rest, alive on hover, honest while working.
   (`figure.card` outguns the shell's `.card …` rules.)
   -------------------------------------------------------------------------- */
.wrap .grid { gap:16px; }
.grid:empty { display:none; }
#sl-results:not(:empty) { margin-bottom:16px; }

figure.card { margin:0; transition:border-color var(--sn-t-micro) var(--sn-ease),
  box-shadow var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease); }
figure.card:not(.is-pending):not(.is-error):not(.is-skel):hover {
  border-color:var(--sn-hairline-strong); box-shadow:var(--sn-elev-2); transform:translateY(-1px); }
figure.card .media > img, figure.card .media > video { transition:transform var(--sn-t-panel) var(--sn-ease); }
figure.card:not(.is-pending):not(.is-error):hover .media > img,
figure.card:not(.is-pending):not(.is-error):hover .media > video { transform:scale(1.02); }

figure.card .over { padding:32px 12px 11px;
  background:linear-gradient(180deg, transparent 0%, rgba(10,9,8,.28) 45%, rgba(10,9,8,.86) 100%); }
figure.card .badge-type { letter-spacing:.08em; background:rgba(10,9,8,.62); }
figure.card .acts button { transition:background var(--sn-t-hover) var(--sn-ease), transform var(--sn-t-press) var(--sn-ease); }
figure.card .acts button:active { transform:scale(.94); }
figure.card .acts button:focus-visible { outline:none; box-shadow:0 0 0 2px rgba(245,244,239,.6); opacity:1; }

/* pending — shimmer under the ring so "working" reads at a glance */
figure.card.is-pending .media { position:relative; }
figure.card.is-pending .media::before { content:""; position:absolute; inset:0;
  background:linear-gradient(100deg, transparent 32%, var(--sn-hover) 50%, transparent 68%);
  background-size:200% 100%; animation:sl-shimmer 1.8s linear infinite; }
figure.card.is-pending .over { opacity:1; transform:none; }
figure.card.is-pending .pending-in .lbl { color:var(--sn-ink-2); }

/* error — quietly wrong, never alarming; the card says "nothing was charged" */
figure.card.is-error { border-color:color-mix(in srgb, var(--sn-error) 32%, var(--sn-hairline)); }
figure.card.is-error .media { background:var(--sn-error-soft); }
figure.card.is-error .pending-in { color:var(--sn-error); }
figure.card.is-error .pending-in .sn-ic { width:20px; height:20px; }
figure.card.is-error .over { opacity:1; transform:none; }

/* skeleton — the grid's loading state (replaces the bare centered spinner) */
figure.card.is-skel { pointer-events:none; box-shadow:none; }
figure.card.is-skel .media { background:linear-gradient(100deg,
  var(--sn-bg-raised) 36%, color-mix(in srgb, var(--sn-surface) 55%, var(--sn-bg-raised)) 50%, var(--sn-bg-raised) 64%);
  background-size:200% 100%; animation:sl-shimmer 1.6s linear infinite; }

/* audio result card — flat well, one round play control */
.card.is-audio .media { display:grid; place-items:center; background:var(--sn-bg-deep); }
.card.is-audio .media .aud-play { width:52px; height:52px; border-radius:50%; display:grid; place-items:center;
  background:var(--sn-surface); border:1px solid var(--sn-hairline-strong); color:var(--sn-ink);
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease),
  transform var(--sn-t-micro) var(--sn-ease-spring); }
.card.is-audio .media .aud-play .sn-ic-slot, .card.is-audio .media .aud-play .sn-ic { width:20px; height:20px; }
.card.is-audio:hover .media .aud-play { border-color:var(--sn-accent); color:var(--sn-accent); transform:scale(1.05); }

/* ---------------------------------------------------------------------------
   §5 DROPZONE (Animate, Scroll Scene) — a solid well inside the composer.
   -------------------------------------------------------------------------- */
.sl-drop { position:relative; display:block; width:100%; min-height:140px; border:1px solid var(--sn-hairline-strong);
  border-radius:var(--sn-r-card); background:var(--sn-bg-raised); cursor:pointer; overflow:hidden; outline:none;
  transition:border-color var(--sn-t-micro) var(--sn-ease), background var(--sn-t-micro) var(--sn-ease); }
.sl-drop:hover, .sl-drop:focus-visible { border-color:var(--sn-accent); }
.sl-drop.over { border-color:var(--sn-accent); background:var(--sn-accent-soft); }
.sl-drop.busy { opacity:.7; }
.sl-drop-empty { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:10px; color:var(--sn-ink-2); font:.875rem var(--sn-sans); text-align:center; padding:12px; }
.sl-drop-empty .sn-ic-slot { width:38px; height:38px; border-radius:50%; background:var(--sn-surface);
  border:1px solid var(--sn-hairline); display:grid; place-items:center; color:var(--sn-ink-3); }
.sl-drop-empty .sn-ic-slot .sn-ic { width:18px; height:18px; }
.sl-drop:hover .sl-drop-empty .sn-ic-slot { color:var(--sn-accent); }
.sl-drop-preview { display:block; width:100%; height:100%; min-height:140px; max-height:260px;
  object-fit:cover; object-position:50% 42%; }
.sl-drop-clear { position:absolute; top:8px; right:8px; width:28px; height:28px; display:grid; place-items:center;
  border:none; border-radius:var(--sn-r-input); background:rgba(10,9,8,.55); backdrop-filter:blur(6px); color:#F5F4EF; cursor:pointer; }
.sl-drop-clear:hover { background:rgba(10,9,8,.78); }
.sl-drop-clear .sn-ic-slot, .sl-drop-clear .sn-ic { width:14px; height:14px; }

/* ---------------------------------------------------------------------------
   §6 EMPTY STATES — one voice everywhere.
   -------------------------------------------------------------------------- */
.sn-empty.empty-wrap { padding:56px 0 64px; }
/* 🔴 §63 — THE DISPLAY VOICE IS A GEOMETRIC SANS, NOT AN EDITORIAL SERIF.
   Rob: "we use a serif editorial display ... OpenArt uses a clean geometric sans ... this is a big
   part of why it reads 'editorial' not 'studio'." He is right, and it is the cheapest big lever in
   the whole redesign: a serif headline over a dark room reads as a magazine feature about filmmaking.
   A studio tool states things. Archivo (--sn-sans) is already the UI face and is already loaded, so
   this is a voice change, not a payload change. The serif is NOT deleted — it stays available for
   genuinely editorial surfaces (auth, landing, billing), which are a different room. */
.sn-empty .sn-empty-title { font:600 1.125rem/1.3 var(--sn-sans); letter-spacing:-.01em; color:var(--sn-ink); margin:0; padding:0; }
.sn-empty .sn-empty-sub { font:.875rem/1.55 var(--sn-sans); color:var(--sn-ink-2); margin:8px 0 18px; }
.sn-empty .sn-empty-ic { box-shadow:0 0 40px var(--sn-accent-glow); }

.loading-full { min-height:40vh; }

/* ---------------------------------------------------------------------------
   §7 CONFIRM / QUOTE — tabular money, one serif total.
   -------------------------------------------------------------------------- */
.q-line { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:9px 0;
  border-bottom:1px solid var(--sn-hairline); font:.875rem/1.4 var(--sn-sans); color:var(--sn-ink-2); }
.q-line b { color:var(--sn-ink); font-weight:600; font-variant-numeric:tabular-nums; text-align:right; }
.q-line:last-child { border-bottom:none; }
.q-line.is-total { border-bottom:none; margin-top:2px; }
.q-line.is-total b { font:600 1.125rem/1.2 var(--sn-sans); font-variant-numeric:tabular-nums; color:var(--sn-accent-ink); }
.sn-modal-body .q-after, .sl-quote .q-after { color:var(--sn-ink-3); font-size:.8125rem; margin:8px 0 0; }

/* Throughline quote card */
.sl-quote .q-total { font:600 2.125rem/1 var(--sn-sans); letter-spacing:-.02em; font-variant-numeric:tabular-nums; margin:14px 0 2px; }
.sl-quote .q-after { margin:0 0 14px; }
.sl-quote .q-line { padding:8px 0; }
.sl-stepper-cr { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }

/* ---------------------------------------------------------------------------
   §8 MANAGE VIEWS — stats, settings, tools, tables, recipes.
   -------------------------------------------------------------------------- */
.wrap .stat3 { gap:16px; margin-bottom:28px; }
.wrap .stat3 .sn-card { padding:18px 20px; }
.wrap .stat3 .k { font:600 .6875rem var(--sn-mono); text-transform:uppercase; letter-spacing:.06em; color:var(--sn-ink-3); }
.wrap .stat3 .v { margin-top:10px; font-variant-numeric:tabular-nums; }

.wrap .settings-card { padding:22px 24px; margin-bottom:16px; }
.wrap .settings-card h3 { letter-spacing:-.01em; }

/* Voice & tools — the footer of every tool card is a real row, not a drift */
.tool-foot { display:flex; align-items:center; gap:8px; margin-top:14px; padding-top:14px;
  border-top:1px solid var(--sn-hairline); flex-wrap:wrap; }
.tool-foot .grow { flex:1; }
.settings-card .sn-field + .sn-field { margin-top:12px; }

/* recipe / help link cards */
a.recipe-card { display:block; text-decoration:none; color:inherit; }
.recipe-card .rc-ic { display:inline-flex; width:22px; height:22px; color:var(--sn-accent); }
.recipe-card .rc-name { font:600 1rem/1.3 var(--sn-sans); letter-spacing:-.005em; margin-top:12px; color:var(--sn-ink); }
.recipe-card .rc-sub { color:var(--sn-ink-2); font:.875rem/1.5 var(--sn-sans); margin-top:4px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
/* Model + use-count, in the model gallery's chip vocabulary (.mg-chip is scoped to .model-gallery). */
.recipe-card .rc-meta { display:flex; flex-wrap:wrap; gap:4px; margin-top:10px; }
.recipe-card .rc-chip { height:19px; padding:0 7px; border-radius:var(--sn-r-xs);
  background:var(--sn-chip-bg); color:var(--sn-ink-2);
  font:500 .625rem/19px var(--sn-mono); letter-spacing:.02em; white-space:nowrap; }

/* The library/results filter's "nothing matches" line — sits directly under the grid it filters. */
.lib-nomatch { color:var(--sn-ink-2); font:.875rem/1.5 var(--sn-sans); margin:8px 0 0; }
.lib-nomatch[hidden] { display:none; }

/* ---------------------------------------------------------------------------
   §9 REDUCED MOTION — the global kill-switch lives in senova-ui; belt+braces
   for the looping bits so nothing pulses for anyone who opted out.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  #stage > .gen, #stage > .wrap { animation:none; }
  figure.card.is-pending .media::before, figure.card.is-skel .media { animation:none; }
  .cmp-gen.is-busy::before { animation:none; border-top-color:rgba(255,255,255,.35); }
  figure.card:hover .media > img, figure.card:hover .media > video { transform:none; }
}

/* =============================================================================
   SHARED RESULT SYSTEM (Fable, pass 2) — quick-actions, detail drawer, model
   gallery, refined states — plus the Image tool's reference/style inputs.
   Same rules: --sn-* tokens only, solid hairlines, both themes, price only on
   the accent button + confirm/quote.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §10 RESULT QUICK-ACTIONS — one glass pill, top-right, on hover/focus.
   Markup: .card > .acts > button[data-act=animate|vary|upscale|download]
           + .sep + button[data-act=more]. Every button carries an aria-label
           AND data-sn-tooltip (the DS tooltip is the visible label).
   The pill is dark glass over media in BOTH themes — its inks are fixed ivory.
   -------------------------------------------------------------------------- */
figure.card .acts { top:8px; right:8px; gap:1px; padding:3px; border-radius:var(--sn-r-input);
  background:rgba(10,9,8,.62); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  box-shadow:0 1px 4px rgba(0,0,0,.25);
  opacity:0; transform:translateY(-3px);
  transition:opacity var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease); }
figure.card:hover .acts, figure.card:focus-within .acts { opacity:1; transform:none; }
figure.card .acts button { width:26px; height:26px; border-radius:var(--sn-r-input); background:transparent;
  backdrop-filter:none; -webkit-backdrop-filter:none; color:rgba(245,244,239,.85); }
figure.card .acts button:hover { background:rgba(245,244,239,.16); color:#F5F4EF; }
figure.card .acts button:focus-visible { outline:none; box-shadow:0 0 0 2px rgba(245,244,239,.6); opacity:1; }
figure.card .acts button .sn-ic-slot, figure.card .acts button .sn-ic { width:14px; height:14px; }
figure.card .acts .sep { width:1px; height:14px; margin:0 3px; align-self:center;
  background:rgba(245,244,239,.22); flex:none; }
/* pending/error/skeleton cards never show actions */
figure.card.is-pending .acts, figure.card.is-skel .acts { display:none; }
figure.card.is-error .acts { display:none; }

/* ---------------------------------------------------------------------------
   §11 ASSET DETAIL DRAWER — right slide-in; replaces the bare lightbox for
   results. Markup contract (see spec): .sl-drawer-scrim > aside.sl-drawer >
   .dw-head / .dw-media / .dw-body(.dw-sec-k, .dw-prompt, .dw-xray, .dw-actions,
   .dw-foot). Sits BELOW .sn-scrim (1200) so confirm sheets stack above it.
   -------------------------------------------------------------------------- */
@keyframes sl-drawer-in { from { transform:translateX(28px); opacity:0; } to { transform:none; opacity:1; } }
.sl-drawer-scrim { position:fixed; inset:0; z-index:1100; background:var(--sn-scrim);
  backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px); animation:sn-fade 200ms var(--sn-ease); }
.sl-drawer { position:fixed; top:0; right:0; bottom:0; z-index:1110;
  width:min(440px, 100vw); background:var(--sn-surface);
  border-left:1px solid var(--sn-hairline); box-shadow:var(--sn-elev-3);
  display:flex; flex-direction:column; animation:sl-drawer-in var(--sn-t-panel) var(--sn-ease-out); }

/* head — kind badge, mono timestamp, close */
.sl-drawer .dw-head { flex:none; display:flex; align-items:center; gap:10px;
  height:52px; padding:0 12px 0 16px; border-bottom:1px solid var(--sn-hairline); }
.sl-drawer .dw-head .dw-when { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
.sl-drawer .dw-head .grow { flex:1; }

/* media — cinematic floor in both themes, contained, never cropped */
.sl-drawer .dw-media { flex:none; display:grid; place-items:center;
  background:var(--sn-bg-deep); border-bottom:1px solid var(--sn-hairline);
  min-height:180px; max-height:46vh; overflow:hidden; }
.sl-drawer .dw-media > img, .sl-drawer .dw-media > video {
  display:block; max-width:100%; max-height:46vh; object-fit:contain; }
.sl-drawer .dw-media .aud-well { display:grid; place-items:center; width:100%; padding:44px 0; }

/* body — scrolls; head/media stay put */
.sl-drawer .dw-body { flex:1; overflow-y:auto; padding:18px 20px 20px;
  display:flex; flex-direction:column; }
.sl-drawer .dw-sec-k { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.07em; color:var(--sn-ink-3); margin:0 0 8px; line-height:1; }
.sl-drawer .dw-sec-k:not(:first-child) { margin-top:20px; }

/* the prompt — a quiet well with a copy affordance */
.sl-drawer .dw-prompt { position:relative; padding:11px 36px 11px 13px; border-radius:var(--sn-r-input);
  background:var(--sn-bg-raised); border:1px solid var(--sn-hairline);
  font:.875rem/1.6 var(--sn-sans); color:var(--sn-ink); overflow-wrap:anywhere; }
.sl-drawer .dw-prompt .dw-copy { position:absolute; top:6px; right:6px; width:26px; height:26px;
  display:grid; place-items:center; border:none; border-radius:var(--sn-r-input); background:transparent;
  color:var(--sn-ink-3); cursor:pointer; opacity:0;
  transition:opacity var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
.sl-drawer .dw-prompt:hover .dw-copy, .sl-drawer .dw-prompt .dw-copy:focus-visible { opacity:1; }
.sl-drawer .dw-prompt .dw-copy:hover { background:var(--sn-hover); color:var(--sn-ink); }
.sl-drawer .dw-prompt .dw-copy:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.sl-drawer .dw-prompt .dw-copy .sn-ic-slot, .sl-drawer .dw-prompt .dw-copy .sn-ic { width:13px; height:13px; }

/* the xray — spec chips: key in mono caps, value in ink */
.sl-drawer .dw-xray { display:flex; flex-wrap:wrap; gap:6px; }
.xr-chip { display:inline-flex; align-items:center; gap:7px; height:26px; padding:0 10px;
  border-radius:var(--sn-r-input); background:var(--sn-chip-bg); border:1px solid transparent; }
.xr-chip .xr-k { font:600 .5625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.06em; color:var(--sn-ink-3); line-height:1; }
.xr-chip .xr-v { font:500 .75rem/1 var(--sn-mono); color:var(--sn-ink);
  max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* a clickable chip (Reuse seed lives here too, as an affordance on the seed chip) */
button.xr-chip { cursor:pointer;
  transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
button.xr-chip:hover { border-color:var(--sn-hairline-strong); background:var(--sn-bg-raised); }
button.xr-chip:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }

/* the action set — one loud verb, a balanced pair, a quiet utility row */
.sl-drawer .dw-actions { margin-top:22px; padding-top:16px; border-top:1px solid var(--sn-hairline);
  display:flex; flex-direction:column; gap:8px; }
.sl-drawer .dw-actions .dw-lead { width:100%; height:38px; border-radius:var(--sn-r-input); gap:8px; font-weight:600; }
.sl-drawer .dw-actions .dw-pair { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.sl-drawer .dw-actions .dw-pair .sn-btn { height:34px; }
.sl-drawer .dw-actions .dw-util { display:flex; gap:4px; margin-top:2px; }
.sl-drawer .dw-actions .dw-util .sn-btn { flex:1; }

/* foot — delete on the far edge, away from everything constructive */
.sl-drawer .dw-foot { margin-top:auto; padding-top:18px; display:flex; align-items:center; gap:8px; }
.sl-drawer .dw-foot .dw-id { font:.6875rem var(--sn-mono); color:var(--sn-ink-3);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.sl-drawer .dw-foot .grow { flex:1; }

@media (max-width:520px) { .sl-drawer { width:100vw; border-left:none; } }

/* ---------------------------------------------------------------------------
   §12 MODEL GALLERY — the rich picker. A wider .sn-menu variant; rows carry a
   one-line strength + spec chips + an optional Recommended marker; optional
   group labels (Fast / Balanced / Best). Extends .model-menu so existing
   positioning + dismissal JS works untouched.
   -------------------------------------------------------------------------- */
.sn-menu.model-menu.model-gallery { min-width:340px; max-width:400px; padding:6px;
  max-height:min(460px, 70vh); overflow-y:auto; }
.model-gallery .mg-group { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.07em; color:var(--sn-ink-3); padding:8px 10px 5px; line-height:1; }
.model-gallery .mg-group:not(:first-child) { margin-top:5px; padding-top:11px;
  border-top:1px solid var(--sn-hairline); }
.model-gallery .sn-menu-item { height:auto; align-items:flex-start; gap:10px;
  padding:8px 10px; border-radius:var(--sn-r-input); }
.model-gallery .sn-menu-item.is-active { background:var(--sn-selected); }
.model-gallery .sn-menu-ic { width:16px; height:16px; flex:none; margin-top:2px; color:var(--sn-accent);
  display:inline-flex; align-items:center; justify-content:center; }
.model-gallery .mg-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.model-gallery .mg-name-row { display:flex; align-items:center; gap:8px; min-width:0; }
.model-gallery .mg-name { font:600 .875rem/1.2 var(--sn-sans); color:var(--sn-ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.model-gallery .mg-rec { flex:none; height:17px; padding:0 7px; border-radius:999px;
  background:var(--sn-accent-soft); color:var(--sn-accent-ink);
  font:600 .5625rem/17px var(--sn-mono); text-transform:uppercase; letter-spacing:.05em; }
.model-gallery .mg-strength { font:.75rem/1.35 var(--sn-sans); color:var(--sn-ink-2); }
.model-gallery .mg-chips { display:flex; flex-wrap:wrap; gap:4px; margin-top:3px; }
.model-gallery .mg-chip { height:19px; padding:0 7px; border-radius:var(--sn-r-xs);
  background:var(--sn-chip-bg); color:var(--sn-ink-2);
  font:500 .625rem/19px var(--sn-mono); letter-spacing:.02em; white-space:nowrap; }
.model-gallery .mg-chip.is-cost { color:var(--sn-ink); }
.model-gallery::-webkit-scrollbar { width:8px; }
.model-gallery::-webkit-scrollbar-thumb { background:var(--sn-hairline-strong); border-radius:var(--sn-r-input);
  border:2px solid var(--sn-surface); }

/* ---------------------------------------------------------------------------
   §13 IMAGE TOOL — reference strip (tier 1, under the prompt, above the
   hairline) + style field (a .model-pick in the control strip).
   Markup: .cmp-prompt > textarea + .cmp-refs(.refs-k, .ref-thumb×N, .ref-add,
   .ref-hint). Style: cmpGroup("Style", button.model-pick.style-pick#cm-style).
   -------------------------------------------------------------------------- */
.cmp-refs { display:flex; align-items:center; gap:8px; padding:10px 2px 4px; }
.cmp-refs .refs-k { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.07em; color:var(--sn-ink-3); line-height:1; margin-right:2px; }
.cmp-refs .ref-hint { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); margin-left:2px; }
@media (max-width:640px) { .cmp-refs .ref-hint { display:none; } }

.ref-add { width:44px; height:44px; flex:none; display:grid; place-items:center; cursor:pointer;
  border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-input); background:var(--sn-bg-raised);
  color:var(--sn-ink-3);
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease); }
.ref-add:hover, .ref-add:focus-visible { border-color:var(--sn-accent); color:var(--sn-accent); outline:none; }
.ref-add:focus-visible { box-shadow:var(--sn-ring); }
.ref-add.over { border-color:var(--sn-accent); background:var(--sn-accent-soft); color:var(--sn-accent-ink); }
.ref-add .sn-ic-slot, .ref-add .sn-ic { width:16px; height:16px; }
.ref-add[hidden] { display:none; }

.ref-thumb { position:relative; width:44px; height:44px; flex:none; border-radius:var(--sn-r-input);
  overflow:hidden; border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep); }
.ref-thumb > img { display:block; width:100%; height:100%; object-fit:cover; }
.ref-thumb.is-busy > img { opacity:.5; }
.ref-thumb .ref-x { position:absolute; top:2px; right:2px; width:16px; height:16px;
  display:grid; place-items:center; border:none; border-radius:var(--sn-r-xs);
  background:rgba(10,9,8,.62); color:#F5F4EF; cursor:pointer; opacity:0; padding:0;
  transition:opacity var(--sn-t-hover) var(--sn-ease); }
.ref-thumb:hover .ref-x, .ref-thumb .ref-x:focus-visible { opacity:1; }
.ref-thumb .ref-x:focus-visible { outline:none; box-shadow:0 0 0 2px rgba(245,244,239,.6); }
.ref-thumb .ref-x .sn-ic-slot, .ref-thumb .ref-x .sn-ic { width:9px; height:9px; }

/* the style field — same anatomy as the model field, calmer icon */
.cmp-bar .style-pick { height:32px; padding:0 10px; border-radius:var(--sn-r-input); gap:8px; max-width:200px; }
.cmp-bar .style-pick .sn-ic { color:var(--sn-ink-3); }
.cmp-bar .style-pick .mp-v { max-width:120px; font-size:.8125rem; }
/* style menu — a swatch dot beside each look */
.sn-menu.style-menu { min-width:230px; max-width:280px; padding:6px; }
.style-menu .sn-menu-item { height:36px; border-radius:var(--sn-r-input); gap:10px; }
.style-menu .sn-menu-item.is-active { background:var(--sn-selected); }
.style-menu .st-dot { width:18px; height:18px; flex:none; border-radius:var(--sn-r-xs);
  border:1px solid var(--sn-hairline-strong);
  background:linear-gradient(135deg, var(--sn-accent-soft) 0%, var(--sn-accent) 130%); }
.style-menu .sn-menu-tx b { font:500 .875rem/1.2 var(--sn-sans); color:var(--sn-ink); }
.style-menu .sn-menu-tx > span { font:.6875rem/1.4 var(--sn-mono); color:var(--sn-ink-3); }

/* Singing STYLE picker — grouped chip-segments (Genre / Era / Vibe / …). DS-native,
   replaces the inline-styled menu that rendered "[object Object]" from {id,label} rows. */
.sn-menu.sing-style-menu { min-width:300px; max-width:360px; padding:12px; display:flex; flex-direction:column; gap:13px; }
.sing-style-menu .ssm-grp { display:flex; flex-direction:column; gap:7px; }
.sing-style-menu .ssm-k { font:600 .6875rem var(--sn-mono); text-transform:uppercase; letter-spacing:.05em; color:var(--sn-ink-3); }
.sing-style-menu .ssm-seg { flex-wrap:wrap; height:auto; padding:0; background:none; border:none; box-shadow:none; }
.sing-style-menu .ssm-empty { color:var(--sn-ink-3); font-size:.8125rem; padding:4px 2px; }

/* ---------------------------------------------------------------------------
   §14 REFINED STATES — staggered skeletons; a failed card that offers retry.
   Error markup: .card.is-error > .media > .err-in(.sn-ic-slot, .err-t, .err-s,
   button[data-act=retry]).
   -------------------------------------------------------------------------- */
figure.card.is-skel:nth-child(3n+2) .media { animation-delay:220ms; }
figure.card.is-skel:nth-child(3n)   .media { animation-delay:440ms; }

figure.card.is-error { cursor:default; }
figure.card.is-error .err-in { position:absolute; inset:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:4px; padding:14px; text-align:center; }
figure.card.is-error .err-in > .sn-ic-slot { width:26px; height:26px; border-radius:50%;
  display:grid; place-items:center; background:var(--sn-error-soft); color:var(--sn-error);
  margin-bottom:4px; }
figure.card.is-error .err-in > .sn-ic-slot .sn-ic { width:13px; height:13px; }
figure.card.is-error .err-t { font:600 .8125rem/1.3 var(--sn-sans); color:var(--sn-error); }
figure.card.is-error .err-s { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
figure.card.is-error .err-in [data-act="retry"] { margin-top:8px; }

/* ---------------------------------------------------------------------------
   §15 REDUCED MOTION — addendum for the new pieces.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .sl-drawer, .sl-drawer-scrim { animation:none; }
  figure.card .acts { transform:none; transition:opacity var(--sn-t-micro) var(--sn-ease); }
  figure.card.is-skel:nth-child(3n+2) .media, figure.card.is-skel:nth-child(3n) .media { animation:none; }
}

/* =============================================================================
   THE VIDEO TOOL (Fable, pass 3) — the Camera Moves library, the start-frame
   (image-to-video) affordance in the Video composer, and video result/drawer
   refinements. Same rules: --sn-* tokens only, solid hairlines, both themes,
   price only on the accent button + confirm/quote. Chips that sit ON media are
   dark glass with fixed ivory ink in BOTH themes (same as .badge-type/.acts).
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §16 CAMERA MOVES LIBRARY — a curated, previewable gallery, NOT a dropdown.
   Lives as the FIRST section inside the Video view's .wrap (under the hero,
   above the results toolbar); closes with its own hairline so results read
   as a separate band. Markup contract (see spec):
   section.cam-lib#sl-cammoves > .sec-head(+#cam-clear) > p.cam-sub
     > .cam-cats(button.cam-cat[data-cat]) > .cam-grid#cam-grid
     > button.cam-card[data-move][data-cat](.is-active|.is-playing|.is-off)
       > .cam-media(img.cam-poster, video.cam-clip, .cam-top, .cam-dur,
         .cam-check) + .cam-tx(.cam-name, .cam-k).
   Selecting a move is $0 — so NO price appears anywhere in this section.
   -------------------------------------------------------------------------- */
.cam-lib { margin:0 0 32px; padding:0 0 28px; border-bottom:1px solid var(--sn-hairline); }
.cam-lib .sec-head { margin-bottom:2px; }
.cam-lib .cam-sub { font:.875rem/1.55 var(--sn-sans); color:var(--sn-ink-2); margin:0 0 16px; max-width:64ch; }

/* category chips — one row of quiet pills; the active one borrows the
   sound-button's pressed voice (accent-soft fill, accent ink, no border) */
.cam-cats { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 16px; }
.cam-cat { display:inline-flex; align-items:center; gap:7px; height:30px; padding:0 13px;
  border:1px solid var(--sn-hairline-strong); border-radius:999px; background:none;
  color:var(--sn-ink-2); font:500 .8125rem/1 var(--sn-sans); cursor:pointer; white-space:nowrap;
  transition:color var(--sn-t-hover) var(--sn-ease), border-color var(--sn-t-hover) var(--sn-ease),
    background var(--sn-t-hover) var(--sn-ease); }
.cam-cat:hover { color:var(--sn-ink); border-color:var(--sn-ink-3); }
.cam-cat:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.cam-cat .n { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
.cam-cat.is-active { background:var(--sn-accent-soft); border-color:transparent;
  color:var(--sn-accent-ink); font-weight:600; }
.cam-cat.is-active .n { color:inherit; opacity:.7; }

/* the grid — small cinematic tiles; 18 moves land as three calm rows */
.cam-grid { display:grid; gap:12px; grid-template-columns:repeat(6,1fr); }
@media (max-width:1180px) { .cam-grid { grid-template-columns:repeat(4,1fr); } }
@media (max-width:820px)  { .cam-grid { grid-template-columns:repeat(3,1fr); } }
@media (max-width:560px)  { .cam-grid { grid-template-columns:repeat(2,1fr); } }

/* the move card — poster at rest, clip on hover; name row beneath the media */
.cam-card { position:relative; display:flex; flex-direction:column; margin:0; padding:0;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); overflow:hidden;
  background:var(--sn-surface); cursor:pointer; text-align:left; box-shadow:var(--sn-elev-1);
  transition:border-color var(--sn-t-micro) var(--sn-ease), box-shadow var(--sn-t-micro) var(--sn-ease),
    transform var(--sn-t-micro) var(--sn-ease); }
.cam-card:hover { border-color:var(--sn-hairline-strong); box-shadow:var(--sn-elev-2); transform:translateY(-1px); }
.cam-card:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.cam-card.is-off { display:none; }   /* filtered out by a category chip */

.cam-card .cam-media { position:relative; display:block; aspect-ratio:16/9;
  background:var(--sn-bg-deep); overflow:hidden; }
.cam-media .cam-poster, .cam-media .cam-clip { position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block; }
/* the looping clip sits over the poster and fades in once JS has it playing
   (card gains .is-playing on mouseenter/focus after play() resolves) */
.cam-media .cam-clip { opacity:0; transition:opacity 200ms var(--sn-ease); }
.cam-card.is-playing .cam-clip { opacity:1; }

/* duration chip — bottom-right, dark glass, mono (matches .badge-type voice) */
.cam-media .cam-dur { position:absolute; right:6px; bottom:6px; z-index:1; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  letter-spacing:.03em; pointer-events:none; }

/* TOP PICK — the curator's mark; solid accent so it reads over any footage */
.cam-media .cam-top { position:absolute; top:6px; left:6px; z-index:1; height:17px; padding:0 7px;
  border-radius:999px; background:var(--sn-accent); color:#FFF8F5;
  font:600 .5625rem/17px var(--sn-mono); text-transform:uppercase; letter-spacing:.05em;
  pointer-events:none; }

/* selected — accent edge + a check coin; the glow stays quiet */
.cam-media .cam-check { position:absolute; top:6px; right:6px; z-index:1; width:20px; height:20px;
  border-radius:50%; display:grid; place-items:center; background:var(--sn-accent); color:#FFF8F5;
  opacity:0; transform:scale(.6); pointer-events:none;
  transition:opacity var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease-spring); }
.cam-media .cam-check .sn-ic-slot, .cam-media .cam-check .sn-ic { width:11px; height:11px; }
.cam-card.is-active { border-color:var(--sn-accent); box-shadow:0 0 0 3px var(--sn-accent-glow); }
.cam-card.is-active .cam-check { opacity:1; transform:none; }

/* name row — move name + tiny category key */
.cam-card .cam-tx { display:flex; align-items:baseline; gap:8px; padding:8px 10px 9px; min-width:0; }
.cam-tx .cam-name { flex:1; font:600 .8125rem/1.25 var(--sn-sans); color:var(--sn-ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cam-tx .cam-k { flex:none; font:600 .5625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.05em; color:var(--sn-ink-3); }
.cam-card.is-active .cam-name { color:var(--sn-accent-ink); }

/* ---------------------------------------------------------------------------
   §17 START FRAME — the Video composer's optional image-to-video source.
   Reuses the Image tool's tier-1 row idiom wholesale: the row is
   .cmp-refs.cmp-src (so .refs-k / .ref-hint / .ref-add / .ref-thumb / .ref-x
   from §13 style it for free); this section only adds the landscape thumb
   shape, the i2v mode chip, and the composer's subtle i2v signal.
   Markup contract (see spec): .cmp-prompt > textarea + .cmp-refs.cmp-src#cm-src.
   -------------------------------------------------------------------------- */
/* the start frame is a FRAME — landscape, not the refs' square swatch */
.cmp-src .ref-add { width:58px; height:40px; border-radius:var(--sn-r-input); }
.cmp-src .ref-thumb { width:58px; height:40px; border-radius:var(--sn-r-input); }

/* mode chip — appears once a frame is set; the one loud-ish signal, still tiny */
.cmp-src .src-mode { display:inline-flex; align-items:center; gap:6px; height:22px; padding:0 9px;
  border-radius:999px; background:var(--sn-accent-soft); color:var(--sn-accent-ink);
  font:600 .625rem var(--sn-mono); text-transform:uppercase; letter-spacing:.05em;
  animation:sl-note-in 180ms var(--sn-ease-out); }
.cmp-src .src-mode .sn-ic-slot, .cmp-src .src-mode .sn-ic { width:11px; height:11px; }

/* the composer whispers the mode: the control-strip hairline warms slightly */
#sl-composer.is-i2v .bar.cmp-bar {
  border-top-color:color-mix(in srgb, var(--sn-accent) 30%, var(--sn-hairline)); }

/* dropping a file anywhere on the prompt tier targets the start frame */
.cmp-src .ref-add.over { border-color:var(--sn-accent); background:var(--sn-accent-soft);
  color:var(--sn-accent-ink); }

/* ---------------------------------------------------------------------------
   §18 VIDEO RESULTS + DRAWER — a persistent duration chip on video cards,
   and a stable 16:9 player well in the detail drawer.
   -------------------------------------------------------------------------- */
/* duration chip — always visible (unlike .over), dark glass in both themes;
   sits above the hover gradient, below the quick-actions pill */
figure.card .dur-chip { position:absolute; right:8px; bottom:8px; z-index:1; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  letter-spacing:.03em; pointer-events:none; }
/* the rising .over gradient would swallow it — lift it while hovered */
figure.card:hover .dur-chip { z-index:2; }
figure.card.is-pending .dur-chip, figure.card.is-skel .dur-chip, figure.card.is-error .dur-chip { display:none; }

/* drawer player — a fixed 16:9 stage so controls never jump while loading */
.sl-drawer .dw-media.is-video { width:100%; aspect-ratio:16/9; min-height:0; max-height:46vh; }
.sl-drawer .dw-media.is-video > video { width:100%; height:100%; max-width:none; max-height:none;
  object-fit:contain; background:var(--sn-bg-deep); }

/* ---------------------------------------------------------------------------
   §19 REDUCED MOTION — addendum for the Video tool. Preview clips never
   autoplay for anyone who opted out; posters carry the whole story.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .cam-media .cam-clip { display:none; }
  .cam-card, .cam-card:hover { transform:none; }
  .cam-media .cam-check { transition:none; transform:none; }
  .cmp-src .src-mode { animation:none; }
}

/* =============================================================================
   THE ANIMATE TOOL (Fable, pass 4) — the source-image HERO (the dropzone grown
   into a cinematic "Source" still with Replace/Remove), the SOURCE → RESULT
   pairing band, and the motion menu's family swatch dots. Styles exactly the
   markup studio-live.js already ships (animateHero / anPairSection / the
   motion menu). The hero classes are SHARED with the Scroll Scene tool (§24) —
   every selector here admits both .an-* and .scn-* variants.
   Same rules: --sn-* tokens only, solid hairlines, both themes; chips that sit
   ON media are dark glass with fixed ivory ink.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §20 SOURCE HERO — a .sl-drop grown into the composer's leading actor.
   Markup: .cmp-prompt > .cmp-media.an-stage (or .scn-stage) >
     .sl-drop.an-hero|.scn-hero(.is-set)#an-drop|#scn-drop >
       input[type=file] + img.sl-drop-preview
       + .sl-drop-empty(.sn-ic-slot, .sl-drop-t, .sl-drop-s)
       + span.an-src-tag|.scn-src-tag
       + .sl-drop-acts(button.sl-drop-swap, button.sl-drop-clear).
   wireDropzone's required children (input/preview/empty/clear) are untouched.
   -------------------------------------------------------------------------- */
.cmp-prompt .cmp-media.an-stage, .cmp-prompt .cmp-media.scn-stage { margin:0 0 12px; }
.sl-drop.an-hero, .sl-drop.scn-hero { min-height:190px; }
/* filled: the still sits contained on the cinematic floor — never cropped */
.sl-drop.an-hero.is-set, .sl-drop.scn-hero.is-set {
  background:var(--sn-bg-deep); border-color:var(--sn-hairline-strong); }
.an-hero .sl-drop-preview, .scn-hero .sl-drop-preview {
  min-height:190px; max-height:320px; object-fit:contain; object-position:center;
  background:var(--sn-bg-deep); }
/* two-line invitation (title in ink, mechanics in mono) */
.sl-drop-empty .sl-drop-t { font:600 .9375rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.sl-drop-empty .sl-drop-s { font:.75rem var(--sn-mono); color:var(--sn-ink-3); }

/* SOURCE tag — dark glass, top-left, appears only once a still is set */
.an-src-tag, .scn-src-tag { position:absolute; top:8px; left:8px; z-index:1; height:18px;
  padding:0 8px; border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  text-transform:uppercase; letter-spacing:.06em; pointer-events:none; opacity:0;
  transition:opacity var(--sn-t-micro) var(--sn-ease); }
.sl-drop.is-set .an-src-tag, .sl-drop.is-set .scn-src-tag { opacity:1; }

/* Replace / Remove — the same glass pill voice as the result cards' .acts */
.sl-drop-acts { position:absolute; top:8px; right:8px; z-index:1; display:flex; gap:1px;
  padding:3px; border-radius:var(--sn-r-input); background:rgba(10,9,8,.62); backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px); box-shadow:0 1px 4px rgba(0,0,0,.25);
  opacity:0; transform:translateY(-3px);
  transition:opacity var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease); }
.sl-drop:not(.is-set) .sl-drop-acts { display:none; }
.sl-drop.is-set:hover .sl-drop-acts, .sl-drop.is-set:focus-within .sl-drop-acts,
.sl-drop.is-set:focus-visible .sl-drop-acts { opacity:1; transform:none; }
@media (hover:none) { .sl-drop.is-set .sl-drop-acts { opacity:1; transform:none; } }
.sl-drop-acts button { width:26px; height:26px; display:grid; place-items:center; border:none;
  border-radius:var(--sn-r-input); background:transparent; color:rgba(245,244,239,.85); cursor:pointer; padding:0; }
.sl-drop-acts button:hover { background:rgba(245,244,239,.16); color:#F5F4EF; }
.sl-drop-acts button:focus-visible { outline:none; box-shadow:0 0 0 2px rgba(245,244,239,.6); }
.sl-drop-acts button .sn-ic-slot, .sl-drop-acts button .sn-ic { width:13px; height:13px; }
/* the clear button inside the pill sheds §5's floating placement */
.sl-drop-acts .sl-drop-clear { position:static; top:auto; right:auto; width:26px; height:26px;
  border-radius:var(--sn-r-input); background:transparent; backdrop-filter:none; -webkit-backdrop-filter:none; }
.sl-drop-acts .sl-drop-clear:hover { background:rgba(245,244,239,.16); }

/* ---------------------------------------------------------------------------
   §21 SOURCE → RESULT PAIRING — "Latest animation": the still pinned beside
   the live result card, joined by a connector coin that glows while rendering.
   Markup: section.an-pair(.is-waiting|.is-pending|.is-done|.is-error)#an-pair >
     .sec-head > .pair-grid > .pair-cell.pair-src(.pair-k(.pair-sub),
     figure.pair-still > img) + .pair-link(.pair-coin) + .pair-cell.pair-out
     (.pair-k, jobCard() | .an-after-empty).
   -------------------------------------------------------------------------- */
@keyframes sl-coin-pulse { 0%,100% { box-shadow:0 0 0 3px var(--sn-accent-glow); }
  50% { box-shadow:0 0 0 8px var(--sn-accent-glow); } }
.an-pair { margin:0 0 32px; padding:0 0 28px; border-bottom:1px solid var(--sn-hairline); }
.pair-grid { display:grid; grid-template-columns:minmax(0,1fr) 44px minmax(0,1fr);
  gap:14px; align-items:stretch; }
.pair-cell { min-width:0; display:flex; flex-direction:column; gap:8px; }
.pair-k { font:600 .625rem var(--sn-mono); text-transform:uppercase; letter-spacing:.07em;
  color:var(--sn-ink-3); line-height:1; }
.pair-k .pair-sub { font:500 .625rem var(--sn-mono); text-transform:none; letter-spacing:.02em; opacity:.75; }
.pair-still { margin:0; flex:1; min-height:200px; border:1px solid var(--sn-hairline);
  border-radius:var(--sn-r-card); background:var(--sn-bg-deep); overflow:hidden;
  display:grid; place-items:center; }
.pair-still > img { display:block; width:100%; height:100%; max-height:320px; object-fit:contain; }
.pair-out figure.card { height:100%; margin:0; }
.an-after-empty { flex:1; min-height:200px; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:8px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card);
  background:var(--sn-bg-raised); color:var(--sn-ink-3); font:.8125rem/1.5 var(--sn-sans);
  text-align:center; padding:16px; }
.an-after-empty .sn-ic-slot, .an-after-empty .sn-ic { width:20px; height:20px; }
/* the connector — quiet at rest, alive while the render runs */
.pair-link { display:grid; place-items:center; }
.pair-coin { width:32px; height:32px; border-radius:50%; display:grid; place-items:center;
  background:var(--sn-surface); border:1px solid var(--sn-hairline-strong); color:var(--sn-ink-3);
  transition:border-color var(--sn-t-micro) var(--sn-ease), color var(--sn-t-micro) var(--sn-ease),
    background var(--sn-t-micro) var(--sn-ease); }
.pair-coin .sn-ic-slot, .pair-coin .sn-ic { width:13px; height:13px; }
.an-pair.is-pending .pair-coin { border-color:var(--sn-accent); color:var(--sn-accent);
  animation:sl-coin-pulse 1.6s var(--sn-ease) infinite; }
.an-pair.is-done .pair-coin { background:var(--sn-accent); border-color:transparent; color:#FFF8F5; }
.an-pair.is-error .pair-coin { border-color:color-mix(in srgb, var(--sn-error) 45%, var(--sn-hairline));
  color:var(--sn-error); }
@media (max-width:760px) {
  .pair-grid { grid-template-columns:minmax(0,1fr); gap:10px; }
  .pair-link { padding:2px 0; }
  .pair-coin { transform:rotate(90deg); }
}

/* ---------------------------------------------------------------------------
   §22 MOTION MENU — family swatch dots: each dot's gradient sketches the move
   (in = converging glow, out = escaping edge, pan = horizontal streak,
   rise = vertical, orbit = conic ring, sway = diagonal drift).
   Markup: .sn-menu.model-menu.motion-menu > button.sn-menu-item[data-mo=…] >
     .mo-dot(.mo-auto) + .sn-menu-tx + .sn-menu-ic.
   -------------------------------------------------------------------------- */
.motion-menu .sn-menu-item { height:auto; min-height:38px; gap:10px; padding:8px 10px; border-radius:var(--sn-r-input); }
.motion-menu .sn-menu-item.is-active { background:var(--sn-selected); }
.motion-menu .sn-menu-tx b { font:500 .875rem/1.25 var(--sn-sans); color:var(--sn-ink); }
.motion-menu .sn-menu-tx > span { font:.6875rem/1.4 var(--sn-mono); color:var(--sn-ink-3); }
.motion-menu .sn-menu-ic { margin-left:auto; color:var(--sn-accent); }
.mo-dot { position:relative; width:18px; height:18px; flex:none; border-radius:var(--sn-r-xs);
  border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-raised); overflow:hidden; }
.mo-dot::after { content:""; position:absolute; inset:0; }
[data-mo="zoom-in"]  .mo-dot::after { background:radial-gradient(circle at 50% 50%, var(--sn-accent) 0%, transparent 62%); }
[data-mo="zoom-out"] .mo-dot::after { background:radial-gradient(circle at 50% 50%, transparent 34%, var(--sn-accent-soft) 72%, var(--sn-accent) 130%); }
[data-mo="pan"]   .mo-dot::after { background:linear-gradient(90deg, transparent 8%, var(--sn-accent) 52%, transparent 92%); }
[data-mo="rise"]  .mo-dot::after { background:linear-gradient(0deg, transparent 12%, var(--sn-accent) 88%); }
[data-mo="orbit"] .mo-dot { border-radius:50%; }
[data-mo="orbit"] .mo-dot::after { background:conic-gradient(from 40deg, var(--sn-accent) 0 26%, var(--sn-accent-soft) 26% 100%); }
[data-mo="sway"]  .mo-dot::after { background:linear-gradient(135deg, var(--sn-accent-soft) 20%, var(--sn-accent) 100%); }
/* "General motion" — an open frame with a quiet center: nothing is imposed */
.mo-dot.mo-auto { background:var(--sn-surface); }
.mo-dot.mo-auto::after { inset:6px; border-radius:50%; background:var(--sn-ink-3); opacity:.5; }

/* ---------------------------------------------------------------------------
   §23 REDUCED MOTION — addendum for the Animate tool.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .an-pair.is-pending .pair-coin { animation:none; box-shadow:0 0 0 3px var(--sn-accent-glow); }
  .sl-drop-acts { transform:none; transition:opacity var(--sn-t-micro) var(--sn-ease); }
}

/* =============================================================================
   THE SCROLL SCENE TOOL (Fable, pass 5) — prompt image → scroll-driven web
   scene. The source HERO reuses §20 wholesale (.scn-stage/.scn-hero/.scn-src-tag
   share every §20 selector); this pass adds the headline field, the grouped
   Look/Motion/Runway/Fit controls, the PREVIEW STAGE (a browser-frame mock that
   loops an honest CSS approximation of the chosen Look against a synced fake
   scrollbar — the "it's scroll-driven" tell), scene result cards, and states.
   Scene builds are 0 cr: "free" rides the accent button; no price anywhere else.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §24 HEADLINE FIELD — the scene's overlaid title, entered in the display
   voice it will render in (serif), under the hero, above the control strip.
   Markup: .cmp-prompt > .cmp-media.scn-stage(§20 hero) + .scn-headline-row >
     span.refs-k + input#scn-headline.
   -------------------------------------------------------------------------- */
.scn-headline-row { display:flex; align-items:center; gap:10px; padding:8px 2px 6px; }
.scn-headline-row .refs-k { flex:none; }
.scn-headline-row input, .scn-headline-row textarea {
  flex:1; min-width:0; border:none; background:none; outline:none; resize:none;
  color:var(--sn-ink); font:600 1.0625rem/1.4 var(--sn-serif); letter-spacing:-.01em;
  padding:2px; min-height:0; caret-color:var(--sn-accent); }
.scn-headline-row input::placeholder, .scn-headline-row textarea::placeholder {
  color:var(--sn-ink-3); font-style:italic; font-weight:400; }

/* ---------------------------------------------------------------------------
   §25 CONTROL GROUPS — Look / Motion / Runway / Fit, all .sn-seg in the §2
   labeled-group vocabulary. Only one new voice: a tiny mono runway annotation.
   Markup: cmpGroup("Runway", .sn-seg#scn-run > button[data-run]
     ("Short<span class=vh>160vh</span>" …)).
   -------------------------------------------------------------------------- */
.cmp-bar .sn-seg .vh { margin-left:5px; font:500 .625rem var(--sn-mono); letter-spacing:.01em;
  color:var(--sn-ink-3); }
.cmp-bar .sn-seg button.is-active .vh { color:var(--sn-ink-2); }
@media (max-width:760px) { .cmp-bar .sn-seg .vh { display:none; } }

/* ---------------------------------------------------------------------------
   §26 PREVIEW STAGE — a framed browser mock that plays the chosen Look as a
   loop, in lock-step with a fake scrollbar thumb (same duration, same linear
   clock: the thumb IS the scroll, the scene answers it). Sits in the .wrap
   above "Your scenes", mirroring the Animate pairing band.
   Markup: #scn-preview-host > section.scn-preview#scn-preview
     (.is-empty|.is-ready|.is-building|.is-built) >
     .sec-head(h2, span.count, .grow, #scn-replay) >
     .scn-frame[data-look=hero-reveal|parallax|dolly-in|turntable]
               [data-int=subtle|standard|bold][data-run=short|medium|tall]
               [data-fit=contain|cover] >
       .scn-chrome(.scn-dots(i×3), .scn-url) +
       .scn-viewport( .scn-demo > img.scn-demo-img#scn-demo-img,
         .scn-headline-demo#scn-headline-demo, .scn-state-chip,
         .scn-scrollbar > .scn-thumb, .scn-empty-in ) +
       .scn-hint(.sn-ic-slot, text).
   Intensity/runway feed two custom props: --scn-amt (motion scale) and
   --scn-dur (loop length = scroll length). Every look keyframe multiplies by
   --scn-amt, so one dial moves the whole demo.
   -------------------------------------------------------------------------- */
.scn-preview { margin:0 0 32px; padding:0 0 28px; border-bottom:1px solid var(--sn-hairline); }
.scn-preview .sec-head { margin-bottom:14px; }

.scn-frame { position:relative; max-width:820px; border:1px solid var(--sn-hairline-strong);
  border-radius:var(--sn-r-card); background:var(--sn-surface); box-shadow:var(--sn-elev-2); overflow:hidden;
  --scn-amt:1; --scn-dur:4.6s;
  transition:border-color var(--sn-t-micro) var(--sn-ease), box-shadow var(--sn-t-micro) var(--sn-ease); }
.scn-frame[data-int="subtle"] { --scn-amt:.55; }
.scn-frame[data-int="bold"]   { --scn-amt:1.55; }
.scn-frame[data-run="short"]  { --scn-dur:3.2s; }
.scn-frame[data-run="tall"]   { --scn-dur:6.4s; }

/* browser chrome — quiet, token-driven, both themes */
.scn-chrome { display:flex; align-items:center; gap:10px; height:34px; padding:0 12px;
  background:var(--sn-bg-raised); border-bottom:1px solid var(--sn-hairline); }
.scn-dots { display:inline-flex; gap:5px; flex:none; }
.scn-dots i { width:8px; height:8px; border-radius:50%; background:var(--sn-hairline-strong); }
.scn-url { height:20px; padding:0 10px; border-radius:var(--sn-r-xs); background:var(--sn-surface);
  border:1px solid var(--sn-hairline); font:.625rem/18px var(--sn-mono); color:var(--sn-ink-3);
  max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* the viewport — cinematic floor; fixed ivory inks in BOTH themes */
.scn-viewport { position:relative; aspect-ratio:16/9; max-height:400px; width:100%;
  background:var(--sn-bg-deep); overflow:hidden; }
.scn-demo { position:absolute; inset:0; display:grid; place-items:center; }
.scn-demo-img { display:block; width:100%; height:100%; object-fit:contain;
  will-change:transform, opacity; }
.scn-frame[data-fit="cover"] .scn-demo-img { object-fit:cover; }
.scn-frame[data-look="turntable"] .scn-demo { perspective:900px; }

/* the overlaid headline — the display voice, live-mirrored from #scn-headline */
.scn-headline-demo { position:absolute; left:24px; right:56px; bottom:20px; z-index:1;
  font:600 clamp(1.25rem, 3.2vw, 1.875rem)/1.15 var(--sn-serif); letter-spacing:-.015em;
  color:#F5F4EF; text-shadow:0 1px 24px rgba(10,9,8,.55); pointer-events:none;
  overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.scn-headline-demo:empty { display:none; }

/* the fake scrollbar — the honest tell. Same clock as the look loop. */
.scn-scrollbar { position:absolute; top:6px; right:5px; bottom:6px; width:5px; z-index:2;
  border-radius:999px; background:rgba(245,244,239,.12); pointer-events:none; }
.scn-thumb { position:absolute; left:0; right:0; top:2px; height:28px; border-radius:999px;
  background:rgba(245,244,239,.45); animation:scn-scroll var(--scn-dur) linear infinite; }

/* the caption under the viewport */
.scn-hint { display:flex; align-items:center; gap:8px; padding:9px 14px;
  border-top:1px solid var(--sn-hairline); font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
.scn-hint .sn-ic-slot, .scn-hint .sn-ic { width:12px; height:12px; color:var(--sn-accent); flex:none; }

/* -- the look demos. One linear clock (--scn-dur); scroll position == time.
      Every distance/angle multiplies --scn-amt so Motion scales the demo. -- */
@keyframes scn-scroll { from { top:2px; } to { top:calc(100% - 30px); } }
@keyframes scn-hero-reveal {
  0%      { opacity:0; transform:translateY(calc(48px * var(--scn-amt))) scale(calc(1 - .05 * var(--scn-amt))); }
  32%,80% { opacity:1; transform:none; }
  100%    { opacity:0; transform:translateY(calc(-14px * var(--scn-amt))); } }
@keyframes scn-parallax {
  from { transform:translateY(calc(26px * var(--scn-amt))) scale(1.1); }
  to   { transform:translateY(calc(-26px * var(--scn-amt))) scale(1.1); } }
@keyframes scn-dolly {
  from { transform:scale(1); }
  to   { transform:scale(calc(1 + .15 * var(--scn-amt))); } }
@keyframes scn-turntable {
  from { transform:rotateY(calc(-12deg * var(--scn-amt))); }
  to   { transform:rotateY(calc( 12deg * var(--scn-amt))); } }
@keyframes scn-hl-rise {
  0%,16%  { opacity:0; transform:translateY(calc(20px * var(--scn-amt))); }
  44%,84% { opacity:1; transform:none; }
  100%    { opacity:0; transform:translateY(calc(-6px * var(--scn-amt))); } }
@keyframes scn-hl-counter {
  from { transform:translateY(calc(-12px * var(--scn-amt))); }
  to   { transform:translateY(calc( 12px * var(--scn-amt))); } }

.scn-frame[data-look="hero-reveal"] .scn-demo-img { animation:scn-hero-reveal var(--scn-dur) linear infinite; }
.scn-frame[data-look="parallax"]    .scn-demo-img { animation:scn-parallax    var(--scn-dur) linear infinite; }
.scn-frame[data-look="dolly-in"]    .scn-demo-img { animation:scn-dolly       var(--scn-dur) linear infinite; }
.scn-frame[data-look="turntable"]   .scn-demo-img { animation:scn-turntable   var(--scn-dur) linear infinite; }
.scn-frame[data-look="parallax"] .scn-headline-demo { animation:scn-hl-counter var(--scn-dur) linear infinite; }
.scn-frame[data-look="hero-reveal"] .scn-headline-demo,
.scn-frame[data-look="dolly-in"]    .scn-headline-demo,
.scn-frame[data-look="turntable"]   .scn-headline-demo { animation:scn-hl-rise var(--scn-dur) linear infinite; }

/* -- states -- */
/* empty: the frame stands (the promise), the floor invites a source */
.scn-empty-in { position:absolute; inset:0; z-index:1; display:none; flex-direction:column;
  align-items:center; justify-content:center; gap:10px; text-align:center; padding:16px;
  color:rgba(245,244,239,.55); font:.875rem/1.5 var(--sn-sans); }
.scn-empty-in .sn-ic-slot { width:38px; height:38px; border-radius:50%;
  background:rgba(245,244,239,.08); border:1px solid rgba(245,244,239,.16);
  display:grid; place-items:center; color:rgba(245,244,239,.6); }
.scn-empty-in .sn-ic-slot .sn-ic { width:18px; height:18px; }
.scn-preview.is-empty .scn-empty-in { display:flex; }
.scn-preview.is-empty .scn-demo-img, .scn-preview.is-empty .scn-headline-demo { display:none; }
.scn-preview.is-empty .scn-thumb { animation:none; top:2px; opacity:.4; }

/* building: the demo holds its breath; a shimmer sweeps the viewport */
.scn-preview.is-building .scn-demo-img, .scn-preview.is-building .scn-headline-demo,
.scn-preview.is-building .scn-thumb { animation-play-state:paused; }
.scn-preview.is-building .scn-viewport::after { content:""; position:absolute; inset:0; z-index:3;
  background:linear-gradient(100deg, transparent 32%, rgba(245,244,239,.10) 50%, transparent 68%);
  background-size:200% 100%; animation:sl-shimmer 1.8s linear infinite; }

/* status chip — dark glass while building, solid accent once saved */
.scn-state-chip { position:absolute; top:10px; left:10px; z-index:4; display:none;
  align-items:center; gap:6px; height:20px; padding:0 9px; border-radius:var(--sn-r-xs);
  background:rgba(10,9,8,.62); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  color:#F5F4EF; font:600 .625rem/20px var(--sn-mono); text-transform:uppercase;
  letter-spacing:.06em; pointer-events:none; }
.scn-state-chip .sn-ic-slot, .scn-state-chip .sn-ic { width:11px; height:11px; }
.scn-preview.is-building .scn-state-chip, .scn-preview.is-built .scn-state-chip { display:inline-flex; }
.scn-preview.is-built .scn-state-chip { background:var(--sn-accent); color:#FFF8F5; }
.scn-preview.is-built .scn-frame { border-color:var(--sn-accent);
  box-shadow:0 0 0 3px var(--sn-accent-glow), var(--sn-elev-2); }

@media (max-width:640px) {
  .scn-viewport { max-height:300px; }
  .scn-headline-demo { left:16px; right:44px; bottom:14px; }
  .scn-url { max-width:140px; }
}

/* ---------------------------------------------------------------------------
   §27 SCENE RESULT CARDS — the "Your scenes" grid. A scene card is the shared
   figure.card + .is-scene: badge "Scene", the runway rides the §18 .dur-chip
   voice ("220vh"), and hover answers with a scroll-flavored drift (rise, not
   zoom). Empty grid uses the shared §6 empty state.
   Markup: figure.card.is-scene[data-asset] > span.badge-type("Scene") +
     .media(img) + span.dur-chip("220vh") + .acts + .over.
   -------------------------------------------------------------------------- */
figure.card.is-scene .media { background:var(--sn-bg-deep); }
figure.card.is-scene:not(.is-pending):not(.is-error):hover .media > img {
  transform:translateY(-2.5%) scale(1.045); }

/* ---------------------------------------------------------------------------
   §28 REDUCED MOTION — addendum for the Scroll Scene tool. The demo becomes a
   composed still (image + headline shown settled, thumb parked mid-runway);
   the caption still says scroll-driven, so nothing is lost but the loop.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .scn-demo-img, .scn-headline-demo, .scn-thumb,
  .scn-preview.is-building .scn-viewport::after { animation:none !important; }
  .scn-demo-img, .scn-headline-demo { opacity:1; transform:none; }
  .scn-thumb { top:calc(50% - 14px); }
  figure.card.is-scene:not(.is-pending):not(.is-error):hover .media > img { transform:none; }
}

/* =============================================================================
   THE VOICE & TOOLS SUITE (Fable, pass 6) — §29–§36. The flat card pile
   becomes a two-mode suite inside the manage wrap: a curated LANDING —
   category bands of rich tool entries — and, one at a time, a focused
   MINI-STUDIO per tool built on the §2 composer vocabulary (each workspace's
   input card IS a `.composer`, so tiers, segs, notes and the accent Generate
   all inherit). New instruments: the voice audition rack, the token-built
   waveform player, the Upscale before/after. Same rules: --sn-* tokens only,
   solid hairlines, both themes, price ONLY on the accent run button +
   confirm/quote. No sliders; .sn-seg carries every scalar.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §29 THE LANDING — #sl-tools.tools-suite: category bands of tool entries.
   Markup contract (see spec): #sl-tools.tools-suite >
     section.tool-band[data-band] > .band-head(.band-k, .band-n, .band-line)
       > .band-grid > button.tool-entry[data-open]
         > .te-ic + .te-tx(.te-name, .te-sub) + .te-meta(.te-chip, .te-go)
     + section.tool-studio[data-tool] ×N (hidden until opened, §30).
   Opening a tool: the container gains .has-open, the tool gains .is-open —
   bands hide, the one workspace shows. No prices on entries.
   -------------------------------------------------------------------------- */
.tools-suite .tool-studio { display:none; }
.tools-suite.has-open .tool-band { display:none; }
.tools-suite.has-open .tool-studio.is-open { display:block; animation:sl-view-in 240ms var(--sn-ease-out) both; }

.tool-band { margin:0 0 30px; }
.tool-band:last-of-type { margin-bottom:8px; }
.band-head { display:flex; align-items:center; gap:10px; margin:0 0 12px; }
.band-head .band-k { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.08em; color:var(--sn-ink-3); line-height:1; }
.band-head .band-n { font:.625rem var(--sn-mono); color:var(--sn-ink-3); opacity:.7; line-height:1; }
.band-head .band-line { flex:1; height:1px; background:var(--sn-hairline); }

.band-grid { display:grid; gap:12px; grid-template-columns:repeat(2, minmax(0,1fr)); }
@media (max-width:880px) { .band-grid { grid-template-columns:1fr; } }

/* the entry — icon coin, serif name, one-liner, kind chip, a go-coin that
   leans toward the click. Calm at rest, warm on hover; never shouts price. */
.tool-entry { display:flex; align-items:center; gap:14px; width:100%; margin:0; padding:15px 16px;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface);
  cursor:pointer; text-align:left; box-shadow:var(--sn-elev-1);
  transition:border-color var(--sn-t-micro) var(--sn-ease), box-shadow var(--sn-t-micro) var(--sn-ease),
    transform var(--sn-t-micro) var(--sn-ease); }
.tool-entry:hover { border-color:var(--sn-hairline-strong); box-shadow:var(--sn-elev-2); transform:translateY(-1px); }
.tool-entry:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }

.tool-entry .te-ic { width:40px; height:40px; flex:none; display:grid; place-items:center;
  border-radius:var(--sn-r-card); background:var(--sn-bg-raised); border:1px solid var(--sn-hairline);
  color:var(--sn-accent);
  transition:background var(--sn-t-hover) var(--sn-ease), border-color var(--sn-t-hover) var(--sn-ease),
    color var(--sn-t-hover) var(--sn-ease); }
.tool-entry .te-ic .sn-ic-slot, .tool-entry .te-ic .sn-ic { width:18px; height:18px; }
.tool-entry:hover .te-ic { background:var(--sn-accent-soft); border-color:transparent; color:var(--sn-accent-ink); }

.tool-entry .te-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:3px; }
.tool-entry .te-name { font:600 1rem/1.25 var(--sn-sans); color:var(--sn-ink); letter-spacing:-.005em; }
.tool-entry .te-sub { font:.8125rem/1.5 var(--sn-sans); color:var(--sn-ink-2);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }

.tool-entry .te-meta { flex:none; display:flex; align-items:center; gap:10px; }
.tool-entry .te-chip { height:19px; padding:0 8px; border-radius:var(--sn-r-xs); background:var(--sn-chip-bg);
  color:var(--sn-ink-2); font:500 .625rem/19px var(--sn-mono); text-transform:uppercase;
  letter-spacing:.05em; white-space:nowrap; }
.tool-entry .te-go { width:26px; height:26px; display:grid; place-items:center; border-radius:50%;
  border:1px solid var(--sn-hairline); color:var(--sn-ink-3);
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease),
    transform var(--sn-t-hover) var(--sn-ease); }
.tool-entry .te-go .sn-ic-slot, .tool-entry .te-go .sn-ic { width:13px; height:13px; }
.tool-entry:hover .te-go { border-color:var(--sn-accent); color:var(--sn-accent); transform:translateX(2px); }
@media (max-width:520px) { .tool-entry .te-chip { display:none; } }

/* ---------------------------------------------------------------------------
   §30 THE MINI-STUDIO — one open tool. Markup contract (see spec):
   section.tool-studio.is-open[data-tool] >
     header.ts-head(button.ts-back[data-back], .ts-ic, .ts-id(h2.ts-name,
       p.ts-sub)) >
     div.composer.tool-composer (the §2 composer, reused wholesale:
       .cmp-prompt > textarea · optional .ts-src wells ·
       optional .voice-lib (§31) · .bar.cmp-bar > .cmp-controls + .cmp-actions
       (.cmp-gen[data-run] with .t + .price) · .cmp-note) >
     figure.ts-result (§33/§34, the featured latest output).
   -------------------------------------------------------------------------- */
.tool-studio .ts-head { display:flex; align-items:center; gap:14px; margin:0 0 20px; }
.ts-head .ts-back { flex:none; height:32px; padding:0 10px 0 7px; gap:6px; border-radius:var(--sn-r-input);
  color:var(--sn-ink-2); font-size:.8125rem; }
.ts-head .ts-back .sn-ic-slot, .ts-head .ts-back .sn-ic { width:14px; height:14px; transform:rotate(180deg); }
.ts-head .ts-back:hover { color:var(--sn-ink); }
.ts-head .ts-ic { width:44px; height:44px; flex:none; display:grid; place-items:center;
  border-radius:var(--sn-r-card); background:var(--sn-accent-soft); color:var(--sn-accent-ink); }
.ts-head .ts-ic .sn-ic-slot, .ts-head .ts-ic .sn-ic { width:20px; height:20px; }
.ts-head .ts-id { min-width:0; }
.ts-head .ts-name { font:600 1.375rem/1.15 var(--sn-serif); letter-spacing:-.015em;
  color:var(--sn-ink); margin:0; }
.ts-head .ts-sub { font:.875rem/1.5 var(--sn-sans); color:var(--sn-ink-2); margin:3px 0 0; }
@media (max-width:640px) {
  .tool-studio .ts-head { flex-wrap:wrap; gap:10px 12px; }
  .ts-head .ts-back { flex-basis:100%; justify-content:flex-start; margin-left:-7px; }
}

.tool-studio .composer.tool-composer { max-width:840px; }
/* short-prompt tools still use a textarea; it just starts lower */
.tool-composer .cmp-prompt textarea.is-short { min-height:48px; }

/* source wells — Upscale / Extend / Sound design / Talking / Lip sync.
   A full-width picker button the shared openAssetPicker() anchors to.
   Lives in tier 1 (above the cmp-bar hairline); two wells stack with 8px. */
.tool-composer .ts-src { display:flex; flex-direction:column; gap:8px; padding:2px 2px 12px; }
.ts-src .src-pick { display:flex; align-items:center; gap:12px; width:100%; min-height:68px;
  padding:11px 14px; border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-card);
  background:var(--sn-bg-raised); cursor:pointer; text-align:left;
  transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
.ts-src .src-pick:hover { border-color:var(--sn-accent); }
.ts-src .src-pick:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.src-pick .sp-ic { width:36px; height:36px; flex:none; display:grid; place-items:center;
  border-radius:var(--sn-r-input); background:var(--sn-surface); border:1px solid var(--sn-hairline);
  color:var(--sn-ink-3); transition:color var(--sn-t-hover) var(--sn-ease); }
.src-pick .sp-ic .sn-ic-slot, .src-pick .sp-ic .sn-ic { width:16px; height:16px; }
.src-pick:hover .sp-ic { color:var(--sn-accent); }
.src-pick .sp-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:3px; }
.src-pick .sp-t { font:500 .875rem/1.3 var(--sn-sans); color:var(--sn-ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.src-pick .sp-s { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
.src-pick .sp-chev { flex:none; color:var(--sn-ink-3); }
.src-pick .sp-chev .sn-ic-slot, .src-pick .sp-chev .sn-ic { width:14px; height:14px; }
/* set — a real thumbnail replaces the icon coin; the well reads chosen */
.src-pick.is-set { background:var(--sn-surface); }
.src-pick.is-set .sp-thumb { width:72px; height:44px; flex:none; border-radius:var(--sn-r-input); overflow:hidden;
  border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep); }
.src-pick.is-set .sp-thumb > img, .src-pick.is-set .sp-thumb > video {
  display:block; width:100%; height:100%; object-fit:cover; }
.src-pick.is-set .sp-s { color:var(--sn-accent-ink); }

/* ---------------------------------------------------------------------------
   §31 THE VOICE RACK — the audition picker (~22 voices). Lives INSIDE the
   Voiceover composer as its own hairline-topped band, between the script and
   the control strip: one instrument, one card. Markup contract (see spec):
   .voice-lib#vo-voices > .vl-tools(.seg-lbl, input.vl-search, .vl-filters >
   button.cam-cat[data-vf]) > .vl-grid > button.voice-tile[data-voice]
     (.is-active|.is-off|.is-auditioning|.is-loading) >
     span.vt-audition[data-audition] (.vt-play + .vt-eq(i×3)) +
     .vt-tx(.vt-name, .vt-k) + .vt-check.
   Tile click SELECTS; the audition coin click PLAYS a sample. Auditioning is
   $0 — no price appears anywhere in the rack.
   -------------------------------------------------------------------------- */
.composer .voice-lib { margin:2px 2px 0; padding:14px 0; border-top:1px solid var(--sn-hairline); }
.voice-lib .vl-tools { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:0 0 12px; }
.voice-lib .vl-tools .seg-lbl { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.07em; color:var(--sn-ink-3); line-height:1; }
.voice-lib .vl-search { height:30px; width:170px; padding:0 11px; border:1px solid var(--sn-hairline-strong);
  border-radius:999px; background:var(--sn-bg-raised); color:var(--sn-ink);
  font:.8125rem var(--sn-sans); outline:none;
  transition:border-color var(--sn-t-hover) var(--sn-ease); }
.voice-lib .vl-search::placeholder { color:var(--sn-ink-3); }
.voice-lib .vl-search:focus { border-color:var(--sn-accent); }
.voice-lib .vl-filters { display:flex; flex-wrap:wrap; gap:6px; }
.voice-lib .vl-filters .cam-cat { height:26px; padding:0 11px; font-size:.75rem; }

.voice-lib .vl-grid { display:grid; gap:8px; grid-template-columns:repeat(auto-fill, minmax(158px,1fr)); }
.voice-tile { position:relative; display:flex; align-items:center; gap:10px; margin:0; padding:8px 10px;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-input); background:var(--sn-bg-raised);
  cursor:pointer; text-align:left; min-width:0;
  transition:border-color var(--sn-t-micro) var(--sn-ease), background var(--sn-t-micro) var(--sn-ease),
    box-shadow var(--sn-t-micro) var(--sn-ease); }
.voice-tile:hover { border-color:var(--sn-hairline-strong); background:var(--sn-surface); }
.voice-tile:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.voice-tile.is-off { display:none; }   /* filtered out by search / a chip */
.voice-tile.is-active { border-color:var(--sn-accent); background:var(--sn-surface);
  box-shadow:0 0 0 3px var(--sn-accent-glow); }

/* the audition coin — a separate click target; play → eq while sounding */
.voice-tile .vt-audition { width:30px; height:30px; flex:none; display:grid; place-items:center;
  border-radius:50%; border:1px solid var(--sn-hairline-strong); background:var(--sn-surface);
  color:var(--sn-ink-2);
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease),
    background var(--sn-t-hover) var(--sn-ease); }
.voice-tile .vt-audition:hover { border-color:var(--sn-accent); color:var(--sn-accent); }
.voice-tile .vt-audition .vt-play, .voice-tile .vt-audition .vt-play .sn-ic { width:12px; height:12px; }
.voice-tile .vt-eq { display:none; align-items:flex-end; gap:2px; height:12px; }
.voice-tile .vt-eq i { width:2px; height:100%; border-radius:1px; background:currentColor;
  transform-origin:bottom; animation:sl-eq 1s var(--sn-ease) infinite; }
.voice-tile .vt-eq i:nth-child(2) { animation-delay:.18s; }
.voice-tile .vt-eq i:nth-child(3) { animation-delay:.36s; }
@keyframes sl-eq { 0%,100% { transform:scaleY(.3); } 50% { transform:scaleY(1); } }
.voice-tile.is-auditioning .vt-audition { background:var(--sn-accent); border-color:transparent; color:#FFF8F5; }
.voice-tile.is-auditioning .vt-play { display:none; }
.voice-tile.is-auditioning .vt-eq { display:flex; }
/* sample being fetched/generated on demand — a quiet spinner in the coin */
.voice-tile.is-loading .vt-play { display:none; }
.voice-tile.is-loading .vt-audition::before { content:""; width:12px; height:12px; border-radius:50%;
  border:2px solid var(--sn-hairline-strong); border-top-color:var(--sn-accent);
  animation:sn-spin .8s linear infinite; }

.voice-tile .vt-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.voice-tile .vt-name { font:600 .8125rem/1.2 var(--sn-sans); color:var(--sn-ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.voice-tile .vt-k { font:.625rem var(--sn-mono); color:var(--sn-ink-3); letter-spacing:.02em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.voice-tile.is-active .vt-name { color:var(--sn-accent-ink); }
.voice-tile .vt-check { width:16px; height:16px; flex:none; display:grid; place-items:center;
  border-radius:50%; background:var(--sn-accent); color:#FFF8F5; opacity:0; transform:scale(.6);
  transition:opacity var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease-spring); }
.voice-tile .vt-check .sn-ic-slot, .voice-tile .vt-check .sn-ic { width:9px; height:9px; }
.voice-tile.is-active .vt-check { opacity:1; transform:none; }
@media (max-width:560px) { .voice-lib .vl-grid { grid-template-columns:repeat(2, minmax(0,1fr)); } }

/* ---------------------------------------------------------------------------
   §32 THE WAVEFORM PLAYER — token-built, no libraries. Markup contract:
   .aud-player[data-audio-player] > button.ap-play + .ap-wave[data-ap-wave]
     (.ap-bars.base(i×44 · each i carries style="--h:0.15…1") +
      .ap-bars.fill (the SAME 44 bars again; clipped to the played fraction) +
      .ap-head) + .ap-time(span[data-ap-cur], .ap-sep, span[data-ap-dur])
   + <audio>. JS sets --p (e.g. "37%") on .ap-wave; .fill and .ap-head follow.
   .is-playing on .aud-player flips the play glyph (JS swaps the icon).
   -------------------------------------------------------------------------- */
.aud-player { display:flex; align-items:center; gap:14px; padding:12px 16px;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-bg-raised); }
.aud-player .ap-play { width:44px; height:44px; flex:none; display:grid; place-items:center;
  border:1px solid var(--sn-hairline-strong); border-radius:50%; background:var(--sn-surface);
  color:var(--sn-ink); cursor:pointer;
  transition:background var(--sn-t-hover) var(--sn-ease), border-color var(--sn-t-hover) var(--sn-ease),
    color var(--sn-t-hover) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease-spring); }
.aud-player .ap-play:hover { border-color:var(--sn-accent); color:var(--sn-accent); transform:scale(1.04); }
.aud-player .ap-play:focus-visible { outline:none; box-shadow:var(--sn-ring); }
.aud-player .ap-play .sn-ic-slot, .aud-player .ap-play .sn-ic { width:16px; height:16px; }
.aud-player.is-playing .ap-play { background:var(--sn-accent); border-color:transparent; color:#FFF8F5; }
.aud-player.is-playing .ap-play:hover { transform:none; }

.aud-player .ap-wave { position:relative; flex:1; height:44px; min-width:0; cursor:pointer; }
.aud-player .ap-bars { position:absolute; inset:0; display:flex; align-items:center; gap:2px;
  pointer-events:none; }
.aud-player .ap-bars i { flex:1; min-width:1px; height:calc(var(--h,.5) * 100%); min-height:3px;
  border-radius:2px; background:var(--sn-hairline-strong); }
.aud-player .ap-bars.fill { clip-path:inset(0 calc(100% - var(--p,0%)) 0 0); }
.aud-player .ap-bars.fill i { background:var(--sn-accent); }
.aud-player .ap-head { position:absolute; left:var(--p,0%); top:4px; bottom:4px; width:2px;
  margin-left:-1px; border-radius:1px; background:var(--sn-accent); opacity:0; pointer-events:none;
  transition:opacity var(--sn-t-hover) var(--sn-ease); }
.aud-player .ap-wave:hover .ap-head, .aud-player.is-playing .ap-head { opacity:1; }

.aud-player .ap-time { flex:none; display:inline-flex; align-items:baseline; gap:5px;
  font:.6875rem var(--sn-mono); color:var(--sn-ink-3); font-variant-numeric:tabular-nums; }
.aud-player .ap-time [data-ap-cur] { color:var(--sn-ink); }
.aud-player .ap-sep { opacity:.6; }
@media (max-width:560px) {
  .aud-player { gap:10px; padding:10px 12px; }
  .aud-player .ap-play { width:38px; height:38px; }
  .aud-player .ap-time [data-ap-dur], .aud-player .ap-sep { display:none; }
}

/* grid audio cards (§4) get a resting mini-waveform behind the play coin */
.card.is-audio .media { position:relative; }
.card.is-audio .aud-mini { position:absolute; left:12%; right:12%; top:50%; height:36px;
  transform:translateY(-50%); display:flex; align-items:center; gap:2px; opacity:.45;
  pointer-events:none; }
.card.is-audio .aud-mini i { flex:1; height:calc(var(--h,.5) * 100%); min-height:2px;
  border-radius:1px; background:var(--sn-ink-3); }
.card.is-audio .media .aud-play { position:relative; z-index:1; }
.card.is-audio.is-playing .aud-mini { opacity:.8; }
.card.is-audio.is-playing .aud-mini i { background:var(--sn-accent); }
.card.is-audio.is-playing .media .aud-play { border-color:var(--sn-accent); color:var(--sn-accent); }

/* ---------------------------------------------------------------------------
   §33 THE FEATURED RESULT + STATES — the tool's latest output, above history.
   Markup contract (see spec): figure.ts-result.sn-card(.is-audio|.is-video|
   .is-compare)(.is-pending|.is-error) >
     .tr-head(.badge-kind, .tr-when, .grow) >
     [ .aud-player | .tr-media > video | .ba-stage (§34) |
       .tr-status(.tr-spin, .tr-msg, .grow, .tr-el[data-elapsed]) ] >
     footer.tr-acts(.sn-btn[data-act=download], [data-act=use], .grow,
       [data-act=more]; error: [data-act=retry] + span.tr-sub).
   -------------------------------------------------------------------------- */
figure.ts-result { margin:16px 0 0; padding:16px; border-radius:var(--sn-r-card); max-width:840px;
  animation:sl-note-in 200ms var(--sn-ease-out); }
.ts-result .tr-head { display:flex; align-items:center; gap:10px; margin:0 0 12px; }
.ts-result .badge-kind { height:20px; padding:0 8px; border-radius:var(--sn-r-xs);
  background:var(--sn-accent-soft); color:var(--sn-accent-ink);
  font:600 .625rem/20px var(--sn-mono); text-transform:uppercase; letter-spacing:.06em; }
.ts-result .tr-when { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }
.ts-result .tr-head .grow { flex:1; }

.ts-result .tr-media { border-radius:var(--sn-r-card); overflow:hidden; background:var(--sn-bg-deep);
  border:1px solid var(--sn-hairline); }
.ts-result .tr-media > video, .ts-result .tr-media > img { display:block; width:100%; max-height:52vh;
  object-fit:contain; background:var(--sn-bg-deep); }

.ts-result .tr-acts { display:flex; align-items:center; gap:8px; margin-top:14px; padding-top:13px;
  border-top:1px solid var(--sn-hairline); flex-wrap:wrap; }
.ts-result .tr-acts .grow { flex:1; }

/* generating — the instrument warming up, honest about time */
.ts-result .tr-status { display:flex; align-items:center; gap:10px; margin-top:12px;
  font:.8125rem/1.4 var(--sn-sans); color:var(--sn-ink-2); }
.ts-result .tr-status .tr-spin { width:14px; height:14px; flex:none; border-radius:50%;
  border:2px solid var(--sn-hairline-strong); border-top-color:var(--sn-accent);
  animation:sn-spin .8s linear infinite; }
.ts-result .tr-status .grow { flex:1; }
.ts-result .tr-status .tr-el { font:.6875rem var(--sn-mono); color:var(--sn-ink-3);
  font-variant-numeric:tabular-nums; }
.ts-result.is-pending .aud-player { pointer-events:none; }
.ts-result.is-pending .ap-play { color:var(--sn-ink-3); }
.ts-result.is-pending .ap-bars i { background:var(--sn-hairline); }
.ts-result.is-pending .ap-bars.fill, .ts-result.is-pending .ap-head,
.ts-result.is-pending .ap-time { display:none; }
.ts-result.is-pending .ap-wave::after { content:""; position:absolute; inset:0;
  background:linear-gradient(100deg, transparent 34%, var(--sn-hover) 50%, transparent 66%);
  background-size:200% 100%; animation:sl-shimmer 1.8s linear infinite; }
.ts-result.is-pending .tr-acts { display:none; }

/* error — quietly wrong; the money line is explicit */
.ts-result.is-error { border-color:color-mix(in srgb, var(--sn-error) 32%, var(--sn-hairline)); }
.ts-result.is-error .tr-status .tr-spin { display:none; }
.ts-result.is-error .tr-status { color:var(--sn-error); }
.ts-result.is-error .aud-player, .ts-result.is-error .tr-media { display:none; }
.ts-result.is-error .tr-acts { border-top:none; padding-top:2px; }
.ts-result.is-error .tr-sub { font:.6875rem var(--sn-mono); color:var(--sn-ink-3); }

/* ---------------------------------------------------------------------------
   §34 UPSCALE BEFORE / AFTER — no slider; a .sn-seg flips the two takes and
   the stage crossfades. Markup contract (see spec): .ba-stage[data-ba]
   (.show-before toggles) > video.ba-before + video.ba-after +
   span.ba-chip.before + span.ba-chip.after; beneath, .tr-acts leads with
   .sn-seg.ba-seg > button[data-ba-show=before|after].
   -------------------------------------------------------------------------- */
.ba-stage { position:relative; aspect-ratio:16/9; border-radius:var(--sn-r-card); overflow:hidden;
  background:var(--sn-bg-deep); border:1px solid var(--sn-hairline); }
.ba-stage > video, .ba-stage > img { position:absolute; inset:0; width:100%; height:100%;
  object-fit:contain; }
.ba-stage .ba-after { opacity:1; transition:opacity 180ms var(--sn-ease); }
.ba-stage.show-before .ba-after { opacity:0; }
/* which take is on stage — dark glass, fixed ivory ink, both themes */
.ba-stage .ba-chip { position:absolute; left:8px; bottom:8px; z-index:1; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  letter-spacing:.03em; pointer-events:none; opacity:0; transition:opacity 180ms var(--sn-ease); }
.ba-stage .ba-chip.after { opacity:1; }
.ba-stage.show-before .ba-chip.after { opacity:0; }
.ba-stage.show-before .ba-chip.before { opacity:1; }
.ts-result .tr-acts .ba-seg { background:var(--sn-bg-raised); }
.ts-result .tr-acts .ba-seg button { min-width:64px; }

/* ---------------------------------------------------------------------------
   §35 HISTORY — #sl-results stays the shared grid; it closes the suite with
   its own hairline-topped head in BOTH modes. Markup: .sec-head.tools-recent
   (h2 "Recent output", span.count, .grow) placed just above #sl-results.
   -------------------------------------------------------------------------- */
.sec-head.tools-recent { margin:32px 0 14px; padding-top:24px; border-top:1px solid var(--sn-hairline); }
.sec-head.tools-recent h2 { font-size:1rem; }

/* ---------------------------------------------------------------------------
   §36 REDUCED MOTION — addendum for the suite. Nothing loops, leans or
   shimmers for anyone who opted out; states stay legible without motion.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .tools-suite.has-open .tool-studio.is-open, figure.ts-result { animation:none; }
  .tool-entry, .tool-entry:hover { transform:none; }
  .tool-entry:hover .te-go { transform:none; }
  .voice-tile .vt-eq i { animation:none; transform:scaleY(.7); }
  .voice-tile.is-loading .vt-audition::before { animation:none; }
  .voice-tile .vt-check { transition:none; transform:none; }
  .aud-player .ap-play:hover { transform:none; }
  .ts-result .tr-status .tr-spin { animation:none; }
  .ts-result.is-pending .ap-wave::after { animation:none; }
  .ba-stage .ba-after, .ba-stage .ba-chip { transition:none; }
}

/* =============================================================================
   THROUGHLINE (Fable, pass 7) — §37–§43. The flagship long-video tool grows
   from a thin stepper into a STORYBOARD EDITOR: Stage A is the pitch (the §2
   composer as hero), Stage B is a director's board of scene cards — each one
   a beat you can rewrite, re-still, re-roll or pin a frame to — with the quote
   riding a sticky rail and the render's progress living IN the board (scenes
   fill in one by one). The finished film premieres on a cinematic screen above
   the board. Same rules: --sn-* tokens only, solid hairlines, both themes,
   price ONLY on the accent button + confirm/quote; chips that sit ON media are
   dark glass with fixed ivory ink. Backend contract untouched.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   §37 THE PITCH — Stage A. The §2 composer wholesale; this pass only adds the
   film-scale brief, the Sound consideration (the #cm-sound pressed idiom on
   #tl-sound, which lives HERE now — it is a creative choice made at the pitch,
   priced later by the quote), and the confident Plan action.
   Markup: .composer#sl-composer.tl-pitch > .cmp-prompt(#tl-brief + refs strip)
     > .bar.cmp-bar > .cmp-controls(cmpGroup Length #tl-len · CMP_DIV ·
       cmpGroup Quality #tl-quality · CMP_DIV · cmpGroup Sound #tl-sound)
     + .cmp-actions(.grow, .cmp-hint, #tl-plan.cmp-gen) > .cmp-note#cm-note.
   -------------------------------------------------------------------------- */
/* a film brief deserves more room than a clip prompt */
.tl-pitch #tl-brief { min-height:96px; }
/* Sound — the same pressed-chip voice as #cm-sound (§2), duplicated here so
   the pitch composer needs no §2 edit */
.cmp-bar #tl-sound { height:32px; border-radius:var(--sn-r-input); }
.cmp-bar #tl-sound[aria-pressed="true"] { background:var(--sn-accent-soft);
  border-color:transparent; color:var(--sn-accent-ink); box-shadow:none; }
.cmp-bar #tl-sound[disabled] { opacity:.45; }
/* Plan — free, and it says so; the busy state inherits .cmp-gen.is-busy */
#tl-plan .price { background:rgba(255,255,255,.16); }

/* ---------------------------------------------------------------------------
   §38 THE BOARD — Stage B layout. A director's wall: scene cards in a
   responsive grid on the left, the quote a sticky rail on the right.
   Markup contract (see spec): .wrap#tl-stage > section.tl-board
     [data-state=planning|planned|rendering|done|error] >
     .sec-head.tl-board-head(h2, span.count, .grow, .tl-progress, #tl-reset)
     + .tl-stall (error state only, §41)
     + section.tl-premiere (done state only, §42)
     + .tl-lane > ol.tl-scenes(li.tl-scene ×N) + aside.sn-card.sl-quote.tl-quote.
   -------------------------------------------------------------------------- */
.tl-board { animation:sl-view-in 260ms var(--sn-ease-out) both; }
.tl-board-head { margin-bottom:18px; }
.tl-board-head .count { font-variant-numeric:tabular-nums; }

.tl-lane { display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:20px; align-items:start; }
.tl-quote { position:sticky; top:24px; }
@media (max-width:980px) {
  .tl-lane { grid-template-columns:minmax(0,1fr); }
  .tl-quote { position:static; order:2; max-width:520px; }
}

.tl-scenes { list-style:none; margin:0; padding:0; display:grid; gap:14px;
  grid-template-columns:repeat(auto-fill, minmax(228px, 1fr)); }
@media (max-width:560px) { .tl-scenes { grid-template-columns:1fr; } }
/* LONGER FILMS — a 20+ shot board must stay a usable workbench, not an endless page. Cap the card grid at a
   viewport-relative height and scroll INSIDE it; short films (a few rows) never reach the cap so nothing
   changes for them. scrollbar-gutter keeps the accent focus-ring clear of the scrollbar. */
.tl-scenes { max-height:78vh; overflow-y:auto; scrollbar-gutter:stable; padding-right:4px;
  overscroll-behavior:contain; }
.tl-scenes::-webkit-scrollbar { width:10px; }
.tl-scenes::-webkit-scrollbar-thumb { background:var(--sn-line, rgba(140,140,150,.35)); border-radius:var(--sn-r-input);
  border:3px solid transparent; background-clip:content-box; }
.tl-scenes::-webkit-scrollbar-track { background:transparent; }
/* The per-film credit-cap warning in the quote rail (LONGER FILMS): a runaway-length film Render is blocked
   with a clear reason instead of a silent 400. Reuses the stall/error tone. */
.tl-quote .q-cap { display:flex; gap:8px; align-items:flex-start; margin:10px 0 2px; padding:10px 12px;
  border-radius:var(--sn-r-input); font-size: 12px; line-height:1.45;
  background:var(--sn-danger-bg, rgba(220,70,70,.10)); color:var(--sn-danger-fg, #c23b3b); }
.tl-quote .q-cap b { font-weight:650; }
.tl-quote .q-cap .sn-ic-slot { flex:none; margin-top:1px; }

/* ---------------------------------------------------------------------------
   §39 THE SCENE CARD — one beat of the film. A frame (the still/clip slot,
   16:9, cinematic floor) over a body (number-anchored title + the editable
   one-line direction). States: .is-planned (empty slot invites a keyframe),
   .is-keyframing (shimmer, "Making still…"), .is-rendering (the live beat:
   accent ring pulse + status), .is-done (clip + CLIP chip), .is-failed
   (quiet error + retry, "nothing was charged"), .is-skel (planning ghost),
   .is-editing (the direction opens into a textarea).
   Markup contract (see spec): li.tl-scene[data-pos] >
     .ts-frame( span.ts-no, img|video.ts-media, .ts-empty, .ts-status,
       span.ts-dur, span.ts-kind, .ts-acts(button[data-sact]×n) )
     + .ts-body( .ts-head-row(h4.ts-title, .ts-dur-b) ,
       button.ts-line | .ts-editor(textarea.ts-edit, .ts-edit-acts) ).
   -------------------------------------------------------------------------- */
.tl-scene { position:relative; display:flex; flex-direction:column; margin:0;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface);
  overflow:hidden; box-shadow:var(--sn-elev-1);
  transition:border-color var(--sn-t-micro) var(--sn-ease), box-shadow var(--sn-t-micro) var(--sn-ease),
    transform var(--sn-t-micro) var(--sn-ease); }
.tl-scene:not(.is-skel):not(.is-failed):hover { border-color:var(--sn-hairline-strong);
  box-shadow:var(--sn-elev-2); transform:translateY(-1px); }

/* the frame — the still/clip slot */
.tl-scene .ts-frame { position:relative; aspect-ratio:16/9; background:var(--sn-bg-deep); overflow:hidden; }
.ts-frame .ts-media { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }

/* scene number — dark glass, top-left, mono, both themes */
.ts-frame .ts-no { position:absolute; top:8px; left:8px; z-index:2; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  letter-spacing:.06em; pointer-events:none; }
/* duration — the §18 .dur-chip voice, bottom-right */
.ts-frame .ts-dur { position:absolute; right:8px; bottom:8px; z-index:2; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .625rem/18px var(--sn-mono);
  letter-spacing:.03em; pointer-events:none; }
/* what the frame holds — STILL once a keyframe lands, CLIP once footage does */
.ts-frame .ts-kind { position:absolute; left:8px; bottom:8px; z-index:2; height:18px; padding:0 7px;
  border-radius:var(--sn-r-xs); background:rgba(10,9,8,.62); backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px); color:#F5F4EF; font:600 .5625rem/18px var(--sn-mono);
  text-transform:uppercase; letter-spacing:.07em; pointer-events:none; }
.tl-scene.is-done .ts-kind { background:var(--sn-accent); color:#FFF8F5; }

/* per-scene actions — the §10 glass pill, top-right, on hover/focus */
.ts-frame .ts-acts { position:absolute; top:8px; right:8px; z-index:3; display:flex; gap:1px;
  padding:3px; border-radius:var(--sn-r-input); background:rgba(10,9,8,.62); backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px); box-shadow:0 1px 4px rgba(0,0,0,.25);
  opacity:0; transform:translateY(-3px);
  transition:opacity var(--sn-t-micro) var(--sn-ease), transform var(--sn-t-micro) var(--sn-ease); }
.tl-scene:hover .ts-acts, .tl-scene:focus-within .ts-acts { opacity:1; transform:none; }
@media (hover:none) { .tl-scene .ts-acts { opacity:1; transform:none; } }
.ts-acts button { width:26px; height:26px; display:grid; place-items:center; border:none; padding:0;
  border-radius:var(--sn-r-input); background:transparent; color:rgba(245,244,239,.85); cursor:pointer; }
.ts-acts button:hover { background:rgba(245,244,239,.16); color:#F5F4EF; }
.ts-acts button:focus-visible { outline:none; box-shadow:0 0 0 2px rgba(245,244,239,.6); opacity:1; }
.ts-acts button .sn-ic-slot, .ts-acts button .sn-ic { width:14px; height:14px; }
.ts-acts .sep { width:1px; height:14px; margin:0 3px; align-self:center;
  background:rgba(245,244,239,.22); flex:none; }
/* while a scene works, its verbs step back */
.tl-scene.is-keyframing .ts-acts, .tl-scene.is-rendering .ts-acts,
.tl-scene.is-skel .ts-acts { display:none; }

/* the body — number-anchored title + the one-line direction */
.tl-scene .ts-body { display:flex; flex-direction:column; gap:6px; padding:10px 12px 12px; }
.ts-body .ts-title { margin:0; font:600 .8125rem/1.3 var(--sn-sans); color:var(--sn-ink);
  letter-spacing:-.005em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* the direction — an editable line, a writing surface at rest */
.ts-body .ts-line { display:block; width:100%; margin:0; padding:6px 8px; border:1px solid transparent;
  border-radius:var(--sn-r-input); background:none; text-align:left; cursor:text;
  font:.75rem/1.55 var(--sn-sans); color:var(--sn-ink-2);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease); }
.ts-body .ts-line:hover { border-color:var(--sn-hairline); background:var(--sn-bg-raised); }
.ts-body .ts-line:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.ts-body .ts-line:empty::before { content:"Add a direction…"; color:var(--sn-ink-3); font-style:italic; }

/* PER-SHOT DIRECTOR CONTROLS (Fable #6) — camera move + shot length as compact pills. Shown only in the
   Shot workbench. On-brand: hairline pills that fill on hover, accent-ringed on focus, themed via vars. */
.ts-director { display:flex; flex-wrap:wrap; gap:6px; }
.ts-director .ts-ctl { display:inline-flex; align-items:center; gap:5px; height:26px; padding:0 8px;
  border:1px solid var(--sn-hairline); border-radius:999px; background:var(--sn-bg-raised);
  color:var(--sn-ink-2); font:500 .6875rem/1 var(--sn-sans); cursor:pointer; max-width:100%;
  transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease); }
.ts-director .ts-ctl:hover { border-color:var(--sn-hairline-strong); background:var(--sn-bg-base); color:var(--sn-ink); }
.ts-director .ts-ctl:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.ts-director .ts-ctl[disabled] { opacity:.5; cursor:default; }
.ts-director .ts-ctl .sn-ic-slot:first-child .sn-ic, .ts-director .ts-ctl > .sn-ic-slot:first-child { width:12px; height:12px; color:var(--sn-accent); }
.ts-director .ts-ctl .ts-ctl-v { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ts-director .ts-ctl .ts-ctl-ch { width:11px; height:11px; color:var(--sn-ink-3); margin-left:-1px; }
.ts-director .ts-ctl .ts-ctl-ch .sn-ic { width:11px; height:11px; }
/* the camera / length popover reuses the model-menu shell; keep it tight */
.sn-menu.shot-ctl-menu { min-width:200px; max-width:260px; }

/* editing — the line opens into a textarea with quiet Save/Cancel */
.tl-scene .ts-editor { display:none; flex-direction:column; gap:8px; }
.tl-scene.is-editing .ts-editor { display:flex; }
.tl-scene.is-editing .ts-line { display:none; }
.tl-scene.is-editing { border-color:color-mix(in srgb, var(--sn-accent) 40%, var(--sn-hairline-strong));
  box-shadow:var(--sn-elev-2), 0 0 0 3px var(--sn-accent-glow); }
.ts-editor .ts-edit { width:100%; min-height:64px; padding:8px 9px; resize:vertical;
  border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-input); background:var(--sn-bg-raised);
  color:var(--sn-ink); font:.75rem/1.55 var(--sn-sans); outline:none; caret-color:var(--sn-accent); }
.ts-editor .ts-edit:focus { border-color:var(--sn-accent); }
.ts-editor .ts-edit-acts { display:flex; gap:6px; }
.ts-editor .ts-edit-acts .sn-btn { height:26px; padding:0 10px; font-size:.6875rem; }
.ts-editor .ts-edit-acts .grow { flex:1; }

/* -- states -- */
/* planned — the empty slot is an invitation, not a hole */
.ts-frame .ts-empty { position:absolute; inset:0; z-index:1; display:none; flex-direction:column;
  align-items:center; justify-content:center; gap:7px; text-align:center; padding:12px;
  color:rgba(245,244,239,.5); font:.6875rem var(--sn-mono); }
.ts-empty .sn-ic-slot { width:30px; height:30px; border-radius:50%;
  background:rgba(245,244,239,.07); border:1px solid rgba(245,244,239,.16);
  display:grid; place-items:center; color:rgba(245,244,239,.55); }
.ts-empty .sn-ic-slot .sn-ic { width:14px; height:14px; }
/* "NO STILL YET" ON TOP OF A DELIVERED STILL — the shipped bug, fixed at the selector.
   `is-planned` means "this scene has no CLIP", and a scene can have a perfectly good keyframe and no
   clip: every board in that state printed "no still yet" over a picture that was right there. The
   placeholder may only show when the frame is genuinely EMPTY, which `:not(:has(.ts-media))` states
   directly. The Producer's rebuilt card no longer emits .ts-empty at all — it picks its floor from
   whether a still exists — so this now only guards the other surfaces that still use the old card. */
.tl-scene.is-planned .ts-frame:not(:has(.ts-media)) .ts-empty { display:flex; }
.tl-scene.is-planned .ts-kind { display:none; }

/* working overlay — keyframing and rendering share the status voice */
.ts-frame .ts-status { position:absolute; inset:auto 0 0 0; z-index:2; display:none;
  align-items:center; gap:8px; padding:8px 10px;
  background:linear-gradient(180deg, transparent 0%, rgba(10,9,8,.72) 60%, rgba(10,9,8,.9) 100%);
  color:#F5F4EF; font:600 .6875rem var(--sn-mono); letter-spacing:.03em; }
.ts-status .ts-spin { width:12px; height:12px; flex:none; border-radius:50%;
  border:2px solid rgba(245,244,239,.3); border-top-color:#F5F4EF;
  animation:sn-spin .8s linear infinite; }
.tl-scene.is-keyframing .ts-status, .tl-scene.is-rendering .ts-status { display:flex; }
.tl-scene.is-keyframing .ts-dur, .tl-scene.is-rendering .ts-dur { display:none; }

/* keyframing — shimmer over the empty floor */
.tl-scene.is-keyframing .ts-frame::before { content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg, transparent 32%, rgba(245,244,239,.09) 50%, transparent 68%);
  background-size:200% 100%; animation:sl-shimmer 1.8s linear infinite; }

/* rendering — the live beat: the still dims a touch, the card wears the ring */
.tl-scene.is-rendering { border-color:var(--sn-accent);
  animation:sl-coin-pulse 1.6s var(--sn-ease) infinite; }
.tl-scene.is-rendering .ts-media { opacity:.8; }
.tl-scene.is-rendering .ts-frame::before { content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg, transparent 32%, rgba(245,244,239,.07) 50%, transparent 68%);
  background-size:200% 100%; animation:sl-shimmer 1.8s linear infinite; }

/* done — calm; the footage speaks, the chip confirms */
.tl-scene.is-done .ts-frame { cursor:pointer; }
.tl-scene.is-done:hover .ts-media { transform:scale(1.02); }
.ts-frame .ts-media { transition:transform var(--sn-t-panel) var(--sn-ease), opacity var(--sn-t-micro) var(--sn-ease); }

/* failed — quietly wrong; the retry is right there, the money line explicit */
.tl-scene.is-failed { border-color:color-mix(in srgb, var(--sn-error) 32%, var(--sn-hairline)); }
.tl-scene.is-failed .ts-frame { background:var(--sn-error-soft); }
.ts-frame .ts-err { position:absolute; inset:0; z-index:1; display:none; flex-direction:column;
  align-items:center; justify-content:center; gap:4px; text-align:center; padding:12px; }
.ts-err > .sn-ic-slot { width:24px; height:24px; border-radius:50%; display:grid; place-items:center;
  background:var(--sn-surface); color:var(--sn-error); margin-bottom:2px; }
.ts-err > .sn-ic-slot .sn-ic { width:12px; height:12px; }
.ts-err .ts-err-t { font:600 .75rem/1.3 var(--sn-sans); color:var(--sn-error); max-width:92%;
  overflow:hidden; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
.ts-err .ts-err-s { font:.625rem var(--sn-mono); color:var(--sn-ink-3); }
/* the retry button — matched on the class, not on a data-sact that has since been renamed twice */
.ts-err .sn-btn { margin-top:6px; height:26px; padding:0 10px; font-size:.6875rem; }
.tl-scene.is-failed .ts-err { display:flex; }
.tl-scene.is-failed .ts-empty, .tl-scene.is-failed .ts-status,
.tl-scene.is-failed .ts-kind, .tl-scene.is-failed .ts-dur { display:none; }

/* planning ghosts — the board sketches itself while the planner thinks */
.tl-scene.is-skel { pointer-events:none; box-shadow:none; }
.tl-scene.is-skel .ts-frame { background:linear-gradient(100deg,
  var(--sn-bg-raised) 36%, color-mix(in srgb, var(--sn-surface) 55%, var(--sn-bg-raised)) 50%, var(--sn-bg-raised) 64%);
  background-size:200% 100%; animation:sl-shimmer 1.6s linear infinite; }
.tl-scene.is-skel:nth-child(3n+2) .ts-frame { animation-delay:220ms; }
.tl-scene.is-skel:nth-child(3n) .ts-frame { animation-delay:440ms; }
.tl-scene.is-skel .ts-ghost { height:11px; border-radius:var(--sn-r-xs); background:var(--sn-bg-raised); }
.tl-scene.is-skel .ts-ghost.w60 { width:60%; }

/* ---------------------------------------------------------------------------
   §40 BOARD PROGRESS — one segment per scene; the whole storyboard IS the
   progress bar. Lives in the board head (wide) and above the render button in
   the quote rail. Markup: .tl-progress > .tl-prog-track(i×N — .is-done filled,
   .is-live pulsing) + span.tl-prog-k("3/6") | .tl-prog-lbl("Assembling…").
   The assembling phase (all clips in, film stitching) sweeps the full track.
   -------------------------------------------------------------------------- */
@keyframes sl-seg-pulse { 0%,100% { opacity:.45; } 50% { opacity:1; } }
.tl-progress { display:flex; align-items:center; gap:10px; min-width:0; }
.tl-progress .tl-prog-track { display:flex; gap:3px; flex:1; min-width:72px; max-width:220px; }
.tl-prog-track i { flex:1; height:4px; border-radius:2px; background:var(--sn-hairline-strong); }
.tl-prog-track i.is-done { background:var(--sn-accent); }
.tl-prog-track i.is-live { background:var(--sn-accent); animation:sl-seg-pulse 1.2s var(--sn-ease) infinite; }
.tl-progress .tl-prog-k { font:600 .6875rem var(--sn-mono); color:var(--sn-ink-2);
  font-variant-numeric:tabular-nums; white-space:nowrap; }
/* §118 — THE RUNNING COST, beside the scene counter. Deliberately quiet: it is a fact the customer should
   be able to find at a glance, not an alarm. Tabular figures so it does not jitter as it climbs. */
.tl-progress .tl-prog-cr { font:600 .6875rem var(--sn-mono); color:var(--sn-ink-3);
  font-variant-numeric:tabular-nums; white-space:nowrap; }
.tl-progress .tl-prog-lbl { font:600 .625rem var(--sn-mono); text-transform:uppercase;
  letter-spacing:.06em; color:var(--sn-accent-ink); white-space:nowrap; }
/* The renderer, in words. run_progress has always been on the wire; nothing read it, so an 80-minute
   render narrated itself as a row of dots. `role=status` so it is also spoken, not just shown. */
.tl-progress .tl-prog-say { font:.75rem/1.3 var(--sn-sans); color:var(--sn-ink-2); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
@media (max-width:640px){ .tl-progress .tl-prog-say { display:none; } }
/* assembling — every segment breathes together under the sweep */
.tl-progress.is-assembling .tl-prog-track i { background:var(--sn-accent);
  animation:sl-seg-pulse 1.2s var(--sn-ease) infinite; }
.tl-progress.is-assembling .tl-prog-track i:nth-child(2n) { animation-delay:.3s; }
.tl-progress.is-assembling .tl-prog-track i:nth-child(3n) { animation-delay:.6s; }
/* the quote rail's copy sits above the button, full width */
.tl-quote .tl-progress { margin:2px 0 10px; }
.tl-quote .tl-progress .tl-prog-track { max-width:none; }
@media (max-width:700px) { .tl-board-head .tl-progress .tl-prog-track { display:none; } }

/* ---------------------------------------------------------------------------
   §41 THE QUOTE RAIL + STALL — §7's .sl-quote refined for the board. The rail
   states the deal (beats · length · model · sound · total · balance-after) and
   holds the one loud verb. A stalled render surfaces .tl-stall with Resume.
   Markup: aside.sn-card.sl-quote.tl-quote > h3.sl-card-title("The quote") >
     .q-line×n (Sound row shows a plain value — the choice was made at the
     pitch) > .q-line.is-total > .q-after > [.tl-progress] > #tl-render.cmp-gen.
   Stall: .tl-stall > .sn-ic-slot + .tl-stall-tx(b, span) + button[data-act=resume].
   -------------------------------------------------------------------------- */
.tl-quote { padding:18px 20px 20px; }
.tl-quote .sl-card-title { margin-bottom:6px; }
.tl-quote #tl-render { width:100%; margin-top:12px; height:42px; }
.tl-quote #tl-render:focus-visible { box-shadow:var(--sn-ring); }
/* rendering — the rail goes quiet and lets the board carry the news */
.tl-board[data-state="rendering"] .tl-quote .q-line:not(.is-total) { color:var(--sn-ink-3); }
.tl-board[data-state="rendering"] .tl-quote #tl-render { pointer-events:none; }

/* the stall bar — warm, honest, one action */
.tl-stall { display:flex; align-items:center; gap:12px; margin:0 0 18px; padding:12px 14px;
  border:1px solid color-mix(in srgb, var(--sn-warn) 32%, var(--sn-hairline));
  border-radius:var(--sn-r-card); background:var(--sn-warn-soft); animation:sl-note-in 200ms var(--sn-ease-out); }
.tl-stall > .sn-ic-slot { width:28px; height:28px; flex:none; border-radius:50%;
  display:grid; place-items:center; background:var(--sn-surface); color:var(--sn-warn); }
.tl-stall > .sn-ic-slot .sn-ic { width:14px; height:14px; }
.tl-stall .tl-stall-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.tl-stall .tl-stall-tx b { font:600 .8125rem/1.35 var(--sn-sans); color:var(--sn-ink); }
/* The body is a PARAGRAPH about the customer's money, not machine output — it reads in the UI face at a
   size a person is meant to read (the mono/.6875rem look said "stack trace" in the one banner whose whole
   job is to be read; OpenArt's editor never speaks to the user in mono). ink-2, not ink-3: this is the
   sentence that explains the headline, not a caption. Width capped for measure — a 300-char reason at
   full banner width is a wall, at ~72ch it is a paragraph. */
.tl-stall .tl-stall-tx span { font:.75rem/1.5 var(--sn-sans); color:var(--sn-ink-2); max-width:72ch; }
.tl-stall [data-act="resume"] { flex:none; }
/* The server's own failure sentence can be a full clause ("scene 3 did not render — re-run to finish;
   rendered scenes are free"), so it wraps rather than being clipped to one mono line like the stall's
   fixed copy. It is the most useful text on the board when it appears. */
.tl-stall .tl-stall-tx span { white-space:normal; overflow-wrap:anywhere; }
/* A film that shipped but logged a problem: a quiet note under the premiere, never an error block. */
.tl-partial { margin:-6px 0 16px; font:.75rem/1.5 var(--sn-sans); }

/* ---------------------------------------------------------------------------
   §42 THE PREMIERE — the finished film, presented like a screening: a dark
   screen with a quiet accent halo, controls, then the take-it-with-you row.
   Sits ABOVE the board (which remains below as the archive of its beats).
   Markup: section.tl-premiere > .sec-head(h2 "The premiere", span.count)
     > figure.tl-screen(video[controls][poster]) >
     .tl-prem-acts( a.sn-btn.sn-btn-accent[download](.t)("Download film"),
       a.sn-btn.sn-btn-secondary("Open in a tab"), span.grow,
       button#tl-reset.sn-btn.sn-btn-ghost("Make another film") ).
   -------------------------------------------------------------------------- */
.tl-premiere { margin:0 0 32px; padding:0 0 28px; border-bottom:1px solid var(--sn-hairline);
  animation:sl-view-in 300ms var(--sn-ease-out) both; }
.tl-premiere .sec-head { margin-bottom:14px; }
.tl-screen { margin:0; max-width:920px; border-radius:var(--sn-r-card); overflow:hidden;
  border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep);
  box-shadow:var(--sn-elev-3), 0 0 60px var(--sn-accent-glow); }
.tl-screen { position:relative; }
.tl-screen > video, .tl-screen > .tl-film { display:block; width:100%; max-height:62vh; object-fit:contain;
  background:var(--sn-bg-deep); cursor:pointer; }
/* Big centred play affordance — fades out while the film is playing. */
.tl-bigplay { position:absolute; inset:0; margin:auto; width:74px; height:74px; border:none;
  display:grid; place-items:center; border-radius:50%; cursor:pointer;
  background:rgba(10,9,8,.5); backdrop-filter:blur(6px); color:#F5F4EF;
  transition:opacity var(--sn-t-panel) var(--sn-ease), transform var(--sn-t-press) var(--sn-ease-spring); }
.tl-bigplay > span { display:grid; place-items:center; }
.tl-bigplay svg { width:32px; height:32px; }
.tl-bigplay:hover { transform:scale(1.06); }
.tl-screen.is-playing .tl-bigplay { opacity:0; pointer-events:none; transform:scale(.86); }

/* ── The OpenArt-style scrubber: a filmstrip you scrub. Frame-accurate click,
      buttery rAF drag. Rounded + on-brand; adapts to light/dark via tokens. ── */
.tl-scrub { display:flex; align-items:center; gap:12px; max-width:920px; margin-top:14px;
  padding:8px 12px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card);
  background:var(--sn-surface); box-shadow:var(--sn-elev-1); user-select:none;
  -webkit-user-select:none; }
.tl-sc-pp, .tl-sc-btn { flex:none; display:grid; place-items:center; width:38px; height:38px;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-input); background:var(--sn-bg-raised);
  color:var(--sn-ink); cursor:pointer; padding:0;
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease),
    background var(--sn-t-hover) var(--sn-ease), transform var(--sn-t-press) var(--sn-ease); }
.tl-sc-pp { width:42px; height:42px; }
.tl-sc-pp svg, .tl-sc-btn svg { width:20px; height:20px; }
.tl-sc-pp:hover, .tl-sc-btn:hover { border-color:var(--sn-accent); color:var(--sn-accent); }
.tl-sc-pp:active, .tl-sc-btn:active { transform:scale(.94); }
.tl-sc-pp:focus-visible, .tl-sc-btn:focus-visible, .tl-sc-track:focus-visible {
  outline:none; box-shadow:var(--sn-ring); }

.tl-sc-track { position:relative; flex:1 1 auto; min-width:0; height:44px; cursor:pointer;
  touch-action:none; border-radius:var(--sn-r-input); }
.tl-sc-rail { position:absolute; inset:0; border-radius:var(--sn-r-input); overflow:hidden;
  border:1px solid var(--sn-hairline); background:var(--sn-bg-deep); }
.tl-sc-strip { position:absolute; inset:0; display:flex; }
.tl-sc-strip .tl-sb-cell { flex:1 1 0; min-width:0; background-size:cover; background-position:center;
  box-shadow:inset -1px 0 0 rgba(0,0,0,.28); }
.tl-sc-strip .tl-sb-cell.is-ph { background:linear-gradient(135deg, var(--sn-bg-raised), var(--sn-surface)); }
.tl-sc-scrim { position:absolute; inset:0; pointer-events:none; background:rgba(10,9,8,.34); }
.tl-sc-played { position:absolute; inset:0; transform-origin:left center; transform:scaleX(0);
  pointer-events:none; will-change:transform;
  background:linear-gradient(90deg, color-mix(in srgb, var(--sn-accent) 16%, transparent),
    color-mix(in srgb, var(--sn-accent) 6%, transparent)); }
.tl-sc-ticks i { position:absolute; top:0; bottom:0; width:1px; margin-left:-.5px;
  background:rgba(255,255,255,.32); pointer-events:none; }
.tl-sc-knob { position:absolute; top:50%; left:0; width:4px; height:calc(100% + 10px);
  border-radius:3px; background:var(--sn-accent); pointer-events:none; will-change:transform;
  transform:translate(-50%,-50%); box-shadow:0 0 0 1px rgba(0,0,0,.28), 0 2px 7px rgba(0,0,0,.4); }
.tl-sc-knob::after { content:""; position:absolute; left:50%; top:50%; width:14px; height:14px;
  border-radius:50%; background:var(--sn-accent); transform:translate(-50%,-50%);
  box-shadow:0 0 0 2px var(--sn-surface), 0 2px 6px rgba(0,0,0,.45);
  transition:width var(--sn-t-press) var(--sn-ease-spring), height var(--sn-t-press) var(--sn-ease-spring); }
.tl-scrub.is-scrubbing .tl-sc-knob::after { width:18px; height:18px; }
.tl-scrub.is-scrubbing .tl-sc-track { cursor:grabbing; }

/* Hover / drag thumbnail bubble — reads the cached sprite, never re-decodes. */
.tl-sc-preview { position:absolute; left:0; bottom:calc(100% + 12px); opacity:0; z-index:5;
  pointer-events:none; will-change:transform;
  transition:opacity var(--sn-t-hover) var(--sn-ease); }
.tl-sc-preview.is-on { opacity:1; }
.tl-sc-prev-img { display:block; width:124px; aspect-ratio:16/9; transform:translateX(-50%);
  border-radius:var(--sn-r-input); border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep) center/cover no-repeat;
  box-shadow:var(--sn-elev-2); }
.tl-sc-prev-t { display:block; width:max-content; margin:5px 0 0; transform:translateX(-50%);
  padding:2px 7px; border-radius:var(--sn-r-xs); background:rgba(10,9,8,.82); color:#fff;
  font:600 .68rem var(--sn-mono); }

.tl-sc-time { flex:none; font:600 .78rem var(--sn-mono); color:var(--sn-ink-3);
  font-variant-numeric:tabular-nums; white-space:nowrap; }
.tl-sc-time b { color:var(--sn-ink); font-weight:600; }
@media (max-width:560px) {
  .tl-scrub { gap:8px; padding:8px; flex-wrap:wrap; }
  .tl-sc-track { order:3; flex-basis:100%; }
  .tl-sc-time { order:2; margin-left:auto; }
}
@media (prefers-reduced-motion:reduce) {
  .tl-bigplay, .tl-sc-preview, .tl-sc-knob::after { transition:none; }
}
.tl-prem-acts { display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  max-width:920px; margin-top:14px; }
.tl-prem-acts .grow { flex:1; }
@media (max-width:560px) {
  .tl-prem-acts .grow { display:none; }
  .tl-prem-acts .sn-btn { flex:1; }
}

/* ---------------------------------------------------------------------------
   §43 REDUCED MOTION — addendum for Throughline. Nothing pulses, shimmers or
   drifts; state colors and labels carry the whole story.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce) {
  .tl-board, .tl-premiere, .tl-stall { animation:none; }
  .tl-scene, .tl-scene:hover { transform:none; }
  .tl-scene.is-rendering { animation:none; box-shadow:0 0 0 3px var(--sn-accent-glow); }
  .tl-scene.is-keyframing .ts-frame::before, .tl-scene.is-rendering .ts-frame::before,
  .tl-scene.is-skel .ts-frame { animation:none; }
  .ts-status .ts-spin { animation:none; }
  .tl-scene.is-done:hover .ts-media { transform:none; }
  .tl-prog-track i.is-live, .tl-progress.is-assembling .tl-prog-track i {
    animation:none; opacity:1; }
}

/* ==== PRODUCER — 3-tab Shot / Scene / Timeline Director (over ONE board) ====================
   Additive to the storyboard board view. Tokens only; matches the studio-live system. */
.tl-tabs { display:flex; gap:4px; margin:0 0 16px; border-bottom:1px solid var(--sn-hairline); }
.tl-tab { display:inline-flex; align-items:center; gap:7px; padding:9px 14px; margin-bottom:-1px; border:0; border-bottom:2px solid transparent; background:none; color:var(--sn-ink-3); font:600 .82rem/1 var(--sn-sans); cursor:pointer; transition:color var(--sn-t-hover); }
.tl-tab:hover { color:var(--sn-ink-2); }
.tl-tab.is-active { color:var(--sn-ink); border-bottom-color:var(--sn-accent); }
.tl-tab .sn-ic-slot { width:15px; height:15px; }
/* Shot view — the same wired cards in a compact grid */
.tl-scenes.is-grid { grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); }
.tl-scenes.is-grid .tl-scene .ts-body { padding:10px 12px; }
/* Scene view — the Cast/World/Objects continuity panel spans the lane above the scenes */
.tl-entities { grid-column:1 / -1; padding:16px 18px; }
.tl-ent-head { display:flex; flex-direction:column; gap:2px; margin-bottom:14px; }
.tl-ent-head b { font:600 .95rem var(--sn-sans); color:var(--sn-ink); }
.tl-ent-head span { font:500 .8rem var(--sn-sans); color:var(--sn-ink-3); }
.tl-ent-grid { display:grid; grid-template-columns:1fr 1fr; gap:20px; }
@media (max-width:720px){ .tl-ent-grid { grid-template-columns:1fr; } }
.tl-ent-col h4 { margin:0 0 10px; display:flex; align-items:center; gap:7px; font:600 .82rem var(--sn-sans); color:var(--sn-ink); }
.tl-ent-col h4 .sn-ic-slot { width:14px; height:14px; color:var(--sn-ink-2); }
.tl-ent-chips { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
.tl-ent-chip { display:inline-flex; align-items:center; gap:7px; padding:4px 10px 4px 4px; border:1px solid var(--sn-hairline-strong); border-radius:999px; background:var(--sn-bg-raised); }
.tl-ent-chip img { width:26px; height:26px; border-radius:50%; object-fit:cover; }
.tl-ent-chip b { font:600 .72rem var(--sn-sans); color:var(--sn-ink-2); }
.tl-ent-empty { font:500 .82rem var(--sn-sans); color:var(--sn-ink-3); }
.tl-ent-world { margin:0; font:500 .85rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.tl-ent-foot { grid-column:1 / -1; margin:14px 0 0; padding-top:12px; border-top:1px solid var(--sn-hairline); font:500 .78rem/1.5 var(--sn-sans); color:var(--sn-ink-3); }
/* Timeline view — a horizontal filmstrip of the film's shape */
.tl-filmstrip { grid-column:1 / -1; }
.tl-film-track { display:flex; gap:8px; overflow-x:auto; padding:4px 2px 12px; scroll-snap-type:x proximity; }
.tl-film-cell { position:relative; flex:none; width:180px; aspect-ratio:16/9; border-radius:var(--sn-r-input); overflow:hidden; border:1px solid var(--sn-hairline); background:var(--sn-bg-deep); cursor:pointer; padding:0; scroll-snap-align:start; transition:border-color var(--sn-t-hover), transform var(--sn-t-hover); }
.tl-film-cell:hover { border-color:var(--sn-accent); transform:translateY(-2px); }
.tl-film-cell.is-clip { border-color:var(--sn-accent-soft); }
.tl-film-cell img, .tl-film-cell video { width:100%; height:100%; object-fit:cover; display:block; }
.tl-film-ph { display:flex; align-items:center; justify-content:center; width:100%; height:100%; color:var(--sn-ink-3); }
.tl-film-ph .sn-ic-slot { width:20px; height:20px; opacity:.5; }
.tl-film-no { position:absolute; left:6px; top:6px; padding:1px 6px; border-radius:var(--sn-r-xs); background:rgba(0,0,0,.55); color:#fff; font:600 .68rem/1.5 var(--sn-mono); }
.tl-film-dur { position:absolute; right:6px; bottom:6px; padding:1px 6px; border-radius:var(--sn-r-xs); background:rgba(0,0,0,.55); color:#fff; font:600 .66rem/1.5 var(--sn-mono); }
.tl-film-note { margin:2px 0 0; font:500 .8rem var(--sn-sans); color:var(--sn-ink-3); }
.tl-empty { padding:30px; text-align:center; color:var(--sn-ink-3); font:500 .85rem var(--sn-sans); }
/* Pre-plan Director intro — so Producer reads as a Director before a board exists */
.tl-intro { padding:8px 2px 4px; animation:sl-view-in 260ms var(--sn-ease-out) both; }
.tl-intro-h { margin:0 0 16px; font:600 1.05rem var(--sn-sans); letter-spacing:-.01em; color:var(--sn-ink); }
.tl-intro-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:14px; }
@media (max-width:760px){ .tl-intro-grid { grid-template-columns:1fr; } }
.tl-intro-card { padding:16px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface); display:flex; flex-direction:column; gap:6px; }
.tl-intro-card .sn-ic-slot { width:20px; height:20px; color:var(--sn-accent); }
.tl-intro-card b { font:600 .92rem var(--sn-sans); color:var(--sn-ink); }
.tl-intro-card span { font:500 .82rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }
.tl-intro-note { margin:16px 0 0; font:500 .85rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.tl-intro-note b { color:var(--sn-ink); font-weight:600; }

/* ==== PRODUCER — template gallery (entry, pre-plan) ==================================== */
.tl-entry { padding:8px 2px 4px; animation:sl-view-in 260ms var(--sn-ease-out) both; }
.tl-entry-h { margin:0 0 16px; font:600 1.05rem var(--sn-sans); letter-spacing:-.01em; color:var(--sn-ink); }
.tl-tpl-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; }
@media (max-width:900px){ .tl-tpl-grid { grid-template-columns:repeat(2,1fr); } }
@media (max-width:520px){ .tl-tpl-grid { grid-template-columns:1fr; } }
.tl-tpl { display:flex; align-items:center; gap:12px; text-align:left; padding:14px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface); cursor:pointer; transition:border-color var(--sn-t-hover), transform var(--sn-t-hover), background var(--sn-t-hover); }
.tl-tpl:hover { border-color:var(--sn-accent); transform:translateY(-2px); background:var(--sn-bg-raised); }
.tl-tpl:focus-visible { outline:none; border-color:var(--sn-accent); box-shadow:0 0 0 3px var(--sn-accent-soft); }
.tl-tpl-ic { flex:none; width:38px; height:38px; border-radius:var(--sn-r-input); display:flex; align-items:center; justify-content:center; background:var(--sn-accent-soft); color:var(--sn-accent-ink); }
.tl-tpl-ic .sn-ic-slot { width:19px; height:19px; }
.tl-tpl-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.tl-tpl-tx b { font:600 .9rem var(--sn-sans); color:var(--sn-ink); }
.tl-tpl-tx span { font:500 .76rem/1.35 var(--sn-sans); color:var(--sn-ink-3); overflow:hidden; text-overflow:ellipsis; }
.tl-tpl-meta { flex:none; align-self:flex-start; padding:2px 7px; border-radius:var(--sn-r-xs); background:var(--sn-bg-deep); color:var(--sn-ink-3); font:600 .68rem/1.5 var(--sn-mono); }
.tl-entry-note { margin:16px 0 0; font:500 .85rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.tl-entry-note b { color:var(--sn-ink); font-weight:600; }

/* ==== PRODUCER — "Your films" (toolbar audit 4.4: a way back into a film you paid to render) ===== */
.tl-recent { margin-top:22px; }
.tl-recent[hidden] { display:none; }
.tl-recent-h { margin:0 0 10px; font:600 .8rem/1 var(--sn-sans); letter-spacing:.04em; text-transform:uppercase; color:var(--sn-ink-3); }
.tl-recent-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
@media (max-width:900px){ .tl-recent-grid { grid-template-columns:repeat(2,1fr); } }
@media (max-width:520px){ .tl-recent-grid { grid-template-columns:1fr; } }
.tl-recent-card { display:flex; align-items:center; gap:11px; text-align:left; padding:9px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface); cursor:pointer; transition:border-color var(--sn-t-hover), transform var(--sn-t-hover), background var(--sn-t-hover); }
.tl-recent-card:hover { border-color:var(--sn-accent); transform:translateY(-2px); background:var(--sn-bg-raised); }
.tl-recent-card:focus-visible { outline:none; border-color:var(--sn-accent); box-shadow:0 0 0 3px var(--sn-accent-soft); }
.tl-recent-thumb { flex:none; width:54px; height:34px; border-radius:var(--sn-r-input); display:flex; align-items:center; justify-content:center; background:var(--sn-bg-deep) center/cover no-repeat; color:var(--sn-ink-3); }
.tl-recent-thumb .sn-ic-slot { width:16px; height:16px; }
.tl-recent-tx { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.tl-recent-tx b { font:600 .85rem var(--sn-sans); color:var(--sn-ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tl-recent-tx span { font:500 .72rem/1.35 var(--sn-sans); color:var(--sn-ink-3); }

/* ==== PRODUCER — URL → ad ingest (entry) ============================================== */
.tl-url { display:flex; align-items:center; gap:10px; margin-top:14px; padding:10px 12px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); background:var(--sn-surface); }
.tl-url:focus-within { border-color:var(--sn-accent); box-shadow:0 0 0 3px var(--sn-accent-soft); }
.tl-url-ic { flex:none; display:flex; color:var(--sn-ink-3); }
.tl-url-ic .sn-ic-slot { width:17px; height:17px; }
.tl-url-input { flex:1; min-width:0; border:0; background:none; color:var(--sn-ink); font:500 .9rem var(--sn-sans); outline:none; }
.tl-url-input::placeholder { color:var(--sn-ink-3); }
.tl-url .sn-btn { flex:none; }

/* ==== PRODUCER — variation picker + edit-by-chat (scene card) =========================== */
.ts-cands { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin:8px 0 2px; }
.ts-cands-k { font:600 .68rem var(--sn-mono); letter-spacing:.06em; text-transform:uppercase; color:var(--sn-ink-3); margin-right:2px; }
.ts-cand { position:relative; width:52px; height:34px; border-radius:var(--sn-r-input); overflow:hidden; border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep); cursor:pointer; padding:0; display:flex; align-items:center; justify-content:center; color:var(--sn-ink-3); transition:border-color var(--sn-t-hover), transform var(--sn-t-hover); }
.ts-cand:hover { border-color:var(--sn-accent); transform:translateY(-1px); }
.ts-cand img { width:100%; height:100%; object-fit:cover; }
.ts-cand.is-pending, .ts-cand.is-err { cursor:default; }
.ts-cand.is-err { color:var(--sn-error); }
.ts-cand.is-err .sn-ic-slot { width:14px; height:14px; }
.ts-cand .ts-spin { width:14px; height:14px; }
.ts-cand-add { width:34px; height:34px; border-radius:var(--sn-r-input); border:1px dashed var(--sn-hairline-strong); background:none; color:var(--sn-ink-2); cursor:pointer; display:flex; align-items:center; justify-content:center; }
.ts-cand-add:hover { border-color:var(--sn-accent); color:var(--sn-accent); }
.ts-cand-add .sn-ic-slot { width:14px; height:14px; }
/* TAKES — every clip already drawn for this beat; the one in the cut carries the accent ring. Same
   dimensions as the candidate chips so the two strips read as one family. */
.ts-takes { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin:6px 0 2px; }
.ts-take { position:relative; width:52px; height:34px; border-radius:var(--sn-r-input); overflow:hidden; padding:0;
  border:1px solid var(--sn-hairline-strong); background:var(--sn-bg-deep); cursor:pointer;
  transition:border-color var(--sn-t-hover), transform var(--sn-t-hover); }
.ts-take:hover { border-color:var(--sn-accent); transform:translateY(-1px); }
.ts-take:focus-visible { outline:none; box-shadow:var(--sn-ring); }
.ts-take img { width:100%; height:100%; object-fit:cover; display:block; }
.ts-take .ts-take-n { position:absolute; left:3px; top:3px; padding:0 4px; border-radius:4px;
  font:600 .58rem var(--sn-mono); color:var(--sn-ink); background:color-mix(in srgb, var(--sn-bg-deep) 74%, transparent); }
.ts-take.is-on { border-color:var(--sn-accent); box-shadow:0 0 0 2px var(--sn-accent-glow); cursor:default; }
.ts-take.is-on:hover { transform:none; }
.ts-take.is-on .ts-take-n { color:var(--sn-accent); }
/* ==== PRODUCER — scene structure (add / reorder / remove) ============================== */
/* The structural verbs deliberately do NOT join the .ts-acts glass pill on the frame: that pill is the
   generative row (every button in it spends), and a 228px card cannot hold both. These live at the foot of
   the card body, always visible, in the quiet hairline voice — reorder on the left, remove on the right so
   the destructive one is never adjacent to the one you press repeatedly. */
.ts-ord { display:flex; align-items:center; gap:4px; margin:8px 0 0; padding-top:8px;
  border-top:1px solid var(--sn-hairline); }
.ts-ord .grow { flex:1; }
.ts-ord-b { width:26px; height:26px; display:grid; place-items:center; padding:0;
  border:1px solid var(--sn-hairline); border-radius:var(--sn-r-input); background:var(--sn-bg-raised);
  color:var(--sn-ink-2); cursor:pointer;
  transition:border-color var(--sn-t-hover) var(--sn-ease), background var(--sn-t-hover) var(--sn-ease),
    color var(--sn-t-hover) var(--sn-ease); }
.ts-ord-b .sn-ic-slot, .ts-ord-b .sn-ic { width:14px; height:14px; }
.ts-ord-b:hover:not(:disabled) { border-color:var(--sn-hairline-strong); background:var(--sn-surface);
  color:var(--sn-ink); }
.ts-ord-b:focus-visible { outline:none; box-shadow:var(--sn-ring-neutral); }
.ts-ord-b:disabled { opacity:.34; cursor:default; }
/* Remove reads as danger only on approach — at rest it must not shout at a board you are still directing. */
.ts-ord-b.is-del:hover { border-color:var(--sn-error, #c23b3b); color:var(--sn-error, #c23b3b);
  background:var(--sn-danger-bg, rgba(220,70,70,.10)); }

/* ADD A SCENE — the last tile in the list, sized to the cards it sits beside so the grid stays a grid. */
.tl-add-scene { list-style:none; margin:0; display:flex; min-height:132px; }
.tl-add-scene > button { flex:1; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:8px; padding:16px 12px; cursor:pointer;
  border:1px dashed var(--sn-hairline-strong); border-radius:var(--sn-r-card); background:none;
  color:var(--sn-ink-2); text-align:center;
  transition:border-color var(--sn-t-hover) var(--sn-ease), color var(--sn-t-hover) var(--sn-ease),
    background var(--sn-t-hover) var(--sn-ease); }
.tl-add-scene > button:hover { border-color:var(--sn-accent); color:var(--sn-accent);
  background:var(--sn-accent-soft); }
.tl-add-scene > button:focus-visible { outline:none; box-shadow:var(--sn-ring); }
.tl-add-scene > button:disabled { opacity:.5; cursor:default; }
.tl-add-ic { display:grid; place-items:center; width:32px; height:32px; border-radius:50%;
  border:1px solid currentColor; }
.tl-add-ic .sn-ic-slot, .tl-add-ic .sn-ic { width:16px; height:16px; }
.tl-add-tx { display:flex; flex-direction:column; gap:2px; }
.tl-add-tx b { font:600 .8125rem/1.3 var(--sn-sans); }
.tl-add-tx span { font:500 .72rem/1.35 var(--sn-sans); color:var(--sn-ink-3); }
.tl-add-scene > button:hover .tl-add-tx span { color:inherit; }

.ts-chat { display:flex; gap:8px; align-items:center; margin:8px 0 4px; }
.ts-chat-in { flex:1; min-width:0; padding:7px 10px; border:1px solid var(--sn-hairline); border-radius:var(--sn-r-input); background:var(--sn-bg-raised); color:var(--sn-ink); font:500 .82rem var(--sn-sans); outline:none; }
.ts-chat-in:focus { border-color:var(--sn-accent); box-shadow:0 0 0 3px var(--sn-accent-soft); }
.ts-chat-in::placeholder { color:var(--sn-ink-3); }
.ts-chat .sn-btn { flex:none; }

/* ==== PRODUCER — sound reflected on the board ========================================== */
.tl-snd.is-on { border-color:var(--sn-accent); color:var(--sn-accent-ink); background:var(--sn-accent-soft); }
.tl-snd .sn-ic-slot { width:14px; height:14px; }
.ts-audio { display:flex; align-items:center; gap:6px; margin:6px 0 2px; font:500 .78rem/1.4 var(--sn-sans); color:var(--sn-ink-2); }
.ts-audio .sn-ic-slot { width:13px; height:13px; flex:none; color:var(--sn-accent); }
.ts-audio i { font-style:italic; color:var(--sn-ink-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ts-audio span { color:var(--sn-ink-3); }

/* ── Direct social publishing (Fable gap D2) — the Publish modal ─────────────────────────────────────
   Reuses the .sn-scrim / .sn-modal / .sn-modal-body / .sn-modal-foot shell (senova-ds.css); only the
   publish-specific elements are styled here. All colors are tokens, so light + dark themes both work. */
.sn-pub { width:min(460px, 100vw); }
.pub-lead { font:.9375rem/1.5 var(--sn-sans); color:var(--sn-ink-2); margin:0 0 14px; }
.pub-lead b { color:var(--sn-ink); font-weight:600; }
.pub-steps { margin:0 0 16px; padding-left:20px; display:flex; flex-direction:column; gap:8px; }
.pub-steps li { font:.8125rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.pub-note { font:.75rem/1.45 var(--sn-sans); color:var(--sn-ink-3); margin:12px 0 0; }
.pub-ask { font:.875rem/1.55 var(--sn-sans); color:var(--sn-ink-2); margin:4px 0 0;
  padding:12px 14px; background:var(--sn-surface); border:1px solid var(--sn-hairline); border-radius:var(--sn-r-card); }
.pub-field { display:flex; flex-direction:column; gap:6px; margin:0 0 12px; }
.pub-field > span { font:600 .75rem/1 var(--sn-sans); letter-spacing:.02em; text-transform:uppercase; color:var(--sn-ink-3); }
.pub-field input, .pub-field textarea { width:100%; padding:10px 12px; border:1px solid var(--sn-hairline-strong);
  border-radius:var(--sn-r-input); background:var(--sn-surface); color:var(--sn-ink); font:.875rem/1.4 var(--sn-sans); resize:vertical; }
.pub-field input:focus, .pub-field textarea:focus { outline:none; border-color:var(--sn-accent); }
.pub-err { font:.8125rem/1.4 var(--sn-sans); color:var(--sn-danger, #c0392b); margin:0 0 10px; }
.pub-tgts { display:flex; flex-direction:column; gap:8px; margin:0 0 14px; }
.pub-tgt { display:flex; align-items:center; gap:10px; padding:11px 14px; border:1px solid var(--sn-hairline-strong);
  border-radius:var(--sn-r-card); background:var(--sn-surface); cursor:pointer; }
.pub-tgt input { accent-color:var(--sn-accent); width:16px; height:16px; flex:none; }
.pub-tgt .sn-ic-slot { width:16px; height:16px; flex:none; color:var(--sn-ink-2); }
.pub-tgt-l { font:.875rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.pub-tgt.is-off { opacity:.55; cursor:not-allowed; }
.pub-results { display:flex; flex-direction:column; gap:8px; margin-top:14px;
  padding-top:14px; border-top:1px solid var(--sn-hairline); }
.pub-res { display:flex; align-items:center; justify-content:space-between; gap:12px;
  font:.8125rem/1.4 var(--sn-sans); color:var(--sn-ink-2); }
.pub-res-t { color:var(--sn-ink); font-weight:600; }
.pub-res-s { display:inline-flex; align-items:center; gap:7px; }
.pub-res-s a { color:var(--sn-accent); text-decoration:none; }
.pub-res-s a:hover { text-decoration:underline; }
.pub-res.is-fail .pub-res-err { color:var(--sn-danger, #c0392b); }
.pub-res .spin-sm { width:13px; height:13px; border:2px solid var(--sn-hairline-strong);
  border-top-color:var(--sn-accent); border-radius:50%; animation:spin .7s linear infinite; display:inline-block; }
/* ---- Brand Kit panel (Fable gap #5) ---- */
/* "Read my brand" — an address field and one button, on a row that collapses on narrow screens. */
.bk-extract { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.bk-extract .sn-input { flex:1 1 240px; min-width:0; }
.bk-logo-row { display:flex; align-items:center; gap:18px; }
.bk-logo-preview { width:120px; height:88px; border-radius:var(--sn-r-input); border:1px solid var(--sn-hairline-strong); background:var(--sn-surface-2, rgba(255,255,255,.03)); display:flex; align-items:center; justify-content:center; overflow:hidden; flex:0 0 auto; }
.bk-logo-preview img { max-width:100%; max-height:100%; object-fit:contain; }
.bk-logo-ph { color:var(--sn-ink-3, var(--sn-ink-2)); }
.bk-logo-ph .sn-ic-slot { width:26px; height:26px; }
.bk-logo-actions { display:flex; flex-direction:column; gap:8px; align-items:flex-start; }
.bk-palette { display:flex; flex-wrap:wrap; gap:12px; }
.bk-swatch { display:flex; align-items:center; gap:8px; }
.bk-swatch input[type=color] { width:38px; height:38px; padding:0; border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-input); background:none; cursor:pointer; }
.bk-swatch input[type=color]::-webkit-color-swatch-wrapper { padding:2px; }
.bk-swatch input[type=color]::-webkit-color-swatch { border:none; border-radius:var(--sn-r-xs); }
.bk-hex { width:96px; text-transform:lowercase; font-family:var(--sn-mono, "Roboto Mono", monospace); font-size:13px; }
/* ---- On-screen ad captions (Fable gap: on-screen captions) ---- */
.cap-lead { font:.875rem/1.5 var(--sn-sans); color:var(--sn-ink-2); margin:0 0 14px; }
.cap-styles { display:flex; flex-direction:column; gap:8px; margin:0 0 14px; }
.cap-style { display:flex; flex-direction:column; gap:2px; padding:11px 14px; border:1px solid var(--sn-hairline-strong);
  border-radius:var(--sn-r-input); cursor:pointer; transition:border-color .15s, background .15s; position:relative; }
.cap-style input { position:absolute; opacity:0; pointer-events:none; }
.cap-style.is-on { border-color:var(--sn-accent); background:var(--sn-accent-wash, color-mix(in srgb, var(--sn-accent) 8%, transparent)); }
.cap-style-n { font:600 .875rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.cap-style-b { font:.75rem/1.4 var(--sn-sans); color:var(--sn-ink-3, var(--sn-ink-2)); }
.cap-preview { border-radius:var(--sn-r-card); overflow:hidden; background:#000; margin-top:6px; }
.cap-preview video { width:100%; max-height:52vh; display:block; object-fit:contain; }
.cap-done .cap-lead { margin-bottom:10px; }

/* ---------------- Character Library (Fable reusable-character-library gap) ---------------- */
.cl-refs { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
.cl-ref { position:relative; width:72px; height:72px; border-radius:var(--sn-r-input); overflow:hidden; border:1px solid var(--sn-hairline-strong); background:var(--sn-surface-2, rgba(255,255,255,.03)); flex:0 0 auto; }
.cl-ref img { width:100%; height:100%; object-fit:cover; }
.cl-ref-x { position:absolute; top:2px; right:2px; width:20px; height:20px; line-height:18px; text-align:center; border-radius:50%; border:none; background:rgba(0,0,0,.6); color:#fff; cursor:pointer; font-size:15px; padding:0; }
.cl-ref-add { width:72px; height:72px; border-radius:var(--sn-r-input); border:1px dashed var(--sn-hairline-strong); background:none; color:var(--sn-ink-3, var(--sn-ink-2)); cursor:pointer; display:flex; align-items:center; justify-content:center; flex:0 0 auto; }
.cl-ref-add:hover { border-color:var(--sn-accent, #d97757); color:var(--sn-accent, #d97757); }
.cl-ref-add .sn-ic-slot { width:20px; height:20px; }
.cl-list { display:flex; flex-direction:column; gap:12px; }
.cl-card { display:flex; gap:16px; align-items:center; padding:16px 18px; }
.cl-thumb { width:64px; height:64px; border-radius:var(--sn-r-input); overflow:hidden; border:1px solid var(--sn-hairline-strong); background:var(--sn-surface-2, rgba(255,255,255,.03)); display:flex; align-items:center; justify-content:center; flex:0 0 auto; color:var(--sn-ink-3, var(--sn-ink-2)); }
.cl-thumb img { width:100%; height:100%; object-fit:cover; }
.cl-thumb .sn-ic-slot { width:26px; height:26px; }
.cl-body { flex:1 1 auto; min-width:0; }
.cl-body h3 { margin:0 0 2px; }
.cl-body .desc { margin:0; overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.cl-actions { display:flex; gap:8px; align-items:center; flex:0 0 auto; }

/* -------- Spinnable characters — the 360° turntable (migration 0115) --------
   Rob: "when you click the photo it lets you spin it around and view it." The card's photo becomes the
   affordance once a ring exists (grab cursor + a 360° badge), and the viewer stacks every already-paid-for
   angle in one square stage, cross-fading the two the drag is between. Pure opacity over preloaded images:
   the spin costs nothing to render and nothing to re-open. */
.cl-thumb.is-spinnable { cursor:pointer; position:relative; border-color:var(--sn-accent, #d97757); }
.cl-thumb.is-spinnable:focus-visible { outline:2px solid var(--sn-accent, #d97757); outline-offset:2px; }
/* BEFORE a ring exists the photo still invites the click — it opens the priced offer rather than doing
   nothing. Dashed, not solid: this is an invitation to generate, not an already-paid-for spin. */
.cl-thumb.is-offer { cursor:pointer; position:relative; border-style:dashed; }
.cl-thumb.is-offer:hover { border-color:var(--sn-accent, #d97757); }
.cl-thumb.is-offer:focus-visible { outline:2px solid var(--sn-accent, #d97757); outline-offset:2px; }
.cl-spin-badge { position:absolute; bottom:3px; right:3px; padding:1px 5px; border-radius:var(--sn-r-xs); background:rgba(0,0,0,.66);
  color:#fff; font:600 .5625rem/1.5 var(--sn-mono, ui-monospace, monospace); letter-spacing:.04em; pointer-events:none; }
.cl-spin-badge.is-offer { background:rgba(0,0,0,.5); opacity:.82; }
.cl-tt { display:flex; gap:9px; align-items:center; flex-wrap:wrap; margin-top:9px; }
.cl-tt.is-paused .desc, .cl-tt.is-stalled .desc { color:var(--sn-warn, #b7791f); }

/* The build sentinel's banner — shown only when a reload would NOT be safe to take unilaterally (a modal is
   open, a paid ring is building, an unsent prompt is in the composer). Sits above the shell, out of the way. */
.sl-update { position:fixed; left:50%; bottom:18px; transform:translateX(-50%); z-index:9999;
  display:flex; gap:10px; align-items:center; padding:9px 12px; border-radius:var(--sn-r-card);
  background:var(--sn-surface-1, #17110f); border:1px solid var(--sn-hairline-strong);
  box-shadow:0 8px 28px rgba(0,0,0,.32); font:.8125rem/1.4 var(--sn-sans); color:var(--sn-ink-1, #f5eedc); max-width:min(92vw,520px); }
@media (prefers-reduced-motion: no-preference) { .sl-update { animation:sl-update-in .22s ease-out; } }
@keyframes sl-update-in { from { opacity:0; transform:translate(-50%, 8px); } to { opacity:1; transform:translate(-50%, 0); } }
.cl-tt .desc { margin:0; font-size:.75rem; }
/* An identity finding — a real observation about a delivered ring, so it warns without shouting. It takes
   the whole row because it is a sentence, not a chip. */
.cl-tt-warn { flex-basis:100%; font:.75rem/1.45 var(--sn-sans); color:var(--sn-warn); cursor:help; }
.cl-tt-spin { width:13px; height:13px; flex:0 0 auto; border-radius:50%; border:2px solid var(--sn-hairline-strong);
  border-top-color:var(--sn-accent, #d97757); animation:cl-tt-rot .7s linear infinite; }
@keyframes cl-tt-rot { to { transform:rotate(360deg); } }

.tt-modal { max-width:620px; width:100%; }
/* THE TURNTABLE VIEWER — a front image and one button (0118). Everything that supported dragging through a
   ring of stills (grab cursors, the drag hint, position dots, the angle read-out, the decode-gate overlay)
   is DELETED rather than hidden: there is no drag any more, and dead rules are how a deleted control quietly
   comes back. */
.tt-stage { position:relative; width:100%; aspect-ratio:3/4; max-height:64vh; margin:0 auto; border-radius:var(--sn-r-card); overflow:hidden;
  background:var(--sn-surface-2, rgba(255,255,255,.04)); border:1px solid var(--sn-hairline-strong);
  user-select:none; -webkit-user-select:none; }
/* The front still is the RESTING state and is always mounted. The clip sits on top of it and is only
   visible while it plays, so stopping — or a clip that refuses to play at all — reveals the front frame
   instantly instead of leaving a black rectangle. The clip also ENDS on this exact frame, so the hand-back
   at the end of a spin has nothing to reconcile. */
.tt-front, .tt-orbit { position:absolute; inset:0; width:100%; height:100%; object-fit:contain; pointer-events:none; -webkit-user-drag:none; }
.tt-orbit { opacity:0; transition:opacity .15s ease; z-index:2; background:transparent; }
.tt-stage.is-playing .tt-orbit { opacity:1; }
.tt-bar { display:flex; align-items:center; gap:12px; margin-top:11px; }
.tt-play { white-space:nowrap; }
.tt-free { font:.6875rem/1.4 var(--sn-sans); color:var(--sn-ink-3, var(--sn-ink-2)); margin-left:auto; white-space:nowrap; }
@media (prefers-reduced-motion: reduce) { .cl-tt-spin { animation:none; } .tt-orbit { transition:none; } }

/* ---------------------------------------------------------------------------
   §46 PRODUCER V1 — THE CONVERSATION. The chat is not a panel bolted onto the
   board; it is the surface the board is a view of. So it docks LEFT of the work
   at every width that can hold two columns, and stacks above it when it cannot.

   Markup contract:
     .tl-board > .tl-work[.has-thread] > aside.tl-thread + .tl-work-main
     .tl-thread > .tl-thread-head + .tl-thread-body(.tl-msg xN) + .tl-thread-foot
     .tl-msg.is-user      > .tl-bubble
     .tl-msg.is-assistant > .tl-say | .tl-diff
     .tl-diff > .tl-diff-head + (ul.tl-diff-list | p.tl-diff-none)
              + .tl-diff-money + p.tl-diff-law + .tl-diff-acts
   -------------------------------------------------------------------------- */
.tl-work { display:block; }
.tl-work.has-thread { display:grid; grid-template-columns:minmax(300px,352px) minmax(0,1fr); gap:22px; align-items:start; }
.tl-work-main { min-width:0; }
@media (max-width:1180px) {
  .tl-work.has-thread { grid-template-columns:minmax(0,1fr); }
  /* Stacked, the transcript must not eat the whole page before you reach the film. */
  .tl-thread { max-height:44vh; position:static; }
}

.tl-thread { position:sticky; top:24px; display:flex; flex-direction:column; min-width:0;
  max-height:calc(100vh - 190px); border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-card);
  background:var(--sn-surface-2, rgba(255,255,255,.03)); overflow:hidden; }
.tl-thread-head { display:flex; align-items:center; gap:8px; padding:11px 14px;
  border-bottom:1px solid var(--sn-hairline); }
.tl-thread-head b { font:650 .8125rem/1 var(--sn-sans); color:var(--sn-ink); }
/* The sentence no competitor in this category can print. It belongs on the control, not in a footnote. */
.tl-thread-free { margin-left:auto; font:.625rem/1 var(--sn-mono); letter-spacing:.04em; text-transform:uppercase;
  color:var(--sn-ok-fg, #2f9e6b); background:var(--sn-ok-bg, rgba(47,158,107,.12));
  padding:4px 7px; border-radius:999px; }
.tl-thread-body { flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  padding:14px; display:flex; flex-direction:column; gap:12px; scrollbar-gutter:stable; }
.tl-thread-body::-webkit-scrollbar { width:10px; }
.tl-thread-body::-webkit-scrollbar-thumb { background:var(--sn-line, rgba(140,140,150,.35)); border-radius:var(--sn-r-input);
  border:3px solid transparent; background-clip:content-box; }
.tl-thread-foot { margin:0; padding:10px 14px; border-top:1px solid var(--sn-hairline);
  font:.6875rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }

.tl-msg { display:flex; }
.tl-msg.is-user { justify-content:flex-end; }
.tl-bubble { max-width:88%; padding:9px 12px; border-radius:var(--sn-r-card) 13px 3px 13px;
  background:var(--sn-accent-soft, rgba(120,130,255,.14)); color:var(--sn-ink);
  font:500 .8125rem/1.5 var(--sn-sans); white-space:pre-wrap; word-break:break-word; }
.tl-say { max-width:96%; font:.8125rem/1.55 var(--sn-sans); color:var(--sn-ink-2); }
.tl-say.is-thinking { color:var(--sn-ink-3); font-style:italic; }

/* THE DIFF CARD — what changed, what that re-renders, what it costs. Three facts, one card, and the
   money line is the loudest thing in it because it is the only part that can cost anything. */
.tl-diff { width:100%; border:1px solid var(--sn-hairline-strong); border-radius:var(--sn-r-card);
  background:var(--sn-surface, rgba(255,255,255,.05)); overflow:hidden; }
.tl-diff-head { display:flex; align-items:center; gap:7px; padding:9px 12px;
  border-bottom:1px solid var(--sn-hairline); font:650 .75rem/1 var(--sn-sans); color:var(--sn-ink); }
.tl-diff-head .sn-ic-slot { width:14px; height:14px; color:var(--sn-ok-fg, #2f9e6b); }
.tl-diff-list { list-style:none; margin:0; padding:10px 12px; display:flex; flex-direction:column; gap:8px; }
.tl-diff-list li { display:flex; flex-direction:column; gap:2px; font:.75rem/1.45 var(--sn-sans); }
.tl-diff-list li b { font:600 .6875rem/1 var(--sn-mono); letter-spacing:.03em; text-transform:uppercase; color:var(--sn-ink-3); }
.tl-diff-list li s { color:var(--sn-ink-3); text-decoration-color:var(--sn-ink-3); opacity:.75; }
.tl-diff-list li span { color:var(--sn-ink); }
.tl-diff-list li.is-bad span { color:var(--sn-danger-fg, #c23b3b); }
.tl-diff-none { margin:0; padding:10px 12px; font:.8125rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.tl-diff-money { display:flex; align-items:center; gap:10px; padding:9px 12px;
  border-top:1px solid var(--sn-hairline); background:var(--sn-surface-2, rgba(255,255,255,.03)); }
.tl-diff-scenes { display:flex; align-items:center; gap:6px; font:.75rem/1 var(--sn-sans); color:var(--sn-ink-2); }
.tl-diff-scenes .sn-ic-slot { width:13px; height:13px; }
.tl-diff-cr { margin-left:auto; font:650 .8125rem/1 var(--sn-mono); font-variant-numeric:tabular-nums; color:var(--sn-ink); }
.tl-diff-law { margin:0; padding:7px 12px 0; font:.6875rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }
.tl-diff-acts { display:flex; flex-wrap:wrap; align-items:center; gap:7px; padding:10px 12px 12px; }
.tl-diff-kept { font:.6875rem/1.4 var(--sn-sans); color:var(--sn-ok-fg, #2f9e6b); }

/* THE ONE QUESTION (spec 2.1) — chips, never a text box, and an explicit way out of answering. */
.tl-ask { margin:0 0 18px; padding:14px 16px; border-radius:var(--sn-r-card);
  border:1px solid var(--sn-accent-line, rgba(120,130,255,.35)); background:var(--sn-accent-soft, rgba(120,130,255,.08)); }
.tl-ask-head { display:flex; align-items:center; gap:8px; font:600 .9375rem/1.35 var(--sn-sans); color:var(--sn-ink); }
.tl-ask-head .sn-ic-slot { width:16px; height:16px; flex:none; }
.tl-ask-chips { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
.tl-ask-chip { display:flex; flex-direction:column; gap:1px; text-align:left; padding:9px 13px; border-radius:var(--sn-r-input);
  border:1px solid var(--sn-hairline-strong); background:var(--sn-surface, rgba(255,255,255,.06)); cursor:pointer; }
.tl-ask-chip:hover { border-color:var(--sn-accent, #7a84ff); }
.tl-ask-chip b { font:600 .8125rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.tl-ask-chip span { font:.6875rem/1.3 var(--sn-sans); color:var(--sn-ink-3); }
.tl-ask-foot { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-top:12px; }
.tl-ask-foot span { font:.6875rem/1.45 var(--sn-sans); color:var(--sn-ink-3); flex:1; min-width:200px; }

/* THE PRICE BEFORE THE PLAN. A rail in the composer, above the brief, so the cost of the thing you are
   about to ask for is on screen while you are asking for it — not discovered by depletion afterwards. */
.tl-est { display:flex; gap:9px; align-items:flex-start; margin:0 0 8px; padding:9px 12px; border-radius:var(--sn-r-input);
  background:var(--sn-surface-2, rgba(255,255,255,.04)); border:1px solid var(--sn-hairline); }
.tl-est > .sn-ic-slot { width:14px; height:14px; flex:none; margin-top:2px; color:var(--sn-ink-3); }
.tl-est-tx { display:flex; flex-direction:column; gap:2px; min-width:0; }
.tl-est-t { font:.75rem/1.45 var(--sn-sans); color:var(--sn-ink-2); }
.tl-est-t b { color:var(--sn-ink); font-weight:650; font-variant-numeric:tabular-nums; }
.tl-est-s { font:.6875rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }

/* THE SCRIPT — the plan as a director reads it. A scene list with directions, not a wall of cards. */
.tl-script { max-width:760px; }
.tl-script-head h3 { margin:0; font:650 1.0625rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.tl-script-head p { margin:3px 0 0; font:.75rem/1.45 var(--sn-mono); color:var(--sn-ink-3); }
.tl-script-list { list-style:none; margin:18px 0 0; padding:0; display:flex; flex-direction:column; }
.tl-script-scene { display:grid; grid-template-columns:38px minmax(0,1fr); gap:12px; padding:13px 0;
  border-top:1px solid var(--sn-hairline); }
.tl-script-no { font:650 .8125rem/1.4 var(--sn-mono); color:var(--sn-ink-3); font-variant-numeric:tabular-nums; }
.tl-script-dir { margin:0; font:.875rem/1.55 var(--sn-sans); color:var(--sn-ink); }
.tl-script-chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:7px; }
.tl-script-chips span { font:.625rem/1 var(--sn-mono); letter-spacing:.03em; text-transform:uppercase;
  color:var(--sn-ink-3); border:1px solid var(--sn-hairline); border-radius:999px; padding:4px 8px; }
.tl-script-chips span.is-done { color:var(--sn-ok-fg, #2f9e6b); border-color:var(--sn-ok-fg, #2f9e6b); }
.tl-script-vo { margin:8px 0 0; padding-left:11px; border-left:2px solid var(--sn-hairline-strong);
  font:italic .8125rem/1.5 var(--sn-sans); color:var(--sn-ink-2); }
.tl-script-foot { margin:20px 0 0; padding-top:14px; border-top:1px solid var(--sn-hairline);
  font:.75rem/1.5 var(--sn-sans); color:var(--sn-ink-3); }
/* THE FILM AS PICTURES, BESIDE ITS OWN SENTENCES. The script was a text document and the only control
   that draws stills lived behind an approval step, so a customer could talk for ten turns and never see
   an image. A scene with a picture gets a third track; a scene without one keeps the two-track layout
   exactly as it was, so a half-drawn board does not lurch line by line. */
.tl-script-scene.has-still { grid-template-columns:38px 132px minmax(0,1fr); }
/* ⚠️ DO NOT PUT `content-visibility:auto` BACK ON THIS ROW. It was added here on the reasoning that a
   keyframe is a full-resolution still (measured: 2752px wide) going into a 130px box, so a long script is
   a column of very large decodes — and immediately after it deployed, the studio page stopped answering
   CDP entirely (Runtime.evaluate timed out at 45s twice; the same tab answered instantly on /healthz, so
   the renderer was fine and the PAGE was not). content-visibility:auto on an item whose size feeds the
   scroll container that decides its own visibility is a known relayout-loop shape. The saving was never
   measured; the freeze was. If a long script ever does need this, measure it first. */
.tl-script-still { border-radius:var(--sn-r-input); overflow:hidden; background:var(--sn-surface-2, rgba(0,0,0,.06));
  aspect-ratio:16/9; display:grid; place-items:center; border:1px solid var(--sn-hairline); }
.tl-script-still img { width:100%; height:100%; object-fit:cover; display:block; }
.tl-script-still.is-drawing span { font:.625rem/1 var(--sn-mono); letter-spacing:.03em;
  text-transform:uppercase; color:var(--sn-ink-3); }
@media (max-width:720px) {
  .tl-script-scene.has-still { grid-template-columns:38px minmax(0,1fr); }
  .tl-script-still { grid-column:2; max-width:220px; }
}

/* The direction composer is one line doing one job, so it must not look like the brief control deck. */
.composer.is-direct #tl-direct { min-height:44px; max-height:140px; }

/* ---------------------------------------------------------------------------
   §47 THE SURGICAL EDIT — "change one thing", made the obvious thing to reach
   for. Every other editing control on a shot card re-generates something; this
   one changes only what you name. So it leads the action row and it is the only
   one drawn in the accent colour.

   Markup contract:
     .ts-acts > button[data-sact=region].is-primary  (first child)
     .tl-diff.tl-rg > .tl-diff-head + p.tl-rg-ask + ul.tl-rg-steps
                    + .tl-diff-money + p.tl-rg-cmp + .tl-diff-acts
   -------------------------------------------------------------------------- */
.ts-acts button.is-primary { color:var(--sn-accent-fg, #fff); background:var(--sn-accent, #7a84ff); }
.ts-acts button.is-primary:hover { filter:brightness(1.08); }

.tl-rg .tl-diff-head .sn-ic-slot { color:var(--sn-accent, #7a84ff); }
/* What they actually asked for, quoted back. A surgical edit is only as good as our reading of the
   sentence, so the sentence is on the card where a misreading is obvious before it costs anything. */
.tl-rg-ask { margin:10px 12px 0; font:italic .8125rem/1.5 var(--sn-sans); color:var(--sn-ink); }
.tl-rg-steps { list-style:none; margin:9px 0 0; padding:0 12px; display:flex; flex-direction:column; gap:7px; }
.tl-rg-steps li { display:flex; flex-direction:column; gap:1px; }
.tl-rg-steps li b { font:600 .75rem/1.3 var(--sn-sans); color:var(--sn-ink); }
.tl-rg-steps li span { font:.6875rem/1.4 var(--sn-sans); color:var(--sn-ink-3); }
/* The comparison. `.is-good` only when the surgical route genuinely is cheaper — on an already-filmed
   shot it is not, and that line is deliberately styled as plain text rather than a saving. */
.tl-rg-cmp { margin:0; padding:8px 12px 0; font:.6875rem/1.45 var(--sn-sans); color:var(--sn-ink-3); }
.tl-rg-cmp.is-good { color:var(--sn-ok-fg, #2f9e6b); }
