/* ════════════════════════════════════════════════════════════════════
   /work/splice — page styles.

   Loads THIRD, after ../../styles.css and ../../v2.css. Everything here is
   either (a) a component this page adds, or (b) the ported .cp-* vocabulary
   the two sibling demo pages already carry in their own stylesheets.

   THREE RULES THIS FILE OBEYS, each of which has broken a page on this site:

   1. SEMANTIC TOKENS ONLY. No colour literal appears below. The live Carbon
      values are applied by splice.js, the same way v2.js applyState() does.
   2. NOTHING REACHES INSIDE #root. The Splice app owns every pixel in there.
      #root itself is styled (it is our container's child) but no descendant
      selector crosses into it.
   3. THE APP'S OWN STYLESHEET LANDS ON THIS PAGE and carries a bare
      `body { background / color / font }` rule. Load order of that file is
      decided by the build, so every page-level body rule here is written as
      `body.splice-page` (0,1,1) to outrank a bare `body` (0,0,1) in either
      order.
   ════════════════════════════════════════════════════════════════════ */

/* ── 0. the page's own ground, defended against the app's body rule ── */
body.splice-page {
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-base);
  overflow-x: hidden;
}
/* scrollbar-safe full-bleed backstop, and the clip stays on BODY, never on the root.
   `overflow-x: clip` creates no scroll container, which is why it looked safe here, but
   Chromium leaves the ROOT computing clip rather than resetting it to visible, and that
   makes the root the sticky scrollport, so .topbar stops pinning on this page alone.
   Measured against the four sibling pages, which all pin; forcing the root back to
   visible restored it here. collapse.css:10-12 records the same rule, which is why no
   sibling sets it. body.splice-page's own overflow-x: hidden (above) does the clipping.
   --spl-vw, published by splice.js, is the real visible width; 100vw below is the no-JS
   fallback and does include the scrollbar. */

.cp-bg { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
.splice-page > main, .splice-page > .footer { position: relative; z-index: 1; }

/* accent as a FIGURE (text, glyph, hairline you have to read) versus accent as
   a SURFACE. The palette keeps lime in both themes, and lime is unreadable as a
   figure on paper, so splice.js derives --accent-fig per theme exactly the way
   it derives --line and --text-secondary. Falls back to --accent if absent. */

/* ── 1. topbar breadcrumb (defined in the demo pages, not in the site CSS) ── */
.cp-crumb {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: var(--tracking-mono);
  color: var(--text-secondary); text-decoration: none;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 5px 11px;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.cp-crumb:hover { color: var(--text-primary); border-color: var(--line-strong); background: var(--surface-panel); }
.cp-crumb .arr { color: var(--accent-fig, var(--accent)); font-style: normal; display: inline-flex; transition: transform var(--dur-base) var(--ease-out); }
.cp-crumb:hover .arr { transform: translateX(-3px); }
.cp-crumb .sep { opacity: 0.5; }
.cp-crumb .cur { color: var(--text-primary); }

/* ── 2. reveals — VISIBLE by default. Hidden only once splice.js has confirmed
   it will run (html.cv-ready), with a timeout backstop in the script. No JS, a
   thrown error or reduced motion all leave the page fully painted. ── */
.cv-rv { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  html.cv-ready .cv-rv { opacity: 0; transform: translateY(18px); transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out); }
  html.cv-ready .cv-rv.is-in { opacity: 1; transform: none; }
  html.cv-ready .cv-rv.d1 { transition-delay: 90ms; }
  html.cv-ready .cv-rv.d2 { transition-delay: 180ms; }
  html.cv-ready .cv-rv.d3 { transition-delay: 270ms; }
}

/* ── 3. the post header ───────────────────────────────────────────── */
.cp-head { position: relative; isolation: isolate; padding-block: clamp(56px, 11vh, 128px) clamp(48px, 8vh, 92px); }
.cp-head::before {
  content: ""; position: absolute; z-index: -2; pointer-events: none;
  top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: var(--spl-vw, 100vw); background: var(--surface-page);
}
.cp-head-bg {
  position: absolute; z-index: -1; pointer-events: none;
  top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: var(--spl-vw, 100vw); height: 100%;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 12%, #000 62%, transparent 98%);
  mask-image: linear-gradient(180deg, transparent, #000 12%, #000 62%, transparent 98%);
}
.splice-page .byteline { background: var(--surface-page); }

.cp-kicker {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--accent-fig, var(--accent));
  display: inline-flex; align-items: center; gap: 10px;
}
.cp-title {
  font-family: var(--font-display); font-stretch: 125%; font-weight: 800;
  text-transform: uppercase; letter-spacing: -0.01em; line-height: 0.92;
  font-size: clamp(56px, 11vw, 150px); margin: 16px 0 0; color: var(--text-primary);
}
.cp-title .tick { color: var(--accent-fig, var(--accent)); }
.cp-dek {
  margin: 26px 0 0; max-width: 760px; text-wrap: pretty;
  font-family: var(--font-display); font-stretch: 110%; font-weight: 500;
  font-size: clamp(20px, 2.4vw, 30px); line-height: 1.32; color: var(--text-primary);
}
.cp-hero-tags { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 28px; }
.cp-hero-tags .t {
  font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: var(--tracking-mono);
  color: var(--text-primary); border: 1px solid var(--line-strong); border-radius: var(--radius-1);
  padding: 7px 13px; background: color-mix(in srgb, var(--surface-panel) 70%, transparent);
}
.cp-hero-tags .t::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 8px; background: var(--accent-fig, var(--accent)); vertical-align: middle; }
.cp-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; margin-top: 30px; }
.cp-meta .m {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: var(--tracking-mono);
  color: var(--text-secondary); display: inline-flex; align-items: center; gap: 8px;
}
.cp-meta .m::before { content: "·"; color: var(--text-secondary); margin-right: 10px; }
.cp-meta .m:first-child::before { content: none; }
.cp-meta .tag {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--accent-ink); background: var(--accent);
  padding: 4px 9px; border-radius: var(--radius-1); text-decoration: none;
  border-bottom: 1px solid var(--accent-ink);
}
.cp-meta .tag:hover { filter: brightness(1.08); }

/* ── 4. prose ─────────────────────────────────────────────────────── */
.cp-lede {
  margin: 0; max-width: 880px; text-wrap: pretty; font-family: var(--font-body);
  font-size: clamp(20px, 2vw, 28px); line-height: 1.45; color: var(--text-primary); font-weight: 500;
}
.cp-prose { max-width: 740px; margin-top: 28px; display: flex; flex-direction: column; gap: 20px; }
.cp-prose p { margin: 0; font-size: var(--text-base); line-height: var(--leading-body); color: var(--text-secondary); text-wrap: pretty; }
.cp-prose strong { color: var(--text-primary); font-weight: 600; }
.cp-term { font-family: var(--font-mono); font-size: 0.9em; color: var(--text-primary); }
.cp-prose code, .cp-inline-code {
  font-family: var(--font-mono); font-size: 0.88em; color: var(--text-primary);
  background: var(--surface-panel); border: 1px solid var(--line); border-radius: var(--radius-1); padding: 1px 6px;
}
.cp-note {
  font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.6; color: var(--text-secondary);
  border-left: 2px solid var(--accent-fig, var(--accent)); padding-left: 16px; margin: 32px 0 0; max-width: 64ch;
}
.cp-close-actions { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 36px; }
.cp-stages-note { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); margin-top: 22px; }

/* ── 5. section 02 — the demo stage ───────────────────────────────────
   A full-bleed 16:9 monitor. It is a direct child of <main>, outside .col, so
   it needs no 100vw breakout. Width is capped so the locked 16:9 box can never
   be taller than the viewport, and the whole thing is a containment boundary:
   whatever the app does inside #root cannot reach the document scroll. ── */
.spl-stage {
  position: relative; margin-block: 34px 0; margin-inline: auto;
  width: 100%; max-width: max(calc((100vh - 190px) * 16 / 9), 1040px);
  background: var(--surface-panel);
  border-block: 1px solid var(--line-strong);
}
.spl-bar {
  display: flex; align-items: center; gap: 14px; padding: 11px 16px;
  border-bottom: 1px solid var(--line); background: var(--surface-page);
}
.spl-dots { display: inline-flex; gap: 6px; align-items: center; }
.spl-dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--line-strong); display: block; }
.spl-quit {
  width: 9px; height: 9px; border-radius: 50%; background: var(--line-strong);
  border: 0; padding: 0; display: grid; place-items: center; cursor: default;
  font-family: var(--font-mono); line-height: 1;
  transition: background var(--dur-fast) var(--ease-out), width var(--dur-fast) var(--ease-out);
}
.spl-quit .g { font-size: 9px; color: var(--accent-ink); opacity: 0; }
.spl-title { font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--text-secondary); }
.spl-title b { color: var(--text-primary); font-weight: 500; }
.spl-spacer { flex: 1; }
.spl-seedline { font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); white-space: nowrap; }
.vtd-meter {
  font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary);
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--radius-1); padding: 3px 9px;
  background: var(--surface-panel);
}
.vtd-meter .dot { color: var(--success); }
.vtd-meter .dot.warn { color: var(--accent); }

.spl-frame {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  background: var(--surface-page); overflow: hidden;
}
/* The app's host. Styled as a box only, and now it cannot be reached into even by accident:
   the app lives in an iframe, so its document is a separate scrolling and styling world. That
   also retires the overscroll-behavior this rule used to need, because an iframe contains its
   own scroll chain by construction rather than by a property we had to remember to set. */
#spl-app { position: absolute; inset: 0; overflow: hidden; }
.spl-app-frame {
  display: block; width: 100%; height: 100%; border: 0; background: var(--surface-page);
}

/* the poster: an opaque sibling over #root, never a child of it */
.spl-poster {
  position: absolute; inset: 0; z-index: 2; display: grid; place-items: center;
  background: var(--surface-page); padding: 24px; text-align: center;
}
.spl-poster::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0.5;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 32px 32px;
}
.spl-poster-in { position: relative; display: flex; flex-direction: column; align-items: center; gap: 18px; }
.spl-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--text-secondary); }
.spl-eyebrow::first-letter { color: var(--accent-fig, var(--accent)); }
.spl-play {
  display: inline-flex; align-items: center; gap: 14px; cursor: pointer;
  font-family: var(--font-mono); font-size: 14px; letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--accent-ink); background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius-1); padding: 15px 28px; white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease-out), filter var(--dur-fast) var(--ease-out);
}
.spl-play .glyph { font-size: 13px; }
.spl-play:hover { filter: brightness(1.08); }
.spl-play:active { transform: translateY(1px); }
.spl-play:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 3px; }
.spl-sub { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); }
.spl-rotate { display: none; position: relative; flex-direction: column; align-items: center; gap: 10px; max-width: 34ch; }
.spl-rotate .glyph { font-family: var(--font-mono); font-size: 26px; color: var(--accent-fig, var(--accent)); }
.spl-rotate .line { font-family: var(--font-mono); font-size: var(--text-sm); letter-spacing: var(--tracking-mono); color: var(--text-primary); }
.spl-rotate .sub { font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.5; color: var(--text-secondary); text-wrap: pretty; }
/* no .spl-rotate-link rules: the rotate prompt in index.html carries .glyph,
   .line and .sub only, so both rules for it matched nothing. */

.spl-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding: 10px 16px; border-top: 1px solid var(--line); background: var(--surface-page);
  font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary);
}
.spl-hint b { color: var(--text-primary); font-weight: 500; }
.spl-note { opacity: 0.8; }

/* running, in the page (the overlay closed again) */
.spl-stage[data-state="live"] .spl-poster { display: none; }

/* ── 6. the open stage. It fills the TAB, not the screen: a fixed overlay
   over the viewport, so the browser keeps its own chrome and the page keeps
   its tab. The Fullscreen API is deliberately not used. The overlay is
   .spl-stage rather than #root, so the page's own titlebar and its quit
   control travel with the app. ── */
.spl-stage.is-cssfs {
  max-width: none; width: 100%; height: 100%; margin: 0;
  display: flex; flex-direction: column; background: var(--surface-page); border: 0;
}
html.cv-ready .spl-stage.is-cssfs { transform: none; }
/* The overlay has to leave main's stacking context before its z-index can mean
   anything: .splice-page > main is position:relative + z-index:1, so a 4000
   inside it still painted under .topbar (1000), .meter-chip (9000),
   .crt-overlay (9400) and .xhair (9500). Measured with .is-cssfs applied:
   elementFromPoint over the meter chip returned the chip, and at scroll 0 over
   the topbar returned a nav link. 9700 clears all four and stays under
   .loader's 10000. This rule is now on the only path there is, where before it
   carried a fallback the native top layer made inert, so it is load-bearing on
   every open and is asserted by probing the open overlay rather than reasoned
   about. splice.js puts spl-fs-lock on html and body for the scroll lock. */
body.splice-page.spl-fs-lock > main { z-index: auto; }
.spl-stage.is-cssfs { position: fixed; inset: 0; z-index: 9700; height: 100dvh; }
.spl-stage.is-cssfs .spl-frame { aspect-ratio: auto; flex: 1 1 auto; min-height: 0; }
.spl-stage.is-cssfs .spl-poster { display: none; }
.spl-stage.is-cssfs .spl-foot { display: none; }
.spl-stage.is-cssfs .spl-quit { background: var(--accent); cursor: pointer; width: 13px; height: 13px; }
.spl-stage.is-cssfs .spl-quit .g { opacity: 1; }
.spl-quit:focus-visible { outline: 2px solid var(--accent-fig, var(--accent)); outline-offset: 3px; }

/* The quit dot is the only way out of the stage from inside the page, and it draws at 13x13, which
   is half the 24px WCAG 2.5.8 floor and well under what a finger wants. It passes today only on the
   spacing exception, because nothing else clickable sits near it. Grow the TARGET without growing
   the dot: a transparent ::after takes the hit area to 29x29 and the bezel still reads as three
   small dots. Sits outside .spl-dots' flex flow, so no sibling moves. */
.spl-quit { position: relative; }
.spl-quit::after { content: ""; position: absolute; inset: -8px; border-radius: 50%; }
html.spl-fs-lock, body.splice-page.spl-fs-lock { overflow: hidden; }

/* ── 7. the legend under the stage ──
   Two inherited rules have to be answered here, both by longhand:
   body[data-density="air"] .sec sets padding-top:170px, and .sec's padding
   SHORTHAND removes the 24px gutter .col supplies (invisible above 1280px,
   where the column is already inset, and visible on every laptop below it). */
body.splice-page .sec.col { padding-inline: 24px; }
body.splice-page .spl-sec-tail { padding-block-start: 18px; }
.spl-legend { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.spl-legend span { font-family: var(--font-mono); font-size: 11px; color: var(--text-secondary); display: inline-flex; align-items: center; gap: 8px; }
.spl-legend i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.spl-legend .sw-colony { background: var(--accent); }
.spl-legend .sw-invader { background: transparent; box-shadow: inset 0 0 0 2px var(--text-primary); }
.spl-legend .sw-reagent { background: var(--hot); }

/* ── 8. the boot sequence ────────────────────────────────────────────
   Play does not snap to fullscreen. The poster turns into a short console
   boot: the play control drops away, five mono lines type themselves in over
   a scan sweep, and only then does the stage open, unfolding from its middle
   like a monitor coming on rather than cutting. Reduced motion skips it. ── */
.spl-boot {
  display: none; position: relative; list-style: none; margin: 0; padding: 0;
  width: min(340px, 80%); text-align: left;
  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-secondary);
}
.spl-stage[data-state="booting"] .spl-boot { display: block; }
.spl-stage[data-state="booting"] .spl-poster-in,
.spl-stage[data-state="booting"] .spl-rotate { display: none; }
.spl-boot li { opacity: 0; display: flex; gap: 10px; align-items: baseline; padding: 3.5px 0; white-space: nowrap; }
.spl-boot li::before { content: "▸"; color: var(--accent-fig, var(--accent)); font-size: 10px; }
.spl-boot li.ok { color: var(--text-primary); }
.spl-boot li.ok::before { content: "●"; }
/* The five lines advance on their OWN animation timeline, staggered here rather than by a timer
   per line in splice.js. A same-origin iframe shares the parent's main thread, so the app's module
   compile sat exactly where those timers needed to fire: measured on the live page with the thread
   blocked for 400ms, the gaps read 197, 473, 198, 208 against 195 intended, a 278ms stall in the
   middle of the console. An animation keeps its own clock and a busy main thread cannot reschedule
   it. Opacity is 1 in both stops and the clip does the reveal, which is exactly what the old .on
   class did, so the look is unchanged; backwards fill holds a line fully clipped until its delay
   elapses. 195ms stagger x 5 lines with a 300ms reveal means the last one finishes at 1080ms. */
.spl-boot.is-booting li { animation: spl-type 300ms steps(26, end) both; }
.spl-boot.is-booting li:nth-child(2) { animation-delay: 195ms; }
.spl-boot.is-booting li:nth-child(3) { animation-delay: 390ms; }
.spl-boot.is-booting li:nth-child(4) { animation-delay: 585ms; }
.spl-boot.is-booting li:nth-child(5) { animation-delay: 780ms; }
@keyframes spl-type {
  from { opacity: 1; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

.spl-stage[data-state="booting"] .spl-poster::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: 0.5; animation: spl-scan 1.2s var(--ease-inout) infinite;
}
@keyframes spl-scan { from { top: 0; } to { top: 100%; } }

.spl-stage.is-opening .spl-frame { animation: spl-unfold 520ms var(--ease-out) both; }
@keyframes spl-unfold {
  from { clip-path: inset(47% 0 47% 0); opacity: 0.25; }
  60%  { opacity: 1; }
  to   { clip-path: inset(0 0 0 0); opacity: 1; }
}
.spl-stage.is-opening .spl-bar { animation: spl-barin 300ms var(--ease-out) 140ms both; }
.spl-stage.is-opening .spl-foot { animation: spl-barin 300ms var(--ease-out) 140ms both; }
@keyframes spl-barin { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.spl-stage.is-closing .spl-frame { animation: spl-fold 240ms var(--ease-inout) both; }
@keyframes spl-fold {
  from { clip-path: inset(0 0 0 0); opacity: 1; }
  to   { clip-path: inset(48% 0 48% 0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .spl-boot li { opacity: 1; animation: none; }
  .spl-stage[data-state="booting"] .spl-poster::after { display: none; }
  .spl-stage.is-opening .spl-frame,
  .spl-stage.is-opening .spl-bar,
  .spl-stage.is-opening .spl-foot,
  .spl-stage.is-closing .spl-frame { animation: none; }
}

/* ── 9. section 03 — the eight run steps.
   Reuses .award / .award-in / .award-cover from v2.css, laid out two up on a
   shared hairline grid instead of one full-width row per step, so eight of
   them read as one run rather than eight essays. The .awards-bar base pairing
   is NOT restated here: v2.css:2249 carries the light-theme fix for it and a
   rule in this file would land after it. ── */
.cp-stages-awards { margin-top: 34px; max-width: 1080px; }
.cp-stage-row {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  border-left: 1px solid var(--line);
}
.cp-stage-lite { border-right: 1px solid var(--line); }
.cp-stage-lite .award-in {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 11px; padding: 24px 22px 26px;
}
.cp-stage-lite .a-year {
  position: static; right: auto; top: auto;
  display: flex; flex-direction: row; align-items: baseline; gap: 9px;
  width: auto; flex-shrink: 0; line-height: 1.4;
  font-size: 11px; letter-spacing: var(--tracking-mono); color: var(--text-secondary);
}
.cp-stage-lite .a-year b { font-weight: inherit; color: var(--accent-fig, var(--accent)); }
.cp-stage-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.cp-stage-body h3 {
  margin: 0; font-family: var(--font-display); font-stretch: 125%; font-weight: 800;
  text-transform: uppercase; letter-spacing: -0.01em;
  font-size: clamp(19px, 1.7vw, 25px); line-height: 1.04; color: var(--text-primary);
}
.cp-stage-body p { margin: 0; font-size: var(--text-sm); line-height: 1.6; color: var(--text-secondary); text-wrap: pretty; max-width: 48ch; }
.cp-stage-lite .award-in::before, .cp-stage-lite .award-in::after { content: none; display: none; }
.cp-stage-lite:hover .award-in { transform: none; }
@media (prefers-reduced-motion: reduce) { .cp-stage-lite .award-cover { display: none; } }

/* The eight run-step covers must not outlive a page with no JS. v2.css:618 makes .award-cover an
   opaque plate and v2.css:621 only slides it away on .award.in, which nothing but the reveal script
   adds, so without JS section 03 renders as eight blank panels. Verified by loading the page with
   JavaScript disabled: the card screenshots as a flat rectangle. That contradicts this page's own
   design, which is that no JS leaves it fully painted.

   Same idiom as .cv-rv above: uncovered by default, covered only once the script has said it will
   animate. Scoped to .cp-stage-lite so the base v2.css behaviour, which the home page and
   /work/collapse also rely on, is untouched. Both of those share the underlying bug and need the
   same fix in v2.css, which is a separate change because the home page's v2.js sets no cv-ready. */
.cp-stage-lite .award-cover { transform: translateX(101%); }
html.cv-ready .cp-stage-lite .award-cover { transform: none; }
html.cv-ready .cp-stage-lite.award.in .award-cover { transform: translateX(101%); }


/* ── 10. accent-as-figure, for the accent glyphs that live in v2.css.
   In dark --accent-fig IS --accent, so these are no-ops there. In light they
   are the difference between a visible mark and a 1.06:1 one. ── */
.splice-page .sec-label::before,
.splice-page .seam-label::before,
.splice-page .byteline::before { color: var(--accent-fig, var(--accent)); }

/* The seam's "current stage" pip. splice.js marks pip 6 .cur and v2.css:1687
   blinks it, seamblink2 0.6s steps(2) infinite, so the one thing section 3 of
   splice.js promises does not move below the hero is moving on every byteline.
   Under data-seam="s7" the pip sits at opacity 0.7 (v2.css:1760) and the
   keyframe takes it to 0.8 twice a second. It keeps its accent border from
   v2.css:1765, so the current stage still reads; only the motion goes. */
.splice-page .seam-build.cur { animation: none; }

.splice-page .footer { margin-top: 108px; }

/* ══════════════════════════════════════════════════════════════════════
   11. THE NARROW-VIEWPORT BLOCK — back-portable

   Lift this whole block into collapse.css and reconcile.css unchanged. The
   only per-page value is --cp-title-len, an inline custom property on the h1
   holding the glyph count of the title including its period (Splice. = 7,
   Collapse. = 9, Reconcile. = 10). It defaults to 10, the longest title in
   the family, so a page that forgets it is still correct.

   Archivo 800 at font-stretch 125% measures 0.71em to 0.81em per glyph for
   these titles, letterform-dependent (443px for RECONCILE. at 56px, 406px for
   COLLAPSE. at 56px, 277px for SPLICE. at 56px). 0.84 is the divisor, which
   leaves 14px of slack on the worst sibling. The rule never exceeds the
   desktop clamp, so nothing above 560px moves.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 560px) {
  /* fault 1 — the headline overran its box. Now it is sized off the box. */
  .cp-title {
    font-size: min(
      clamp(56px, 11vw, 150px),
      calc((100vw - 48px) / (var(--cp-title-len, 10) * 0.84))
    );
    line-height: 0.94;
  }

  /* fault 2 — the topbar's right cluster did not collapse. The nav takes its
     own row (the home page's own answer to this width), the frame-time meter
     goes, and the breadcrumb shortens to "← posts". Nothing is removed. */
  .topbar .in { flex-wrap: wrap; gap: 10px 14px; padding: 12px 16px; }
  .topbar nav { order: 3; flex: 1 0 100%; gap: 18px; justify-content: space-between; }
  .topbar .right { gap: 12px; }
  .topbar .nav-meter { display: none; }
  .cp-crumb .sep, .cp-crumb .cur { display: none; }

  /* everything else that can push a phone sideways */
  .cp-head { padding-block: 44px 40px; }
  .cp-dek { font-size: 19px; }
  .cp-lede { font-size: 19px; }
  .cp-prose { margin-top: 22px; }
  .meter-chip { display: none; }
}

/* the demo, narrow: the run steps go one up and the stage drops its seedline.
   Which face of the poster shows is NOT decided here. The TEMPORARY block
   directly below owns that, and while it stands it is the notice in both
   orientations, never the play control. */
@media (max-width: 900px) {
  .cp-stage-row { grid-template-columns: 1fr; }
  .cp-stage-lite .award-in { padding: 22px 16px 24px; }
  .spl-seedline { display: none; }
}
/* TEMPORARY: the demo is desktop-only until the phone layout pass lands. Delete this one
   block and the poster's phone path returns exactly as delivered. The gate is 900px rather than
   760 because a phone in landscape is still a phone and must not fall through to the play button,
   and because the stage's own width floor is 1040px, so anything under that is squeezed anyway. */
@media (max-width: 900px) {
  .spl-poster-in { display: none; }
  .spl-rotate { display: flex; }
}

@media (max-width: 760px) and (orientation: portrait) {
  .spl-stage { max-width: none; }
  .spl-frame { aspect-ratio: 5 / 6; }
  .spl-foot .spl-note { display: none; }
}
@media (max-width: 900px) and (orientation: landscape) {
  .spl-stage { max-width: none; }
  .spl-frame { aspect-ratio: auto; height: calc(100vh - 190px); min-height: 240px; }
}
