/* FrankenBits landing — POC styles.
   Full-bleed video "stages"; one visible at a time via .is-active. Dark/atmospheric to match
   the Twinmotion renders (near-monochrome, single focal light). */

:root {
  --bg: #05060a;
  --fg: #e8ecf2;
  --accent: #cfe3ff;      /* cool lightning-white */
  --dim: #8a93a6;
  --cta-bg: rgba(207, 227, 255, 0.08);
  --cta-border: rgba(207, 227, 255, 0.45);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The HTML `hidden` attribute must win over author display rules (e.g. .lab-nav{display:flex}).
   Without this, display:flex overrides UA [hidden]{display:none} and elements meant to be toggled
   via JS (lab-nav, mirror-settle, mirror-loop, hero-still, dest-video) show when they shouldn't. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  overflow: hidden;
}

/* ── stages ─────────────────────────────────────────────── */
.stage {
  position: fixed;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.9s ease;
  z-index: 0;
}
.stage.is-active {
  opacity: 1;
  visibility: visible;
  z-index: 1;
}

.bg-video,
.bg-still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;      /* full-bleed, no letterbox */
  background: var(--bg);
}

/* loop video: start transparent and fade in only once it's actually painting a frame (JS adds
   .is-shown on 'playing'/'canplay'), so the poster (Image5_002.png) holds underneath for ~1-2s
   instead of tearing on the poster→first-decoded-frame swap. Same anti-flash grammar as the
   mirror-ball handoffs below. */
#loop-video {
  opacity: 0;
  transition: opacity 0.6s ease;
}
#loop-video.is-shown { opacity: 1; }
/* poster still under the loop: visible on load, fades out once the video is painting */
#loop-poster {
  z-index: 0;
  transition: opacity 0.6s ease;
}
#loop-poster.is-hidden { opacity: 0; }

/* dive → settle → loop handoffs: each incoming mirror-ball clip starts transparent and fades in
   over the previous clip's last frame once it's actually painting (JS adds .is-shown on the
   'playing' event). This bridges decode latency so there's no flash/blip between clips.
   #mirror-loop sits above #mirror-settle (which sits above #dive-video) during each crossfade. */
#mirror-settle,
#mirror-loop {
  opacity: 0;
  transition: opacity 0.9s ease;
}
#mirror-settle { z-index: 2; }        /* above #dive-video */
#mirror-loop   { z-index: 3; }        /* above #mirror-settle */
#mirror-settle.is-shown,
#mirror-loop.is-shown { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  #mirror-settle,
  #mirror-loop { transition: none; }
}

/* ── overlay (stage 1) ──────────────────────────────────── */
.overlay {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1.1rem;
  /* keep text legible over bright lightning */
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.9);
}

.brand {
  font-size: clamp(2.4rem, 8vw, 6rem);
  letter-spacing: 0.35em;
  font-weight: 300;
  padding-left: 0.35em; /* optical balance for the letter-spacing */
}
.tagline {
  color: var(--dim);
  font-size: clamp(0.85rem, 2vw, 1.1rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ── TITLE SEQUENCE (movie intro) ────────────────────────────
   Staged credit cards shown over the loop on every arrival at the loop stage. Cards fade in/out
   one at a time, then the Enter button fades in under the final card.
   NO-BOUNCE: the band is a FIXED-HEIGHT stage and every card is absolutely centered inside it, so
   swapping cards never changes the overlay height — the brand above and button below stay put.
   (The earlier version toggled position:absolute↔relative, which resized the band and bounced
   the brand.) */
.title-band {
  position: relative;
  width: 100%;
  height: 11rem;               /* fixed stage height → button never shifts as cards cycle. Grown
                                  from 9rem to fit the final card's stacked brand + identity line. */
  display: block;
}
.title-card {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  opacity: 0;
  transition: opacity 0.8s ease;
  pointer-events: none;
}
.title-card.is-shown { opacity: 1; }

/* ── PER-CARD TYPE SCALE ─────────────────────────────────────────────────────
   The FRANKENBITS brand is no longer ever-present above the cards, so each card stands alone and
   needs its own weight in the arc: understated studio bumper → mood line → the discipline (main +
   smaller sub) → tiny production credit → the IDENTITY payoff (brand + Perfect Storm Labs). The
   `.title-lab` base is the mid "big line"; per-card overrides below tune the hierarchy. */

/* base "big line" (default for a card's primary line) */
.title-lab {
  font-size: clamp(1.4rem, 4vw, 2.6rem);
  font-weight: 300;
  letter-spacing: 0.18em;
  padding-left: 0.18em;
  color: var(--accent);
  text-shadow: 0 0 24px rgba(120, 200, 255, 0.35);
  text-align: center;
}

/* Card 1 — studio bumper: subordinate to the big lines but comfortably readable (bumped up). */
[data-title-card="1"] .title-lab {
  font-size: clamp(1.3rem, 3.4vw, 2.1rem);
  color: var(--dim);
  letter-spacing: 0.2em;
}

/* Card 2 — the invitation: serif-italic mood line (sets tone, not a headline) */
[data-title-card="2"] .title-lab {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  font-size: clamp(1.2rem, 3.2vw, 2rem);
  letter-spacing: 0.08em;
  padding-left: 0;
  color: var(--accent);
}

/* Card 3 — the discipline: main line mid-size, the B2B line a smaller subordinate (was wrongly
   rendering big because it carried .title-lab; scope it down here). */
[data-title-card="3"] .title-lab {
  font-size: clamp(1.2rem, 3.2vw, 2.1rem);
}
[data-title-card="3"] .title-sub-b2b {
  font-size: clamp(0.8rem, 1.9vw, 1.05rem);
  color: #7fb0cf;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-shadow: none;
}

/* Card 5 — the identity payoff: brand prominent but SIZED TO FIT the band above the Perfect Storm
   Labs line (smaller than the old standalone hero brand), identity line as the resolving sub. */
[data-title-card="5"] .brand {
  font-size: clamp(1.8rem, 5vw, 3.4rem);
  letter-spacing: 0.3em;
  padding-left: 0.3em;
  font-weight: 300;
  margin: 0;
  text-shadow: 0 0 28px rgba(120, 200, 255, 0.4), 0 2px 24px rgba(0,0,0,0.9);
}
[data-title-card="5"] .title-lab {
  font-size: clamp(1rem, 2.6vw, 1.5rem);
  letter-spacing: 0.24em;
  padding-left: 0.24em;
  color: var(--accent);
}

.title-sub {
  color: var(--dim);
  font-size: clamp(0.8rem, 1.9vw, 1.05rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
}
.title-sub-b2b { color: #7fb0cf; letter-spacing: 0.14em; }

/* production credit (italic serif, silent-film grammar) */
.title-credit {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  color: var(--dim);
  font-size: clamp(1.1rem, 2.8vw, 1.7rem);
  letter-spacing: 0.1em;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .title-card { transition: none; }
}

/* ── buttons ────────────────────────────────────────────── */
.cta,
.nav-link {
  margin-top: 1rem;
  padding: 0.8rem 2.2rem;
  font: inherit;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--cta-bg);
  border: 1px solid var(--cta-border);
  border-radius: 2px;
  cursor: pointer;
  backdrop-filter: blur(3px);
  transition: background 0.3s ease, transform 0.15s ease;
}
.cta:hover,
.nav-link:hover { background: rgba(207, 227, 255, 0.18); }
.cta:active,
.nav-link:active { transform: translateY(1px); }

/* Enter button is PENDING during the title sequence: invisible + non-interactive until the
   sequence completes, then JS removes .is-pending to fade it in. */
.cta.is-pending {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.8s ease;
}
@media (prefers-reduced-motion: reduce) {
  .cta.is-pending { transition: none; }
}

/* ── PROJECTED WORDMARK (site identity) ──────────────────────
   The homepage had no marking of what you're looking at. This casts "Perfect Storm Labs" over the
   beacon in the SAME projected-light grammar as the nav, but ignites a heartbeat LATER (JS adds
   .wordmark-lit ~700ms after the nav reveal) so it reads as its own entity powering on — not part
   of the same flash as the links. Anchored by --bx/--by (% of frame) over the beacon/orb.
   Web-side stand-in for a future in-world sign (wall placard / book on the bench). */
.proj-wordmark {
  position: absolute;
  left: var(--bx, 47%);
  top: var(--by, 36%);
  transform: translate(-50%, -50%);
  z-index: 5;
  pointer-events: none;              /* identity only, not a link */
  text-align: center;
  opacity: 0;                        /* dark until JS ignites it (.wordmark-lit) */
}
.proj-wordmark .pw-main {
  display: block;
  font-size: clamp(1.1rem, 2.6vw, 2.1rem);
  letter-spacing: 0.3em;
  padding-left: 0.3em;               /* optical balance for the tracking */
  text-transform: uppercase;
  font-weight: 300;
  white-space: nowrap;
  color: #d6f3ff;                    /* same cyan core as the links */
  /* two tight DARK stops first (contrast edge over the bright orb), then the cyan bloom */
  text-shadow:
    0 1px 2px rgba(0, 10, 20, 0.95),
    0 0 3px  rgba(0, 10, 20, 0.9),
    0 0 5px  rgba(190, 240, 255, 0.95),
    0 0 16px rgba(90, 210, 255, 0.8),
    0 0 36px rgba(60, 170, 255, 0.55),
    0 0 68px rgba(40, 140, 255, 0.35);
}
/* JS adds .wordmark-lit a heartbeat after the nav → ignite, then settle into the living flicker */
.proj-wordmark.wordmark-lit {
  animation:
    projIgnite 0.7s ease forwards,
    projFlicker 5.0s ease-in-out 0.7s infinite;
}

@media (prefers-reduced-motion: reduce) {
  /* no flash/flicker: once lit, just show steady (keep the centering transform) */
  .proj-wordmark.wordmark-lit { animation: none; opacity: 1; }
}

/* ── PROJECTED-LIGHT NAV ─────────────────────────────────────
   Destinations read as cyan energy "projected" onto the bench by the orb — no boxes, just
   glowing light-text that brightens the wood (mix-blend-mode: screen). Laid in a shallow arc
   following the bench perspective, igniting from the center outward. Lives as a web overlay so
   the text stays crisp/editable; later it can register to in-scene objects (rolling mirror balls
   or a flip-up board) once those are rendered. */
.lab-nav {
  position: absolute;
  z-index: 5;                        /* above the mirror-ball hub videos (settle z2, loop z3) */
  inset: 0;                          /* full-bleed: .projection anchors links by % of the frame */
  pointer-events: none;              /* gutter ignores clicks; links re-enable below */
}

/* Full-bleed anchoring layer: each .proj-link is absolutely placed at its ball's --bx/--by.
   (Was a centered flex row; now the balls define the layout. See webdev/ball_anchoring_NOTES.md.) */
.projection {
  position: relative;
  width: 100%;
  height: 100%;
  pointer-events: none;              /* only the links take clicks */
}

/* projected light label: glow-text, no chrome, blends onto the wood */
.proj-link,
.proj-link:link,
.proj-link:visited {
  margin: 0;
  padding: 0.35rem 0.5rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: clamp(0.8rem, 1.5vw, 1.05rem);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  color: #d6f3ff;                                   /* near-white cyan core (also for :visited) */
}

.proj-link {
  /* anchored to its ball: --bx/--by are % of the frame; translate centers the label on the point */
  position: absolute;
  left: var(--bx, 50%);
  top: var(--by, 72%);
  transform: translate(-50%, -50%);
  pointer-events: auto;              /* .projection/.lab-nav ignore clicks; links re-enable here */
  /* glowing cyan light-text (no box). NOTE: deliberately NOT using mix-blend-mode here — screen
     blend rendered the text flat/invisible in testing; the layered glow alone sells "projected".
     The two tight DARK stops first give the light text a contrast edge so it stays legible when a
     label overlaps a bright area (the central orb glow); the cyan bloom stops sell the projection. */
  text-shadow:
    0 1px 2px rgba(0, 10, 20, 0.95),
    0 0 3px  rgba(0, 10, 20, 0.9),
    0 0 4px  rgba(190, 240, 255, 0.95),
    0 0 12px rgba(90, 210, 255, 0.8),
    0 0 28px rgba(60, 170, 255, 0.55),
    0 0 54px rgba(40, 140, 255, 0.35);
  /* ignite from center outward: --delay set per item (0 at center, larger at the edges) */
  opacity: 0;
  animation:
    projIgnite 0.6s ease forwards,
    projFlicker 4.2s ease-in-out infinite;
  animation-delay:
    var(--delay, 0ms),
    calc(var(--delay, 0ms) + 0.6s);
}

.proj-link:hover {
  color: #ffffff;
  text-shadow:
    0 0 6px  rgba(220, 250, 255, 1),
    0 0 18px rgba(120, 225, 255, 0.95),
    0 0 42px rgba(80, 190, 255, 0.7),
    0 0 80px rgba(60, 170, 255, 0.5);
}
.proj-link:focus-visible {
  outline: 1px solid rgba(150, 225, 255, 0.8);
  outline-offset: 4px;
}

/* Investor Relations — not built yet: dimmer, cooler, non-interactive, "soon" tag */
.proj-link.is-placeholder {
  color: #7fb0cf;
  cursor: default;
  text-shadow: 0 0 6px rgba(90, 160, 200, 0.5), 0 0 16px rgba(70, 130, 180, 0.3);
}
.proj-link.is-placeholder::after {
  content: " · soon";
  opacity: 0.55;
  font-size: 0.78em;
  letter-spacing: 0.1em;
}

/* flicker up as the apparatus "powers" the projection on */
@keyframes projIgnite {
  0%   { opacity: 0; }
  35%  { opacity: 0.85; }
  48%  { opacity: 0.2; }    /* arc flicker */
  65%  { opacity: 1; }
  78%  { opacity: 0.55; }
  100% { opacity: 1; }
}

/* subtle living flicker on the projected light (energy isn't perfectly steady) */
@keyframes projFlicker {
  0%, 100% { opacity: 1; }
  47%      { opacity: 0.92; }
  50%      { opacity: 0.98; }
  53%      { opacity: 0.9; }
}

/* "back outside" — small projected label in the corner, out of the destination fan */
.nav-corner {
  position: absolute;
  z-index: 6;
  bottom: 1.4rem;
  left: 1.4rem;
  margin: 0;
  pointer-events: auto;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  transform: none;
  animation: none;
  opacity: 0.85;
}

.forge-back {
  position: absolute;
  z-index: 3;
  top: 1.5rem;
  left: 1.5rem;
  margin: 0;
}

/* SKIP button — directly beneath "Back to the Lab" (same top-left corner), jumps to the end of
   the forge journey (the shield payoff). Matches .forge-back exactly (same .nav-link size/weight);
   only the top offset differs so it stacks beneath. */
.forge-skip {
  position: absolute;
  z-index: 3;
  top: 4.6rem;            /* stacked just under .forge-back (1.5rem top + its height + a small gap) */
  left: 1.5rem;
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  /* keep the anchor-centering transform; just stop the flicker/ignite and show at full opacity */
  .proj-link { animation: none; opacity: 1; transform: translate(-50%, -50%); }
}

@media (prefers-reduced-motion: reduce) {
  .bench-deck .nav-link { animation: none; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .stage { transition: none; }
}

/* ── SILENT-FILM INTERTITLE ─────────────────────────────────────────────────
   Shown BETWEEN autochained forge cuts to cover the swap/buffer and give the sequence movie-like
   flair. Full-bleed over the journey stage; fades via .is-shown (same grammar as the video
   handoffs). CSS/text for now — to use a RENDERED card later, set `background-image` on
   `.intertitle-frame` (or replace #intertitle-text with an <img>) and the layout still holds. */
.intertitle {
  position: fixed;            /* top-level overlay → can cover ANY stage (load, lab, journey) */
  inset: 0;
  z-index: 50;               /* above all stages/nav */
  display: grid;
  place-items: center;
  background: #000;           /* hard black = the hard-cut boundary; hides the clip swap */
  opacity: 0;
  transition: opacity 0.45s ease;
  pointer-events: none;       /* purely decorative; never traps clicks */
}
.intertitle.is-shown { opacity: 1; }

/* the framed "card": thin double-rule border like a title card, with a soft vignette */
.intertitle-frame {
  position: relative;
  max-width: min(78vw, 60rem);
  padding: clamp(1.5rem, 5vw, 3.5rem) clamp(2rem, 7vw, 5rem);
  border: 1px solid rgba(207, 227, 255, 0.22);
  outline: 1px solid rgba(207, 227, 255, 0.1);
  outline-offset: 6px;
  background:
    radial-gradient(ellipse at center, rgba(207, 227, 255, 0.04) 0%, transparent 70%),
    #04050a;
  /* background-image hook: drop a rendered card here later and it fills the frame */
  background-size: cover;
  background-position: center;
}

/* CARD-IMAGE MODE: when JS sets a rendered card (showCard), the frame BECOMES the image —
   drop the text-card chrome (border/vignette/padding) and show the art at its own aspect ratio,
   fit to the viewport. The card art already has its own ornate border, so no outer frame. */
.intertitle.has-card .intertitle-frame {
  max-width: min(92vw, 70rem);
  width: min(92vw, 70rem);
  aspect-ratio: var(--card-ar, 648 / 330);   /* per-card ratio (set by showCard from the image's
                                                natural size); default = the sliced-card ratio */
  padding: 0;
  border: none;
  outline: none;
  background-color: #000;
  background-size: contain;    /* show the whole card, never crop the border */
  background-repeat: no-repeat;
  background-position: center;
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.9);
}
.intertitle.has-card .intertitle-text { display: none; }   /* image carries the words */

.intertitle-text {
  margin: 0;
  text-align: center;
  color: var(--accent);
  font-family: Georgia, "Times New Roman", serif;   /* silent-film serif */
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0.06em;
  line-height: 1.4;
  font-size: clamp(1.25rem, 3.5vw, 2.4rem);
  text-shadow: 0 0 18px rgba(207, 227, 255, 0.35), 0 2px 6px rgba(0, 0, 0, 0.8);
}
/* empty caption → clean black hold: hide the framed box entirely (set by JS via .no-text) */
.intertitle.no-text .intertitle-frame { border: none; outline: none; background: transparent; }
.intertitle-text:empty { display: none; }

/* subtle film grain + flicker so the card reads as "projected", not a flat web div */
.intertitle::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.06;
  background-image: repeating-radial-gradient(
    circle at 50% 50%,
    rgba(255, 255, 255, 0.08) 0 1px,
    transparent 1px 2px
  );
  animation: intertitle-flicker 2.6s steps(8) infinite;
}
@keyframes intertitle-flicker {
  0%, 100% { opacity: 0.05; }
  50%      { opacity: 0.08; }
}

@media (prefers-reduced-motion: reduce) {
  .intertitle { transition: none; }
  .intertitle::after { animation: none; }
}

/* ── SHIELD PAYOFF (end of the forge journey = the second hub) ───────────────
   The shield loop is a slow non-rotating beaker energy-pulse that takes over when forge_cut_3
   ends on the shield frame. Anti-flash: start transparent, fade in once painting (JS adds
   .is-shown) over cut_3's held last frame — same grammar as the mirror settle→loop handoff.
   Sits above #dest-video so the crossfade reads cleanly. */
#shield-loop {
  opacity: 0;
  transition: opacity 0.9s ease;
  z-index: 2;                 /* above #dest-video (the held cut_3 last frame) */
}
#shield-loop.is-shown { opacity: 1; }

/* CREDITS ROLL — a slow upward scroll over the live shield, silent-film grammar (bookends the
   opening title sequence). Full-bleed layer; the scroll column starts below the frame and rises
   out the top. Fades in via .is-shown; the scroll runs via .is-rolling. The roll stays in the
   UPPER/MID band so it clears the LED nav band (~y71%) where the links ignite afterward. */
.shield-credits {
  position: absolute;
  inset: 0;
  z-index: 4;                 /* above the shield loop, below nothing it needs to block */
  overflow: hidden;
  pointer-events: none;       /* never traps clicks over the loop */
  opacity: 0;
  transition: opacity 0.8s ease;
  /* mask so text softly appears/vanishes at the top & bottom edges (projector feel) */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 70%, transparent 86%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 70%, transparent 86%);
}
.shield-credits.is-shown { opacity: 1; }

.credits-scroll {
  position: absolute;
  left: 50%;
  top: 100%;                  /* starts just below the viewport, scrolls up into view */
  transform: translateX(-50%);
  width: min(90vw, 40rem);
  text-align: center;
  /* keep the roll in the upper/mid region: it rises from bottom and exits top, passing through
     the clear zone above the beaker, not lingering on the LED band. */
}
.shield-credits.is-rolling .credits-scroll {
  animation: creditsRoll 22s linear forwards;   /* keep duration == CREDITS_ROLL_MS in app.js */
}

/* silent-film credit typography (echoes .title-* + .intertitle-text) */
.credits-scroll p { margin: 0.55rem 0; line-height: 1.4; text-shadow: 0 2px 18px rgba(0,0,0,0.85); }
.cr-head {
  font-size: clamp(1.4rem, 3.5vw, 2.4rem);
  font-weight: 300;
  letter-spacing: 0.22em;
  padding-left: 0.22em;
  color: var(--accent);
  text-shadow: 0 0 24px rgba(120, 200, 255, 0.4), 0 2px 18px rgba(0,0,0,0.85);
}
.cr-sub {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  color: var(--dim);
  font-size: clamp(0.95rem, 2vw, 1.2rem);
}
.cr-role {
  margin-top: 1.4rem;
  font-size: clamp(0.75rem, 1.6vw, 0.95rem);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #7fb0cf;
}
.cr-name { font-size: clamp(0.85rem, 1.8vw, 1.05rem); color: var(--fg); letter-spacing: 0.04em; }
.cr-gap  { color: var(--dim); opacity: 0.5; }
.cr-fine {
  margin-top: 1.4rem;
  font-size: clamp(0.72rem, 1.4vw, 0.85rem);
  letter-spacing: 0.1em;
  color: var(--dim);
  font-style: italic;
}

@keyframes creditsRoll {
  from { top: 100%; }
  to   { top: -120%; }        /* fully off the top (column height-dependent; generous end) */
}

/* shield nav uses the shared .lab-nav / .proj-link projected-light grammar (anchored by --bx/--by
   to the 4 LED marks). Just ensure it layers above the shield loop + credits. */
.shield-nav { z-index: 6; }

@media (prefers-reduced-motion: reduce) {
  #shield-loop { transition: none; }
  .shield-credits { transition: none; }
  /* no long auto-scroll: show the credits block statically (centered) so nothing moves */
  .shield-credits.is-rolling .credits-scroll { animation: none; top: 50%; transform: translate(-50%, -50%); }
}
