/* Megalith Media — approval site.
 *
 * Built for someone who has never used an admin panel. That means: large type,
 * large tap targets, one obvious action per card, and colour that carries meaning
 * (green = good to go, amber = needs a look, red = no) rather than decoration.
 *
 * Palette matches the public site so the two feel like one brand.
 */

:root {
  --cream: #FBF7F0;
  --cream-2: #F3EDE2;
  --paper: #FFFFFF;
  --ink: #1E2A22;
  --ink-2: #46564B;
  --ink-3: #6E7F73;
  --line: #E2DACB;
  --line-2: #CFC4B0;

  --green: #1F6B45;
  --green-2: #2C8A5B;
  --green-soft: #E4F1E9;
  --gold-ink: #8A5A12;
  --gold-soft: #FBEEDA;
  --coral: #B23A22;
  --coral-soft: #FBE6E0;
  --amber: #8A5A12;

  --ok: #1F6B45;
  --ok-bg: #E4F1E9;
  --warn: #8A5A12;
  --warn-bg: #FBEEDA;
  --no: #B23A22;
  --no-bg: #FBE6E0;
  --wait: #46564B;
  --wait-bg: #EFEAE0;

  --r: 14px;
  --r-sm: 9px;
  --shadow: 0 1px 2px rgba(30, 42, 34, .06), 0 6px 20px rgba(30, 42, 34, .06);
  --shadow-lg: 0 10px 40px rgba(30, 42, 34, .16);
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

/* A class that sets `display` beats the browser's own `[hidden] { display: none }`,
 * which would leave the invisible modal backdrop covering the page and eating every
 * click. This rule makes `hidden` mean hidden, always. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0 0 .35em; line-height: 1.2; font-weight: 700; letter-spacing: -.01em; }
h2 { font-size: 1.6rem; }
h3 { font-size: 1.15rem; }
h4 { font-size: 1rem; }
p { margin: 0 0 .7em; }
a { color: var(--green); }
code { font-family: var(--mono); font-size: .86em; background: var(--cream-2); padding: .1em .35em; border-radius: 4px; }
img { max-width: 100%; display: block; }

/* A LONG UNBREAKABLE STRING MUST NOT WIDEN THE PAGE.
 *
 * `.muted` carries file paths, hashes and model names — `assets/film/sc-01/v01/shots/...` is one
 * word as far as the line breaker is concerned. A single such paragraph in the Admin view was
 * 611px wide inside a 328px column and pushed the whole page 252px sideways on a phone.
 * `anywhere` breaks only when there is no other option, so ordinary prose is unaffected. */
.muted { color: var(--ink-3); font-size: .88em; overflow-wrap: anywhere; }
.mono { font-family: var(--mono); font-size: .84em; }
.nowrap { white-space: nowrap; }
.big { font-size: 1.5rem; font-weight: 700; margin: .1em 0; }

/* ── Login ─────────────────────────────────────────────────────────────── */

.login {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1100px 500px at 50% -10%, #E7F2EA 0%, transparent 60%),
    var(--cream);
}

.login__card {
  width: 100%;
  max-width: 420px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 34px 30px 30px;
  box-shadow: var(--shadow-lg);
}

.login__mark {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--green);
  margin-bottom: 14px;
}

.login__title { font-size: 1.9rem; margin-bottom: .15em; }
.login__sub { color: var(--ink-3); font-size: .95rem; margin-bottom: 22px; }
.login__err {
  margin: 14px 0 0;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: var(--no-bg);
  color: var(--no);
  font-size: .9rem;
  font-weight: 600;
}

.field { display: block; margin-bottom: 14px; }
.field > span {
  display: block;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px;
}
.field input, .field select {
  width: 100%;
  padding: 12px 13px;
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: var(--cream);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-sm);
}
.field input:focus, .field select:focus {
  outline: none;
  border-color: var(--green-2);
  box-shadow: 0 0 0 3px rgba(44, 138, 91, .18);
  background: var(--paper);
}
.field--inline { display: inline-block; margin: 0 10px 10px 0; }
.field--inline > span { font-size: .72rem; }

/* ── Buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 11px 16px;
  font: inherit;
  font-size: .94rem;
  font-weight: 650;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: transform .06s ease, background .12s ease, border-color .12s ease;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover { background: var(--cream-2); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: default; transform: none; }

.btn--primary { background: var(--green); border-color: var(--green); color: #fff; }
.btn--primary:hover { background: var(--green-2); border-color: var(--green-2); }
.btn--ok { background: var(--ok-bg); border-color: #B9DCC8; color: var(--ok); }
.btn--ok:hover { background: #D6EBDD; }
.btn--warn { background: var(--warn-bg); border-color: #EBD3A6; color: var(--warn); }
.btn--warn:hover { background: #F7E4C4; }
.btn--no { background: var(--no-bg); border-color: #F0C6BA; color: var(--no); }
.btn--no:hover { background: #F7D8CF; }
.btn--ghost { background: transparent; border-color: var(--line-2); color: var(--ink-2); }
.btn--ghost:hover { background: var(--cream-2); }
.btn--sm { padding: 7px 11px; font-size: .85rem; }
.btn--wide { width: 100%; padding: 13px; font-size: 1rem; }

.link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 650;
  color: var(--green);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link:hover { color: var(--green-2); }

/* ── Top bar ───────────────────────────────────────────────────────────── */

.top {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 10px 20px;
  background: rgba(251, 247, 240, .92);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.top__left { display: flex; align-items: baseline; gap: 8px; }
.top__mark { font-weight: 800; font-size: .95rem; letter-spacing: -.01em; }
.top__sep { color: var(--line-2); }
.top__page { font-weight: 650; color: var(--ink-2); }

.top__nav { display: flex; gap: 4px; flex-wrap: wrap; flex: 1; }
.navbtn {
  padding: 8px 13px;
  font: inherit;
  font-size: .9rem;
  font-weight: 600;
  color: var(--ink-2);
  background: transparent;
  border: 1.5px solid transparent;
  border-radius: 999px;
  cursor: pointer;
}
.navbtn:hover { background: var(--cream-2); }
.navbtn--on { background: var(--green); border-color: var(--green); color: #fff; }
.navbtn--admin { margin-left: auto; }

.top__right { display: flex; align-items: center; gap: 10px; }
.top__user { font-size: .86rem; color: var(--ink-3); font-weight: 600; }

/* The menu button. It exists in the DOM at every width but is only ever shown by
 * the phone block at the bottom of this file — on a desktop the nav is already
 * laid out in the bar and a second way in would just be noise. */
.navtoggle {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  font: inherit;
  font-size: .9rem;
  font-weight: 650;
  color: var(--ink-2);
  background: var(--paper);
  border: 1.5px solid var(--line-2);
  border-radius: 999px;
  cursor: pointer;
}
.navtoggle:hover { background: var(--cream-2); }
.navtoggle__bars { display: block; width: 16px; }
.navtoggle__bars i {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.navtoggle__bars i + i { margin-top: 3px; }
.navtoggle__label { white-space: nowrap; }

/* ── Layout ────────────────────────────────────────────────────────────── */

.main { max-width: 1280px; margin: 0 auto; padding: 26px 20px 80px; }

.shead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.shead h2 { margin: 0; }
.shead__sub { color: var(--ink-3); margin: .25em 0 0; max-width: 70ch; font-size: .95rem; }
.shead__right { display: flex; gap: 8px; align-items: center; }

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 20px;
  margin-bottom: 18px;
  box-shadow: var(--shadow);
}
.card--wide { grid-column: span 2; }
.card--flag { border-color: #EBD3A6; background: linear-gradient(180deg, #FFFDF8, var(--paper)); }
.card > h3:first-child { margin-top: 0; }

.cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px;
  margin-bottom: 18px;
}
.cols .card { margin-bottom: 0; }

.row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; margin: 12px 0; }

/* ── Stats ─────────────────────────────────────────────────────────────── */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 18px;
  text-align: left;
  font: inherit;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  cursor: pointer;
}
.stat:hover { border-color: var(--green-2); }
.stat--hot { border-color: #EBD3A6; background: linear-gradient(180deg, #FFFDF8, var(--paper)); }
.stat__n { font-size: 2.1rem; font-weight: 800; line-height: 1; letter-spacing: -.02em; }
.stat__l { font-size: .88rem; color: var(--ink-3); font-weight: 600; }

/* ── Tags and pills ────────────────────────────────────────────────────── */

.tag {
  display: inline-block;
  padding: 3px 9px;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .02em;
  border-radius: 999px;
  background: var(--wait-bg);
  color: var(--wait);
  /* A TAG CAN BE A SENTENCE. "angle: three-quarter — expected side" is 249px of unbreakable
   * text in a 390px column, and it pushed the Props view sideways on a phone. `anywhere` lets
   * it wrap inside the pill instead of widening the page.
   *
   * `white-space: normal`, NOT `nowrap`. The pill was `nowrap`, and nowrap beats overflow-wrap:
   * the box was correctly clamped to 132px and the TEXT still ran 240px past it. A short tag
   * never reaches `max-width`, so nothing that used to sit on one line now wraps. */
  max-width: 100%;
  overflow-wrap: anywhere;
  white-space: normal;
}
.tag--ok { background: var(--ok-bg); color: var(--ok); }
.tag--warn { background: var(--warn-bg); color: var(--warn); }
.tag--no { background: var(--no-bg); color: var(--no); }
.tag--wait { background: var(--wait-bg); color: var(--wait); }
.tag--muted { background: var(--cream-2); color: var(--ink-3); }

.pill {
  display: inline-block;
  padding: 3px 9px;
  font-size: .72rem;
  font-weight: 700;
  border-radius: 999px;
  background: var(--cream-2);
  color: var(--ink-2);
  border: 1px solid var(--line);
  white-space: nowrap;
}
.pill--model { background: #EEF3FB; border-color: #D3E0F5; color: #2A4A7A; }

.count {
  display: inline-block;
  min-width: 1.5em;
  padding: 1px 7px;
  margin-left: 6px;
  font-size: .78rem;
  font-weight: 700;
  text-align: center;
  border-radius: 999px;
  background: var(--cream-2);
  color: var(--ink-3);
  vertical-align: middle;
}

/* ── Decision bar ──────────────────────────────────────────────────────── */

.decide { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.decide__note {
  margin: 8px 0 0;
  font-size: .86rem;
  color: var(--ink-2);
  background: var(--cream-2);
  border-left: 3px solid var(--line-2);
  padding: 7px 10px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}

/* ── Todo list ─────────────────────────────────────────────────────────── */

.todo { list-style: none; margin: 0; padding: 0; }
.todo li {
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  font-size: .96rem;
}
.todo li:last-child { border-bottom: 0; padding-bottom: 0; }
.todo b { color: var(--green); }

.briefs { list-style: none; margin: 0; padding: 0; }
.briefs li { padding: 9px 0; border-bottom: 1px solid var(--line); font-size: .93rem; }
.briefs li:last-child { border-bottom: 0; }

/* ── Cadence chips ─────────────────────────────────────────────────────── */

.cadence { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
.chip {
  padding: 10px 16px;
  font: inherit;
  font-size: .93rem;
  font-weight: 650;
  color: var(--ink-2);
  background: var(--cream);
  border: 1.5px solid var(--line-2);
  border-radius: 999px;
  cursor: pointer;
}
.chip:hover { background: var(--cream-2); }
.chip--on { background: var(--green); border-color: var(--green); color: #fff; }

/* ── Ideas ─────────────────────────────────────────────────────────────── */

.ideas { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
.ideas--tight .idea { opacity: .82; }

.idea {
  display: flex;
  flex-direction: column;
  padding: 16px;
  background: var(--cream);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
}
.idea--approved { border-color: #B9DCC8; background: #F6FBF8; }
.idea--rejected { opacity: .6; }
.idea__top { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.idea h4 { margin: 0 0 .3em; font-size: 1.05rem; }
.idea__hook {
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--green);
  margin: 0 0 .5em;
  font-style: italic;
}
.idea__why { font-size: .9rem; color: var(--ink-2); margin-bottom: .5em; }
.idea__src {
  font-size: .84rem;
  color: var(--ink-3);
  background: var(--paper);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-sm);
  padding: 7px 10px;
  margin-bottom: .6em;
}
.idea__cast { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: .84rem; margin-bottom: .5em; }
.idea__build { font-size: .84rem; color: var(--ink-2); margin-bottom: .3em; }
.cast {
  width: 34px; height: 34px;
  object-fit: cover;
  border-radius: 8px;
  border: 1.5px solid var(--line);
}

/* ── The cast gap ──────────────────────────────────────────────────────────
   A story that needs a character canon does not have used to be hidden in
   silence. This panel is that silence, ended: it names the person who is
   missing and offers the one button that makes them.

   It is an invitation, not an error — so it is warm paper and green, never
   red. Red in this app means "no", and nothing here has gone wrong. */

.castgap {
  margin: 0 0 12px;
  padding: 12px 13px 13px;
  background: var(--paper);
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r-sm);
}
.castgap__head { margin: 0 0 .25em; font-size: 1rem; }
.castgap__math { margin: 0 0 .2em; font-size: .9rem; color: var(--ink-2); }
.castgap__next { margin: 0 0 .7em; font-size: .9rem; color: var(--ink-2); }
.castgap__acts { display: flex; flex-wrap: wrap; gap: 8px; }

/* Waiting on a build. The dot is the only motion in the app, and it is the
   honest signal that something is happening rather than stuck. */
.castgap__working {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: .9rem;
  font-weight: 600;
  color: var(--green);
}
.castgap__dot {
  flex: none;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--green-2);
  animation: castpulse 1.4s ease-in-out infinite;
}
@keyframes castpulse {
  0%, 100% { opacity: .3; transform: scale(.8); }
  50% { opacity: 1; transform: scale(1); }
}

/* The server's own words, shown rather than swallowed. */
.castgap__err {
  margin: .7em 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--no-bg);
  color: var(--no);
  font-size: .88rem;
  font-weight: 600;
}

/* A failed build's raw child-process output. It is worth keeping — it is the
   only record of why a generation died — but it is not worth showing a
   non-technical reviewer, so it lives behind a summary and is never the first
   thing on the card. It used to be dumped straight into .castgap__err. */
.castgap__detail {
  margin: .5em 0 0;
  font-size: .82rem;
  color: var(--muted);
}
.castgap__detail summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--muted);
}
.castgap__detail pre {
  margin: .5em 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--panel-2, #f4f1ea);
  color: var(--ink, #2b2b2b);
  font-size: .74rem;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* The form. Pre-filled, so the common case is one press. */
.castform { margin-top: 2px; }
.castform .field { margin-bottom: 10px; }
.castform textarea {
  width: 100%;
  padding: 9px 11px;
  font: inherit;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--cream);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-sm);
  resize: vertical;
}
.castform textarea:focus {
  outline: none;
  border-color: var(--green-2);
  box-shadow: 0 0 0 3px var(--green-soft);
}
.castform__hint { margin: 0 0 10px; font-size: .84rem; color: var(--ink-3); }

/* The optional extras. A dashed rule above them says "these are a different
   kind of question" without needing a heading, and the helper line says the
   one thing the client has to know: none of it is required. Dashed, not solid,
   because nothing here is a boundary he has to cross to press the button. */
.castform__more {
  margin: 4px 0 12px;
  padding-top: 12px;
  border-top: 1px dashed var(--line-2);
}
.castform__hint--more { margin-bottom: 12px; }

.castform__acts { display: flex; flex-wrap: wrap; gap: 8px; }

/* A person we do not have, sitting in the row where their picture will go. */
.cast--missing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: transparent;
  border-style: dashed;
  border-color: var(--line-2);
}

/* ── The script ────────────────────────────────────────────────────────────
   The client's complaint was that there was "nothing to view/edit". This is
   the answer to the first half of it: the script, as prose, in the order it
   plays.

   The audience is the least technical person in the loop, so this is a
   numbered sequence of shots, not a table of fields. Each shot says what is
   said, what is written on screen, and what it sounds like — in that order,
   because that is the order it matters in.

   White paper on the cream card, the same trick .castgap uses, so it reads as
   a document sitting inside the card rather than as more card. */

.script {
  margin: 0 0 12px;
  padding: 12px 13px 13px;
  background: var(--paper);
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
}
/* Nothing to show yet. Dashed, like the cast gap, because both are an
   invitation to press a button rather than a finished thing. */
.script--none, .script--waiting { border-style: dashed; border-color: var(--line-2); }

.script__head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: .3em;
}
.script__head { margin: 0; font-size: 1rem; }
.script__meta {
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--ink-3);
  white-space: nowrap;
}
.script__note { margin: 0 0 .7em; font-size: .9rem; color: var(--ink-2); }
.script__note--gate { margin-bottom: 0; color: var(--ink-3); }

/* "You changed this by hand" — the same amber the app uses everywhere else
   for "needs a look", because a hand-edited line is worth noticing. */
.script__edited {
  display: inline-block;
  margin: 0 0 .6em;
  padding: 2px 9px;
  font-size: .72rem;
  font-weight: 700;
  border-radius: 999px;
  background: var(--gold-soft);
  color: var(--gold-ink);
}

/* The server's own words, shown rather than swallowed. */
.script__err {
  margin: .7em 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--no-bg);
  color: var(--no);
  font-size: .88rem;
  font-weight: 600;
}
/* A failed build's raw child-process output. Worth keeping — it is the only
   record of why a generation died — but not worth showing a non-technical
   reviewer first, so it lives behind a summary. */
.script__detail { margin: .5em 0 0; font-size: .82rem; color: var(--ink-3); }
.script__detail summary { cursor: pointer; font-weight: 600; color: var(--ink-3); }
.script__detail pre {
  margin: .5em 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--cream-2);
  color: var(--ink);
  font-size: .74rem;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* The waiting dot. The same pulse the cast gap and the running stage use —
   one motion in this app, meaning one thing: something is happening. */
.script--waiting .script__head::after {
  content: '';
  display: inline-block;
  width: 6px; height: 6px;
  margin-left: 7px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--green-2);
  animation: castpulse 1.4s ease-in-out infinite;
}

/* ── One shot ──────────────────────────────────────────────────────────────
   A numbered sequence, not a table. The number is a CSS counter rather than a
   column of its own, so the shots stay numbered in order however the beats
   move around. */

.script__beats {
  list-style: none;
  counter-reset: beat;
  margin: 0;
  padding: 0;
}
.beat {
  counter-increment: beat;
  position: relative;
  padding: 10px 0 10px 30px;
  border-top: 1px solid var(--line);
}
.beat:first-child { border-top: 0; padding-top: 4px; }
.beat::before {
  content: counter(beat);
  position: absolute;
  left: 0;
  top: 11px;
  width: 20px; height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .72rem;
  font-weight: 700;
  color: var(--green);
  background: var(--green-soft);
  border-radius: 50%;
}
.beat:first-child::before { top: 5px; }
.beat:hover::before { background: var(--green); color: #fff; }

.beat__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-bottom: .35em; }
.beat__shot { font-size: .95rem; font-weight: 700; color: var(--ink); }
.beat__fact { font-size: .78rem; color: var(--ink-3); }
.beat__fact b { color: var(--ink-2); font-weight: 600; }

/* The label is a gutter, not a heading: uppercase and quiet, so the eye can
   skip it and read only the words. Fixed width so the three lines of a shot
   all start in the same place. */
.beat__label {
  display: inline-block;
  min-width: 58px;
  margin-right: 6px;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  vertical-align: 1px;
}
.beat__vo { margin: 0 0 .25em; font-size: .95rem; color: var(--ink); }
.beat__vo--none { color: var(--ink-3); font-style: italic; }
.beat__ost { margin: 0 0 .25em; font-size: .9rem; color: var(--ink-2); }
.beat__sfx { margin: 0; font-size: .86rem; color: var(--ink-3); }

/* The one thing on this panel that says "you can change this". A ghost button
   at rest, so it never competes with the words, and green the moment the
   pointer is anywhere near the shot it belongs to. */
.beat__edit { margin-top: 7px; }
.beat:hover .beat__edit { border-color: var(--green-2); color: var(--green); }

/* The editor. Deliberately the same shape as .castform above: two plain
   boxes, a save, a cancel. Nothing here is a JSON editor, and nothing here is
   a shot-spec table. */
.scriptform { margin-top: 9px; }
.scriptform .field { margin-bottom: 10px; }
.scriptform .field > span { font-size: .72rem; }
.scriptform textarea {
  width: 100%;
  padding: 9px 11px;
  font: inherit;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--cream);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r-sm);
  resize: vertical;
}
.scriptform textarea:focus {
  outline: none;
  border-color: var(--green-2);
  box-shadow: 0 0 0 3px var(--green-soft);
}
.scriptform__acts { display: flex; flex-wrap: wrap; gap: 8px; }
.scriptform__err {
  margin: .6em 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: var(--no-bg);
  color: var(--no);
  font-size: .88rem;
  font-weight: 600;
}

/* ── Stage pipeline ────────────────────────────────────────────────────── */

.stages {
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin-top: auto;
  padding-top: 14px;
  flex-wrap: wrap;
}
.stage {
  flex: 1 1 0;
  min-width: 74px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 9px 6px;
  font: inherit;
  border-radius: var(--r-sm);
  border: 1.5px solid var(--line-2);
  background: var(--paper);
  cursor: pointer;
}
.stage__name { font-size: .74rem; font-weight: 700; letter-spacing: .02em; }
.stage__state { font-size: .72rem; color: var(--ink-3); font-weight: 600; }
.stage--ready { border-color: var(--green-2); background: var(--green-soft); }
.stage--ready .stage__state { color: var(--green); }
.stage--ready:hover { background: #D6EBDD; }
.stage--ok { border-color: #B9DCC8; background: var(--ok-bg); }
.stage--ok .stage__state { color: var(--ok); }
.stage--warn { border-color: #EBD3A6; background: var(--warn-bg); }
.stage--warn .stage__state { color: var(--warn); }
/* The step happening right now. `queued` is a quiet grey, `done` is a settled
   green; this one has to be the loudest thing in the row, because "which step
   are we on" is the question the client actually asked. */
.stage--running {
  border-color: var(--green-2);
  border-width: 2px;
  background: var(--green-soft);
  box-shadow: 0 0 0 3px rgba(44, 138, 91, .16);
}
.stage--running .stage__name { color: var(--green); }
.stage--running .stage__state { color: var(--green); }
.stage--running .stage__state::after {
  content: '';
  display: inline-block;
  width: 5px; height: 5px;
  margin-left: 5px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--green-2);
  animation: castpulse 1.4s ease-in-out infinite;
}
.stage--no { border-color: #F0C6BA; background: var(--no-bg); }
.stage--no .stage__state { color: var(--no); }
.stage--locked { opacity: .45; cursor: default; }
.stage__arrow { align-self: center; color: var(--line-2); font-size: .8rem; }

/* ── Cards with pictures ───────────────────────────────────────────────── */

.grid { display: grid; gap: 18px; }
.grid--chars { grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); }
.grid--pics { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }

.pcard {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  box-shadow: var(--shadow);
}
.pcard--approved { border-color: #B9DCC8; }
.pcard--rejected { opacity: .62; }
.pcard--changes { border-color: #EBD3A6; }

.pcard__img {
  position: relative;
  background: var(--cream-2);
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.pcard__img img { width: 100%; height: 100%; object-fit: cover; }
.pcard__none {
  display: grid;
  place-items: center;
  height: 100%;
  color: var(--ink-3);
  font-size: .86rem;
}
.pcard__lock {
  position: absolute;
  top: 9px; left: 9px;
  padding: 3px 9px;
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  border-radius: 999px;
  background: rgba(31, 107, 69, .92);
  color: #fff;
}
.pcard__lock--open { background: rgba(138, 90, 18, .92); }
.pcard__rev {
  position: absolute;
  top: 9px; right: 9px;
  padding: 3px 8px;
  font-size: .7rem;
  font-weight: 700;
  font-family: var(--mono);
  border-radius: 999px;
  background: rgba(30, 42, 34, .72);
  color: #fff;
}
.pcard__body { padding: 14px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.pcard__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.pcard__head h3 { margin: 0; font-size: 1.08rem; }
.pcard--sm .pcard__img { aspect-ratio: 1 / 1; }
.pcard--sm h4 { margin: 0; font-size: .95rem; }

/* Creating a first pass. The control sits where the picture would be, on the cards
   that have none — that is the whole point of it, so it is styled to read as the
   card's next action rather than as a footnote. */
.pcard__create {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 2px;
}
.pcard__create .btn { width: 100%; }
.pcard__create--busy .pcard__createnote { color: var(--ink-2); }
.pcard__create--failed .pcard__createnote { color: var(--no); }
.pcard__create--off .btn { opacity: .55; cursor: not-allowed; }
.pcard__createnote {
  margin: 0;
  font-size: .78rem;
  line-height: 1.35;
  color: var(--ink-3);
}

/* The batch control, above the grid. It sits on its own line so it reads as an
   action on the whole grid rather than on the first card. */
.createall {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  padding: 12px 14px;
  background: var(--cream-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.createall--open { display: block; }
.createall__lead { margin: 0 0 8px; font-size: .95rem; }
.createall__note { margin: 0; font-size: .84rem; color: var(--ink-2); }
.createall__row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.createall__skip { margin: 6px 0 0; font-size: .82rem; color: var(--ink-2); }
.createall__skip summary { cursor: pointer; }
.createall__skip ul { margin: 8px 0 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }

/* The voice surface, on the character card below the decision bar.
   It is separated by a rule because it is a second, independent judgement about
   the same character: the picture can be approved while the voice is still
   unchosen, and the card has to make that readable at a glance. */
.voice {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--line-2);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.voice__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.voice__label {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-3);
}
.voice__pick {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
  /* Measured at 34px before this — under the 44px a thumb needs. min-height
     rather than more padding, so the text stays optically centred. */
  min-height: 44px;
  padding: 7px 9px;
  font: inherit;
  font-size: .86rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
}
.voice__pick:disabled { opacity: .55; cursor: not-allowed; }
.voice__status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.voice__who { font-size: .84rem; color: var(--ink-2); }
.voice__note {
  margin: 0;
  font-size: .78rem;
  line-height: 1.4;
  color: var(--ink-3);
}
.voice__note--warn { color: var(--warn); }
/* --- The workspace boundary, said out loud ---------------------------------
   VOICE-CONTRACT.md §2.3: the app authenticates as ONE ElevenLabs workspace,
   and no API lists another workspace's private voices. So the picker can never
   show a voice the user cloned elsewhere, and a list that says nothing about
   that reads as "your voice is gone". This note is the sentence that stops the
   list from lying by omission; the `--empty` variant is the same sentence as a
   real empty state, for the user whose "Your voices" group has nothing in it.
   Sized and wrapped for the 390px phone: the button is inline, so it drops to
   its own line rather than pushing the card wider than the viewport. */
.voice__boundary {
  margin: 0;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--ink-3);
}
.voice__boundary--empty {
  padding: 10px 12px;
  color: var(--ink-2);
  background: var(--cream-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.voice__boundarylead { display: block; margin-bottom: 2px; color: var(--ink); }
/* 44px, not the 24px the 2px/8px padding gives it. This is the primary control
   of the empty state — the one action that can cross the boundary the note
   describes — and the clone panel's own CSS already records why: a control a
   person has to hit precisely is the defect this flow was fixed for once
   already (the file input measured 21px on a 390px phone). Measured before this
   rule: 96x24 at 390x844. */
.voice__boundaryopen { margin-left: 6px; padding: 2px 10px; min-height: 44px; font-size: .76rem; }
.voice__reload { margin-left: 6px; padding: 2px 8px; font-size: .76rem; }
.voice__passage {
  padding: 8px 10px;
  background: var(--cream-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.voice__lines {
  margin: 0;
  font-size: .84rem;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
}
.voice__meta { margin: 6px 0 0; font-size: .76rem; color: var(--ink-3); }
.voice__play { width: 100%; }
/* The free sample sits directly under the picker, because that is the moment the
 * question "what does this voice sound like?" is actually being asked. It is
 * styled as the lighter, secondary action it is — the paid "Hear them talk"
 * button below stays the visually heavier one, since it is the one that answers
 * the real question (this character, these lines). */
.voice__row--sample { margin-top: 8px; align-items: baseline; }
.voice__sample { flex: 0 0 auto; }
.voice__sample-note { margin: 0; flex: 1 1 12ch; }
.voice__audio { width: 100%; height: 34px; }
.voice__decide { margin-top: 0; }
.voice__history { font-size: .8rem; }

/* ── The voice picker modal ────────────────────────────────────────────────
   The old picker was a <select> of 263 options. You cannot audition from a
   dropdown: choosing closed it, and hearing a voice meant spending tokens. The
   modal is the listen-compare-listen loop the client asked for, so it is sized
   to hold a real list rather than a handful of rows. */
/* ── The v4 emotion row ────────────────────────────────────────────────────
   The tag is shown WITH the evidence that produced it, because the client chose
   to review the inference rather than author it. A tag he cannot see the reason
   for is a tag he can only accept or reject, not correct. */
.voice__emotion { margin-top: 8px; }
.voice__v4 {
  display: inline-block;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: 4px;
  background: var(--ink);
  color: var(--paper);
  font-size: .62rem;
  letter-spacing: .04em;
  vertical-align: middle;
}
.voice__emorow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.voice__emopick {
  flex: 0 0 auto;
  min-height: 34px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 6px);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: .8rem;
}
.voice__emowhy { flex: 1 1 16ch; font-size: .74rem; color: var(--ink-soft); }

.voice__pickrow { display: flex; align-items: center; gap: 8px; }
.voice__pickbtn {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  text-align: left;
  min-height: 44px;
}
.voice__pickcaret { opacity: .55; }
.voice__pickchange { flex: 0 0 auto; min-height: 44px; padding: 2px 10px; font-size: .76rem; }
/* The real <select> stays in the DOM as the value carrier the change handler
   reads, but is not what the user interacts with. `display:none` would remove it
   from the accessibility tree entirely; this keeps it reachable to script while
   taking no space and no tab stop. */
.voice__pick--hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.picker { display: flex; flex-direction: column; gap: 12px; padding: 20px 20px 24px; }
.picker__title { margin: 0; font-size: 1.1rem; }
.picker__sub { margin: 0; color: var(--ink-soft); font-size: .82rem; }

.picker__filters { display: flex; flex-direction: column; gap: 8px; }
.picker__axis { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.picker__axislabel {
  flex: 0 0 58px;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-soft);
}
.picker__chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; }
.picker__chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  min-height: 32px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: .78rem;
  cursor: pointer;
}
.picker__chip:hover { border-color: var(--ink-soft); }
.picker__chip.is-on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.picker__count { opacity: .6; font-size: .72rem; }
.picker__search {
  flex: 1 1 200px;
  min-height: 34px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 6px);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: .82rem;
}

.picker__resultline { display: flex; align-items: center; gap: 10px; font-size: .82rem; color: var(--ink-soft); }
.picker__clear {
  border: 0; background: transparent; color: var(--ink);
  text-decoration: underline; cursor: pointer; font: inherit; font-size: .8rem;
  padding: 4px 2px;
}

/* The list is the point: it scrolls so the filters stay put while the user
   auditions down the page. */
.picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 46vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 6px);
}
.picker__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
}
.picker__row:last-child { border-bottom: 0; }
.picker__row.is-chosen { background: color-mix(in srgb, var(--green) 12%, transparent); }
.picker__pick {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-height: 44px;
  padding: 4px 2px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.picker__name { font-size: .86rem; }
.picker__desc { font-size: .74rem; color: var(--ink-soft); }
.picker__play { flex: 0 0 auto; min-height: 36px; padding: 2px 12px; font-size: .78rem; }
.picker__nosample { flex: 0 0 auto; font-size: .72rem; color: var(--ink-soft); font-style: italic; }
.picker__tick { flex: 0 0 auto; color: var(--green); font-weight: 700; }
.picker__none { margin: 0; padding: 18px 4px; color: var(--ink-soft); font-size: .84rem; }
/* Measured at 20px tall before this. Padding, not `display:flex`, because
   `summary` is a list-item and flex would delete the disclosure marker. */
.voice__history summary { cursor: pointer; color: var(--ink-2); padding: 12px 0; }
.voice__histlist {
  margin: 8px 0 0;
  padding-left: 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.voice__histlist li { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.voice__histwhen { color: var(--ink-3); font-size: .76rem; }
.voice__histnote { color: var(--ink-2); }
/* --- Clone from a recording, and the public library search -----------------
   Both panels were rendered by app.js with no rules at all, so the file input
   fell back to the browser default and measured 21px tall on a 390px phone —
   under the 44px a thumb needs, and the one control in this flow a person has
   to hit precisely. Everything here is sized for a thumb first. */
.voice__clone,
.voice__library { font-size: .8rem; }
.voice__clone > summary,
.voice__library > summary {
  cursor: pointer;
  color: var(--ink-2);
  padding: 12px 0;
}
.voice__clonebody,
.voice__libbody {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px 0 8px;
}
.voice__file,
.voice__name,
.voice__libq {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 7px 9px;
  font: inherit;
  font-size: .84rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
}
/* A file input's own button is not stylable, but the box around it is. */
.voice__file { padding: 9px; }
.voice__file::file-selector-button {
  min-height: 30px;
  margin-right: 8px;
  padding: 4px 10px;
  font: inherit;
  font-size: .8rem;
  color: var(--ink);
  background: var(--cream-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.voice__create,
.voice__libsearch,
.voice__libadd {
  min-height: 44px;
  padding: 8px 12px;
  font: inherit;
  font-size: .84rem;
}
.voice__create { width: 100%; }
.voice__librow {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.voice__liblist { margin: 0; padding: 0; list-style: none; }
.voice__libname { font-weight: 600; color: var(--ink); }
.voice__libmeta { font-size: .76rem; color: var(--ink-3); }
.voice__librow .voice__audio { height: 34px; }
.voice__clonedone {
  margin: 0;
  padding: 8px 10px;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--ink-2);
  background: var(--cream-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.voice__cloneerr { color: var(--no); }
.voice__kling { width: 100%; }
.voice__klingid {
  margin: 0;
  font-size: .78rem;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.voice__klingid code {
  font-size: .78rem;
  padding: 1px 5px;
  background: var(--cream-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  word-break: break-all;
}
.voice__copy { padding: 2px 8px; font-size: .76rem; }
.createall__skip li { line-height: 1.35; }

.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  margin: 4px 0;
  font-size: .86rem;
}
.facts dt { color: var(--ink-3); font-weight: 650; }
.facts dd { margin: 0; color: var(--ink); }

.note {
  font-size: .86rem;
  color: var(--ink-2);
  background: var(--cream);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  margin: 4px 0;
}

.thumbs { display: flex; gap: 5px; flex-wrap: wrap; margin: 4px 0; }
.thumbs img {
  width: 46px; height: 46px;
  object-fit: cover;
  border-radius: 7px;
  border: 1.5px solid var(--line);
  cursor: zoom-in;
}
/* The strip's thumbnails are buttons so every plate in it is clickable and
 * reachable by keyboard. The reset keeps the existing look: `.thumbs img` above
 * still does all the sizing, and this only removes the button chrome. */
.thumbs button {
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  border-radius: 7px;
  cursor: zoom-in;
}
.thumbs button:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

.toggle { font-size: .88rem; color: var(--ink-2); display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }

/* ── Doctrine document ─────────────────────────────────────────────────── */

.doc { max-width: 78ch; font-size: .96rem; }
.doc h2 { font-size: 1.35rem; margin-top: 1.6em; padding-top: .6em; border-top: 1px solid var(--line); }
.doc h3 { font-size: 1.1rem; margin-top: 1.4em; }
.doc h4, .doc h5 { font-size: 1rem; margin-top: 1.2em; }
.doc ul { padding-left: 1.2em; }
.doc li { margin-bottom: .3em; }
.doc blockquote {
  margin: 1em 0;
  padding: .6em 1em;
  border-left: 3px solid var(--green-2);
  background: var(--green-soft);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-style: italic;
}
.doc__fig { margin: 1.2em 0; }
.doc__fig img { border-radius: var(--r-sm); border: 1px solid var(--line); }
.doc__fig figcaption { font-size: .82rem; color: var(--ink-3); margin-top: .4em; }

.renders {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin: 16px 0;
}
.render { margin: 0; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.render img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
.render figcaption { display: block; padding: 10px 12px; font-size: .85rem; }
.render figcaption b { display: block; margin-bottom: 3px; }
.render figcaption span { color: var(--ink-3); }

/* ── Tables ────────────────────────────────────────────────────────────── */

.tbl { width: 100%; border-collapse: collapse; font-size: .9rem; margin-top: 10px; }
.tbl th {
  text-align: left;
  font-size: .74rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 8px 10px;
  border-bottom: 2px solid var(--line);
}
.tbl td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.tbl tr:last-child td { border-bottom: 0; }
.tbl--log td { font-size: .84rem; }

/* A TABLE SCROLLS ITSELF, NOT THE PAGE.
 *
 * `display: block` on a <table> keeps the table layout for its rows (the browser wraps them in
 * an anonymous table box) but makes the element itself a scroll container. Without it, a table
 * with five columns is as wide as its widest row and pushes the whole PAGE sideways — the
 * Scripts view was 248px too wide and the Admin view 252px too wide on a 390px phone, which is
 * the screen the client reads this on. The page no longer scrolls sideways; the table does. */
.tbl, .beats {
  display: block;
  overflow-x: auto;
  max-width: 100%;
  -webkit-overflow-scrolling: touch;
}

.beats { width: 100%; border-collapse: collapse; font-size: .88rem; margin-top: 10px; }
.beats th {
  text-align: left;
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 8px 10px;
  border-bottom: 2px solid var(--line);
}
.beats td { padding: 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.beats__img { width: 130px; }
.beats__img img { width: 110px; border-radius: 7px; border: 1px solid var(--line); margin-bottom: 4px; }
.beats__img .muted { display: block; font-size: .76rem; }
.beats__vo { display: block; margin-top: 5px; font-size: .76rem; font-style: italic; }

/* ── Videos ────────────────────────────────────────────────────────────── */

.vids { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 18px; margin-top: 12px; }
.vcard {
  display: flex;
  flex-direction: column;
  background: var(--cream);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.vcard__player { background: #10160F; aspect-ratio: 16 / 9; display: grid; place-items: center; }
.vcard__player video { width: 100%; height: 100%; object-fit: contain; }
.vcard__body { padding: 14px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.vcard__body h4 { margin: 0; }
.vcard__models { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0; }
.vcard__disc { font-size: .82rem; color: var(--ink-3); margin: 0; }
/* The clone disclosure. Deliberately not muted to --ink-3 like the line above:
   this is the one claim on the card that a viewer could be misled by, so it
   reads at full strength and the name is bold. */
.vcard__voice { font-size: .84rem; color: var(--ink-2); margin: 4px 0 0; }

/* ── Empty state ───────────────────────────────────────────────────────── */

.empty {
  padding: 34px 20px;
  text-align: center;
  color: var(--ink-3);
  background: var(--cream);
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r);
}
.empty p { margin: 0; }

/* ── The Universal Asset Viewer ────────────────────────────────────────── */

/* The picture viewer Magieva already uses, in Megalith's palette. The layout is
 * the Universal Asset Viewer's: a backdrop, a panel inset from the edge, a fixed
 * left metadata rail, a media pane with a zoom bar, and a right rail. Magieva
 * puts the conversation in the right rail; here it is the decision, drawn with
 * the same .decide bar the cards use. */

/* The plate is the control. A real <button>, so Enter and Space work without a
 * keydown handler and the plate is in the tab order. */
.pcard__open {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}
.pcard__open:focus-visible { outline: 3px solid var(--green); outline-offset: -3px; }

#uav { position: fixed; inset: 0; z-index: 90; }
.uav__backdrop { position: absolute; inset: 0; background: rgba(20, 28, 22, .62); }
.uav__panel {
  position: absolute;
  inset: 32px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.uav__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
  padding: 6px 16px;
  border-bottom: 1.5px solid var(--line);
  background: var(--cream-2);
}
.uav__title { margin: 0; font-size: 1.05rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uav__x {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--ink-2);
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.uav__x:hover { background: var(--cream); color: var(--ink); }

.uav__body { display: flex; flex: 1; min-height: 0; overflow: hidden; }

.uav__rail { display: flex; flex-direction: column; overflow: hidden; background: var(--paper); }
/* Magieva's rails are a fixed 320px and 384px, and at 1920 they still are.
 * Below that they narrow with the viewport rather than stepping at a
 * breakpoint. A breakpoint puts a cliff in the middle of the laptop range — at
 * 1401px the picture drops from 760px wide to 569px — and the picture is the
 * thing that has to stay bigger than the card it came from. The minimums are
 * chosen so that at 1366×768 even the widest plate in the project (1680×720,
 * 21:9) renders larger than its 399×299 card thumbnail in BOTH dimensions. */
.uav__rail--left { flex-grow: 0; flex-shrink: 0; flex-basis: clamp(236px, 17.5vw, 320px); border-right: 1.5px solid var(--line); }
.uav__rail--right { flex-grow: 0; flex-shrink: 0; flex-basis: clamp(268px, 20vw, 384px); border-left: 1.5px solid var(--line); }
.uav__railTop { flex: 1; min-height: 0; overflow: auto; padding: 22px; border-bottom: 1.5px solid var(--line); }
.uav__rail--right .uav__railTop { border-bottom: 0; }
.uav__railBottom { flex: 0 0 auto; max-height: 46%; overflow: auto; padding: 16px 22px; }
.uav__h3 { font-size: .95rem; margin: 0 0 10px; }
.uav__sub { color: var(--ink-3); font-size: .84rem; margin: 0 0 10px; }

.uav__card { border: 1.5px solid var(--line); border-radius: var(--r-sm); padding: 12px; margin-bottom: 12px; }
.uav__card--id { position: relative; background: var(--cream-2); }
.uav__eyebrow {
  margin: 0;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.uav__name { margin: 3px 0 0; font-weight: 700; font-size: 1rem; overflow-wrap: anywhere; }
.uav__glyph {
  position: absolute;
  top: 10px; right: 10px;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  font-size: .8rem;
  color: var(--green);
  background: var(--green-soft);
  border-radius: 7px;
}
.uav__card--rel { display: grid; grid-template-columns: 72px 1fr; gap: 4px 8px; font-size: .8rem; }
.uav__k { color: var(--ink-3); font-weight: 650; }
.uav__v { color: var(--ink); overflow-wrap: anywhere; }
.uav__none { color: var(--ink-3); font-style: italic; }

.uav__vershead { display: flex; align-items: baseline; justify-content: space-between; font-size: .8rem; font-weight: 700; }
.uav__film { display: flex; gap: 8px; overflow-x: auto; padding: 10px 1px 3px; }
.uav__ver {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 8px 10px;
  font: inherit;
  font-size: .78rem;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.uav__ver--on { border-color: var(--green); background: var(--green-soft); }
.uav__ver:disabled { cursor: default; opacity: .75; }
.uav__verlabel { font-weight: 700; font-family: var(--mono); }
.uav__verstatus { color: var(--ink-3); text-transform: capitalize; }

.uav__meta { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; }
.uav__chip {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  font-size: .8rem;
  color: var(--ink-2);
  background: var(--cream-2);
  border-radius: 7px;
}
.uav__metabody { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.uav__metalabel { font-size: .74rem; color: var(--ink-3); }
.uav__metaval { font-size: .9rem; font-weight: 650; overflow-wrap: anywhere; }

.uav__mid { display: flex; flex: 1; min-width: 0; flex-direction: column; overflow: hidden; }
/* Fullscreen: the media pane alone fills the viewport, rails and all. A CSS
 * state rather than the Fullscreen API, so it behaves identically in every
 * browser and cannot be refused by a permission prompt or a gesture rule.
 * z-index 95 sits inside #uav's own stacking context, so it covers the panel
 * and both rails without escaping above the app's toast layer. */
.uav__mid--full { position: fixed; inset: 0; z-index: 95; background: var(--paper); }
.uav__ctrl {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 12px;
  min-height: 44px;
  padding: 6px 12px;
  border-bottom: 1.5px solid var(--line);
}
.uav__ctrlgroup { display: flex; align-items: center; gap: 6px; }
.uav__pct { min-width: 56px; text-align: center; font-size: .86rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.uav__on { background: var(--green-soft); border-color: var(--green); color: var(--green); }

.uav__stage { flex: 1; min-height: 0; display: flex; padding: 32px; overflow: auto; background: var(--cream-2); }
.uav__stage--fit, .uav__stage--fill { overflow: hidden; }
.uav__center { flex: 1; min-width: 0; min-height: 0; display: flex; }
.uav__frame {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* `safe` keeps a zoomed-in picture reachable: an overflowing centred flex item
   * is clipped on the leading edge and cannot be scrolled back to. Browsers that
   * do not know `safe` keep the plain centring above. */
  align-items: safe center;
  justify-content: safe center;
}
.uav__img { display: block; background: var(--paper); box-shadow: 0 18px 50px rgba(30, 42, 34, .34); }
.uav__img--fit { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.uav__img--fill { width: 100%; height: 100%; object-fit: cover; }
.uav__img--zoom { max-width: none; height: auto; }

.uav__spin { position: absolute; inset: 0; display: grid; place-items: center; }
.uav__spin i {
  width: 34px; height: 34px;
  border: 3px solid var(--line-2);
  border-top-color: var(--green);
  border-radius: 50%;
  animation: uavspin .8s linear infinite;
}
@keyframes uavspin { to { transform: rotate(360deg); } }

.uav__fail {
  max-width: 46ch;
  padding: 22px;
  text-align: center;
  background: var(--paper);
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r-sm);
}
.uav__failalt { margin: 0 0 6px; font-weight: 700; }
.uav__failmsg { margin: 0; color: var(--ink-3); font-size: .88rem; overflow-wrap: anywhere; }

@media (prefers-reduced-motion: reduce) { .uav__spin i { animation-duration: 2.4s; } }
/* No rail breakpoint here on purpose — the rails narrow with clamp() instead,
 * so there is no width at which the picture suddenly shrinks. See .uav__rail. */
@media (max-width: 900px) {
  .uav__panel { inset: 12px; }
  .uav__body { flex-direction: column; overflow: auto; }
  .uav__rail--left, .uav__rail--right { flex: 0 0 auto; border: 0; border-top: 1.5px solid var(--line); }
  .uav__rail--left { border-top: 0; }
  .uav__railTop { overflow: visible; }
  .uav__mid { flex: 0 0 auto; }
  .uav__stage { min-height: 46vh; }
  /* Fullscreen still fills the viewport on a phone; the stage must not keep the
   * stacked layout's minimum height once the pane is fixed to the screen. */
  .uav__mid--full .uav__stage { min-height: 0; }
}

/* ── The scene drill-down ──────────────────────────────────────────────── */

/* The same shell as the Universal Asset Viewer — backdrop, inset panel, left
 * rail, centre, right rail — because the client asked for that structure by
 * name and it is already in this file. It sits BELOW #uav (85 vs 90) so a plate
 * opened from inside the panel stacks on top of it.
 *
 * The one rule this surface lives by is that it must not lie. There are zero
 * LOD0 frames on disk, so `.scene__slot` is a dashed outline with words in it
 * and NO fill: it must never be mistakable for a photograph that failed to
 * load, and never for a grey placeholder standing in for a picture. */

#scene { position: fixed; inset: 0; z-index: 85; }
.scene__backdrop { position: absolute; inset: 0; background: rgba(20, 28, 22, .62); }
.scene__panel {
  position: absolute;
  inset: 32px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
  border: 1.5px solid var(--line-2);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.scene__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
  padding: 6px 16px;
  border-bottom: 1.5px solid var(--line);
  background: var(--cream-2);
}
.scene__title {
  margin: 0;
  font-size: 1.05rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.scene__num {
  display: inline-block;
  min-width: 1.9em;
  margin-right: 8px;
  padding: 1px 7px;
  font-size: .82rem;
  font-weight: 700;
  text-align: center;
  border-radius: 999px;
  background: var(--green-soft);
  color: var(--green);
}
.scene__x {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 1.15rem;
  line-height: 1;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-2);
  cursor: pointer;
}
.scene__x:hover { background: var(--cream-2); color: var(--ink); }

.scene__body { display: flex; flex: 1; min-height: 0; overflow: hidden; }
.scene__rail {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
}
.scene__rail--left {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: clamp(236px, 17.5vw, 320px);
  border-right: 1.5px solid var(--line);
}
.scene__rail--right {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: clamp(268px, 20vw, 384px);
  border-left: 1.5px solid var(--line);
}
.scene__railTop { flex: 1; min-height: 0; overflow: auto; padding: 14px; }

.scene__h3 {
  margin: 0 0 8px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.scene__h3--gap { margin-top: 18px; }

.scene__card {
  margin-bottom: 10px;
  padding: 10px 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--cream);
}
.scene__card--id { background: var(--cream-2); }
.scene__eyebrow {
  margin: 0 0 3px;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.scene__name { margin: 0; font-weight: 700; color: var(--ink); }
.scene__sub { margin: 3px 0 0; font-size: .84rem; color: var(--ink-3); }
.scene__synopsis { margin: 0; font-size: .88rem; color: var(--ink-2); line-height: 1.45; }

/* "No picture yet" is words, never an empty frame. */
.scene__noplate {
  margin: 6px 0 0;
  font-size: .84rem;
  color: var(--ink-3);
  font-style: italic;
}
.scene__castrow { margin-bottom: 10px; }
.scene__castrow:last-child { margin-bottom: 0; }
.scene__castname { margin: 0; font-weight: 600; font-size: .9rem; color: var(--ink); }

.scene__mid { display: flex; flex-direction: column; flex: 1; min-width: 0; overflow: hidden; }
.scene__ctrl {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  min-height: 44px;
  padding: 6px 12px;
  border-bottom: 1.5px solid var(--line);
}
.scene__ctrltext { margin: 0; font-size: .86rem; font-weight: 600; color: var(--ink-2); }
.scene__stage { flex: 1; min-height: 0; overflow: auto; padding: 14px; background: var(--cream); }

.scene__shot {
  margin-bottom: 14px;
  padding: 14px;
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  box-shadow: var(--shadow);
}
.scene__shot:last-child { margin-bottom: 0; }
.scene__shot--on { border-color: var(--green-2); box-shadow: 0 0 0 2px var(--green-soft), var(--shadow); }
.scene__shothead { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.scene__shotpick {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
}
.scene__shotpick:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; border-radius: 4px; }
.scene__shotid { font-weight: 700; }
.scene__shotslug { font-size: .86rem; color: var(--ink-3); }
.pill--lod { background: var(--green-soft); border-color: #B9DCC8; color: var(--green); }
.scene__shotdesc { margin: 8px 0 0; font-size: .9rem; color: var(--ink-2); line-height: 1.45; }

.scene__lod {
  margin-top: 12px;
  padding: 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--cream);
}
.scene__lodhead {
  margin: 0 0 8px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.scene__slots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }

/* An empty frame slot. Dashed, unfilled, and full of words — deliberately not a
 * grey rectangle, because a grey rectangle reads as a picture. */
.scene__slot {
  padding: 10px;
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r-sm);
  background: none;
  text-align: center;
}
.scene__slot--gate { border-color: var(--green-2); border-style: solid; }
.scene__slotname {
  margin: 0;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.scene__slotempty { margin: 4px 0 0; font-size: .82rem; font-style: italic; color: var(--ink-3); }
.scene__slotwhy { margin: 6px 0 0; font-size: .78rem; color: var(--ink-3); }
.scene__slot .decide { justify-content: center; margin-top: 8px; }
.scene__slot .decide .btn { font-size: .78rem; padding: 4px 8px; }
.scene__lodrule { margin: 8px 0 0; font-size: .8rem; color: var(--ink-3); line-height: 1.45; }

/* The honest art gap. It states the shot cannot be shot and offers no button,
 * because there is no plate to build a start frame from. */
.scene__gap {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1.5px solid #EBC3B8;
  border-left-width: 4px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--coral-soft);
}
.scene__gaptitle { margin: 0; font-weight: 700; color: var(--coral); }
.scene__gapbody { margin: 4px 0 0; font-size: .86rem; color: var(--ink-2); line-height: 1.45; }

.scene__shotdecide { margin-top: 12px; padding-top: 12px; border-top: 1.5px solid var(--line); }

/* The scene row in the Scripts table. A real <button>, so Enter and Space work
 * and the row is in the tab order. */
.scene__open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
}
.scene__open:hover b { color: var(--green); }
.scene__open:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; border-radius: 4px; }

@media (max-width: 900px) {
  .scene__panel { inset: 12px; }
  .scene__body { flex-direction: column; overflow: auto; }
  .scene__rail--left, .scene__rail--right { flex: 0 0 auto; border: 0; border-top: 1.5px solid var(--line); }
  .scene__rail--left { border-top: 0; }
  .scene__railTop { overflow: visible; }
  .scene__mid { flex: 0 0 auto; overflow: visible; }
  .scene__stage { overflow: visible; }
  .scene__slots { grid-template-columns: 1fr; }
}

/* ── Toast ─────────────────────────────────────────────────────────────── */

/* The confirmation banner.
 *
 * TWO THINGS WERE WRONG WITH THIS, and both made a recorded decision look like a
 * button that did nothing:
 *
 * 1. `z-index: 60` put it UNDER every overlay that matters — the scene view (85),
 *    the Universal Asset Viewer (90) and the modal (70). Approving a shot from
 *    inside a scene therefore drew the confirmation BEHIND the panel the reader
 *    was looking at.
 * 2. `bottom: 26px` put it at the bottom of the viewport, the furthest point from
 *    the button that was just pressed on a long page. It is now at the top, where
 *    the eye already is, and it drops in from above rather than rising from below.
 *
 * It is deliberately NOT the same green as the approve button: a banner that
 * matches the control it is confirming reads as part of the control.
 */
.toast {
  position: fixed;
  left: 50%;
  top: 18px;
  transform: translateX(-50%);
  z-index: 200;
  max-width: min(90vw, 520px);
  padding: 13px 22px;
  font-size: .94rem;
  font-weight: 650;
  color: #fff;
  background: var(--green);
  border-radius: 999px;
  box-shadow: var(--shadow-lg);
  animation: drop .18s ease;
}
.toast--err { background: var(--coral); }
@keyframes drop { from { opacity: 0; transform: translate(-50%, -10px); } }

/* ── Modal ─────────────────────────────────────────────────────────────── */

.modal { position: fixed; inset: 0; z-index: 70; display: grid; place-items: center; padding: 24px; }
.modal__backdrop { position: absolute; inset: 0; background: rgba(20, 28, 22, .62); }
.modal__box {
  position: relative;
  max-width: min(92vw, 900px);
  max-height: 88vh;
  overflow: auto;
  background: var(--paper);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.modal__box img { width: 100%; }
.modal__x {
  position: absolute;
  top: 8px; right: 10px;
  width: 36px; height: 36px;
  font-size: 1.4rem;
  line-height: 1;
  color: #fff;
  background: rgba(20, 28, 22, .6);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  /* The button is positioned but was never given a stacking order, and
     #modal-body comes after it in the DOM — so any image in the lightbox painted
     over the button and swallowed the click. Escape and the backdrop still
     worked, which is why it went unnoticed until the animatic player was driven. */
  z-index: 2;
}

/* ── Sound ─────────────────────────────────────────────────────────────── */

.card--sound { border-left: 4px solid var(--green); }
.sound__rule {
  font-size: .95rem;
  color: var(--ink);
  background: var(--ok-bg);
  border-radius: 8px;
  padding: 10px 12px;
  margin: 4px 0 10px;
}
.pill--ok { background: var(--ok-bg); border-color: #B9DCC8; color: var(--ok); }
.sound__list { margin-top: 10px; }
.sound__list summary { cursor: pointer; font-size: .88rem; color: var(--ink-2); }
.sound__list ul { margin: 8px 0 0; padding-left: 18px; columns: 2; column-gap: 24px; }
.sound__list li { font-size: .84rem; margin-bottom: 3px; break-inside: avoid; }
.sound__shots { margin: 8px 0 2px; }
.sound__shots-label {
  display: block;
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
  margin-bottom: 3px;
}
.sound__shots ul { margin: 0; padding-left: 16px; }
.sound__shots li { font-size: .84rem; color: var(--ink-2); }

/* ── Small screens ─────────────────────────────────────────────────────── */

@media (max-width: 720px) {
  .main { padding: 18px 14px 70px; }
  .card { padding: 16px; }
  .card--wide { grid-column: span 1; }
  .top { gap: 10px; padding: 9px 14px; }
  .top__nav { order: 3; width: 100%; overflow-x: auto; flex-wrap: nowrap; }
  /* The right cluster wraps rather than overflowing.
   *
   * The version toggle joined this row and pushed the header 67px past a 390px
   * viewport — a horizontal scrollbar on every view, which is the exact defect
   * the "no view scrolls sideways" check exists to catch. The row already sends
   * the nav to its own line at this width; letting the right cluster wrap too
   * keeps every control reachable instead of hiding one to make room.
   *
   * Written as a wrap rather than a narrower toggle on purpose: the width of
   * this cluster depends on the model pill's text, which changes with what the
   * registry reports. A fix tuned to the 67px measured today would come back the
   * moment that string grew. */
  .top__right { flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }
  .navbtn--admin { margin-left: 0; }
  .ideas, .grid--chars, .grid--pics, .vids { grid-template-columns: 1fr; }
  .decide .btn { flex: 1 1 auto; }
  .beats__img { width: 90px; }
  .beats__img img { width: 76px; }
  .sound__list ul { columns: 1; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ── The motion readout ────────────────────────────────────────────────────
   The client's complaint was visual — "the action/acting is devoid (they're not
   really moving at all)" — so the readout is visual: one bar per second of each
   shot, scaled against the gate's ceiling. A dead shot is a row of stubs. A shot
   carried by one burst is a single tall bar among stubs. That shape is the
   evidence, and it is exactly what the average hid: the rejected cut averaged
   3.33 while sitting flat at ~1.5 for six seconds and spiking to 10.96 last. */
.motion { margin: 10px 0 2px; }
.motion__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.motion__label {
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
}
.motion__list { list-style: none; margin: 0; padding: 0; }
.motion__row {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
}
.motion__id { font-size: .78rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.motion__bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 30px;
  border-bottom: 1px solid var(--cream-2);
}
.motion__bars i { display: block; width: 7px; background: var(--green-2); border-radius: 1px 1px 0 0; }
/* Below the floor the shot is standing still; above the ceiling it is a louder
   film than the one being made. Both are failures, and both are visible here. */
.motion__bars i.is-dead { background: var(--coral); }
.motion__bars i.is-hot { background: var(--gold-ink); }
.motion__num { font-size: .76rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.motion__row--bad .motion__id { color: var(--coral); font-weight: 600; }
.motion__note { font-size: .78rem; margin: 8px 0 0; line-height: 1.5; }
.pill--warn { background: var(--coral-soft); border-color: #EBC3B8; color: var(--coral); }

@media (max-width: 720px) {
  .motion__row { grid-template-columns: 20px 1fr; row-gap: 2px; }
  .motion__num { grid-column: 2; }
  .motion__bars i { width: 5px; }
}

/* ── Framing ────────────────────────────────────────────────────────────────
   The start-frame gate. The audience is the least technical person in the room,
   so the frame is large, the buttons are big, and the cost of a wrong frame is
   stated on the page rather than implied. */

.pill--bad { background: var(--coral-soft); border-color: #EBC3B8; color: var(--coral); }

/* The gate bar: "N of 7 approved" and what that means for the video spend. */
.gate { border-left: 4px solid var(--line-2); }
.gate--ok { border-left-color: var(--green); background: linear-gradient(180deg, #FCFEFC, var(--paper)); }
.gate--wait { border-left-color: var(--gold-ink); background: linear-gradient(180deg, #FFFDF8, var(--paper)); }
.gate__top { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; }
.gate__n { display: flex; align-items: baseline; gap: 8px; }
.gate__n b { font-size: 2.4rem; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.gate__n span { font-size: .95rem; color: var(--ink-3); }
.gate__state { flex: 1 1 320px; font-size: .95rem; line-height: 1.55; }
.gate--ok .gate__state b { color: var(--ok); }
.gate--wait .gate__state b { color: var(--gold-ink); }
.gate__rule {
  margin: 16px 0 6px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--cream-2);
  font-size: .9rem;
  line-height: 1.6;
  color: var(--ink-2);
}

.frames { display: grid; gap: 22px; margin-top: 22px; }

.fcard { display: block; }
.fcard--approved { border-color: #B9DCC8; }
.fcard--changes { border-color: #EBD3A6; }
.fcard--rejected { border-color: #F0C6BA; }

.fcard__top { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px; }
.fcard__top h3 { margin: 0 0 4px; }
.fcard__top p { margin: 0; }

.fcard__body { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 22px; margin-top: 16px; }

.fcard__frame { margin: 0; }
.fcard__frame img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--cream-2);
}
.fcard__frame figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.fcard__why {
  margin: 10px 0 0;
  padding: 10px 12px;
  border-left: 3px solid var(--line-2);
  background: var(--cream);
  border-radius: 0 8px 8px 0;
  font-size: .85rem;
  line-height: 1.55;
  color: var(--ink-2);
}

.fcard__meta h4 { margin: 0 0 6px; font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3); }
.fcard__meta h4:not(:first-child) { margin-top: 18px; }
.fcard__meta p { margin: 0; line-height: 1.6; }
.fcard__beats { margin: 10px 0 0; padding-left: 20px; line-height: 1.6; }
.fcard__beats li { margin-bottom: 4px; }

.fcard__cast { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--cream-2); }
.fcard__cast h4 { margin: 0 0 12px; font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3); }
.cast { display: flex; flex-wrap: wrap; gap: 14px; }
.cast__item { display: flex; gap: 12px; align-items: flex-start; }
.cast__item > img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--cream-2);
}
.cast__txt { font-size: .88rem; line-height: 1.5; }
.cast__refs { display: flex; gap: 6px; margin-top: 6px; }
.cast__refs img {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--cream-2);
}

.caveats {
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--gold-soft);
  border: 1px solid #EBD3A6;
}
.caveats b { display: block; margin-bottom: 6px; color: var(--gold-ink); font-size: .88rem; }
.caveats ul { margin: 0; padding-left: 20px; line-height: 1.6; font-size: .87rem; color: var(--ink-2); }
.caveats li { margin-bottom: 4px; }

.fcard__prev { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--cream-2); }
.fcard__prev h4 { margin: 0 0 8px; font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3); }
.fcard__prevrow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 6px; }
.fcard__prev p { margin: 0 0 4px; line-height: 1.55; }

.fcard__decide { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--cream-2); }
.fcard__notewrap { display: block; margin-bottom: 12px; }
.fcard__notewrap > span { display: block; margin-bottom: 6px; font-size: .85rem; color: var(--ink-3); }
.fcard__note {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: var(--paper);
  font: inherit;
  font-size: .92rem;
  color: var(--ink);
  resize: vertical;
}
.fcard__note:focus { outline: 2px solid var(--green-2); outline-offset: 1px; border-color: var(--green-2); }

.decide--big { gap: 12px; }
.btn--big { padding: 14px 22px; font-size: 1rem; font-weight: 600; }

@media (max-width: 900px) {
  .fcard__body { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  .gate__n b { font-size: 2rem; }
  .decide--big { flex-direction: column; }
  .btn--big { width: 100%; }
}

/* ── Animatic ──────────────────────────────────────────────────────────────── */
/* Held frames, in order, with their timing. The audience is the least technical
   person in the room: big pictures, big buttons, no numbers he did not ask for. */

.anim__cuts {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0 6px;
}
.anim__cut { background: var(--paper); border: 1px solid var(--line-2); color: var(--ink-2); }
.anim__cut:hover { border-color: var(--green-2); color: var(--ink); }
.anim__cut--on {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}
.anim__cut--on:hover { color: #fff; }

.anim__video { margin: 18px 0; }
.anim__video video { width: 100%; max-height: 62vh; border-radius: var(--r-sm); background: #000; }
.anim__video--none { background: var(--cream-2); border-style: dashed; }
.anim__video--none p { margin: 0; }

.anim__warn { margin: 18px 0; background: var(--gold-soft); border-color: var(--line-2); }
.anim__warn b { display: block; margin-bottom: 6px; }
.anim__warn p { margin: 0 0 6px; color: var(--ink-2); font-size: .95rem; }
.anim__warn p:last-child { margin-bottom: 0; }

.anim__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin: 22px 0 14px;
}
.anim__head h3 { margin: 0 0 2px; }
.anim__head p { margin: 0; }

.anim__strip { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }

.anim__card { display: flex; flex-direction: column; gap: 12px; }
.anim__card--approved { border-color: var(--ok); }
.anim__card--changes { border-color: var(--warn); }
.anim__card--rejected { border-color: var(--no); }

.anim__frame { position: relative; }
.anim__frame img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--cream-2);
  display: block;
}
.anim__role {
  position: absolute;
  top: 10px;
  left: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(20, 28, 22, .72);
  color: #fff;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
}

.anim__meta { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.anim__hold {
  margin: 0;
  font-weight: 700;
  font-size: .95rem;
  color: var(--green);
  letter-spacing: .01em;
}
.anim__what { margin: 0; color: var(--ink); font-size: 1.02rem; line-height: 1.45; }
.anim__synth { margin: 0; font-size: .88rem; }

/* The player. It runs off the cut's own timeline, so the bar is the real edit. */
.player { display: flex; flex-direction: column; }
.player__img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: #000; display: block; }
.player__body { padding: 16px 18px 18px; }
.player__bar {
  height: 5px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
  margin-bottom: 14px;
}
.player__bar i { display: block; height: 100%; width: 0; background: var(--green); }
.player__cap {
  margin: 0 0 6px;
  font-size: 1.25rem;
  line-height: 1.4;
  color: var(--ink);
  min-height: 2.6em;
}
.player__meta { margin: 0 0 14px; color: var(--ink-3); font-size: .9rem; }
.player__ctrls { display: flex; gap: 12px; flex-wrap: wrap; }

@media (max-width: 900px) {
  .anim__strip { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  .anim__cuts { flex-direction: column; }
  .anim__cut { width: 100%; }
  .anim__head { flex-direction: column; align-items: stretch; }
  .player__cap { font-size: 1.1rem; }
  .player__ctrls { flex-direction: column; }
}

/* ── The frame ladder ──────────────────────────────────────────────────────
   The second axis of the animatic. The row above it is DURATION (the same
   frames re-timed); this row is DENSITY (how many pictures the film is cut
   from). Same tokens, same button idiom, so the two rows read as siblings
   rather than as two different designs. */

.lod { margin: 18px 0; }
.lod__head { margin-bottom: 4px; }
.lod__head h3 { margin: 0 0 6px; }
.lod__head p { margin: 0; max-width: 68ch; }

/* The tier buttons carry a number, a unit and the code name, so they are
   taller than the duration buttons. They deliberately do NOT reuse
   .anim__cut: that class means "a duration cut" to the e2e suite, and a
   second row of them would read as eight cut lengths. Same look, own name. */
.lod__tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 16px 0 0;
}
.lod__tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 132px;
  padding: 10px 18px;
  line-height: 1.15;
  background: var(--paper);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  cursor: pointer;
  font: inherit;
  transition: background .12s, border-color .12s, color .12s;
}
.lod__tab:hover { border-color: var(--green-2); color: var(--ink); }
.lod__tab--on {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}
.lod__n { font-size: 1.5rem; font-weight: 700; letter-spacing: -.01em; }
.lod__unit { font-size: .82rem; font-weight: 600; opacity: .85; }
.lod__code {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .06em;
  opacity: .6;
  margin-top: 2px;
}

.lod__blurb {
  margin: 14px 0 0;
  color: var(--ink-2);
  font-size: 1.02rem;
  line-height: 1.5;
  max-width: 68ch;
}

/* The three buckets. Colour is the existing status vocabulary: green = we
   already have it, amber = it costs money. */
.lod__split {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 16px 0 0;
}
.lod__bucket {
  flex: 1 1 150px;
  padding: 12px 16px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--cream-2);
}
.lod__bucket b { display: block; font-size: 1.45rem; line-height: 1.1; }
.lod__bucket span { display: block; margin-top: 3px; font-size: .86rem; color: var(--ink-2); }
.lod__bucket--reuse { background: var(--ok-bg); border-color: var(--ok); }
.lod__bucket--reuse b { color: var(--ok); }
.lod__bucket--extract { background: var(--wait-bg); border-color: var(--wait); }
.lod__bucket--extract b { color: var(--wait); }
.lod__bucket--generate { background: var(--warn-bg); border-color: var(--warn); }
.lod__bucket--generate b { color: var(--warn); }
.lod__bucket--cost { background: var(--paper); }
.lod__bucket--cost b { color: var(--ink); }

.lod__stripwrap { margin-top: 20px; }
.lod__stripnote { margin: 0 0 12px; }
.lod__strip { margin-top: 0; }

/* Read-only cards: no decision bar, so they are shorter than .anim__card.
   The internals mirror .anim__frame/.anim__role/.anim__meta/.anim__hold/
   .anim__what but are named lod__* so the e2e suite's animatic selectors
   keep counting only the duration cut's frames. */
.lod__card { gap: 10px; }
.lod__card--generate { border-color: var(--warn); }
.lod__card--extract { border-color: var(--line-2); }
.lod__frame {
  position: relative;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--cream-2);
}
.lod__frame img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.lod__role {
  position: absolute;
  left: 8px;
  bottom: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(20, 28, 22, .72);
  color: #fff;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .02em;
}
.lod__meta { display: flex; flex-direction: column; gap: 4px; }
.lod__hold {
  margin: 0;
  font-family: var(--mono);
  font-size: .78rem;
  color: var(--ink-3);
}
.lod__what {
  margin: 0;
  font-size: .92rem;
  line-height: 1.4;
  color: var(--ink-2);
}
.lod__nofile {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-sm);
  background: var(--cream-2);
  border: 1px dashed var(--line-2);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-3);
  font-size: .9rem;
}
.lod__shared {
  font-size: .78rem;
  color: var(--ink-3);
  letter-spacing: .01em;
}

@media (max-width: 720px) {
  .lod__tiers { flex-direction: column; }
  .lod__tab { width: 100%; min-width: 0; }
  .lod__bucket { flex: 1 1 100%; }
}

/* ── The phone bar ─────────────────────────────────────────────────────── */

/* Eleven nav items do not fit across a phone. They used to be laid out on one
 * flex line that ran off the right edge of a header which could not scroll, so
 * ten of the eleven were simply unreachable — including Animatic, which is where
 * the storylines and beats get reviewed. Below 720px the nav becomes a panel
 * behind a Menu button instead.
 *
 * The panel is toggled by the `hidden` attribute, not by a class, because
 * `[hidden] { display: none !important }` near the top of this file is the one
 * rule that reliably wins. Everything here is inside the media query, so the
 * desktop bar is untouched. */
@media (max-width: 720px) {
  .top {
    gap: 10px;
    padding: 10px 14px;
  }

  /* The page name is already the first thing in the view, so the bar does not
   * need to repeat it. Dropping it is what buys the Menu button its room. */
  .top__sep,
  .top__page { display: none; }

  .navtoggle { display: inline-flex; }

  /* `order` puts the button between the wordmark and the account controls
   * without moving anything in the markup. Below about 380px the wordmark, the
   * button and the account controls cannot share a line, so the account controls
   * drop to a second row — the bar is then 112px, which is what the panel's
   * height cap below is measured against. */
  .top__left { order: 1; }
  .navtoggle { order: 2; }
  .top__right { order: 3; margin-left: auto; }

  /* The panel. `flex-basis: 100%` forces it onto its own line under the bar.
   *
   * Eleven rows of 47px plus 6px gaps are 589px tall, which is taller than a 568px
   * phone. Without a cap the last rows — Animatic and Admin among them — sit below
   * the fold, and because the bar is `position: sticky` the page scroll does not
   * carry them into view: the panel scrolls with the header, so they stay
   * off-screen however far you scroll. That is the same class of bug as the one
   * this whole section fixes, so the panel gets its own scroll and a height that
   * always leaves the bar visible. 130px clears the tallest bar (112px, measured at
   * 320px where the account controls wrap to a second row) with room to spare. */
  .top__nav {
    order: 4;
    flex: 1 1 100%;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 6px;
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
    max-height: calc(100vh - 130px);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* 44px is the smallest thing a thumb hits reliably. The desktop pill is 40px,
   * so the height is set explicitly rather than left to padding. */
  .navbtn {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 16px;
    font-size: 1rem;
    border-radius: var(--r-sm);
    text-align: left;
  }

  /* `margin-left: auto` is what parks Admin on the right of the desktop bar.
   * In a stacked panel it would just push the button off on its own. */
  .navbtn--admin { margin-left: 0; }

  /* The account controls are the least important thing in the bar on a phone,
   * and the model pill is the widest of them. */
  .top__user { display: none; }
  .pill--model { display: none; }
}

/* --- the realtime timeline player ------------------------------------------
 *
 * THE ARTIFACT IS A TIMELINE; an MP4 is one rendering of it. This plays the
 * timeline directly, so the layout is built around watching a still and
 * reaching the transport with one thumb.
 *
 * MOBILE IS THE PRIMARY TARGET: the picture is on top, the transport sits
 * directly under it, and every control is at least 44px. Desktop is the same
 * thing with the panel moved alongside the picture.
 */

.anim__play {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  justify-content: space-between;
  margin: 18px 0;
  background: var(--green-soft);
  border-color: var(--green-2);
}
.anim__play-copy { flex: 1 1 260px; min-width: 0; }
.anim__play h3 { margin: 0 0 4px; }
.anim__play p { margin: 0; }
.anim__play .btn { flex: 0 0 auto; }

/* The lightbox becomes the player: full-bleed on a phone. */
.modal--rtp { padding: 0; place-items: stretch; }
.modal--rtp .modal__box {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  border-radius: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.modal--rtp .modal__x { z-index: 5; }

.rtp {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.rtp__stage { position: relative; flex: 0 0 auto; background: #000; }
.rtp__img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  background: #000;
}
.rtp__badge,
.rtp__tier {
  position: absolute;
  bottom: 10px;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(20, 28, 22, .78);
  color: #fff;
  font-size: .78rem;
  line-height: 1.2;
}
.rtp__badge { left: 10px; }
.rtp__tier { right: 10px; font-variant-numeric: tabular-nums; }
.rtp__badge[hidden] { display: none; }

.rtp__panel {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px calc(18px + env(safe-area-inset-bottom, 0px));
  background: var(--paper);
}

.rtp__line {
  margin: 0;
  min-height: 1.4em;
  color: var(--ink);
  font-size: .98rem;
  line-height: 1.4;
}

/* The hit area is 44px; the track inside it is 8px. */
.rtp__scrub {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  margin: 0;
  background: transparent;
  cursor: pointer;
  touch-action: pan-y;
}
.rtp__scrub::-webkit-slider-runnable-track {
  height: 8px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--cream-2);
}
.rtp__scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -10px;
  border: 3px solid var(--paper);
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 1px 4px rgba(30, 42, 34, .3);
}
.rtp__scrub::-moz-range-track {
  height: 8px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--cream-2);
}
.rtp__scrub::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 3px solid var(--paper);
  border-radius: 50%;
  background: var(--green);
}

.rtp__times {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  color: var(--ink-3);
  font-size: .86rem;
  font-variant-numeric: tabular-nums;
}

.rtp__transport { display: flex; gap: 10px; align-items: stretch; }
.rtp__btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 56px;
  padding: 0 12px;
  font-family: inherit;
  font-size: 1.15rem;
  color: var(--ink);
  background: var(--cream-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.rtp__btn:hover { background: var(--cream); }
.rtp__btn--play {
  flex: 2 1 0;
  font-size: 1.3rem;
  color: #fff;
  background: var(--green);
  border-color: var(--green);
}
.rtp__btn--play:hover { background: var(--green-2); }
.rtp__btn--wide { width: 100%; font-size: .98rem; }

.rtp__group { display: flex; flex-direction: column; gap: 6px; }
.rtp__label {
  color: var(--ink-3);
  font-size: .78rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.rtp__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.rtp__chip {
  flex: 1 1 auto;
  min-width: 68px;
  min-height: 44px;
  padding: 0 12px;
  font-family: inherit;
  font-size: .92rem;
  color: var(--ink-2);
  background: var(--paper);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  cursor: pointer;
}
.rtp__chip:hover { border-color: var(--green-2); color: var(--ink); }
.rtp__chip--on { color: #fff; background: var(--green); border-color: var(--green); }

.rtp__note { margin: 0; color: var(--ink-3); font-size: .84rem; line-height: 1.45; }

/* Desktop is the expanded version: the panel moves alongside the picture. */
@media (min-width: 900px) {
  .modal--rtp { padding: 24px; place-items: center; }
  .modal--rtp .modal__box {
    width: min(96vw, 1180px);
    height: auto;
    max-height: 92vh;
    border-radius: var(--r);
    overflow: auto;
  }
  .rtp {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    align-items: start;
    height: auto;
  }
  .rtp__panel { overflow: visible; }
}

/* ── The script, and files that are gone ───────────────────────────────── */

/* The top-level script is the heart of the project. Same accent as the sound
 * card, so it reads as one of the existing panels rather than a new one. */
.card--script { border-left: 4px solid var(--green); }
.script__logline {
  font-size: 1.02rem;
  color: var(--ink-2);
  max-width: 70ch;
  margin: 2px 0 14px;
}
.tbl--versions td, .tbl--scenes td { font-size: .86rem; }
.scene__synopsis { display: block; margin-top: 3px; max-width: 46ch; }

/* A picture or a video whose file is not in the project. It replaces the
 * element in place, so the layout keeps its shape and the reviewer gets a
 * sentence naming the file instead of a broken-image icon. */
.media-none {
  display: grid;
  place-items: center;
  min-height: 90px;
  padding: 14px;
  text-align: center;
  color: var(--ink-3);
  font-size: .86rem;
  overflow-wrap: anywhere;
  background: var(--cream);
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r-sm);
}

/* ============================================================================
   PROPS
   A prop is an OBJECT WITH STATES. The page has to make that legible, because
   the whole reason the helmet drifted is that its states were never visible to
   anyone. So: the diagram first (the whole object at a glance), then one card
   per STATE with its own approval, then the per-wearer variants, then the
   mechanism as a strip.
   ============================================================================ */

.prop {
  background: var(--paper);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  padding: 22px;
  margin-bottom: 26px;
  box-shadow: var(--shadow);
}

.prop__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.prop__head h2 { margin: 0 0 4px; font-size: 1.5rem; }
.prop__head p { margin: 0; max-width: 62ch; }
.prop__meta { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

.prop__sub {
  margin: 26px 0 12px;
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-soft);
  border-top: 1px solid var(--line);
  padding-top: 16px;
}

/* The generated diagram. It is wide, so it gets the full column and a scroll
   rather than being squeezed into a card. */
.prop__diagram { margin-bottom: 8px; }
.prop__diagram img {
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--cream-2);
  display: block;
}
.prop__diagram p { margin: 8px 0 0; font-size: .82rem; }

.grid--props { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }

/* The mechanism strip: six frames in a row, each captioned with its step. */
.thumbs { display: flex; flex-wrap: wrap; gap: 10px; }
.thumbs button {
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--cream-2);
  cursor: zoom-in;
  overflow: hidden;
  width: 84px;
  height: 84px;
}
.thumbs button img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumbs--mech figure { margin: 0; width: 132px; }
.thumbs--mech button { width: 132px; height: 132px; }
.thumbs--mech figcaption {
  margin-top: 5px;
  font-size: .72rem;
  color: var(--ink-soft);
  line-height: 1.3;
  /* A FILE NAME IS ONE LONG WORD. `battle-helmet__mech-03__open.png` is 249px of unbreakable
   * text inside a 132px figure, so it pushed the whole Props view 38px sideways on a phone.
   * `anywhere` (not `break-word`) is the one that also affects intrinsic sizing, which is what
   * a flex/grid track measures. */
  overflow-wrap: anywhere;
}

/* One row per state inside a variant card, so the approval badge and the state
   key sit together and it is obvious which state is being referred to. */
.prop__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-size: .82rem;
}
.prop__rowkey {
  flex: 0 0 62px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: .76rem;
  color: var(--ink-soft);
}

.prop__never { margin: 6px 0 0; padding-left: 20px; color: var(--ink-soft); font-size: .86rem; }
.prop__never li { margin-bottom: 3px; }

/* ---------------------------------------------------------------------------
   THE AUDIO PLAY
   --------------------------------------------------------------------------- */

.aud__passes { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; margin-top: 14px; }
.aud__pass {
  border: 1px solid var(--line, #2a2a2a);
  border-radius: 10px;
  padding: 14px;
  background: rgba(255, 255, 255, 0.02);
}
.aud__pass-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.aud__pass-head b { font-size: 15px; }
.aud__pass audio { width: 100%; margin-top: 10px; }
/* The older performances, kept in `_archive/` beside the live file. Each one is a full player,
   because "listen to older versions" means listen, not read a file name. */
.aud__hist { margin-top: 10px; }
.aud__hist summary { cursor: pointer; font-size: 12.5px; color: #2F5D50; font-weight: 600; }
.aud__hist-row {
  display: grid; grid-template-columns: 130px auto auto minmax(0, 1fr);
  gap: 10px; align-items: center; margin-top: 8px;
}
.aud__hist-when { font-size: 12px; color: #8A8175; font-variant-numeric: tabular-nums; }
.aud__hist-row audio { width: 100%; }
.aud__hist-why {
  margin: 4px 0 0; padding-left: 10px; border-left: 2px solid #E0D8C9;
  font-size: 12px; color: #8A8175; font-style: italic;
}
@media (max-width: 720px) {
  .aud__hist-row { grid-template-columns: minmax(0, 1fr); }
}

/* THE GRID COLUMN IS minmax(0, 1fr), NOT auto.
   A `display: grid` with no explicit columns makes one `auto` column, and an `auto` column is
   sized to its widest item's max-content width — so a single long line of dialogue stretched the
   whole Audio view to 2568px and gave the page a horizontal scrollbar on every screen size.
   `min-width: 0` on the flex child inside does not help: the column has already grown. The
   client reads this page on a phone. */
.aud__beats { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; margin-top: 14px; }
.aud__beat { display: flex; gap: 12px; align-items: flex-start; }
.aud__beat-n {
  flex: 0 0 26px; text-align: right; font-variant-numeric: tabular-nums;
  color: var(--muted, #888); font-size: 12px; padding-top: 3px;
}
.aud__beat-body { flex: 1 1 auto; min-width: 0; }
.aud__beat-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.aud__beat-who { font-weight: 600; font-size: 13px; }
.aud__beat-time {
  margin-left: auto; font-variant-numeric: tabular-nums;
  color: var(--muted, #888); font-size: 12px;
}
.aud__beat-text {
  font-size: 13px; color: var(--fg, #ddd); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.aud__beat-bar {
  position: relative; height: 3px; margin-top: 6px;
  background: rgba(255, 255, 255, 0.06); border-radius: 2px;
}
.aud__beat-bar i {
  position: absolute; top: 0; height: 100%; border-radius: 2px;
  background: var(--accent, #6ea8fe);
}

.aud__arc {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 14px 0;
}
.aud__arc-step {
  border: 1px solid var(--line, #2a2a2a); border-radius: 8px;
  padding: 8px 12px; text-align: center; min-width: 110px;
}
.aud__arc-who { font-size: 11px; color: var(--muted, #888); }
.aud__arc-tag { font-weight: 600; font-size: 14px; margin: 2px 0; }
.aud__arc-line { font-size: 11px; color: var(--muted, #888); }
.aud__arc-arrow { color: var(--muted, #888); }

.aud__arc-checks { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; margin-top: 10px; }
.aud__arc-check { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 13px; }

/* ---------------------------------------------------------------------------
   THE FRAMING GRID, THE SCALE, AND THE FLIPPER

   The client: "i also want to be able to flip through them fullscreen, our put they into a
   visual grid (i can scale up or down to fit more on screen (like the 9 up, but we could show
   the whole film this way w/ hundreds of shots."

   The grid is a CSS grid whose tile width is a custom property, so the scale slider re-lays-out
   without a re-render. The flipper is a fixed overlay over everything, including the scene
   overlay, because it is a viewing mode and not a dialog.
   --------------------------------------------------------------------------- */

.ftools {
  display: flex; flex-wrap: wrap; align-items: center; gap: 18px;
  margin-top: 18px; padding: 10px 14px;
  border: 1px solid #E6DFD2; border-radius: 12px; background: #FDFBF7;
}
.ftools__group { display: flex; align-items: center; gap: 8px; }
.ftools__label { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: #8A8175; }
.ftools__count { font-size: 12px; color: #8A8175; min-width: 74px; }
.ftool {
  font: inherit; font-size: 13px; padding: 6px 12px; cursor: pointer;
  border: 1px solid #DED6C8; border-radius: 999px; background: #fff; color: #3A342C;
}
.ftool:hover { background: #F6F1E8; }
.ftool[aria-pressed="true"] { background: #3A342C; border-color: #3A342C; color: #fff; }
.ftool--go { background: #2F5D50; border-color: #2F5D50; color: #fff; }
.ftool--go:hover { background: #274C42; }
#fscale { width: 190px; accent-color: #2F5D50; }

/* THE GRID. `--tile` is the tile width; the cards become frames only. */
.frames--grid {
  grid-template-columns: repeat(auto-fill, minmax(var(--tile, 220px), 1fr));
  gap: 10px;
}
.frames--grid .fcard { border: 0; background: none; padding: 0; }
.frames--grid .fcard__top,
.frames--grid .fcard__meta,
.frames--grid .fcard__cast,
.frames--grid .fcard__decide,
.frames--grid .fcard__why,
.frames--grid .fcard__frame figcaption { display: none; }
.frames--grid .fcard__body { display: block; margin: 0; }
.frames--grid .fcard__frame img {
  width: 100%; height: auto; display: block; border-radius: 8px;
  border: 1px solid #E6DFD2; background: #EFEAE1;
}
.frames--grid .fcard__frame { position: relative; }
/* The shot number and title ride on the tile, so a contact sheet is still readable. */
.frames--grid .fcard__frame::after {
  content: attr(data-tile-label);
  position: absolute; left: 6px; bottom: 6px;
  font-size: 11px; line-height: 1.2; padding: 3px 7px; border-radius: 6px;
  background: rgba(24, 21, 17, .72); color: #fff; max-width: calc(100% - 12px);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* THE FLIPPER. Fullscreen, above everything, including the scene overlay. */
body.flip-open { overflow: hidden; }
.flip {
  position: fixed; inset: 0; z-index: 9000;
  background: #14120F; color: #F3EFE7;
  display: flex; flex-direction: column;
}
.flip[hidden] { display: none; }
.flip__bar {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 18px; border-bottom: 1px solid #2A2620; flex: 0 0 auto;
}
.flip__pos { font-variant-numeric: tabular-nums; font-size: 13px; color: #B9B1A4; min-width: 68px; }
.flip__title { font-size: 14px; font-weight: 600; }
.flip__spacer { flex: 1 1 auto; }
.flip__btn {
  font: inherit; font-size: 13px; padding: 7px 14px; cursor: pointer;
  border: 1px solid #3A342C; border-radius: 999px; background: #211E19; color: #F3EFE7;
}
.flip__btn:hover { background: #2E2921; }
.flip__btn[aria-pressed="true"] { background: #2F5D50; border-color: #2F5D50; }
.flip__stage { position: relative; flex: 1 1 auto; display: flex; align-items: center; justify-content: center; min-height: 0; padding: 10px; }
.flip__img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 6px; }
.flip__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 56px; height: 96px; font-size: 40px; line-height: 1; cursor: pointer;
  border: 0; border-radius: 10px; background: rgba(255, 255, 255, .07); color: #F3EFE7;
}
.flip__nav:hover { background: rgba(255, 255, 255, .16); }
.flip__nav--prev { left: 14px; }
.flip__nav--next { right: 14px; }
.flip__meta { flex: 0 0 auto; padding: 10px 22px 18px; max-height: 22vh; overflow: auto; }
.flip__intent { margin: 0 0 6px; font-size: 13px; color: #D8D1C4; }
.flip__action { margin: 0; font-size: 13px; color: #9E968A; }

@media (max-width: 720px) {
  .ftools { gap: 10px; }
  #fscale { width: 120px; }
  .flip__nav { width: 40px; height: 72px; font-size: 30px; }
}

/* The shot's OWN version, which is not the cut's. A re-render or a length change moves it;
   moving the shot in the edit does not. The hash is shown because it is what makes the claim
   checkable — two cards showing v01 with different hashes would be a bug, and now you can see it. */
.fcard__ver { margin: 2px 0 0; font-size: 12px; }
.fcard__hash { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: #A79E90; }

/* ---------------------------------------------------------------------------
   THE VERSION TREE
   The client: "We're not actually using ANY of this versioning, as you can clearly see in the
   interface." A version nobody can see is a version nobody has.
   --------------------------------------------------------------------------- */
.vgroup { margin-top: 18px; }
.vgroup__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.vgroup__head h3 { margin: 0; }
.vlist { list-style: none; margin: 12px 0 0; padding: 0; }
.vrow {
  display: grid; grid-template-columns: 84px 132px minmax(0, 1.6fr) minmax(0, 1.4fr);
  gap: 12px; align-items: baseline;
  padding: 8px 10px; border-top: 1px solid #EFEAE1; font-size: 13px;
}
.vrow:first-child { border-top: 0; }
.vrow--current { background: #F4F8F5; border-radius: 8px; }
.vrow__ver { font-weight: 600; font-variant-numeric: tabular-nums; }
.vrow__when { color: #8A8175; font-size: 12px; font-variant-numeric: tabular-nums; }
.vrow__why { color: #4A443B; }
.vrow__bits { color: #8A8175; font-size: 12px; }
.vhash { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: #A79E90; }
.vcommit { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
@media (max-width: 900px) {
  .vrow { grid-template-columns: 1fr; gap: 2px; }
}

/* The version switcher, in the flipper's bar. Only shown when a shot HAS more than one take —
   a control that does nothing is worse than no control. */
.flip__vers { display: inline-flex; align-items: center; gap: 6px; }
.flip__vlabel {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  min-width: 30px; text-align: center; color: #EDE7DC;
}
.flip__btn--sm { padding: 3px 8px; font-size: 13px; line-height: 1; }
.flip__btn:disabled { opacity: .35; cursor: default; }

/* ---------------------------------------------------------------------------
   THE ARCHIVE — the versions you can open.
   The tree above it is a catalogue of hashes; this is the pictures. The current
   version is first and marked, so the default is always the latest and the
   history is what you reach for deliberately.
   --------------------------------------------------------------------------- */
.arch { margin: 14px 0 20px; }
.arch__name { margin: 0 0 8px; font-size: 13px; font-weight: 600; }
.arch__row {
  display: flex; flex-wrap: wrap; gap: 10px;
}
.arch__item {
  position: relative; padding: 0; border: 1px solid var(--line, #2a2a2a);
  border-radius: 8px; background: #111; cursor: pointer; overflow: hidden;
  width: 150px; transition: border-color .12s, transform .12s;
}
.arch__item:hover { border-color: var(--accent, #6cf); transform: translateY(-1px); }
.arch__item--current { border-color: var(--ok, #3c9); }
.arch__item img {
  display: block; width: 100%; height: 96px; object-fit: cover; background: #000;
}
.arch__ver {
  display: block; padding: 4px 6px; font-size: 11px; text-align: center;
  color: #bbb; background: #181818; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis;
}
.arch__item--current .arch__ver { color: var(--ok, #3c9); font-weight: 600; }
/* A MIX IS NOT A THUMBNAIL. The archive listed the audio correctly and rendered it as
   `<img src="...table-read.mp3">` — a broken-image icon where a player belongs. */
.arch__item--audio {
  display: flex; flex-direction: column; gap: 6px; cursor: default;
  width: 260px; padding: 8px 10px; background: #181818;
}
.arch__item--audio:hover { transform: none; }
.arch__item--audio audio { width: 100%; }
.archview { text-align: center; }
.archview__label { margin: 0 0 10px; font-size: 13px; color: #ccc; }
.archview__img { max-width: 100%; max-height: 74vh; border-radius: 8px; }

/* ---------------------------------------------------------------------------
   THE SCENE TABS — one per scene, so "our original scene" is never the only one.
   The client: "Even the audio is like a one off for just our original scene!"
   --------------------------------------------------------------------------- */
.scenetabs {
  display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0 4px;
}
.scenetab {
  font: inherit; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 16px; border-radius: 10px;
  border: 1px solid #E0D8C9; background: #FBF8F2; color: #3A342C;
}
.scenetab:hover { background: #F4EEE2; }
.scenetab--on { background: #3A342C; border-color: #3A342C; color: #fff; }
.scenetab b { font-size: 15px; }
.scenetab__sub { font-size: 11.5px; color: #8A8175; }
.scenetab--on .scenetab__sub { color: #CFC7B8; }

/* ---------------------------------------------------------------------------
   THE VERSION GRAPH — script > scene > cut > shot, every level numbered.
   --------------------------------------------------------------------------- */
.vgraph__scene { margin-top: 16px; }
.vgraph__head h4 { margin: 0 0 4px; }
.vgraph__two {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: 18px; margin-top: 14px;
}
@media (max-width: 980px) { .vgraph__two { grid-template-columns: 1fr; } }
.vgraph__cut, .vgraph__shots {
  border: 1px solid #EFEAE1; border-radius: 10px; padding: 12px 14px; background: #FDFBF7;
}
.vgraph__cut h5, .vgraph__shots h5 {
  margin: 0 0 6px; font-size: 12px; letter-spacing: .05em; text-transform: uppercase; color: #8A8175;
}
.vgraph__big { margin: 0 0 4px; font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* A scene version with no cut says so in words, not in a dash. "cut —" is a blank wearing a
   label: the reader cannot tell whether the cut is missing or the page failed to find it. */
.vgraph__big--none { font-size: 20px; color: #A8762A; }
.vgraph__hist { margin-top: 10px; }
.vgraph__hist summary { cursor: pointer; font-size: 12.5px; color: #2F5D50; }
.vlist--shots .vrow--shot { grid-template-columns: 110px 96px minmax(0, 1fr); align-items: center; }
.vrow--script { grid-template-columns: 84px 132px minmax(0, 1.6fr) minmax(0, 1.4fr); }
.vrow__bits--vers { display: flex; flex-wrap: wrap; gap: 6px; }
.verthumb {
  position: relative; padding: 0; cursor: pointer; overflow: hidden;
  border: 1px solid #E0D8C9; border-radius: 7px; background: #EFEAE1;
  width: 84px; height: 56px; display: block;
}
.verthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.verthumb span {
  position: absolute; left: 3px; bottom: 3px;
  font-size: 10px; padding: 1px 5px; border-radius: 5px;
  background: rgba(24, 21, 17, .74); color: #fff;
}
.verthumb--current { border-color: #2F5D50; box-shadow: 0 0 0 1px #2F5D50; }
.verthumb--none {
  cursor: default; display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, #F2EDE4, #F2EDE4 6px, #EAE4D8 6px, #EAE4D8 12px);
}
.verthumb--none span { position: static; background: none; color: #8A8175; }

/* ---------------------------------------------------------------------------
   THE ANIMATIC GRID — the client: "put them into a visual grid (i can scale up
   or down to fit more on screen (like the 9 up, but we could show the whole film
   this way w/ hundreds of shots".
   --------------------------------------------------------------------------- */
#ascale { width: 190px; accent-color: #2F5D50; }
.frames--grid.anim__strip {
  grid-template-columns: repeat(auto-fill, minmax(var(--tile, 220px), 1fr));
  gap: 10px;
}
.frames--grid .anim__card { border: 0; background: none; padding: 0; }
.frames--grid .anim__meta,
.frames--grid .fcard__decide { display: none; }
.frames--grid .anim__frame img,
.frames--grid .anim__pending {
  width: 100%; height: auto; display: block; border-radius: 8px;
  border: 1px solid #E6DFD2; background: #EFEAE1;
}
.frames--grid .anim__role { display: none; }

/* A FRAME WHOSE PICTURE IS STILL RENDERING. Not a broken image — a labelled placeholder.
   Scene 02's frames are being rendered right now, and the timeline plays through them. */
.anim__pending {
  aspect-ratio: 16 / 9; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  background: repeating-linear-gradient(45deg, #F2EDE4, #F2EDE4 8px, #EAE4D8 8px, #EAE4D8 16px);
  color: #8A8175; text-align: center; padding: 8px;
}
.anim__pending-mark { font-size: 26px; line-height: 1; }
.anim__pending-text { font-size: 12px; font-weight: 600; }
.anim__pending-shot { font-size: 11px; font-variant-numeric: tabular-nums; }
.player__img--pending {
  display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, #1B1916, #1B1916 10px, #221F1B 10px, #221F1B 20px);
  color: #B9B1A4; font-size: 14px;
}
.player__scene { margin: 0 0 8px; font-size: 13px; color: #D8D1C4; }
.anim__play-btns { display: flex; flex-wrap: wrap; gap: 10px; }

/* ---------------------------------------------------------------------------
   THE VERSION BANNER — the same numbers on every view that shows a scene.
   The client: "Framing vs. animatic btw, shoudl be THE SAME versions". They
   were not: the Animatic view named its scene version and its cut and the
   Framing view named neither, so the two pages could have been showing
   different cuts with nothing on screen to say so.
   --------------------------------------------------------------------------- */
.vbar {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin: 10px 0 4px; padding: 8px 14px;
  border: 1px solid #E0D8C9; border-left: 3px solid #2F5D50; border-radius: 8px;
  background: #FBF8F2; font-size: 13px; color: #3A342C;
}
.vbar b { font-size: 14px; }
.vbar__sep { color: #C4BBAA; }
.vbar__ver { font-variant-numeric: tabular-nums; font-weight: 600; }
.vbar__cut { font-variant-numeric: tabular-nums; font-weight: 600; }
.vbar__none { font-weight: 600; color: #A8762A; }
.vbar__why { color: #8A8175; font-size: 12px; }
.vbar__note { color: #8A8175; font-size: 12px; margin-left: auto; }
.anim__scene { margin: 2px 0 0; font-size: 11.5px; }

/* ── The site version toggle (v01 side) ──────────────────────────────────────
 * The client ruled "Two full builds, switchable at the top". v02 lives at /v02
 * as a separate document, so this is a link and not a script — which is the
 * point: a broken v02 cannot strand you, and a broken v01 cannot either.
 * Styled as a switch rather than a button because it navigates between two
 * whole builds, not between two views of one. */
.vswitch {
  display: inline-flex; align-items: center; text-decoration: none;
  border: 1px solid var(--line-2, #333); border-radius: 999px; overflow: hidden;
  font-family: var(--mono, ui-monospace, monospace); font-size: 11.5px;
}
.vswitch__on { background: var(--accent, #6ea8fe); color: #06101f; padding: 3px 9px; font-weight: 700; }
.vswitch__off { color: var(--ink-3, #6f7a8a); padding: 3px 9px; }
.vswitch:hover .vswitch__off { color: var(--ink, #e8ecf2); }
