/* =========================================================================
   Shadow — download site
   =========================================================================

   Measured against the reference rather than guessed at, because two earlier
   passes were guessed at and both were wrong.

   What reading their shipped CSS actually established:
     · the face is Geist, not Inter — 400 for body, 500 for emphasis, with
       negative tracking of about -0.01em that keeps large type tight;
     · body text is rgba(255,255,255,0.7) and headings rgb(214,214,214) —
       neither is pure white, which is why nothing glares;
     · there is a whole layer of 6.5px monospace marginalia at 16-25% white
       ("S.00", "ROUTE /HOME", "45PX GRID / 1:1"). It reads as a technical
       drawing and costs nothing;
     · the grid is 45px, by their own annotation.

   The layout is a single centred column with its edges drawn as hairlines.
   Everything hangs off those two rules: the wordmark and the nav sit on top of
   it, the headline starts at its left, the phone ends at its right.
   ========================================================================= */

@font-face {
  font-family: 'Geist';
  src: url('assets/fonts/Geist-Variable.woff2') format('woff2-variations');
  font-weight: 100 900; font-display: swap;
}
@font-face {
  font-family: 'GeistMono';
  src: url('assets/fonts/GeistMono-Variable.woff2') format('woff2-variations');
  font-weight: 100 900; font-display: swap;
}
@font-face {
  font-family: 'Cinzel';
  src: url('assets/fonts/Cinzel-Variable.ttf') format('truetype-variations');
  font-weight: 400 900; font-display: swap;
}

:root {
  --bg: #08080a;
  --ink: #d6d6d6;
  --ink-strong: #fafafa;
  --ink-2: rgba(255, 255, 255, 0.7);
  --ink-3: rgba(255, 255, 255, 0.42);
  --ink-4: rgba(255, 255, 255, 0.25);
  --ink-5: rgba(255, 255, 255, 0.16);

  --amber: #ff5000;
  --amber-hot: #ff6a24;

  --rule: rgba(255, 255, 255, 0.055);
  --edge-faint: rgba(255, 255, 255, 0.07);
  --edge: rgba(255, 255, 255, 0.12);

  --sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'GeistMono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
  --serif: 'Cinzel', Georgia, serif;

  --col: 736px;   /* the content column */
  --grid: 45px;   /* their annotation says 45px, and it is */
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }

body {
  position: fixed; inset: 0;
  overflow: hidden; overscroll-behavior: none; touch-action: none;
  background: var(--bg); color: var(--ink);
  font-family: var(--sans); font-size: 16px;
  -webkit-font-smoothing: antialiased;
}

/* The page switches on. It does not fade in.
   This was the last dissolve left on the site, and it was the very first thing
   anyone saw -- a page whose entire argument is that nothing dissolves, opening
   with a half-second dissolve. The reveal fires at the end of module execution,
   after every stage is built, measured and positioned, so there is nothing to
   cover up: turning it on instantly is safe as well as more honest. */
body.is-loading .scene { opacity: 0; }

::selection { background: var(--amber); color: #fff; }
:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; border-radius: 4px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.skip {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  padding: 10px 16px; background: #16161a; color: var(--ink-strong);
  border: 1px solid var(--edge); border-radius: 8px;
  font: 500 14px/1 var(--sans); text-decoration: none;
  transform: translateY(-200%); transition: transform 160ms ease;
}
.skip:focus { transform: none; }

/* ------------------------------------------------------------ backdrop -- */

#backdrop { position: fixed; inset: 0; z-index: 0; display: block; width: 100%; height: 100%; pointer-events: none; }

/* 45px dot grid. The dots carry a little amber so the field is not neutral
   grey — it is the same light everything else on the page is lit by. */
.grid {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background-image: radial-gradient(rgba(255, 106, 36, 0.16) 1px, transparent 1px);
  background-size: var(--grid) var(--grid);
  mask-image: radial-gradient(130% 105% at 50% 45%, #000 22%, transparent 84%);
}

body::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(58% 52% at 50% 46%, rgba(255, 80, 0, 0.12), transparent 68%),
    radial-gradient(70% 60% at 4% 96%, rgba(255, 80, 0, 0.07), transparent 62%);
}


/* ================================================================ scene == */

/*
  Everything the camera moves. ONE transform on this element; nothing inside it
  transforms independently, or the two would multiply and the geometry would
  stop being predictable.
*/
.scene {
  position: fixed; inset: 0; z-index: 20;
  will-change: transform;
}

/*
  Where the hero column sits in a viewport — one rule, two viewports.

  `.again__view` is the inside of the miniature page at the end of the loop, and
  that miniature has to centre its column by exactly the same arithmetic as the
  real one or the two will not land on each other at the wrap. Sharing the
  declaration is the only way to guarantee they cannot drift apart: change the
  padding here and both move together.
*/
.scene, .again__view {
  display: grid; place-items: center;
  /* Room for the section pill, which used to sit on top of the lead. */
  padding-bottom: 24px;
}

.column {
  position: relative;
  width: min(var(--col), calc(100vw - 44px));
  padding: 0 34px;
  /* The two hairlines the whole layout hangs off. */
  border-left: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
}

/* ------------------------------------------------------------- topbar --- */

.topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: clamp(26px, 5vh, 54px); }
.topbar__brand { display: inline-flex; align-items: center; gap: 9px; color: var(--ink-strong); text-decoration: none; }
.topbar__brand img { display: block; width: 22px; height: 22px; }
.topbar__word { font-family: var(--serif); font-weight: 600; font-size: 20px; letter-spacing: 0.06em; }
.topbar__nav { display: flex; align-items: center; gap: 12px; font-size: 14.5px; }
.topbar__nav a { color: var(--ink-3); text-decoration: none; transition: color 150ms ease; }
.topbar__nav a:hover { color: var(--ink-strong); }
.topbar__slash { color: var(--ink-5); }

/* --------------------------------------------------------------- hero --- */

/*
  Two real columns. The headline used to sit alone in the grid with the lead
  and the buttons stacked underneath it, so the phone's full height was added
  to the copy's full height and the buttons ended up below the fold on any
  laptop. Copy on the left, phone on the right, both vertically centred.
*/
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(20px, 3.5vw, 56px);
}
.hero__copy { display: flex; flex-direction: column; align-items: flex-start; }

.hero__title {
  margin: 0;
  font-size: clamp(34px, min(5.1vw, 8.4vh), 72px);
  line-height: 0.96; font-weight: 500; letter-spacing: -0.032em;
  color: var(--ink-strong);
}

.lead {
  margin: clamp(14px, 2.4vh, 26px) 0 0;
  font-size: 16.5px; line-height: 1.62; letter-spacing: -0.01em;
  color: var(--ink-2); max-width: 46ch;
}

.cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: clamp(18px, 3vh, 30px); }
.cta__note { margin: 14px 0 0; font-size: 12.5px; color: var(--ink-4); }

/* ------------------------------------------------------------ buttons --- */

.btn {
  appearance: none; border: 1px solid transparent; border-radius: 8px;
  font-family: var(--sans); font-size: 14.5px; font-weight: 500; letter-spacing: -0.01em;
  cursor: pointer; padding: 0; position: relative; overflow: hidden;
  will-change: transform;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.btn__label { display: flex; align-items: center; gap: 9px; padding: 13px 20px; will-change: transform; }
.btn__state { font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.16em; text-transform: uppercase; opacity: 0.66; font-weight: 400; }
.btn svg { width: 15px; height: 15px; flex: none; }
.btn--primary { background: var(--amber); color: #fff; }
.btn--primary:hover { background: var(--amber-hot); }
.btn--secondary { background: #171719; border-color: var(--edge-faint); color: var(--ink-strong); }
.btn--secondary:hover { background: #1e1e21; border-color: var(--edge); }
.btn[aria-disabled='true'] { cursor: default; }

/* -------------------------------------------------------------- phone --- */

.phone { position: relative; }
.phone__glow {
  position: absolute; inset: -16% -24%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(255, 80, 0, 0.3), transparent 68%);
  filter: blur(24px); pointer-events: none;
}
.phone__body {
  position: relative;
  /* Nothing scales past this box any more — the bezel is sized from the card's
     box instead — but its drop shadow still falls outside it. (The 6px padding
     below is inert: the containing block for absolutely positioned children is
     the PADDING box, which equals the border box here, so the rim you see is
     .card's own left/top/right: 6px, not this.) */
  overflow: visible;
  width: clamp(132px, min(15vw, 26vh), 208px);
  aspect-ratio: 1080 / 2400;
  padding: 6px;
}
/*
  The bezel art lives here, not on .phone__body, because #card is a CHILD of
  the body: anything done to the body is done to the card and everything inside
  it, and the middle of the zoom went blank the one time that was tried.

  Its box is written every frame by camera.js — the card's box, grown by the
  rim — so the horizontal edges are pinned here and the vertical ones are not.
  There is deliberately no `bottom`: with top, height and bottom all set the
  declarations are over-constrained and the browser silently drops one, and it
  is better not to have to know which.

  It is repainted rather than composited now, which is a real cost and is the
  price of the shape being right. Scaling a fixed box uniformly is what made
  the frame and the card two unrelated rectangles: at t = 0.60 the card was
  485x273 inside a 1791x3981 frame — 1854px of grey above and below a landscape
  sliver. A non-uniform scale cannot substitute either; it turns a 31px corner
  into a 31 x 7.75px ellipse, which is a different way of losing the bezel.
*/
.phone__frame {
  position: absolute; left: 0; right: 0; top: 0; height: 100%;
  /* 25 + 6 — the card's radius plus the rim, so the two are concentric. This is
     exactly what camera.js writes at e = 0, and the recursion clone keeps this
     static value: they have to agree at the seam, which is why this moves from
     30 to 31. Left at 30 it would put a 1px corner discontinuity at four
     corners at the wrap. */
  border-radius: 31px;
  pointer-events: none;
  background: linear-gradient(160deg, #2b2c31, #101013 44%, #232429);
  box-shadow: 0 50px 100px -34px rgba(0,0,0,0.95), 0 0 0 1px rgba(255,255,255,0.06) inset;
}
/*
  Inside .card now. inset:0 of the card is the same box inset:6px of the body
  was — measured identical at rest — but the card clips, so the highlight can
  slide out of frame instead of fading.

  `border-radius: inherit` rather than nothing: body.static-layout sets
  .card { overflow: visible }, so in reduced motion there is no clip at all and
  a square 12%-white corner would poke out at the top left of a 25px-rounded
  card. Inheriting takes the card's own radius, including the per-frame value
  the camera writes, so it is right in both paths.
*/
.phone__gloss {
  position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 4;
  will-change: transform;
  background: linear-gradient(150deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0) 32%);
}

/*
  The card IS the phone's screen and the camera's focal point. It clips, and
  the page lives inside it. Its radius is driven per-frame by camera.js.
*/
/*
  Absolutely positioned INSIDE the phone, which is the whole point: the camera
  animates this box's height every frame, and if it were in flow that would
  reflow the phone, move the card's centre, and invalidate the transform
  origin the camera measured at rest. The card then drifts out of frame
  halfway through the zoom and you get an empty screen — which is exactly what
  happened. Out of flow, its size can move nothing.
*/
.card {
  position: absolute; left: 6px; top: 6px; right: 6px;
  height: calc(100% - 12px);
  border-radius: 25px; overflow: hidden; background: #000;
  z-index: 2;
}
/* Centred in the card, not pinned to its corner. The card ends up taller
   than the viewport (it has to, to cover it), so a plane at the top-left
   corner would finish a thousand pixels above the fold. Both centres
   coincide, and the card centre is what the camera walks to the middle of
   the screen. */
  .plane { position: absolute; top: 50%; left: 50%; transform-origin: 50% 50%; }
/*
  Anchored to the BOTTOM edge, not the top, and that is load-bearing.

  This image is the page above the fold. Its bottom edge is the page break that
  the first section's top edge is welded to, and main.js scrolls it up by
  exactly one page across the opening. Anchored at the top, `cover` ate the
  screenshot from the BOTTOM as the card opened — the frame closing on content
  that stayed put, which is the opposite of a scroll. Anchored at the bottom the
  crop eats from the top, the content is rigid against the break, and the last
  thing you see before the section arrives is the bottom of the home screen.

  It costs nothing at rest, which is what keeps the recursion clone identical.
  `.phone__body` is aspect-ratio 1080/2400 with 6px of padding, so the card is
  (2.222w - 12) / (w - 12) tall: 2.344 at the clamp's 132px floor, 2.306 at
  1280x720, 2.297 at the 208px ceiling. All of them are taller than the image's
  own 2.222, so `cover` is height-driven at rest and crops nothing vertically —
  measured 0.000px. The vertical anchor is inert until the card starts to open.
*/
.card__shot {
  position: absolute; inset: 0; z-index: 3;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%;
  pointer-events: none;
}

/* -------------------------------------------------------------- stages -- */

/*
  A cell in the strip, not a layer in a stack. Absolutely positioned at its
  own multiple of 100% and never transformed or faded individually — the reel
  above it does all the moving, so sections travel past each other with hard
  edges instead of dissolving into one another.
*/
.reel-zoom { position: absolute; inset: 0; transform-origin: 50% 50%; will-change: transform; }
.reel { position: absolute; top: 0; left: 0; width: 100%; will-change: transform; }
.stage {
  position: absolute; left: 0; width: 100%;
  /* Each section clips its own content. Without this the art parallaxes out
     of its own frame and into its neighbour, so mid-pan you see two god
     figures and two eyebrows at once and neither section has an edge. With
     it, one frame leaves as the next arrives — which is the whole point of
     panning rather than dissolving. */
  overflow: hidden;
  display: grid; place-items: center;
  padding: max(80px, 10vh) clamp(22px, 5vw, 80px) max(96px, 12vh);
  backface-visibility: hidden;
}
.stage__inner {
  position: relative; z-index: 2; width: min(760px, 100%);
  display: flex; flex-direction: column; align-items: flex-start; gap: 15px;
}
.stage--download .stage__inner { align-items: center; text-align: center; }

.eyebrow { margin: 0; font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--amber); }
.stage__num { font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; color: var(--ink-4); }
.stage__title { margin: 0; font-size: clamp(32px, 4.4vw, 58px); line-height: 1.02; font-weight: 500; letter-spacing: -0.03em; color: var(--ink-strong); }
.stage__lead { margin: 0; max-width: 52ch; font-size: 17px; line-height: 1.62; letter-spacing: -0.01em; color: var(--ink-2); }
.stage__note { margin: 0; font-size: 13px; line-height: 1.6; color: var(--ink-4); max-width: 50ch; }

.facts { margin: 8px 0 0; display: grid; grid-template-columns: minmax(130px, max-content) 1fr; width: 100%; border-top: 1px solid var(--edge-faint); }
.facts dt, .facts dd { margin: 0; padding: 12px 0; border-bottom: 1px solid var(--edge-faint); font-size: 13px; line-height: 1.5; }
.facts dt { font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-4); padding-right: 22px; }
.facts dd { color: var(--ink-2); }

.stage__god { position: absolute; right: max(1vw, 8px); bottom: 7vh; z-index: 1; width: clamp(110px, 19vw, 250px); pointer-events: none; opacity: 0.38; }
.stage__god img { display: block; width: 100%; height: auto; }
.stage__relief { position: absolute; inset: 0; z-index: 0; display: grid; place-items: center; pointer-events: none; overflow: hidden; }
.stage__relief img {
  width: min(1000px, 88vw); max-height: 76vh; object-fit: contain;
  opacity: 0.16; mix-blend-mode: screen;
  mask-image: radial-gradient(62% 58% at 50% 50%, #000 38%, transparent 76%);
}

.plates { position: absolute; inset: -6% -3%; z-index: 0; pointer-events: none; }
.plate { position: absolute; inset: 0; background-repeat: no-repeat; background-position: center; background-size: cover; will-change: transform; }
.plate--colonnade-far { opacity: 0.28; }
.plate--colonnade-near { opacity: 0.18; mix-blend-mode: screen; }

/* ----------------------------------------------------------- platforms -- */

.platforms { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: 14px; width: 100%; max-width: 640px; margin-top: 6px; }
.platform {
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  padding: 22px; border: 1px solid var(--edge-faint); border-radius: 14px;
  background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.017));
  text-align: left; box-shadow: 0 16px 34px -18px rgba(0,0,0,0.9);
}
.platform__name { margin: 0; font-size: 20px; font-weight: 500; letter-spacing: -0.02em; color: var(--ink-strong); }
.platform__sub { margin: 0; font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; color: var(--ink-4); }
.platform__state {
  margin-top: 8px; font-family: var(--mono); font-size: 9px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--amber);
  border: 1px solid rgba(255, 80, 0, 0.3); border-radius: 999px; padding: 5px 11px;
  background: rgba(255, 80, 0, 0.07);
}
.platform__note { margin: 4px 0 0; font-size: 12px; color: var(--ink-4); }

/* ----------------------------------------------------------------- nav -- */

.nav {
  position: fixed; left: 50%; bottom: max(30px, 4vh); transform: translateX(-50%); z-index: 70;
  display: flex; align-items: center; gap: 2px; padding: 5px;
  border: 1px solid var(--edge-faint); border-radius: 999px;
  background: rgba(10,10,12,0.72); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  max-width: calc(100vw - 28px); overflow-x: auto; scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
.nav__item {
  appearance: none; border: 0; background: none; color: var(--ink-4);
  font: 500 12.5px/1 var(--sans); letter-spacing: -0.01em;
  padding: 9px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  transition: color 150ms ease;
}
.nav__item:hover { color: var(--ink-2); }
.nav__item.is-current { color: var(--ink-strong); }
.nav__marker { position: absolute; left: 0; bottom: 3px; height: 2px; border-radius: 2px; background: var(--amber); pointer-events: none; will-change: transform, width; }
.nav__marker--trail { filter: blur(2.5px); }


.noscript { position: relative; z-index: 90; max-width: 60ch; margin: 0 auto; padding: 16vh 22px; }
.noscript h1 { font-family: var(--serif); letter-spacing: 0.1em; }
.noscript p { color: var(--ink-2); line-height: 1.65; }

/* ==========================================================================
   Static layout — reduced motion, or no scripting.
   ========================================================================== */

body.static-layout { position: static; overflow: visible auto; touch-action: auto; height: auto; }
body.static-layout .scene { position: relative; inset: auto; transform: none !important; display: block; padding: 90px 0 60px; }
body.static-layout .column { margin: 0 auto; }
body.static-layout .phone__body { width: clamp(180px, 40vw, 240px); margin: 0 auto; }
body.static-layout .card { overflow: visible; }
body.static-layout .plane { position: static; transform: none !important; width: auto !important; height: auto !important; }
body.static-layout .card__shot { position: static; height: auto; }
body.static-layout .plates { display: none; }
body.static-layout .stage {
  position: relative; inset: auto; padding: clamp(60px, 11vh, 130px) clamp(22px, 5vw, 80px);
  opacity: 1 !important; filter: none !important; transform: none !important;
  border-top: 1px solid var(--edge-faint);
}
body.static-layout .stage__inner { margin: 0 auto; }
body.static-layout .stage__god { position: static; margin: 22px auto 0; }
body.static-layout .stage__relief { position: static; margin-top: 24px; }
body.static-layout .stage__relief img { max-height: 38vh; opacity: 0.3; }
body.static-layout .nav { position: static; transform: none; margin: 26px auto; }
body.static-layout .nav__marker { display: none; }
body.static-layout .grid { position: absolute; height: 100%; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important; scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------- narrow --- */

@media (max-width: 820px) {
  .column { padding: 0 20px; border: 0; }
  .topbar { margin-bottom: 40px; }
  .hero { grid-template-columns: 1fr; gap: 28px; }
  .phone { justify-self: center; }
  .hero__title { font-size: clamp(34px, 9vw, 52px); }
  .lead { margin-top: 28px; font-size: 16px; }
  .cta { width: 100%; }
  .btn { flex: 1 1 auto; }
  .btn__label { justify-content: center; }
  .facts { grid-template-columns: 1fr; }
  .facts dt { padding-bottom: 0; border-bottom: 0; }
  .facts dd { padding-top: 4px; }
  .stage__god { opacity: 0.22; width: 42vw; right: -3vw; bottom: 4vh; }
}

/* ================================================================ panel == */

/*
  The slide-in build log. Its scroller carries `data-native-scroll`, which is
  how the loop knows to stop eating wheel events inside it — see loop.js.
*/
.panel { position: fixed; inset: 0; z-index: 120; }
.panel[hidden] { display: none; }

.panel__scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0; transition: opacity 280ms ease;
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.panel.is-open .panel__scrim { opacity: 1; }

.panel__sheet {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(468px, 100vw);
  display: flex; flex-direction: column;
  background: #0b0b0d;
  border-left: 1px solid var(--edge-faint);
  box-shadow: -40px 0 90px -30px rgba(0, 0, 0, 0.9);
  transform: translateX(100%);
  transition: transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.panel.is-open .panel__sheet { transform: none; }

.panel__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px 0;
}
.panel__kicker {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--ink-4);
}
.panel__close {
  appearance: none; display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 0; padding: 6px; cursor: pointer; color: var(--ink-3);
  transition: color 150ms ease;
}
.panel__close:hover { color: var(--ink-strong); }
.panel__close svg { width: 15px; height: 15px; }
.panel__esc {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.16em;
  text-transform: uppercase;
  border: 1px solid var(--edge-faint); border-radius: 4px; padding: 3px 6px;
}

.panel__title { padding: 26px 20px 18px; border-bottom: 1px solid var(--edge-faint); }
.panel__title h2 { margin: 0; font-size: 26px; font-weight: 500; letter-spacing: -0.03em; color: var(--ink-strong); }
.panel__count { margin: 6px 0 0; font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-4); }

.panel__scroll { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 4px 0 0; }
.panel__scroll::-webkit-scrollbar { width: 8px; }
.panel__scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
.panel__scroll:focus-visible { outline-offset: -3px; }

.log { list-style: none; margin: 0; padding: 0; }
.log__row {
  display: grid; grid-template-columns: 74px 1fr; gap: 12px;
  padding: 14px 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.log__date {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.14em;
  color: var(--ink-4); padding-top: 3px; white-space: nowrap;
}
.log__body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.log__title { font-size: 14px; font-weight: 500; letter-spacing: -0.012em; color: var(--ink); line-height: 1.35; }
.log__blurb { font-size: 12.5px; line-height: 1.55; color: var(--ink-3); }
.log__hash { font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.1em; color: var(--ink-5); }

.panel__end {
  margin: 0; padding: 20px; text-align: center;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-5);
}

body.static-layout .panel { position: fixed; }

.topbar__link {
  appearance: none; background: none; border: 0; padding: 0;
  font-family: var(--sans); font-size: 14.5px; color: var(--ink-3);
  cursor: pointer; transition: color 150ms ease;
}
.topbar__link:hover { color: var(--ink-strong); }

/* ------------------------------------------------------------ the wall -- */

/* Three real screens at two depths. The middle one is forward and lit; the
   outer two are pushed back, dimmed and slightly rotated, so the group reads
   as a room rather than as a row of thumbnails. */
.stage--showcase .stage__inner { align-items: center; text-align: center; }
.stage--showcase .stage__lead { margin-left: auto; margin-right: auto; }

.wall {
  display: flex; align-items: flex-end; justify-content: center;
  gap: clamp(10px, 2.2vw, 28px);
  margin-top: 12px; width: 100%;
  perspective: 1200px;
}
.wall__item {
  margin: 0; position: relative;
  width: clamp(96px, 13vw, 176px);
  /* A static depth cue, not an animation: the two flanking screens sit further
     back, so they are dimmer and smaller. --depth is written once from the
     content data and never changes, which made the transition below dead code
     that only ever showed up in audits as a fade. */
  opacity: calc(0.45 + (var(--depth) - 0.9) * 5.5);
  transform: scale(var(--depth)) translateZ(0);
}
.wall__item:nth-child(1) { transform: scale(var(--depth)) rotateY(9deg); transform-origin: right center; }
.wall__item:nth-child(3) { transform: scale(var(--depth)) rotateY(-9deg); transform-origin: left center; }
.wall__item img {
  display: block; width: 100%; height: auto;
  border-radius: 14px;
  border: 1px solid var(--edge-faint);
  background: #000;
  box-shadow: 0 30px 60px -26px rgba(0, 0, 0, 0.95);
}
.wall__item:nth-child(2) img { box-shadow: 0 34px 70px -22px rgba(255, 80, 0, 0.22), 0 30px 60px -26px rgba(0,0,0,0.95); }
.wall__item figcaption {
  margin-top: 9px;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--ink-4);
}
@media (max-width: 820px) {
  .wall { gap: 8px; }
  .wall__item { width: 26vw; }
}

/* ============================================================== the slab == */

/*
  One full-bleed panel of the accent in an otherwise black scroll. It exists to
  change the temperature once — a long dark page needs a moment that resets the
  eye, or every section starts to read the same however good each one is.
*/
.stage--features { padding: 0; }
.stage--features .stage__inner {
  width: 100%; max-width: none; height: 100%;
  display: grid; grid-template-rows: auto auto 1fr; gap: 0;
  background: linear-gradient(150deg, #ff5000, #d63c00 62%, #b83100);
  color: #fff;
}
.stage--features .eyebrow { color: rgba(255,255,255,0.62); }
.stage--features .stage__title {
  color: #fff; max-width: 15ch;
  font-size: clamp(30px, 4vw, 54px);
}
.stage--features > .stage__inner > .eyebrow,
.stage--features > .stage__inner > .stage__title {
  padding-left: clamp(22px, 5vw, 68px); padding-right: clamp(22px, 5vw, 68px);
}
.stage--features > .stage__inner > .eyebrow { padding-top: clamp(28px, 6vh, 60px); }
.stage--features > .stage__inner > .stage__title { padding-bottom: clamp(18px, 4vh, 40px); }

.ledger {
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  border-top: 1px solid rgba(255,255,255,0.16);
}
.ledger__cell {
  position: relative;
  padding: clamp(20px, 3.4vh, 38px) clamp(22px, 4vw, 54px);
  border-right: 1px solid rgba(255,255,255,0.16);
  border-bottom: 1px solid rgba(255,255,255,0.16);
  display: flex; flex-direction: column; gap: 9px;
}
.ledger__cell:nth-child(2n) { border-right: 0; }
.ledger__cell:nth-child(n+3) { border-bottom: 0; padding-bottom: 84px; }
.ledger__num {
  position: absolute; top: clamp(20px, 3.4vh, 38px); right: clamp(22px, 4vw, 54px);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em;
  color: rgba(255,255,255,0.5);
}
.ledger__head { margin: 0; font-size: clamp(16px, 1.5vw, 20px); font-weight: 500; letter-spacing: -0.02em; }
.ledger__body { margin: 0; font-size: clamp(13px, 1.05vw, 15px); line-height: 1.55; color: rgba(255,255,255,0.82); max-width: 42ch; }

@media (max-width: 820px) {
  .ledger { grid-template-columns: 1fr; grid-template-rows: repeat(4, 1fr); }
  .ledger__cell { border-right: 0; }
  .ledger__cell:nth-child(n+3) { border-bottom: 1px solid rgba(255,255,255,0.16); }
  .ledger__cell:last-child { border-bottom: 0; }
}

/* ========================================================== the recursion == */

/*
  The page inside the page. `.again__screen` holds a live clone of the hero
  column, and the last span of the loop flies into it until the clone sits
  exactly where the real hero sits — then the wrap swaps them.
*/
/* Copy above, screen below — centred over each other put the headline
   straight across the miniature hero it is describing. */
.stage--recursion { align-content: start; }
/* Compact on purpose -- every pixel this copy does not take is a pixel the
   device gets, and the device is the point of the section. */
.stage--recursion .stage__inner {
  align-items: center; text-align: center;
  gap: 6px; padding-top: clamp(6px, 1.8vh, 20px);
}
.stage--recursion .stage__title { font-size: clamp(22px, 3.1vw, 44px); margin: 0; }
.stage--recursion .stage__lead { font-size: clamp(11px, 1.05vw, 14px); margin: 0; }
/* An explicit band under the copy. Sizing the screen by WIDTH let it grow
   taller than the space left for it and climb back over the headline.

   The band starts far higher than it used to. At 46% the device was a
   letterbox strip along the bottom of the frame, small enough that there was
   nothing in it to read -- which is exactly why the ending felt like it cut
   straight to the landing page. It now takes most of the frame. */
.again {
  position: absolute; left: 0; right: 0;
  top: 25%; bottom: clamp(24px, 3.2vh, 44px);
  display: grid; place-items: center; pointer-events: none; z-index: 0;
}
/*
  Width is written by measure(), not by this sheet, and that is not fussiness.

  It used to be `height: 100%; width: auto; aspect-ratio: 16/9; max-width: ...`,
  which is the obvious spelling and is quietly wrong. `height: 100%` is a
  DEFINITE size, so the ratio derives the width from it — and when `max-width`
  then clamps that width, nothing goes back and re-derives the height. The ratio
  is simply abandoned. On desktop the cap never binds and it looks correct; at
  390x844 it binds hard and the device came out 289 x 606. A browser window
  taller than it is wide does not read as a browser window at all, which undoes
  the entire point of the ending on every phone.

  There is no CSS spelling that fits a fixed-ratio box inside BOTH a width cap
  and a height cap — you always have to make one axis definite, and whichever
  one you choose is the one that breaks when the other constraint binds. So the
  min() is taken in JS, where both numbers are already known, and `height: auto`
  lets the ratio do the rest. --again-dev-w is the fallback for the one frame
  before measure() first runs.
*/
.again__screen {
  position: relative;
  width: var(--again-dev-w, 60%);
  height: var(--again-dev-h, 34%);
  border-radius: 12px;
  overflow: hidden;
  background: #0d0d0f;
  border: 1px solid var(--edge);
  box-shadow: 0 60px 130px -40px rgba(0,0,0,0.96), 0 0 110px -30px rgba(255,80,0,0.28);
}
/* The rail: the sections you have just walked, as browser history. */
.again__rail {
  position: absolute; top: 34px; left: 0; bottom: 0; width: 23%;
  border-right: 1px solid var(--edge-faint);
  padding: 12px 0; display: flex; flex-direction: column; gap: 1px;
  background: rgba(255,255,255,0.014);
}
.again__railcap {
  font-family: var(--mono); font-size: clamp(6px, 0.5vw, 7.5px);
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(255,255,255,0.22);
  padding: 0 clamp(9px, 1.1vw, 15px) 8px;
}
.again__row {
  font-size: clamp(7px, 0.68vw, 10px); color: var(--ink-4);
  padding: clamp(4px, 0.6vh, 7px) clamp(9px, 1.1vw, 15px);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-left: 2px solid transparent;
}
.again__row.is-on {
  color: var(--ink-2); background: rgba(255, 80, 0, 0.10);
  border-left-color: var(--amber);
}
/* The canvas the page lives in. Clips like a real viewport, and scales with
   everything else during the fly-in, so its edges pass the frame rather than
   cropping the hero at the seam.

   `right: 0` and `bottom: 0` stay. Once this box holds a page fitted to it, the
   fly-in maps THIS BOX onto the viewport at the seam, so the mock's own 1px
   --edge border lands within a pixel of the frame edge. Inset the canvas to buy
   margin and you paint a band of device grey down two sides of what is meant to
   read as a viewport. The margin is bought in JS instead, by the one pixel of
   underscan on --again-page-scale in measure().

   Its ground is the PAGE's, not the device's. The page inside is fitted by
   WIDTH and the canvas is squarer than the viewport (1.468 vs 1.778 at
   1280x720), so a band is left uncovered top and bottom — 41.7px each at
   1280x720, 71.6px at 1920x1080. Painting it the page's own ground is what
   makes that band not exist. */
.again__canvas {
  position: absolute; top: 34px; left: 23%; right: 0; bottom: 0;
  overflow: hidden;
  background: var(--bg);
}

/*
  The page inside the browser, as a page.

  This used to be the bare hero column at scale 0.32 — 236px wide in a 705px
  canvas. At that size the lead is 5.3px tall, both buttons 4.6px and the
  wordmark 6.4px: dark grey on black, i.e. gone. The one thing left bright
  enough to read was the lit phone, so the ending read as a phone floating in an
  empty window — and a phone was what you had just flown into.

  (It was worse than that, and the sizing was only half of it: the camera was
  also driving this clone's `.phone__frame` at scale(11.3168) for the whole
  close, blowing a 187x416 opaque gradient up to 2118x4707 and painting it over
  the miniature's topbar, headline, lead and buttons. That is fixed in
  camera.js/index.html by passing the real bezel by id; making the miniature
  bigger before that fix would only have made the slab bigger too.)

  `.again__page` is a stand-in VIEWPORT instead. main.js sizes it to the scene's
  own layout box and scales it by canvasWidth / viewportWidth, so it renders the
  way this page renders in a browser window: ground, grid, topbar, headline,
  lead, both buttons, phone.

  It tightens the seam rather than loosening it. The fly-in matches the clone
  column to the real column; because the miniature is a faithful UNIFORM scale
  of the whole viewport, matching the column lands every other layer with it.

  Nothing here is driven by scroll position. The scale is a custom property
  written only by measure(), so it is constant across the wrap by construction.
*/
.again__page {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(var(--again-page-scale, 0.55));
  transform-origin: 50% 50%;
  background: var(--bg);
}
.again__view { position: absolute; inset: 0; }

/* The real grid is `position: fixed` and a SIBLING of #scene, so cloning the
   column can never bring it along — the miniature carries its own copy of the
   node and only the positioning changes.

   z-index 0, NOT the real grid's 1. `.again__page` has a transform, so it is a
   stacking context of its own, and inside it `.again__view` is z-index:auto — a
   grid at z-index 1 would paint OVER the headline and the phone, the reverse of
   the real page (.grid z-index 1, .scene z-index 20), and the dots lying over
   the hero would vanish at the wrap. Same 45px pitch, same mask, resolved
   against a box that IS the viewport at the wrap, so the lattice does not shift
   when the real one takes over. */
.again__page .grid { position: absolute; z-index: 0; }

/* body::before's two glows, copied. They are painted underneath an opaque WebGL
   canvas on the normal path and are the visible ground on the .no-webgl path,
   and the miniature has to show what the page is actually showing.

   Honest about what this does NOT achieve: the real ground is a shader, and a
   flat fill cannot match a shader. The ground still steps slightly at the wrap.
   It is a large reduction of what shipped before — where the whole viewport at
   the seam was the clone bezel's opaque gradient and the dot lattice popped in
   from nothing — but it is not zero, and closing it properly means drawing a
   second small backdrop into the canvas. */
.again__page::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(58% 52% at 50% 46%, rgba(255, 80, 0, 0.12), transparent 68%),
    radial-gradient(70% 60% at 4% 96%, rgba(255, 80, 0, 0.07), transparent 62%);
}

/* On a narrow screen the device's `max-width: min(92vw, ...)` binds, which
   leaves the canvas at 70% of the viewport width and the fly-in with almost
   nothing to cover. Pulling the device in to 74vw puts the travel back and
   composes better with air around it. Must sit after the .again__screen rule
   above; same specificity, so order decides. */
/* (The 74vw narrow cap moved into measure() with the rest of the sizing — see
   AGAIN_DEV_CAP in main.js. Splitting it across a media query and a JS min()
   would have been two sources of truth for one number.) */

/* Reduced motion turns this page into a document, and those rules unpick the
   phone so the screenshot can flow down it. The miniature is a PICTURE of the
   animated page, though, so inside it the phone has to stay a phone. */
body.static-layout .again__page .card { overflow: hidden; }
body.static-layout .again__page .card__shot { position: absolute; height: 100%; }

/*
  On a portrait screen the closing device is a PHONE browser, not a desktop one.

  This is a correctness fix wearing a design fix's clothes. The canvas clips,
  and at the seam the fly-in has mapped the canvas onto the viewport — so unless
  the canvas is at least as tall-relative-to-its-width as the viewport is, it
  crops the very page it is supposed to be handing over to. The condition is
  exactly canvasH / canvasW >= vh / vw.

  Desktop passes it comfortably: 635/917 = 0.692 against 935/1920 = 0.487.
  A 390x844 phone fails it by a factor of 3.8: 0.573 against 2.164. Measured
  consequence at the seam — the arriving page reached its correct 390x844, but
  the canvas around it was 392x225, so 619 of the viewport's 844 pixels were
  cropped away. You saw the landing page as a letterbox band, and then the wrap
  replaced it with the whole page in one frame. That is the "doesn't smoothly
  enter" — the fly-in was never wrong, the window around it was too short.

  No landscape mock can satisfy the condition on a portrait screen, at any size.
  So the mock turns portrait, the rail (a desktop-browser idiom anyway) goes,
  and the canvas runs the full width under the address bar. main.js does the
  sizing; see the coverage block in measure().
*/
body.again-portrait .again__rail { display: none; }
body.again-portrait .again__canvas { left: 0; }
/* A phone browser has one address bar, not a tab strip. */
body.again-portrait .again__tab { display: none; }
.again__tab {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
  margin-left: 6px; padding: 4px 11px; border-radius: 5px 5px 0 0;
  background: rgba(255,255,255,0.055);
  border: 1px solid var(--edge-faint); border-bottom: none;
  font-size: clamp(6.5px, 0.55vw, 8.5px); color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34%;
}
.again__favi { width: 7px; height: 7px; border-radius: 2px; background: var(--amber); flex: none; }
.stage--recursion .stage__inner { position: relative; z-index: 2; pointer-events: none; }
.stage--recursion .stage__title,
.stage--recursion .eyebrow,
.stage--recursion .stage__lead {
  text-shadow: 0 2px 30px rgba(0,0,0,0.9);
}


/* ============================================================ the beam == */

/*
  One shaft of raking light per section, swept by scroll. It is the closest
  cold equivalent of the reference's flame: something that is genuinely alive
  while you travel through a section, rather than a still frame that slides.

  Deliberately additive light on the background — never opacity on the
  content, which is the thing that makes a page feel like a slideshow.
*/
.stage__beam {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: var(--lit, 0);
  background:
    radial-gradient(58% 120% at var(--sweep, 50%) 108%,
      rgba(255, 122, 40, 0.20), transparent 62%),
    linear-gradient(103deg,
      transparent calc(var(--sweep, 50%) - 26%),
      rgba(255, 168, 96, 0.055) calc(var(--sweep, 50%) - 4%),
      rgba(255, 196, 130, 0.085) var(--sweep, 50%),
      rgba(255, 168, 96, 0.05) calc(var(--sweep, 50%) + 4%),
      transparent calc(var(--sweep, 50%) + 26%));
  mix-blend-mode: plus-lighter;
}
.stage__relief, .stage__god, .stage__inner { will-change: transform; }

/* ===================================================== the last screen == */

/*
  The ending needs to look like a DEVICE you are flying into, not a rectangle
  the page happens to be in. A browser chrome — bar, three dots, a URL — reads
  as one instantly, and it is honest: this really is Shadow rendering this page.
*/
.again__chrome {
  position: absolute; top: 0; left: 0; right: 0; height: 34px; z-index: 3;
  display: flex; align-items: center; gap: 7px; padding: 0 12px;
  background: #141417;
  border-bottom: 1px solid var(--edge-faint);
  transform-origin: 50% 50%;
}
.again__dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.16); }
.again__url {
  margin-left: 8px; flex: 1; height: 18px; border-radius: 5px;
  background: rgba(255,255,255,0.05);
  display: flex; align-items: center; padding: 0 9px;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 0.08em; color: var(--ink-4);
}
/* (The clone used to need a +17px nudge to clear the chrome bar. It now sits
   inside .again__canvas, which already starts below it.) */


/* ==================================================== motes in the light = */

/*
  See src/motes.js. Above the scene and the grain, below the download bar and
  the build-log panel -- it is atmosphere, and atmosphere must never be the
  thing you click.
*/
#motes {
  position: fixed; inset: 0; z-index: 22; pointer-events: none;
  mix-blend-mode: plus-lighter;
}
body.static-layout #motes { display: none; }
@media (prefers-reduced-motion: reduce) { #motes { display: none; } }


/* ============================================================== rails === */

/*
  Hairline rails, and the light that travels along them. See src/rails.js for
  the arithmetic; this file only describes what the light looks like.

  · z-index 21 — above .scene (20), below #motes (22), the download bar (70)
    and the panel (120). It cannot go below the scene: `.card` is
    `background: #000` and from the moment the camera is pushed in it covers the
    entire viewport, so anything under .scene is invisible for five sixths of
    the loop.

  · No mix-blend-mode. #motes is plus-lighter, and a blend mode composites
    against its backdrop, so leaving the rails as ordinary alpha means the dust
    blends OVER them for free and the page keeps one full-viewport blend group
    instead of two. At these alphas over a near-black ground, source-over and
    plus-lighter are the same picture.

  · No mask on the layer. The outward falloff `.grid` gets from a full-viewport
    radial mask is folded into each rail's own alpha in JS instead — a mask over
    a container whose children move every frame is a viewport-sized surface that
    has to keep up with them.

  Nothing here animates the `opacity` property; the travelling segment's alpha
  lives inside its own gradient stops, so what changes is how much light it
  emits. (Note .stage__beam DOES drive `opacity` from `pos` — it has since
  before this layer existed. This one deliberately does not follow that
  precedent.)
*/
.rails {
  position: fixed; inset: 0; z-index: 21; pointer-events: none;
  overflow: hidden;
  --hot: 0.3;       /* segment brightness, written per frame by rails.js */
  --seg-h: 128px;   /* segment length, written once by rails.js */
}

/*
  The rails ARE the column's hairlines while the loop is running, so the column
  must not draw its own. Two hairlines cannot coincide by being drawn twice:
  0.055 over 0.055 composites to 0.107, which puts a band twice as bright as the
  rail on exactly the column's vertical extent, top and bottom edges and all.

  All columns, not `#scene > .column`. The recursion clone is a `.column` too,
  and at the seam it renders at exactly 1:1 — leaving its borders in would hand
  a short hard hairline over to a full-height gradient one frame later.

  .static-layout keeps its borders: there are no rails there, only rules.
*/
body:not(.static-layout) .column { border-color: transparent; }

/*
  Positioned with `left`, never with a transform.

  These are 1px rules. A transform lands one at a fractional device pixel and the
  browser resolves that by painting two grey columns instead of one bright one —
  rails.js snaps `left` to a whole device pixel for the same reason. Only the
  segments move, and only in Y, so a hairline is never resampled across its own
  width.
*/
.rail {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, var(--rail-a, 0.03)) 13%,
    rgba(255, 255, 255, var(--rail-a, 0.03)) 87%,
    rgba(255, 255, 255, 0) 100%);
}
/* The two the layout genuinely hangs off. Their alpha is --rule's, unattenuated
   by the outward falloff, because they are structure rather than atmosphere. */
.rail--spine {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, var(--rail-a, 0.055)) 6%,
    rgba(255, 255, 255, var(--rail-a, 0.055)) 94%,
    rgba(255, 255, 255, 0) 100%);
}

/*
  A head and a tail, in the grid's own amber rather than a new colour. Parked one
  segment-length above the top of its rail so that phase 0 and phase 1 are both
  off-screen — which is what makes the recycle, and therefore the loop's wrap,
  invisible. The travel is the transform; the shape never changes.

  `calc()` inside an rgba alpha invalidates the whole `background` declaration if
  it is not understood, not just one stop — so on an ancient WebView the
  segments simply do not paint and the base hairlines survive. A sane failure,
  but a silent one.
*/
.rail__seg {
  position: absolute; left: 0; width: 1px;
  top: calc(var(--seg-h) * -1); height: var(--seg-h);
  transform-origin: 50% 50%;
  will-change: transform;
  background: linear-gradient(180deg,
    rgba(255, 208, 168, 0) 0%,
    rgba(255, 208, 168, calc(var(--hot) * var(--seg-a, 1) * 0.95)) 10%,
    rgba(255, 122,  40, calc(var(--hot) * var(--seg-a, 1) * 0.60)) 26%,
    rgba(255, 122,  40, calc(var(--hot) * var(--seg-a, 1) * 0.15)) 64%,
    rgba(255, 122,  40, 0) 100%);
}

/* Reduced motion, and the no-script/static layout: the rules stay, because they
   are drawing, not motion. Only the travelling light stops. */
.rails.is-still .rail__seg { display: none; }
@media (prefers-reduced-motion: reduce) { .rail__seg { display: none; } }
body.static-layout .rails { position: absolute; height: 100%; }

/*
  On a phone the closing device is WIDTH-limited, not height-limited: the cap is
  74vw and 16/9 makes that 162px tall inside a band 606px tall. Centred, it sat
  marooned with 222px of nothing above and below it.

  The cap itself stays. Z_close is vw / canvasW and the canvas is 77% of the
  device, so the width fraction IS the fly-in's travel — 74vw gives 1.77, and
  widening to 92vw to fill the band would drop it to 1.41 and take the flight
  out of the ending to fix a composition problem. Shrink the band instead: the
  device then sits just under the copy that names it, which is where it belongs.
*/
@media (max-width: 820px) {
  /* Anchored to the TOP of the band, not centred in it. The band still has to
     reach the bottom of the frame so the device has somewhere to rise FROM
     during the approach, but a 162px object centred in a 446px band sat 372px
     below the headline naming it. Start-aligned it sits just under the copy,
     and the approach still lifts it up from below the fold exactly as before —
     that motion is a transform on .again, not a property of its box. */
  .again { top: 30%; place-items: start center; }
}
