/*  Orion — landing page.
 *
 *  Dark only, deliberately. No theme toggle, no prefers-color-scheme: the app
 *  is used in a dark room and the page is the same room. The chrome is
 *  near-black plus one teal, exactly as the editor's interface is, and teal is
 *  reserved for live numbers — never a button, never a wash. One more ink is
 *  allowed: the amber of film-rebate edge markings, for the frame counter in
 *  the corner, and nothing else. Every other color on this page comes out of
 *  the photographs, which is the whole point: it is a photo editor, so the
 *  photographs do the talking.
 *
 *  Motion is scroll-driven and the visitor operates nothing. Four scenes are
 *  scrubbed — the hero develops the raw, a statement lights up word by word,
 *  a proxy wipes to full resolution, a gradient mask sweeps a sky — via
 *  html.scrub, which JavaScript adds only when motion is allowed. Without it
 *  (no JS, reduced motion) every scene is an ordinary section showing its
 *  finished state: the CSS default is the developed photograph, and the
 *  script is what rewinds it.
 *
 *  Display type is the system face — SF Pro on every Mac, which is who this
 *  page is for; an Apple app should read like one. The wordmark is Space
 *  Grotesk and the readouts Space Mono, both self-hosted — no font service,
 *  no third-party request. Same reason the app has no accounts: the page
 *  should work when its owner is the only server left.
 */

@font-face {
  font-family: "Space Mono";
  src: url("fonts/SpaceMono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Space Mono";
  src: url("fonts/SpaceMono-700.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}
/* The wordmark face — a plain modern grotesk, kin to the readout mono. */
@font-face {
  font-family: "Space Grotesk";
  src: url("fonts/SpaceGrotesk-var.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}

:root {
  --ink:    #08090b;   /* ground — a stop deeper than the app, this is a cinema */
  --panel:  #141618;   /* raised */
  --rail:   #24282b;   /* hairlines */
  --ivory:  #f0f3f4;   /* headings */
  --body:   #c9d2d5;   /* running text, a step down from headings */
  --slate:  #9aa6ab;   /* muted, cool, biased toward the accent */
  --teal:   #4DB6C4;   /* live numbers only */
  --rebate: #F0C674;   /* film-edge amber — the frame counter only */

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
  --disp: var(--sans);
  --mono: "Space Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --gut: clamp(1.25rem, 5vw, 6rem);
  --measure: 32rem;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--ivory);
  font-family: var(--sans);
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;          /* the body never scrolls sideways */
}

img { display: block; max-width: 100%; height: auto; }
p { margin: 0; }

/* ---------- Display type ----------
 * Heavy, tight and optically sized — the headlines are the loud thing on
 * this page, so everything else stays quiet. */
.disp {
  margin: 0;
  font-family: var(--disp);
  font-optical-sizing: auto;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 0.96;
  text-wrap: balance;
}
/* Section headers: caps, tracked, medium — the engraved-plate register.
 * The drama belongs to the photographs and the figures, not the words. */
h2.disp {
  font-size: clamp(1.8rem, 4.4vw, 3.8rem);
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  line-height: 1.04;
}
/* The figure moments — a measurement as the headline, set thin and
 * enormous, with its unit small beside it. */
.disp--num {
  font-size: clamp(2.9rem, 11vw, 10.5rem);
  font-weight: 200;
  letter-spacing: -0.01em;
  text-transform: none;
  line-height: .95;
  font-variant-numeric: tabular-nums;
}
.disp--num small {
  font-size: .32em;
  font-weight: 500;
  letter-spacing: .08em;
}

/* The tracked line under a figure — what the number means. */
.kicker {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--body);
  text-shadow: 0 1px 10px rgba(8,9,11,.85);
}

.skip {
  position: absolute; left: -9999px;
  background: var(--ivory); color: var(--ink);
  padding: 0.7rem 1.1rem; border-radius: 0 0 8px 0; z-index: 99;
}
.skip:focus { left: 0; top: 0; }

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Photographs surface like prints in the tray: from black, up to full
 * density. Images that also carry the parallax transform skip the scale so
 * the two never fight over one property. */
html.js .fadei {
  opacity: 0;
  filter: brightness(.3) saturate(.7);
  transition: opacity 1.3s ease, filter 1.4s ease, transform 1.8s cubic-bezier(.16,1,.3,1);
}
html.js .fadei:not(.plx) { transform: scale(1.045); }
html.js .fadei.in { opacity: 1; filter: none; }
html.js .fadei.in:not(.plx) { transform: none; }

/* Film grain over the two darkest scenes — a print, not a screenshot.
 * Static and very quiet; it exists to be felt, not seen. */
.grain {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
}

/* ---------- Reveals ----------
 * Hiding is gated on html.js — the class the script adds as its first act —
 * so a browser with JavaScript off never hides anything. The failure mode of
 * a reveal must be "no animation", never "no page". */
html.js .rv {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity .8s cubic-bezier(.16,1,.3,1), transform .8s cubic-bezier(.16,1,.3,1);
}
html.js .rv.in { opacity: 1; transform: none; }
.d1 { transition-delay: .10s; }
.d2 { transition-delay: .20s; }
.d3 { transition-delay: .30s; }
.d4 { transition-delay: .45s; }

.lines .l { display: block; overflow: hidden; }
.lines .l > span { display: block; }
html.js .lines .l > span {
  transform: translate3d(0, 118%, 0);
  transition: transform .95s cubic-bezier(.19,1,.22,1);
}
html.js .lines.in .l > span { transform: none; }
.lines .l:nth-child(2) > span { transition-delay: .09s; }
.lines .l:nth-child(3) > span { transition-delay: .18s; }
/* Clipping sits tight on the glyphs, so give descenders room to land. */
.lines .l { padding-bottom: 0.06em; margin-bottom: -0.06em; }

/* ---------- Hero — scroll to develop ---------- */

.dev { position: relative; height: 100svh; }
html.scrub .dev { height: 330svh; }

.dev__pin {
  position: sticky; top: 0;
  height: 100svh;
  overflow: hidden; isolation: isolate;
}

/* Two transforms that never fight: the wrapper carries the scroll-driven
 * settle (script), the image carries the slow ambient drift (animation). */
.dev__zoom { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
html.scrub .dev__zoom { will-change: transform; }
.dev__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 58% 50%;
  animation: drift 44s ease-in-out infinite alternate;
}
@keyframes drift {
  from { transform: scale(1.05) translate3d(-0.8%, 0, 0); }
  to   { transform: scale(1.12) translate3d(0.8%, -1%, 0); }
}
html.scrub .dev__img { will-change: filter, transform; }

/* An eyepiece, not a scrim: vignetted at the edges, a band of dark at the
   foot for the readout, and otherwise out of the photograph's way. */
.dev__grade {
  position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(115% 115% at 50% 48%, transparent 52%, rgba(8,9,11,.55) 100%),
    linear-gradient(to top, rgba(8,9,11,.8) 0%, rgba(8,9,11,.3) 14%, transparent 34%),
    linear-gradient(to bottom, rgba(8,9,11,.45) 0%, transparent 16%);
}

/* ---------- The viewfinder ----------
 * Frame brackets, a thirds grid, an AF point, the wordmark where the
 * eyepiece display sits. The whole assembly is one layer: pushing through
 * it at the end of the hero scales and fades everything at once. All the
 * furniture draws in currentColor, so state changes are one property. */

/* The ocular. Everything seen through the eyepiece is inside; the script
 * opens the clip from a small rounded window to the full frame. No script,
 * no clip — the finder simply rests over the developed photograph. */
.dev__eye { position: absolute; inset: 0; }
html.scrub .dev__eye { will-change: clip-path; }

/* ---------- Centre-spot ND ----------
 * A real filter, not a scrim. Multiply is what a neutral density does to
 * light — it scales rather than adds — so the clipped showroom behind the
 * wordmark comes down about a stop and a half while the asphalt, already at
 * L 0.008, does not move at all. The photograph keeps its shape; a flat
 * black wash would have flattened it. Elliptical and softly fallen off, so
 * the yellow car on one edge and the white one on the other stay lit. */
.vf__nd {
  position: absolute; inset: 0; z-index: 1;
  /* Neutral black at 0.56 alpha, which composites to exactly a multiply by
   * 0.44 — the density without the blend mode, one less compositing layer,
   * and no mask needed because the gradient is its own falloff. The core
   * holds out to 52%: the tracked wordmark is wider than the viewport, so a
   * tighter core left the last letter standing on the white car unfiltered. */
  background: radial-gradient(ellipse 58% 34% at 50% 47%,
    rgba(0,0,0,.56) 0%, rgba(0,0,0,.56) 52%, rgba(0,0,0,0) 100%);
}

.vf {
  position: absolute; inset: 0; z-index: 2;
  transform-origin: 50% 50%;
}
/* Hidden until the eye reaches the finder; the script fades it up. */
html.scrub .vf { will-change: transform, opacity; opacity: 0; }

.vf__frame {
  position: absolute; inset: clamp(14px, 3.5vw, 44px);
  color: rgba(240,243,244,.85);
  background-image:
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor);
  background-position:
    left top, left top, right top, right top,
    left bottom, left bottom, right bottom, right bottom;
  background-size:
    34px 2px, 2px 34px, 34px 2px, 2px 34px,
    34px 2px, 2px 34px, 34px 2px, 2px 34px;
  background-repeat: no-repeat;
}

.vf__grid {
  position: absolute; inset: clamp(14px, 3.5vw, 44px);
  background-image:
    linear-gradient(rgba(240,243,244,.08), rgba(240,243,244,.08)),
    linear-gradient(rgba(240,243,244,.08), rgba(240,243,244,.08)),
    linear-gradient(rgba(240,243,244,.08), rgba(240,243,244,.08)),
    linear-gradient(rgba(240,243,244,.08), rgba(240,243,244,.08));
  background-size: 1px 100%, 1px 100%, 100% 1px, 100% 1px;
  background-position: 33.33% 0, 66.66% 0, 0 33.33%, 0 66.66%;
  background-repeat: no-repeat;
}

/* The AF point sits at the upper thirds intersection, on the silver car —
 * where a hand would put it. It confirms, turning teal, the live-state
 * color, the moment the develop lands. */
.vf__af {
  position: absolute; left: 63%; top: 34%;
  width: 58px; height: 40px;
  transform: translate(-50%, -50%);
  color: rgba(240,243,244,.5);
  transition: color .3s ease, filter .3s ease;
  background-image:
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor), linear-gradient(currentColor, currentColor);
  background-position:
    left top, left top, right top, right top,
    left bottom, left bottom, right bottom, right bottom;
  background-size:
    9px 1.5px, 1.5px 9px, 9px 1.5px, 1.5px 9px,
    9px 1.5px, 1.5px 9px, 9px 1.5px, 1.5px 9px;
  background-repeat: no-repeat;
}
.vf.lock .vf__af {
  color: var(--teal);
  filter: drop-shadow(0 0 7px rgba(77,182,196,.7));
}

/* The platform badge, top of the finder, where a camera names itself.
 * Outside the ocular clip, so it is on screen before the eye is. */
.vf__eyebrow {
  position: absolute;
  top: calc(clamp(14px, 3.5vw, 44px) + 14px);
  left: 0; right: 0;
  text-align: center;
  z-index: 2;
}
.badge {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .5em 1.05em;
  border: 1px solid rgba(240,243,244,.22);
  border-radius: 999px;
  background: rgba(20,22,24,.55);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  font-family: var(--sans);
  font-size: .82rem; letter-spacing: .02em;
  color: var(--ivory);
}
.badge svg { width: .95em; height: .95em; margin-top: -.12em; }

/* The wordmark block sits outside the clip too, and takes the same exit
 * transform as the finder on the push through. */
.vf__center {
  position: absolute; inset: 0;
  z-index: 2;
  transform-origin: 50% 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(.9rem, 2vh, 1.4rem);
  text-align: center;
  padding: 0 1.2rem;
  pointer-events: none;
}
.vf__name {
  margin: 0;
  font-family: "Space Grotesk", var(--sans);
  font-weight: 340;
  font-size: clamp(3.4rem, 15vw, 13.5rem);
  letter-spacing: .26em; margin-right: -.26em;   /* recenter the tracked text */
  text-transform: uppercase;
  line-height: 1;
  color: #fff;
  /* A contact edge, not a glow. The old pair was a 34px and a 70px blur —
   * at that radius they light the area around a letter without ever
   * defining it, and the teal one read as a rendering artifact. Display
   * glyphs in a finder carry a thin dark edge so they survive whatever the
   * scene is doing; this is that edge, plus one wide blur for weight. */
  text-shadow:
    0 0 2px rgba(8,9,11,.85),
    0 0 5px rgba(8,9,11,.6),
    0 2px 30px rgba(8,9,11,.7);
}
.vf__sub {
  color: var(--ivory);
  font-size: clamp(1rem, .95rem + .4vw, 1.25rem);
  text-shadow: 0 0 3px rgba(8,9,11,.8), 0 1px 16px rgba(8,9,11,.85);
}

/* Top status line. Tiny, tracked, pushed to the corners of the frame the
 * brackets draw, so it reads as engraving on the display and not as text. */
.vf__status {
  position: absolute;
  left: calc(clamp(14px, 3.5vw, 44px) + 12px);
  right: calc(clamp(14px, 3.5vw, 44px) + 12px);
  top: calc(clamp(14px, 3.5vw, 44px) + 12px);
  display: flex; justify-content: space-between;
  font-size: .58rem; letter-spacing: .22em; text-transform: uppercase;
  /* The corners sit outside the ND, and the left one lands on the lit
   * showroom — .52 disappeared there. A finder's engraving is quiet, not
   * faint, and it carries the same contact edge the wordmark does. */
  color: rgba(240,243,244,.74);
  text-shadow: 0 0 3px rgba(8,9,11,.95), 0 1px 12px rgba(8,9,11,.9);
}

/* ---------- The readout ----------
 * Two lines at the foot of the finder: the shot, then the develop. */
.vf__readout {
  position: absolute;
  left: var(--gut); right: var(--gut);
  bottom: clamp(1.2rem, 3.5vh, 2.5rem);
  z-index: 1;
}
.vf .dev__hud { margin-top: .85rem; }

.vf__shot {
  display: flex; align-items: center;
  gap: .4rem 1.15rem;
  font-size: .74rem; letter-spacing: .04em;
  color: var(--ivory);
  text-shadow: 0 1px 10px rgba(8,9,11,.9);
}
.shot__c { white-space: nowrap; }
.shot__c i { font-style: normal; color: var(--slate); }
/* The live one, and the only teal on the line. */
.shot__ev { color: var(--teal); font-weight: 700; }
.shot__u { color: var(--slate); font-size: .62rem; letter-spacing: .18em; }

/* The exposure scale. A ±3 EV rule with a taller centre post and a needle
 * the develop drives — the one instrument on the page with a pointer. */
/* The scale travels with the number it points at, hard right, so the pair
 * reads as one instrument instead of a rule adrift in the middle. */
.evb {
  position: relative;
  flex: 0 1 clamp(88px, 22vw, 210px);
  height: 13px;
  margin: 0 .25rem 0 auto;
}
.evb::before {          /* the ticks, one per third of a stop */
  content: ""; position: absolute; left: 0; right: 0; top: 7px; height: 4px;
  background-image: repeating-linear-gradient(
    to right,
    rgba(240,243,244,.55) 0 1px, transparent 1px 5.5556%);
}
.evb::after {           /* the centre post — nought */
  content: ""; position: absolute; left: 50%; top: 2px;
  width: 1px; height: 9px; margin-left: -.5px;
  background: rgba(240,243,244,.75);
}
.evb__n {
  position: absolute; top: 0; left: 50%;
  width: 5px; height: 5px;
  margin-left: -2.5px;
  background: var(--teal);
  clip-path: polygon(50% 100%, 0 0, 100% 0);
  filter: drop-shadow(0 0 5px rgba(77,182,196,.75));
}

/* ---------- The numbers strip ---------- */

.strip {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1.6rem 3rem;
  padding: clamp(2.5rem, 7vh, 4.5rem) var(--gut);
  border-bottom: 1px solid var(--rail);
}
.strip__lede {
  max-width: 34rem; color: var(--body);
  font-size: clamp(1rem, .95rem + .4vw, 1.25rem);
  text-wrap: balance;
}

/* The numbers, set thin and enormous over small tracked labels, hairlines
 * between. No capsules — a specification, not a row of buttons. */
.stats { display: flex; flex-wrap: wrap; gap: clamp(1.6rem, 4vw, 3.4rem); }
.stat { display: flex; flex-direction: column; gap: .5rem; }
.stat + .stat { border-left: 1px solid rgba(240,243,244,.14); padding-left: clamp(1.6rem, 4vw, 3.4rem); }
.stat b {
  font-family: var(--disp); font-weight: 200;
  font-size: clamp(2.3rem, 1.6rem + 3vw, 4.2rem);
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ivory);
  font-variant-numeric: tabular-nums;
}
.stat__l {
  font-family: var(--mono); font-size: .62rem;
  letter-spacing: .24em; text-transform: uppercase;
  color: var(--slate);
}

/* The slider. A real rail with a real thumb, dragged by the scroll — the
 * page's one moving control, and the visitor is the one moving it. */
.dev__rail {
  position: absolute; right: clamp(1.1rem, 3vw, 3.2rem); top: 50%;
  height: 34svh; width: 2px;
  transform: translateY(-50%);
  background: rgba(240,243,244,.16);
  border-radius: 1px;
  z-index: 1;
}
.dev__fill {
  position: absolute; left: 0; top: 0; width: 100%; height: 0%;
  background: var(--teal); border-radius: 1px;
}
.dev__thumb {
  position: absolute; left: 50%; top: 0;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--ivory);
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 3px rgba(8,9,11,.5), 0 0 18px rgba(77,182,196,.55);
}
html:not(.scrub) .dev__rail { display: none; }

/* The readout. Final values in the HTML — the page is honest without the
 * script — and the script rewinds them to zero before the scrub begins. */
.dev__hud {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: .4rem 1.6rem;
  margin-top: 1.6rem; padding-top: 1rem;
  border-top: 1px solid rgba(240,243,244,.14);
  font-size: .76rem; letter-spacing: .06em;
  color: var(--body);
  text-shadow: 0 1px 10px rgba(8,9,11,.85);
  position: relative; z-index: 1;
}
.dev__hud b { color: var(--teal); font-weight: 700; }
.hud__ms { margin-left: auto; }

/* The invitation, over the black, under the window. */
.dev__cue {
  position: absolute; left: 0; right: 0;
  bottom: clamp(1.4rem, 4vh, 2.8rem);
  z-index: 2;
  text-align: center;
  letter-spacing: .3em; text-transform: uppercase; font-size: .72rem;
  color: var(--ivory);
}
.cue__tick { display: inline-block; animation: tick 2.2s ease-in-out infinite; }
@keyframes tick { 0%,100% { transform: translateY(0); opacity: .4; } 50% { transform: translateY(4px); opacity: 1; } }
html:not(.scrub) .dev__cue { display: none; }

/* ---------- Statement — lit word by word ----------
 * The script wraps the words; until each is reached it sits in the dark.
 * Without the script there are no .w spans and the line is simply lit. */

.sayscene { position: relative; }
html.scrub .sayscene { height: 180svh; }
.say__pin {
  display: flex; align-items: center;
  min-height: 60svh;
  padding: clamp(4rem, 10vh, 8rem) var(--gut);
}
html.scrub .say__pin { position: sticky; top: 0; height: 100svh; min-height: 0; }
.say {
  max-width: 56rem;
  font-size: clamp(1.9rem, 5vw, 4.2rem);
  font-weight: 600; letter-spacing: -0.015em;
  /* 1.22, not tighter: an inline background fills the font's content box,
   * about 1.17em here, so anything under that and the highlighter band on
   * one line eats the descenders of the line above. The marker needs the
   * air it would need on paper. */
  line-height: 1.22;
  color: var(--ivory);
}
.say em { font-style: normal; color: #fff; }
html.scrub .say .w { color: #262c30; transition: color .35s ease; }
html.scrub .say .w.on { color: var(--ivory); }
/* The second sentence is run over with a highlighter as the scroll reaches
 * each word: rebate amber, ink-dark text, the marker's slight overshoot.
 * The band is drawn as a gradient grown from 0 to full width rather than a
 * background faded up, because a fade spends its whole transition as a
 * half-transparent amber over near-black — a murky olive block. A stroke
 * is always fully inked; it just is not there yet. */
html.scrub .say em .w,
html.scrub .say em .w.on {
  /* The ink never changes. A marker passes over writing that is already
   * dark; it does not recolour it. Any per-word colour flip cannot stay in
   * step with a band sweeping across that same word, so mid-stroke a letter
   * ends up half ink-on-amber and half ink-on-black. Holding the dark still
   * removes the problem instead of timing around it — and 8.9:1 on the
   * rebate amber is a comfortable read. */
  color: #262c30;
}
html.scrub .say em .w {
  background-image: linear-gradient(var(--rebate), var(--rebate));
  background-repeat: no-repeat;
  /* The overshoot is in the band's own geometry, not a box-shadow: a shadow
   * is the shape of the whole border box, so it can only snap in at full
   * width, and it snapped. This grows with the stroke. */
  background-position: -0.06em 0;
  background-size: 0% 100%;
  transition: background-size .3s cubic-bezier(.4,0,.3,1);
}
html.scrub .say em .w.on { background-size: calc(100% + 0.12em) 100%; }

/* ---------- Speed — the proxy wipe ---------- */

.wipe { position: relative; height: 100svh; }
html.scrub .wipe { height: 230svh; }
.wipe__pin {
  position: sticky; top: 0;
  height: 100svh;
  display: flex; align-items: flex-end;
  padding: var(--gut);
  overflow: hidden; isolation: isolate;
}
.wipe__img, .wipe__proxy img {
  position: absolute; inset: 0; z-index: -3;
  width: 100%; height: 100%; object-fit: cover;
}
/* What everyone else shows you while you drag. Clipped away as the sweep
   passes; clipped away entirely when there is no script. */
.wipe__proxy { position: absolute; inset: 0; z-index: -2; clip-path: inset(0 0 0 100%); }
.wipe__proxy img { position: absolute; inset: 0; z-index: auto; filter: saturate(.7) blur(16px); transform: scale(1.04); }
html.scrub .wipe__proxy { will-change: clip-path; clip-path: inset(0 0 0 0); }

.wipe__edge {
  position: absolute; inset: 0; z-index: -1;
  pointer-events: none; opacity: 0;
}
html.scrub .wipe__edge { will-change: transform, opacity; opacity: 1; }
.wipe__edge::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 1px;
  background: rgba(240,243,244,.85);
  box-shadow: 0 0 22px rgba(240,243,244,.5);
}
.wipe__lab {
  position: absolute; top: 16%;
  font-size: .64rem; letter-spacing: .28em; color: var(--ivory);
  text-shadow: 0 1px 8px rgba(8,9,11,.9);
  white-space: nowrap;
}
.wipe__lab--l { left: 0; transform: translateX(calc(-100% - 14px)); }
.wipe__lab--r { left: 14px; opacity: .7; }

.wipe__wash {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(to top, var(--ink) 1%, rgba(8,9,11,.88) 22%, rgba(8,9,11,.12) 62%);
}
.wipe__copy { position: relative; z-index: 1; max-width: 44rem; }

/* ---------- The mask sweep ---------- */

.maskscene { position: relative; height: 100svh; }
html.scrub .maskscene { height: 220svh; }

.mask__pin {
  position: sticky; top: 0;
  height: 100svh;
  display: flex; align-items: flex-end;
  padding: var(--gut);
  overflow: hidden; isolation: isolate;
}
.mask__img {
  position: absolute; inset: 0; z-index: -3;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 45%;
}

/* The mask itself: a shade that ends in a feathered ramp, with the editor's
 * own three guide lines riding the ramp — full, center, zero — and a handle
 * on the center line. Tilted a few degrees, as a hand would place it. The
 * script slides the whole assembly down; at rest (no script) it sits where
 * the scrub would finish. */
.mask__sweep {
  position: absolute; left: -12%; right: -12%;
  top: -200%; height: 200%;
  z-index: -2;
  transform: translate3d(0, 55%, 0) rotate(-4deg);
  pointer-events: none;
}
html.scrub .mask__sweep { transform: translate3d(0, 0, 0) rotate(-4deg); will-change: transform; }
.mask__shade {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(4,6,9,.82) 0%, rgba(4,6,9,.82) 64%, rgba(4,6,9,0) 96%);
}
.mask__line {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px dashed rgba(240,243,244,.75);
}
.mask__line--full   { top: 64%; }
.mask__line--center { top: 80%; }
.mask__line--zero   { top: 96%; }
.mask__handle {
  position: absolute; left: 50%; top: 0;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--ivory);
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 3px rgba(4,6,9,.45), 0 0 14px rgba(240,243,244,.35);
}

.mask__wash {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, var(--ink) 1%, rgba(8,9,11,.86) 20%, transparent 58%);
}
.mask__copy { max-width: 44rem; }
.mask__copy p { margin-top: 1.2rem; max-width: var(--measure); color: var(--body); }

/* ---------- Proof ----------
 * The screenshot lands like a print laid on a table: tilted away, settling
 * flat as it arrives. The script drives it; at rest it simply lies flat. */

.proof {
  padding: clamp(5rem, 13vh, 10rem) var(--gut);
  display: flex; justify-content: center;
  perspective: 1400px;
}
.shot {
  margin: 0; max-width: 74rem;
  border-radius: 14px; overflow: hidden;
  border: 1px solid var(--rail);
  box-shadow: 0 40px 120px rgba(0,0,0,.65), 0 0 90px rgba(77,182,196,.08);
  transform-origin: 50% 100%;
}
html.scrub .shot { will-change: transform; }
.shot img { width: 100%; }
.shot figcaption {
  padding: .85rem 1.1rem;
  background: var(--panel);
  border-top: 1px solid var(--rail);
  font-size: .74rem; letter-spacing: .06em; color: var(--body);
}

/* ---------- Color ----------
 * The glasshouse photograph is a vertical, so it stands at full section
 * height on the right, nearly uncropped, dissolving into the dark on its
 * leading edge — no cut, no frame, the words in the black beside it. */

.duo {
  position: relative;
  min-height: 100svh;
  display: flex; align-items: center;
  padding: clamp(4rem, 10vh, 7rem) var(--gut);
  overflow: hidden;
  --imgw: min(72svh, 55vw);          /* the photo panel's width */
}
.duo h2 { font-size: clamp(2.6rem, 6.2vw, 5.6rem); }
.duo__text { position: relative; z-index: 2; max-width: 42rem; }
.duo__text p { margin-top: 1.2rem; max-width: var(--measure); color: var(--body); }

.duo__img {
  position: absolute; top: 0; bottom: 0; right: 0;
  width: var(--imgw); height: 100%;
  object-fit: cover;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 22%);
  mask-image: linear-gradient(to right, transparent, #000 22%);
  z-index: 0;
}

/* Parallax images get headroom to move inside their section. */
.plx { top: -8%; height: 116%; }
html.scrub .plx { will-change: transform; }

/* ---------- Ledger ---------- */

.ledger { padding: clamp(6rem, 16vh, 13rem) var(--gut); }
.ledger__lede { margin-top: 1.3rem; max-width: var(--measure); color: var(--body); }
/* The claim links to the evidence — the research/ directory, unsourced
 * list and all. Quiet underline, same register as the source link below. */
.ledger__lede a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(154,166,171,.55);
  text-underline-offset: .25em;
  transition: color .25s ease, text-decoration-color .25s ease;
}
.ledger__lede a:hover { color: var(--ivory); text-decoration-color: var(--ivory); }
.ledger__lede a:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }

.cites { list-style: none; margin: 2.6rem 0 0; padding: 0; max-width: 56rem; }
.cites li {
  display: flex; justify-content: space-between; gap: 1.5rem;
  padding: 1rem 0; border-top: 1px solid var(--rail);
  font-family: var(--mono); font-size: .8rem;
  transition: background .25s ease, padding-left .25s ease;
}
.cites li:hover { background: rgba(20,22,24,.65); padding-left: .6rem; }
.cites li:last-child { border-bottom: 1px solid var(--rail); }
/* The rows arrive as a cascade, one after the next. */
.cites li:nth-child(2) { transition-delay: .07s; }
.cites li:nth-child(3) { transition-delay: .14s; }
.cites li:nth-child(4) { transition-delay: .21s; }
.cites li:nth-child(5) { transition-delay: .28s; }
.cites li:hover { transition-delay: 0s; }
.k { color: var(--ivory); }
.v { color: var(--slate); text-align: right; font-variant-numeric: tabular-nums; }
.v b { color: var(--teal); font-weight: 700; }

/* ---------- Ownership ---------- */

.own {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 6rem);
  align-items: center;
  padding: clamp(6rem, 16vh, 13rem) var(--gut);
  border-top: 1px solid var(--rail);
}
.own__text p { margin-top: 1.2rem; max-width: var(--measure); color: var(--body); }

.sidecar {
  border: 1px solid var(--rail); border-radius: 12px;
  background: var(--panel);
  padding: .4rem 0;
  font-size: .78rem;
}
.sidecar__row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) auto;
  gap: 1rem; align-items: baseline;
  padding: 1rem 1.2rem;
}
.sidecar__row + .sidecar__row { border-top: 1px dashed var(--rail); }
.sidecar__name { color: var(--ivory); display: flex; align-items: center; gap: .55em; }
.fico { width: 1.15em; height: 1.15em; flex: none; stroke: currentColor; fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; opacity: .85; }
.sidecar__what { color: var(--slate); }
.sidecar__meta { color: var(--teal); font-size: .7rem; white-space: nowrap; }

/* ---------- Register ---------- */

.reg {
  padding: clamp(3.5rem, 11vh, 8rem) var(--gut);
  border-top: 1px solid var(--rail);
}
.reg__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(2rem, 5vw, 5rem);
}
.reg__h {
  font-size: .72rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--slate);
  display: flex; align-items: center; gap: .6rem;
  margin: 0;
}
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--rail); }
.dot--on { background: var(--teal); box-shadow: 0 0 12px rgba(77,182,196,.8); }
.reg ul { margin: 1.3rem 0 0; padding: 0; list-style: none; }
.reg li {
  position: relative;
  padding: .5rem 0 .5rem 1.7em;
  border-bottom: 1px solid rgba(36,40,43,.6); font-size: .95rem;
}
.reg li::before {
  content: ""; position: absolute; left: 0; top: .82em;
  width: 1.05em; height: 1.05em;
  background-color: var(--teal);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.4' fill='none' stroke='%23000' stroke-width='1.3'/%3E%3Cpath d='M5.3 8.3l1.9 1.9 3.5-4.2' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.4' fill='none' stroke='%23000' stroke-width='1.3'/%3E%3Cpath d='M5.3 8.3l1.9 1.9 3.5-4.2' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
}
.reg .dim li { color: var(--slate); }
.reg .dim li::before {
  background-color: var(--slate); opacity: .75;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.4' fill='none' stroke='%23000' stroke-width='1.3' stroke-dasharray='2.7 2.5'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.4' fill='none' stroke='%23000' stroke-width='1.3' stroke-dasharray='2.7 2.5'/%3E%3C/svg%3E") center/contain no-repeat;
}
.reg code {
  font-family: var(--mono); font-size: .85em;
  background: var(--panel); padding: .1em .4em; border-radius: 4px;
}
.reg__note { margin-top: 1.1rem; font-size: .85rem; color: var(--slate); }

/* ---------- Close ---------- */

.close {
  position: relative; min-height: 88svh;
  display: flex; align-items: center;
  padding: var(--gut); overflow: hidden; isolation: isolate;
}
.close__img {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}
.close__img.plx { top: -8%; height: 116%; }
.close__wash {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to right, rgba(8,9,11,.95) 18%, rgba(8,9,11,.45) 78%);
}
.close__body { position: relative; z-index: 1; max-width: 44rem; }
.close__body p:not(.status) { margin-top: 1.2rem; color: var(--body); max-width: var(--measure); }
.status { margin-top: 1.4rem; font-size: .76rem; color: var(--slate); }

/* The button wears focus-peaking corners — a viewfinder bracket, not a
 * capsule. The corners reach further on hover, like the frame locking on. */
.cta {
  position: relative;
  display: inline-block; margin-top: .8rem;
  font-family: var(--mono); font-weight: 700;
  font-size: .8rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ivory); text-decoration: none;
  padding: 1.05rem 1.9rem;
  background-image:
    linear-gradient(var(--ivory), var(--ivory)), linear-gradient(var(--ivory), var(--ivory)),
    linear-gradient(var(--ivory), var(--ivory)), linear-gradient(var(--ivory), var(--ivory)),
    linear-gradient(var(--ivory), var(--ivory)), linear-gradient(var(--ivory), var(--ivory)),
    linear-gradient(var(--ivory), var(--ivory)), linear-gradient(var(--ivory), var(--ivory));
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
  background-size:
    16px 2px, 2px 16px,
    16px 2px, 2px 16px,
    16px 2px, 2px 16px,
    16px 2px, 2px 16px;
  background-repeat: no-repeat;
  transition: background-size .35s cubic-bezier(.16,1,.3,1), color .25s ease, transform .3s cubic-bezier(.16,1,.3,1);
}
.cta:hover {
  color: #fff;
  transform: translateY(-2px);
  background-size:
    26px 2px, 2px 26px,
    26px 2px, 2px 26px,
    26px 2px, 2px 26px,
    26px 2px, 2px 26px;
}
.cta:focus-visible { outline: 2px solid var(--teal); outline-offset: 5px; }

/* The secondary route. One page, one primary action: the download wears the
 * viewfinder brackets and the source link is a plain underline, so which one
 * the page wants you to take is never in question. */
.close__body p.alt { margin-top: .7rem; font-size: .84rem; }
.close__body p.alt a {
  color: var(--slate); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: .25em;
  transition: color .25s ease;
}
.close__body p.alt a:hover { color: var(--ivory); }
.close__body p.alt a:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; }

/* ---------- Footer ---------- */

.bar {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem;
  padding: 2rem var(--gut) 3rem;
  border-top: 1px solid var(--rail);
  font-size: .78rem; color: var(--slate);
}

/* ---------- The frame counter ----------
 * Film-rebate edge markings for wherever you are on the roll. Fed by the
 * script; does not exist without it. */
.fr {
  position: fixed; right: clamp(1rem, 2.5vw, 2.2rem); bottom: 1.4rem;
  z-index: 50;
  font-size: .64rem; letter-spacing: .3em; text-transform: uppercase;
  color: var(--rebate);
  opacity: 0; pointer-events: none;
  transition: opacity .45s ease;
  overflow: hidden;
}
.fr.fr--on { opacity: .92; }
.fr span { display: inline-block; }
.fr.tick span { animation: frtick .4s cubic-bezier(.16,1,.3,1); }
@keyframes frtick { from { transform: translateY(120%); } to { transform: none; } }
html:not(.scrub) .fr { display: none; }

/* ---------- The download chip ----------
 * The page's one job, reachable from anywhere on the roll. Dressed like the
 * platform badge — hairline, blur, no fill — because teal is never a button.
 * Hidden over the hero (the close owns the pitch), it surfaces on the same
 * cue as the frame counter. Without the script the close CTA is the route. */
.dl {
  position: fixed; top: 1.1rem; right: clamp(1rem, 2.5vw, 2.2rem);
  z-index: 60;
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ivory); text-decoration: none;
  padding: .7em 1.15em;
  border: 1px solid rgba(240,243,244,.22);
  border-radius: 999px;
  background: rgba(20,22,24,.55);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: 0; visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .45s ease, transform .45s ease,
              border-color .25s ease, color .25s ease,
              visibility 0s linear .45s;
}
.dl.dl--on {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .45s ease, transform .45s ease,
              border-color .25s ease, color .25s ease;
}
.dl:hover { color: #fff; border-color: rgba(240,243,244,.5); }
.dl:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }
.dl__tag { color: var(--slate); letter-spacing: .14em; }
html:not(.scrub) .dl { display: none; }

/* ---------- Narrow ---------- */

@media (max-width: 900px) {
  /* Pinned scenes run shorter on a phone — the same scrub over less thumb
     travel. Progress is computed from the element's own height, so the
     scenes adapt without script changes. */
  html.scrub .dev { height: 250svh; }
  html.scrub .sayscene { height: 150svh; }
  html.scrub .wipe { height: 185svh; }
  html.scrub .maskscene { height: 175svh; }

  /* The wordmark keeps air on both sides of a narrow screen. */
  .vf__name {
    font-size: clamp(2.5rem, 12.5vw, 4rem);
    letter-spacing: .2em; margin-right: -.2em;
  }

  /* The numbers hold one row of three: smaller figures, no separators. */
  .stats { width: 100%; justify-content: space-between; gap: 1rem; }
  .stat + .stat { border-left: 0; padding-left: 0; }
  .stat b { font-size: clamp(1.55rem, 7vw, 2.3rem); }
  .stat__l { font-size: .56rem; letter-spacing: .18em; }

  .cites li { flex-direction: column; gap: .2rem; }
  .v { text-align: left; }
  /* Words up top, the photograph across the foot, dissolving upward. */
  .duo { min-height: 96svh; align-items: flex-start; padding-top: 5rem; }
  .duo__img {
    top: auto; left: 0; right: 0; bottom: 0;
    width: 100%; height: 54%;
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 24%);
    mask-image: linear-gradient(to bottom, transparent, #000 24%);
  }
  .own { grid-template-columns: 1fr; }
  .sidecar__row { grid-template-columns: minmax(0, 1fr) auto; }
  .sidecar__what { display: none; }
  .dev__rail { display: none; }
  .hud--wide { display: none; }
  /* The finder keeps the scale and the live stop on a phone; the focal
     length and the film speed are the ones a narrow line can spare. */
  .vf__shot { font-size: .66rem; gap: .3rem .7rem; }
  .vf__shot .shot__c:first-child, .vf__shot .shot__c:nth-child(4) { display: none; }
  .vf__status { font-size: .52rem; letter-spacing: .14em; }
  .fr { display: none; }
  .wipe__lab { top: 12%; }
}

/* ---------- Reduced motion ----------
 * The scrub never engages (the script checks the same query before adding
 * html.scrub), reveals are instant, and the ambient loops stop. */
@media (prefers-reduced-motion: reduce) {
  html.js .rv, html.js .lines .l > span { opacity: 1 !important; transform: none !important; transition: none !important; }
  .cue__tick { animation: none; }
  .dev__img { animation: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
