/* ═══════════════════════════════════════════════════════════════════════════
   SCENES — one block per scene, in the order the page plays them.
   Tokens and the type grammar live in core.css.
   ═══════════════════════════════════════════════════════════════════════ */

/* The opening sequence's styles lived here — the sheet, the written mark,
   the page turn, the cast shadow. All removed with the card itself. */

/* ═══ NAV ══════════════════════════════════════════════════════════════ */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  display: flex; align-items: center; gap: clamp(12px, 3vw, 40px);
  padding: clamp(12px, 1.7vh, 20px) var(--gutter);
  transition: transform var(--dur-ui) var(--ease-ui), background var(--dur-ui) var(--ease-ui),
              border-color var(--dur-ui) var(--ease-ui), backdrop-filter var(--dur-ui) var(--ease-ui);
  border-bottom: 1px solid transparent;
}
/* Glass is a moment, not the identity — it appears only once the hero has
   gone, so the first screen is paper and type and nothing else. */
.nav[data-lifted="true"] {
  background: var(--glass);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  border-bottom-color: var(--line);
}
.nav[data-hidden="true"] { transform: translateY(-104%); }

/* While the film owns the screen the bar is off it, on every device. The
   transition is the nav's own, so it lifts away rather than blinking out,
   and pointer events go with it so nothing invisible is clickable over the
   picture. */
body[data-film="on"] .nav {
  transform: translateY(-104%);
  pointer-events: none;
}

/* OVER THE FILM THE GLASS IS DARK, NOT PAPER. `--glass` is a translucent
   ivory, which is right for every section painted in paper and wrong for
   the one painted near-black: over the film it read as an opaque grey slab
   across the top of the picture. `data-ground="deep"` is already set on
   <body> for exactly this stretch (it is what inverts the ink), so the bar
   simply takes the stage's own colour there. On a phone, where the backdrop
   blur is off for the compositor's sake, this matters more rather than
   less: without the blur the flat ivory had nothing to sit in. */
body[data-ground="deep"] .nav[data-lifted="true"] {
  background: rgba(13, 11, 9, 0.62);
  border-bottom-color: rgba(246, 243, 236, 0.12);
}
@media (pointer: coarse) {
  body[data-ground="deep"] .nav[data-lifted="true"] { background: rgba(13, 11, 9, 0.86); }
}

.nav-mark { font-size: 1.173rem; margin-right: auto; line-height: 1; }

.nav-index { display: flex; gap: clamp(14px, 2vw, 30px); }
/* Small, precise, uppercase, and tracked one step wider than it was. At
   0.18em the index read as small text; at 0.22em it reads as a set line,
   which is the register the rest of the type is now in. */
/* CONTRAST COMES FROM THE INK, NOT FROM THE WEIGHT. The index was set in
   --ink-2 (6.7:1 on the old flat ivory), and the aged stock that replaced
   it is darker and carries crazing directly under this type, which took
   the measured worst case down into the fives and made a 0.71rem tracked
   cap genuinely hard to read. It is now full --ink at 82% — 11.4:1 on the
   sheet — with the tracking opened one more notch so the extra density
   does not read as a heavier line. The weight is untouched. */
.nav-link {
  position: relative; font-size: 0.816rem; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--ink-82); padding-block: 4px;
}
.nav-link::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: currentColor; transform: scaleX(0); transform-origin: right;
  transition: transform var(--dur-ui) var(--ease-ui);
}
.nav-link:hover { color: var(--ink); }
.nav-link:hover::after { transform: scaleX(1); transform-origin: left; }

/* ── The glass pill ───────────────────────────────────────────────────
   The reference for this is a React "glass button": a wrapper, a blurred
   translucent pill, an inner highlight ring and a soft shadow cast beneath
   it. Rebuilt here in the site's own CSS and palette, because the project
   has no React and no Tailwind to paste that component into, and because
   its default look — a bright rim on a dark ground — is not this page.

   What actually makes it read as glass is three things stacked, and all
   three have to stay: a real backdrop blur, a highlight along the TOP
   inside edge (light falls from above, so the lit edge is the top one),
   and a shadow that sits under the pill rather than around it. The
   translucency alone just looks like a faded button.

   The `.lume` ring already on this element still runs over the top; the
   glass is what it is now catching light on. */
.nav-cta {
  position: relative;
  font-size: 0.816rem; letter-spacing: 0.22em; text-transform: uppercase;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: var(--r-full);
  padding: 10px 20px; color: var(--ink);
  background: linear-gradient(
      160deg,
      rgba(255, 255, 255, 0.62) 0%,
      rgba(248, 246, 241, 0.30) 46%,
      rgba(216, 209, 197, 0.26) 100%);
  -webkit-backdrop-filter: blur(11px) saturate(165%);
          backdrop-filter: blur(11px) saturate(165%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),      /* the lit top edge      */
    inset 0 -1px 0 rgba(23, 20, 18, 0.06),        /* the shaded bottom     */
    0 8px 18px -12px rgba(23, 20, 18, 0.55),      /* the pill's own shadow */
    0 2px 5px -3px rgba(23, 20, 18, 0.30);
  transition: background var(--dur-ui) var(--ease-ui), color var(--dur-ui) var(--ease-ui),
              border-color var(--dur-ui) var(--ease-ui), box-shadow var(--dur-ui) var(--ease-ui),
              transform var(--dur-ui) var(--ease-ui);
}
/* Lifts a pixel and the shadow lengthens with it, which is the only part
   of this that has to be animated for the glass to feel like an object. */
.nav-cta:hover {
  background: var(--ink); color: var(--paper);
  border-color: rgba(23, 20, 18, 0.9);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 12px 24px -14px rgba(23, 20, 18, 0.7),
    0 3px 7px -4px rgba(23, 20, 18, 0.4);
}
.nav-cta:active { transform: translateY(0); }

/* A browser without backdrop-filter would show the gradient over raw page
   and the pill would read as a milky smear, so it takes the flat surface
   the button had before instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav-cta { background: var(--paper-2); border-color: var(--line); }
}

/* 44x44, measured. It was 44x29: the two bars plus 10px of padding is a
   comfortable width and an uncomfortable height, and a control that misses
   the minimum on ONE axis misses it. */
.burger {
  display: none; background: none; border: 0; padding: 10px; cursor: pointer;
  min-width: 44px; min-height: 44px;
}
.burger span { display: block; width: 24px; height: 1px; background: var(--ink); transition: transform var(--dur-ui) var(--ease-ui), opacity var(--dur-fast); }
.burger span + span { margin-top: 7px; }
.burger[aria-expanded="true"] span:first-child { transform: translateY(4px) rotate(45deg); }
.burger[aria-expanded="true"] span:last-child  { transform: translateY(-4px) rotate(-45deg); }

.sheet {
  position: fixed; inset: 0; z-index: 99; background: var(--paper);
  display: grid; place-items: center; opacity: 0; pointer-events: none;
  transition: opacity 0.5s var(--ease);
}
.sheet[hidden] { display: none; }
.sheet[data-open="true"] { opacity: 1; pointer-events: auto; }
.sheet-in { display: grid; gap: 0.4rem; text-align: center; }
.sheet-in > a {
  font-family: var(--f-display); font-variation-settings: var(--vs-mid);
  font-size: clamp(2.185rem, 9.2vw, 3.45rem); font-weight: var(--w-display); letter-spacing: var(--tr-display);
}
.sheet-foot { margin-top: 2rem; font-size: 0.897rem; letter-spacing: 0.14em; color: var(--ink-3); }

/* ═══ BUTTONS ══════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex; align-items: center; gap: 0.7rem;
  font-size: 0.851rem; letter-spacing: 0.2em; text-transform: uppercase;
  padding: 15px 30px; border-radius: var(--r-full);
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.14), 0 1px 2px rgba(23,20,18,0.16);
  transition: background var(--dur-ui) var(--ease-ui), color var(--dur-ui) var(--ease-ui), border-color var(--dur-ui) var(--ease-ui);
  will-change: transform;
}
.btn svg { width: 17px; height: 17px; }
.btn:hover { background: var(--red); border-color: var(--red); }
.btn--quiet { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--quiet:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn--lg { padding: 18px 34px; }


/* ═══ THE ATELIER DRAWINGS ═════════════════════════════════════════════
   Placement, and the placement is the whole discipline. Every drawing is
   anchored to an EDGE, most are cropped by one, none of them is allowed
   within reach of the centre column where the type lives. The two moments
   that carry them are the two that should be the loudest in the whole
   experience — the hero and the closing spread — and nothing between
   those two is decorated at all.

   Ink: `--red` at a tenth, hairline, no fill. The wobble comes from one
   shared SVG filter (see motion/drawings.js). */
.draw {
  position: absolute; display: block; pointer-events: none;
  color: var(--red);
  z-index: 0;
}
.draw svg {
  display: block; width: 100%; height: auto;
  filter: url(#atelier-ink);
}
/* Hairline at every size: the drawings are scaled by their box, never by
   their stroke, exactly like the printed marks. */
.draw svg [stroke], .draw svg path { vector-effect: non-scaling-stroke; }
.draw-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ── The hero ─────────────────────────────────────────────────────────
   A façade rising out of the left margin and a chandelier hung from the
   top right, both cropped. They frame the type column without touching
   it: the façade ends before the text begins, and the chandelier lives in
   the empty upper right where the composition has nothing else to say. */
.draw--facade {
  left: 0; bottom: -6%;
  width: clamp(150px, 15vw, 240px);
  opacity: 0.26;
  transform: translateX(-13%);
}
.draw--chandelier {
  right: clamp(24px, 7vw, 120px); top: -4%;
  width: clamp(96px, 9.5vw, 168px);
  opacity: 0.24;
}

/* A band of ironwork along the hero's right edge, low and cropped, so the
   eye finds it only after the headline. It is the faintest drawing on the
   site: ornament this dense has to be quieter than ornament that is
   sparse, or it reads as texture rather than as a drawing. */
.draw--ironwork {
  right: 0; bottom: 12%;
  width: clamp(180px, 22vw, 330px);
  opacity: 0.2;
  transform: translateX(18%);
}

/* ── The closing spread ───────────────────────────────────────────────
   The last page of the catalogue: a storefront across the foot of the
   section, a staircase turning out of the right edge, both well below and
   outside the statement. The storefront is the argument the whole site
   makes — this is the front you are judged at — so it is the largest
   drawing on the site and still only a tenth of an ink. */
/* IT RISES FROM THE BOTTOM EDGE, IT DOES NOT SIT BEHIND THE SENTENCE.
   At translateY(18%) the fascia ran straight through the closing sub and
   the drawing was doing the one thing the brief forbids. Pushed down until
   only the awning and the fascia are above the section's own edge: the
   page ends on the top of a shopfront, which is both the better crop and
   the better argument. */
.draw--storefront {
  left: 50%; bottom: 0;
  width: clamp(420px, 52vw, 760px);
  opacity: 0.22;
  transform: translateX(-50%) translateY(54%);
}
.draw--staircase {
  right: 0; top: 8%;
  width: clamp(130px, 13vw, 210px);
  opacity: 0.19;
  transform: translateX(22%);
}

/* The closing section has to be able to hold them. */
/* The drawing is CROPPED BY THE SECTION, which is the whole device: the
   page ends on the top of a shopfront. A generous clip margin let it run
   on into the contact form underneath and put a shop window behind the
   name field; 32px is enough for the section's own soft light to bleed
   and nothing like enough for a 470px drawing to escape. */
.closing { position: relative; overflow: clip; overflow-clip-margin: 32px; }

/* The closing spread is a printed page, so it gets printed corners: one
   drawn unit, top left and bottom right, rotated 180 for the second. Two
   rather than four — a full border would box the page in, and the two
   opposite corners are enough to imply the frame between them. */
/* SMALL AND FINE NEEDS MORE ALPHA THAN LARGE AND FINE. At 0.16 the corner
   and the sprig measured present in the DOM and invisible on the page:
   a 130px ornament of hairlines carries a fraction of the ink a 750px
   storefront does, so the same opacity is a different amount of drawing.
   Raised until each is found at reading distance and still missed from
   across the room. */
.draw--corner { width: clamp(88px, 9vw, 150px); opacity: 0.3; }
.draw--corner-tl { left: clamp(10px, 2.4vw, 40px); top: clamp(10px, 2.4vw, 40px); }
.draw--corner-br {
  right: clamp(10px, 2.4vw, 40px); bottom: clamp(10px, 2.4vw, 40px);
  transform: rotate(180deg);
}
/* The sprig sits beside the section label, where an invitation puts one
   beside a date. It is 46px, which is punctuation rather than picture. */
.draw--sprig {
  left: 50%; top: clamp(96px, 13vh, 190px);
  width: clamp(38px, 3.8vw, 58px);
  opacity: 0.34;
  transform: translateX(-50%) rotate(-4deg);
}

/* ── Narrow ───────────────────────────────────────────────────────────
   A phone is re-composed, not shrunk. The chandelier and the staircase
   leave entirely — there is no margin to hang them in — and the two that
   stay are pushed further off their edges so what remains on screen is a
   fragment of a drawing rather than a small complete one, which is the
   difference between an art-directed crop and a sticker. */
@media (max-width: 860px) {
  /* A phone keeps the two drawings that read as fragments and drops the
     four that would need room to be understood. */
  .draw--chandelier, .draw--staircase, .draw--ironwork, .draw--corner { display: none; }
  .draw--sprig { width: 34px; opacity: 0.3; top: clamp(64px, 9vh, 120px); }
  .draw--facade {
    width: clamp(120px, 34vw, 170px); opacity: 0.2;
    transform: translateX(-42%);
  }
  .draw--storefront {
    width: 150vw; opacity: 0.16;
    transform: translateX(-50%) translateY(26%);
  }
}

/* ═══ 01 · HERO ════════════════════════════════════════════════════════ */
.hero-glow {
  width: min(70vw, 900px); aspect-ratio: 1;
  left: 50%; top: 6%; transform: translateX(-58%);
  opacity: 0.7;
}
.closing-glow {
  width: min(60vw, 760px); aspect-ratio: 1;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  opacity: 0.9;
}
.closing-in { position: relative; z-index: 1; }

.hero {
  position: relative;
  min-height: 100vh; min-height: 100svh;
  display: flex; align-items: flex-end;
  padding-block: clamp(120px, 18vh, 200px) clamp(40px, 7vh, 84px);
  overflow: clip;
}
/* THE HERO IS THE PAGE. No plate, no veil, no scrim. Both were left over
   from the film that used to play here, and with nothing behind them they
   were painting ivory over the sheet's own tooth and printing in exactly
   the part of the frame the headline occupies. Deleted at the source
   rather than faded down: the continuity the brief asks for is one sheet
   of paper running from the first pixel of the hero to the last of the
   footer, and a scrim on top of it is a second surface. */
.hero-in { position: relative; z-index: 2; width: 100%; }
/* The eyebrow sits higher in the frame than the rest of the lockup, which
   is past where the scrim still has any strength, and it is the smallest
   type on the screen. On flat paper --ink-3 is correct for this role; over
   a moving picture it measured 2.9:1. Over imagery it takes full ink. */
.hero-eyebrow, .hero-eyebrow.plate {
  color: var(--ink);
  --plate-ink: var(--ink);
}
.hero-eyebrow {
  margin-bottom: clamp(1.6rem, 4vh, 3rem);
  /* Measured 1.7:1 in --ink-3 over the plate. On a picture the label is ink. */
  color: var(--ink);
  --plate-ink: var(--ink);
  font-weight: 500;
}
.hero-eyebrow::after { opacity: 0.32; }

/* THE STATEMENT IS SMALLER AND HAS MORE ROOM. It was set at the site's
   full display scale, where it read as a large UI headline shouting the
   tagline. Editorially it is a subtitle: the phrase a magazine sets under
   a masthead. `--t-hero` comes down here rather than globally (the film
   and the closing statement still want the big scale), the measure is
   held so it always breaks after "made", and the space beneath it is
   roughly doubled so the line is surrounded by page rather than by
   other elements. */
/* THE STATEMENT IS THE CAMPAIGN LINE AGAIN. It came down to 6.4vw when the
   brief wanted it read as an understated subtitle; the atelier brief wants
   the hero to open like a campaign book, and the client has now asked for
   the type to carry that too. 8.2vw sets 118px at 1440 — short of the
   9vw/130px it was before the reduction, because the hero is no longer
   competing with a film plate for attention and it does not need to shout
   over anything.

   The floor is 2.8rem, not 3rem, and that is measured rather than chosen:
   at 3rem the second line ("to be remembered.", whose script runs 1.14em
   wider than the roman) reaches 340px inside a 346px column at 390px wide,
   which is a wrap waiting to happen on the narrowest phone in use. 2.8rem
   leaves it 30px of room at 390 — and measured at 320, where the column is
   only 280px, it wraps anyway (line-box height 2.04x the font size against
   0.99 for a single line). A fixed floor cannot serve both, so below 400px
   the size becomes viewport-relative and the floor comes off: 11.5vw holds
   the second line on ONE line from 320 up, and reaches the same 44.8px at
   390 that the desktop ramp gives.

   The wrap test has to be the LINE-BOX HEIGHT against the font size.
   Counting client rects reports a wrap that is not there, because the
   script is set 1.14em and sits on its own rect. */
.hero-head {
  --t-hero: clamp(3.22rem, 9.43vw, 9.66rem);
  margin-bottom: clamp(2.1rem, 4.8vh, 3.4rem);
  letter-spacing: -0.014em;
}
.hero-line { display: block; overflow: hidden; }
.hero-line > span { display: block; }
.hero-line.is-armed > span { will-change: transform; }
.hero-line.is-done { overflow: visible; }
.hero-script { font-size: 1.14em; margin-left: 0.22em; }
.hero-stop { margin-left: -0.1em; }

.hero-sub { margin-bottom: clamp(1.7rem, 3.4vh, 2.6rem); max-width: 40ch; text-wrap: pretty; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; }

/* THE CUE IS CENTRED, AND IT IS THE ONLY THING ON THE PAGE THAT ASKS FOR
   ANYTHING. It sat in the bottom right corner, where a reader who has not
   yet looked down will never find it. It is now on the vertical axis of
   the viewport at the FOOT of the composition, which is both where the
   eye lands after reading the lockup and where the gesture it is asking
   for begins.

   On a light sheet the brief's "soft off-white" is invisible, so the cue
   takes the same ink as everything else and gets its softness from SIZE,
   TRACKING and OPACITY instead: 0.6rem, half an em of tracking, and a
   weight it never leaves. Measured 7.1:1 on the paper. */
.hero-cue {
  position: absolute; left: 50%; top: auto;
  /* ANCHORED TO THE BOTTOM, NOT TO A PERCENTAGE OF THE HEIGHT. It sat at
     30% of the hero, which is the upper middle — an invitation placed
     where nobody is looking for one. It now sits where a reader's eye
     already is when they finish the composition and where the gesture
     starts, and it is anchored to the bottom edge with a clamp so a short
     laptop screen and a tall desktop put it the same distance from the
     fold rather than the same fraction down the page.

     The centre column is free at this height: the hero's copy is a
     left-hand column that ends around 40% of the width, so the cue has
     the middle of the frame to itself without a single change to the
     layout above it. */
  bottom: clamp(38px, 6.5vh, 88px);
  transform: translateX(-50%);
  z-index: 2; font-size: 0.69rem; letter-spacing: 0.5em; text-indent: 0.5em;
  font-weight: 400; text-transform: uppercase;
  color: var(--ink-70);
  display: grid; justify-items: center; gap: 14px;
}
/* The line is drawn by a travelling segment inside a static hairline
   track, rather than by moving the whole rule: a rule that slides is a
   loading bar, a segment that descends inside a track is a page telling
   you which way it goes. */
.hero-cue::after {
  content: ""; display: block; width: 1px; height: 62px;
  background:
    linear-gradient(rgba(23,20,18,0.55), rgba(23,20,18,0)) 0 0 / 1px 26px no-repeat,
    linear-gradient(rgba(23,20,18,0.12), rgba(23,20,18,0.12));
  animation: sys-cue 3.4s var(--ease) infinite;
}
@keyframes sys-cue {
  0%,  8%  { background-position: 0 -26px, 0 0; opacity: 0.45; }
  46%      { opacity: 1; }
  92%,100% { background-position: 0 62px, 0 0;  opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-cue::after { animation: none; background-position: 0 18px, 0 0; }
}

/* ANYTHING NARROWER THAN 1200 PUTS THE CUE IN THE OPEN BAND ABOVE THE
   LOCKUP, AND THE BAND IS MEASURED FROM THE TOP, NEVER AS A FRACTION OF
   THE HEIGHT. Two measured failures produced this rule. At 1024 the hero's
   buttons end at x=475 and a centred cue starts at x=476 — one pixel is
   not clearance. And a percentage tracks the middle of the VIEWPORT while
   this hero's copy is bottom-aligned, so at 834x1112 a cue at 38% landed
   between the sub and the buttons with its rule running through "See our
   work". Anchored below the masthead, the empty band is above the copy at
   every height. Above 1200 there is 130px of clearance and the cue returns
   to the foot, where it was asked to be. */
@media (max-width: 1200px) {
  .hero-cue { top: clamp(88px, 19vh, 240px); bottom: auto; }
}

/* ═══ THE REEL ═════════════════════════════════════════════════════════
   A travelling scene: a tall block whose panel sticks for the length of the
   journey through it. Sticky stacking does the layering, so there are no
   pin-spacers to collide with the film's own pin or the work reel's own
   gesture. See motion/scenes.js. */
.scene-reel {
  position: relative;
  /* Was 168svh. Three of these run on the homepage, so that was 504svh of
     scroll spent moving a sticky panel a few percent. 126svh keeps a real
     camera move on each one and takes roughly 1200px out of the document,
     which is the difference between a page that travels and a page that
     makes you wait. */
  min-height: 126vh; min-height: 126svh;
  padding-block: 0;
  /* The glows are deliberately placed off-frame so the light reads as
     coming from outside the scene. `clip` contains them without creating a
     scroll container, which `hidden` would, and which would break the
     sticky panel inside.

     THE CLIP MARGIN IS NOT DECORATION. Measured at the usp/process
     boundary, the glow inside the sticky panel extended 308px past the
     section's bottom edge and `clip` cut it dead, drawing a hard
     horizontal line across the page at exactly the section join. That is
     the "background resets between sections" seam. Letting the clip box
     grow past the border lets the light bleed into the next scene, which
     is what makes two sections read as one room.

     It has to be a clip margin rather than `overflow-y: visible`: per
     spec, `visible` paired with a non-visible axis computes to `auto`,
     which creates a scroll container and breaks the sticky panel. A clip
     margin keeps clip on both axes, so there is still no scroll container
     and still no horizontal overflow. */
  overflow: clip;
  overflow-clip-margin: 420px;
}
.scene-glow { z-index: 0; }
.scene-glow--left  { width: min(58vw, 720px); aspect-ratio: 1; left: -12%; top: 8%; }
.scene-glow--mid   { width: min(64vw, 820px); aspect-ratio: 1; left: 50%; top: 46%; transform: translate(-50%, -50%); }
.scene-glow--right { width: min(54vw, 680px); aspect-ratio: 1; right: -10%; top: 14%; }
.scene-in > .wrap { position: relative; z-index: 1; }

.scene-in {
  position: sticky; top: 0;
  min-height: 100vh; min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding-block: clamp(96px, 14vh, 170px);
  will-change: transform, opacity;
}
/* Reduced motion turns the reel back into an ordinary page. */
body[data-reel="calm"] .scene-reel { min-height: 0; }
body[data-reel="calm"] .scene-in { position: static; min-height: 0; }

@media (max-width: 640px) {
  /* A phone screen is short enough that a full travel per scene becomes a
     lot of thumb. The scenes still stick, over a shorter distance. */
  .scene-reel { min-height: 138vh; min-height: 138svh; }
}

/* ═══ 02 · THE IDEA ════════════════════════════════════════════════════ */
.idea-in { display: grid; gap: clamp(1.8rem, 4vw, 3.2rem); }
.idea-head { max-width: 16ch; }
.idea-body {
  display: grid; gap: clamp(2rem, 5vw, 4rem);
  grid-template-columns: 1fr;
  padding-top: clamp(1rem, 3vh, 2.4rem);
  border-top: 1px solid var(--line);
}
@media (min-width: 900px) { .idea-body { grid-template-columns: 1fr 1fr; } }

/* The comparison: the subject is loud, what it earns is quiet. */
/* The two statements are now the scene's headline, so they take display
   scale. The loud half is the Didone at head weight; the quiet half is the
   same family in italic, indented, at about half the size. That pairing —
   heavy roman statement, refined italic answer — is the editorial move the
   whole type system is built on, and at this scale it finally reads. */
/* THE CENTRING RIG.

   The panel is the viewport, not a section with padding: the reel's own
   `.scene-in` padding is removed here so `100svh` really is 100svh, and the
   grid inside is `1fr auto 1fr`. Equal outer rows mean the middle row — the
   statement — lands on the exact vertical centre of the screen no matter
   what the plate above or the note below happen to weigh at that viewport.
   Nothing is absolutely positioned, so there is nothing to overlap on a
   short screen: the outer rows simply shrink toward zero and the statement
   stays where it is.

   `svh` rather than `vh` on purpose. On mobile Safari and Chrome the URL
   bar makes `vh` about 12% taller than what is actually on screen, and
   "the exact centre of the viewport" measured against a box that is taller
   than the viewport is not the centre of anything. */
/* THE SCENE IS LONGER THAN THE OTHERS, and it is the only one that is.
   Every reel on this site runs 126svh, which gives its sticky panel about
   a quarter of a viewport of hold — fine for a scene that is one thought,
   and nowhere near enough for one that is two. At 126svh the whole
   exchange between the two statements was over in 234px of scroll: the
   first line was gone before it had been read. 280svh gives the panel
   1.8 viewports of hold, which is a beat on the first statement, an
   unhurried hand-over, and a beat on the second, before the reel's own
   exit carries it into the film. */
.idea.scene-reel { min-height: 280vh; min-height: 280svh; }
@media (max-width: 640px) { .idea.scene-reel { min-height: 240vh; min-height: 240svh; } }

.idea .scene-in { padding-block: 0; }
.idea-in {
  min-height: 100vh; min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
  text-align: center;
  gap: 0;
}
.idea-plate { align-self: end; margin-bottom: clamp(1.6rem, 5vh, 3.6rem); }

/* Row 2. Both beats occupy the SAME grid cell, so the row is exactly as
   tall as the taller statement and the camera never has to move between
   them. The cell is what is centred; the beats are what cross through it. */
/* No measure on this element. A `ch` here resolves against the h2's own
   inherited body size, not against the 105px the statement is actually set
   in, so an 18ch cap came out around 250px and broke "Your marketing"
   across two lines at every width. The measure belongs on the lines
   themselves, where `ch` means what it says. */
.idea-say {
  grid-row: 2; display: grid; margin: 0;
  width: 100%;
}
.idea-beat {
  grid-area: 1 / 1;
  display: grid; gap: 0.12em;
  align-content: center;
  will-change: opacity, transform;
}
.idea-loud {
  font-family: var(--f-display); font-variation-settings: var(--vs-hero);
  /* The vw term is the one that matters. At 7.4vw the statement only
     reached its 2.4rem floor on a phone and sat at about 59% of the screen
     width — quiet, but not the dominant thing in the frame the brief asks
     for. 11vw carries it to roughly 72% at 390px and 60% at 1440, which is
     the same optical presence at both ends. */
  font-size: clamp(2.99rem, 12.65vw, 8.51rem); font-weight: var(--w-display);
  letter-spacing: var(--tr-display); line-height: 1.0; color: var(--ink);
  /* Measured in the line's OWN size, so it holds the same proportion at
     every viewport: at 1440 this is about 845px and "Your marketing" needs
     695, and at 375 it is about 307px against the 253 it needs there. The
     statement is one line on every screen it will ever meet. */
  max-width: min(100%, 16ch);
  margin-inline: auto;
  text-wrap: balance;
}
/* The quiet half. Italic, smaller, and NOT indented any more: an indent is
   a left-aligned device and it pulled the whole lockup off centre by half
   an em, which at this size is visible. */
.idea-soft {
  font-family: var(--f-display); font-style: italic; font-weight: 450;
  font-variation-settings: var(--vs-mid);
  font-size: clamp(1.552rem, 6.21vw, 3.91rem); color: var(--ink-2);
  line-height: 1.14; letter-spacing: -0.004em;
  max-width: min(100%, 24ch); margin-inline: auto;
}

/* The supporting line sits below the centre, quiet and small. It is the
   footnote to the argument, not part of it, which is why it lives in row 3
   and cannot push the statement off centre. */
.idea-note {
  grid-row: 3; align-self: start;
  margin-top: clamp(1.6rem, 5vh, 3.6rem);
  /* THE TWO PLACES "the digital version of your business" APPEARS ARE NOW
     SET IN STEP. It is said once inside the film (chapter one's lede) and
     once here, under the statement, and the two are the same thought at
     two moments — so they carry the same weight of type rather than one
     being a caption and the other a sentence. Both sit at roughly 1.3rem
     against their own headline. This one was 1.09rem and got lost. */
  max-width: 44ch; font-size: clamp(1.28rem, 1.5vw, 1.52rem); line-height: 1.62;
  color: var(--ink-2);
  opacity: 0; transform: translateY(10px);
  will-change: opacity, transform;
}
.idea-note.is-in { opacity: 1; transform: none;
  transition: opacity 1.1s var(--ease), transform 1.1s var(--ease); }

/* The resting state of beat two, in CSS, so a JS failure leaves a readable
   page rather than an invisible one: with no script at all both beats are
   simply legible, stacked in the same cell only because the script never
   ran to separate them in time. Hence `body[data-boot="done"]` — the class
   is only armed once the modules are running. */
body[data-boot="done"] .idea-beat[data-idea-beat="1"] { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  /* No cross-fade to scrub, so the two beats cannot share a cell: they go
     back to being two lines, one after the other, centred. */
  .idea-say { max-width: none; }
  .idea-beat { grid-area: auto; }
  .idea-say { gap: clamp(1.4rem, 3.2vh, 2.6rem); }
  body[data-boot="done"] .idea-beat[data-idea-beat="1"] { opacity: 1; }
  .idea-beat, .idea-note { opacity: 1 !important; transform: none !important; }
}

/* ═══ 04 · SELECTED WORK ═══════════════════════════════════════════════ */
.work { overflow: clip; }
.work-head { margin-bottom: clamp(1.6rem, 4vh, 3rem); }
.work-head h2 { margin-top: 1rem; }

.work-rail {
  display: flex; align-items: flex-start; gap: clamp(20px, 3.4vw, 60px);
  padding-inline: var(--gutter);
  cursor: grab; touch-action: pan-y;
  will-change: transform;
}
.work-rail[data-dragging="true"] { cursor: grabbing; }
.work-card {
  /* 64vw, not 58: this band (641px to 1024px) is the only place the rail
     is the whole section, so the screenshot has to carry it. The cap rises
     with it so a wide tablet does not stall at the old ceiling. */
  flex: 0 0 auto; width: clamp(260px, 64vw, 820px);
  opacity: 0.45; transition: opacity 0.7s var(--ease);
}
.work-card[data-on="true"] .work-shot { box-shadow: inset 0 1px 0 rgba(255,255,255,0.5), 0 18px 50px -24px rgba(23,20,18,0.4); }
.work-card[data-on="true"] { opacity: 1; }

.work-shot {
  display: block; position: relative; overflow: hidden;
  background: var(--paper-2); border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.5);
}
.work-shot img {
  width: 100%; height: auto; aspect-ratio: 1400 / 875; object-fit: cover;
  transform: scale(1.02); transition: transform 0.9s var(--ease);
}
.work-card[data-on="true"]:hover .work-shot img { transform: scale(1.05); }

/* The VISIT cue only exists on the card the visitor is actually on. */
.work-visit {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.9);
  display: grid; place-items: center;
  width: 96px; height: 96px; border-radius: 50%;
  background: var(--ink); color: var(--paper);
  font-family: var(--f-sans); font-size: 0.713rem;
  letter-spacing: 0.22em; text-transform: uppercase;
  opacity: 0; transition: opacity var(--dur-ui) var(--ease-ui), transform var(--dur-ui) var(--ease-ui);
  pointer-events: none;
}
.work-card[data-on="true"]:hover .work-visit { opacity: 1; transform: translate(-50%, -50%) scale(1); }

.work-meta { display: flex; align-items: baseline; gap: 1rem; margin-top: 1.35rem; flex-wrap: wrap; }
.work-ind { font-size: 0.713rem; letter-spacing: 0.28em; text-transform: uppercase; color: var(--red); }
.work-name {
  font-family: var(--f-display); font-variation-settings: var(--vs-mid);
  font-size: clamp(1.322rem, 2.3vw, 1.955rem); font-weight: var(--w-head); letter-spacing: var(--tr-head);
}
.work-kind { font-size: 1.02rem; color: var(--ink-3); width: 100%; }

.work-foot {
  display: flex; align-items: center; gap: 1rem;
  margin-top: clamp(1.6rem, 3.5vh, 2.6rem);
}
/* The reel's only affordance now: one quiet word, right-aligned. The
   "01 / 08" counter it used to sit beside is gone. */
.work-hint { margin-left: auto; }

/* ── THE ROLL ─────────────────────────────────────────────────────────────
   Two wheels: names turning one way on the left, screenshots turning the
   other way on the right, meeting in the middle of the screen. Everything
   that moves is written by motion/roll.js as a transform on a positioned
   node; nothing here animates, so the stylesheet never fights the scrub.

   The host is empty until the module fills it, and only the module sets
   `data-on`, so a browser that fails the module's own test (touch, narrow,
   reduce-motion) gets a section that is exactly the drag rail it was. */
.work-roll:not([data-on]) { display: none; }

/* The fallback driver pins with CSS instead of with ScrollTrigger, so the
   stage sticks to the top of the viewport for the length the module gives
   the host. Nothing else about the wheel changes. */
.work-roll[data-native="1"] .roll-stage { position: sticky; top: 0; }
.work-roll[data-native="1"] .roll-title,
.work-roll[data-native="1"] .roll-card { will-change: transform, opacity; }

/* The foot is the RAIL'S controls — two arrows and a drag cue. With the
   wheel on, they move nothing, so they go away entirely rather than stay
   hidden-but-tabbable: two focusable buttons that do nothing is worse for
   a keyboard than no buttons at all. */
.work[data-roll="1"] .work-foot { display: none; }

.work[data-roll="1"] .work-rail {
  /* Visually hidden, NOT removed: the rail's cards are the keyboard and
     screen-reader path to all eight projects. Removing them would leave a
     section of decorative divs with one reachable link. */
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

.work-roll { position: relative; width: 100%; }
.roll-stage {
  position: relative; display: flex;
  height: 100vh; height: 100svh; width: 100%; overflow: hidden;
}
.roll-col {
  position: relative; display: flex; width: 50vw;
  align-items: center; justify-content: center; height: 100%;
}
.roll-col--titles { transform: translateX(var(--roll-shift, 0px)); }
.roll-col--shots  { transform: translateX(calc(-1 * var(--roll-shift, 0px))); }
.roll-orbit { position: relative; height: 78vh; }

.roll-title {
  position: absolute; left: 50%; top: 50%;
  /* CENTRED ON ITS OWN ORBIT POINT, like the cards are — but the anchor
     is applied by roll.js as xPercent/yPercent, NOT here. GSAP folds the
     CSS `translate` property into its own transform on first write and
     then drops it, so an anchor declared in this file survives exactly
     until the first frame. See the note in roll.js. */
  width: max-content;
  /* Long names wrap; short ones do not. A cap in ch is what decides that,
     so "The Fit Pelvis" stays on one line and "Sullivan Chiropractic &
     Rehab" breaks where the words allow instead of running under the
     screenshot. */
  max-width: min(15ch, 30vw);
  text-align: center; text-wrap: balance;
  margin: 0; transform-origin: 50% 50%;
  font-family: var(--f-display); font-variation-settings: var(--vs-mid);
  font-size: clamp(1.955rem, 3vw, 3.68rem);
  font-weight: var(--w-head); letter-spacing: var(--tr-head);
  line-height: 1.06; color: var(--ink);
  pointer-events: none; will-change: transform, opacity;
}
/* The name in focus takes the house red the plates and folios use, so the
   wheel reads as part of this site rather than a component dropped in. */
.roll-title[data-on="1"] { color: var(--red); }

.roll-card {
  position: absolute; left: 50%; top: 50%;
  width: var(--roll-card-w, 300px); height: var(--roll-card-h, 188px);
  margin-left: calc(var(--roll-card-w, 300px) * -0.5);
  margin-top: calc(var(--roll-card-h, 188px) * -0.5);
  transform-origin: 50% 50%;
  pointer-events: none; will-change: transform, opacity;
}
.roll-card[data-on="1"] { pointer-events: auto; }

.roll-shot {
  display: block; position: relative; height: 100%; width: 100%;
  overflow: hidden; border-radius: var(--r-lg);
  background: var(--paper-2); border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.5);
}
.roll-card[data-on="1"] .roll-shot {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.5), 0 30px 60px -28px rgba(23,20,18,0.45);
}
.roll-shot img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.02); transition: transform 0.9s var(--ease);
  user-select: none; -webkit-user-drag: none;
}
.roll-card[data-on="1"] .roll-shot:hover img { transform: scale(1.06); }

/* ── THE STACKED WHEEL: phone and portrait tablet ────────────────────────
   One column instead of two. Both wheels are placed at the centre of the
   same box and the module lifts each name clear of its own screenshot, so
   what turns up the screen is a single stack of project-plus-name rather
   than two half-wheels sharing 190px each.

   Everything here is layout only. The turn, the focus curve, the lit
   state and the tap target are the same code that runs on a desktop. */
.work-roll[data-stacked="1"] .roll-stage { display: block; position: relative; }
.work-roll[data-stacked="1"] .roll-col {
  position: absolute; inset: 0; width: 100%;
  transform: none;   /* the column shift is a two-column idea */
}
.work-roll[data-stacked="1"] .roll-orbit {
  position: absolute; left: 50%; top: 50%; height: 0; width: 0;
}
.work-roll[data-stacked="1"] .roll-title {
  /* 30vw is a two-column measure; on one column it forces a name to break
     every second word. The cap here is the card, so a name wraps only when
     it is genuinely wider than the work it belongs to. */
  max-width: min(15ch, 86vw);
  font-size: clamp(1.72rem, 6.4vw, 2.6rem);
}
/* The whole focused card is the tap target on a touch screen, and it is
   the size of a card rather than of a link. */
.work-roll[data-stacked="1"] .roll-shot { touch-action: manipulation; }

/* A coarse pointer never gets a hover state: on a touch screen the browser
   fires one on tap and then leaves it stuck on the last thing touched. */
@media (hover: none) {
  .roll-card[data-on="1"] .roll-shot:hover img { transform: scale(1.02); }
}


/* ═══ 05 · THE FILM ════════════════════════════════════════════════════ */
.film { position: relative; }
.film-stage {
  position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden;
  background: #0d0b09;
}
.film-plane { position: absolute; inset: 0; will-change: transform; }
/* Sits above everything in the stage, including the scrim and the worlds,
   because its whole job is to hide the stage. Driven from ground.js. */
.film-veil {
  position: absolute; inset: 0; z-index: 6; pointer-events: none;
  opacity: 0;
  /* The colour arrives as three space-separated channels rather than a
     finished colour so the mid-stop can take an alpha off the same value.
     Deliberately NOT color-mix(): an unsupported color-mix() inside a
     gradient drops the whole background declaration, and this site has
     already lost every plate label once to exactly that. */
  /* SYMMETRIC, because the stage is veiled at both ends of its life and
     they are opposite ends of the element. Arriving, it rises from the
     bottom of the screen and what shows is its TOP; leaving, it rides up
     out of the screen and what shows is its BOTTOM. A gradient that only
     covered the top left the exit as a hard dark band sitting on the work
     section. Opaque at both edges, clear through the middle, so whichever
     edge is currently touching the page is painted in the page. */
  background: linear-gradient(to bottom,
    rgb(var(--veil-rgb, 245 242 234)) 0%,
    rgb(var(--veil-rgb, 245 242 234)) 10%,
    rgb(var(--veil-rgb, 245 242 234) / 0.55) 32%,
    rgb(var(--veil-rgb, 245 242 234) / 0) 50%,
    rgb(var(--veil-rgb, 245 242 234) / 0.55) 68%,
    rgb(var(--veil-rgb, 245 242 234)) 90%,
    rgb(var(--veil-rgb, 245 242 234)) 100%);
}
.film-video { width: 100%; height: 100%; object-fit: cover; }
.film-scrim {
  position: absolute; inset: 0; pointer-events: none;
  /* Measured against the real frames: at the 0.18 mid-stop the lede fell to
     2.8:1 wherever the footage brightens. These stops hold the copy band
     above AA across the whole clip without flattening the picture. */
  background:
    linear-gradient(to top, rgba(13,11,9,0.90) 0%, rgba(13,11,9,0.74) 30%,
                    rgba(13,11,9,0.34) 62%, rgba(13,11,9,0.38) 100%);
}
.film-copy { position: absolute; inset: 0; display: grid; align-items: end;
             padding: 0 var(--gutter) clamp(76px, 14vh, 150px); }
/* ── THE COPY INSIDE THE FILM ─────────────────────────────────────────
   Bigger, and lit. Two things were wrong with it: it was set at the same
   size as copy that sits on quiet paper, and it sat ON the picture rather
   than IN it.

   Size first. Over a moving image the eye has less to spare, so the
   chapter head now runs to 5.7vw against the page's 5.06, and the
   supporting line is set at its own size rather than inheriting the body
   scale — a lede over footage needs to be read at a glance, not studied.

   Then the light. The glow is deliberately small: a tight 12px halo of the
   picture's own near-white at a third strength, plus a wide, very soft
   fall that only lifts the type off whatever is moving behind it. It is
   not a neon; at these values it reads as the letters catching the light
   in the room rather than as an effect switched on over them. */
.film-ch {
  grid-area: 1 / 1; max-width: 46ch; opacity: 0;
  will-change: opacity, transform;
}
/* The plate sits highest in the copy block, where the scrim is thinnest —
   it is the element that decides how strong the scrim has to be. */
.film-ch .plate {
  color: #f6f3ec; --plate-ink: #f6f3ec; --plate-sweep: rgba(255,255,255,0.95);
  font-size: 0.86rem;
  text-shadow: 0 0 10px rgba(12, 10, 9, 0.55);
}
.film-ch h3 {
  font-family: var(--f-display); font-variation-settings: var(--vs-head);
  font-size: clamp(2.5rem, 5.7vw, 5.1rem); font-weight: var(--w-display);
  letter-spacing: var(--tr-display); line-height: 1.02;
  color: #fbf9f4; margin-block: 1.1rem 1rem;
  text-shadow:
    0 0 12px rgba(246, 243, 236, 0.30),
    0 0 46px rgba(246, 243, 236, 0.16),
    0 2px 22px rgba(12, 10, 9, 0.55);
}
.film-ch p {
  color: rgba(250, 248, 243, 0.94); max-width: 42ch;
  font-size: clamp(1.16rem, 1.72vw, 1.62rem); line-height: 1.5;
  text-shadow: 0 0 10px rgba(246, 243, 236, 0.18), 0 2px 18px rgba(12, 10, 9, 0.6);
}
.film-rail {
  position: absolute; left: var(--gutter); right: var(--gutter); bottom: clamp(34px, 6vh, 56px);
  height: 1px; background: rgba(246,243,236,0.2);
}
.film-rail i { display: block; height: 100%; width: 0; background: rgba(246,243,236,0.85); }
.film-track { height: 780vh; }

/* ── The worlds inside the film ───────────────────────────────────────
   Nine environments, one visible at a time, sitting between the clip and
   the copy. Each is a full-bleed plate; the name is set over it, centred,
   at display scale. No cards, no grid, no list. */
.worlds { position: absolute; inset: 0; overflow: hidden; }
.world {
  position: absolute; inset: 0; opacity: 0;
  will-change: opacity, transform;
}
.world img { width: 100%; height: 100%; object-fit: cover; }
.world::after {
  content: ""; position: absolute; inset: 0;
  /* The world copy is centred, and the linear scrim is at its THINNEST in
     the middle (0.34), which is exactly where the label sits. Measured
     across all nine worlds the small tracked cap fell to 3.83:1, because
     the worst pixel under it is a bright one: a window, a lit wall, a
     reflection. Lightening the label makes that worse, not better, which
     is the same trap the masthead sweep fell into. The ground has to come
     down instead, so a soft radial deepens the centre only, leaving the
     edges of the picture alone. */
  background:
    radial-gradient(76% 56% at 50% 50%, rgba(23,20,18,0.44) 0%,
                    rgba(23,20,18,0.22) 60%, rgba(23,20,18,0) 100%),
    linear-gradient(to top, rgba(23,20,18,0.86) 0%, rgba(23,20,18,0.34) 45%, rgba(23,20,18,0.5) 100%);
}
.worlds-copy {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: 0 var(--gutter); text-align: center; pointer-events: none;
}
.world-say { grid-area: 1 / 1; opacity: 0; will-change: opacity, transform; max-width: 34ch; }
/* 0.72 measured 3.97:1 over the brightest of the nine worlds, which is a
   real AA failure on a small tracked cap. 0.92 clears it on all nine with
   the label still reading as the quietest thing in the frame. */
.world-say .plate { color: rgba(248,246,241,0.92); --plate-ink: rgba(248,246,241,0.92);
  --plate-sweep: rgba(255,255,255,0.98); justify-content: center; margin-bottom: 1.2rem; }
.world-name {
  font-family: var(--f-display); font-variation-settings: var(--vs-head);
  font-weight: var(--w-display);
  font-size: clamp(2.99rem, 9.2vw, 9.2rem); line-height: 0.98;
  letter-spacing: var(--tr-display); color: #fbf9f4;
  /* Same light as the chapter heads, so the two kinds of type inside the
     film belong to one picture. */
  text-shadow:
    0 0 14px rgba(246, 243, 236, 0.28),
    0 0 54px rgba(246, 243, 236, 0.14),
    0 2px 26px rgba(12, 10, 9, 0.5);
}
.world-line {
  margin-top: clamp(0.8rem, 2vh, 1.4rem);
  font-family: var(--f-display); font-style: italic; font-weight: 400;
  font-size: clamp(1.38rem, 2.7vw, 2.18rem); color: rgba(250,248,243,0.94);
  text-wrap: balance;
  text-shadow: 0 0 12px rgba(246, 243, 236, 0.2), 0 2px 20px rgba(12, 10, 9, 0.55);
}


/* ── THE FILM ON A PHONE ──────────────────────────────────────────────
   A PORTRAIT COMPOSITION, NOT A SHRUNK LANDSCAPE ONE. This is the section
   the brief says breaks on phones, and measured on a 390x844 screen the
   cause is arithmetic rather than a bug: a 16:9 clip filling a 0.46:1
   viewport with `object-fit: cover` shows about 26% of the frame's width,
   magnified roughly 2.4x. Everything that is wrong with it follows from
   that one number — the picture reads as an unidentifiable close-up, the
   invented lettering on the laptop screen in the footage becomes the
   largest text on the page, and the copy has nowhere to sit but on top of
   the busiest part of the image.

   So on a narrow screen the clip stops filling the screen and becomes a
   PLATE: full width, its own 16:9 box, held in the upper half of the
   stage with the copy underneath it. Nothing is cropped, nothing is
   magnified, the storytelling is unchanged, and the type finally has a
   quiet ground of its own. The nine worlds take the same plate, so the
   sequence keeps one frame throughout.

   Everything here is layout. The scroll behaviour is untouched: the stage
   still pins, the clip still plays rather than scrubs (a paused-and-seeked
   video often never paints on a mobile decoder), and paint() still runs
   off scroll progress. */
/* THE PLATE IS THE TABLET'S LAYOUT, and it stays exactly as it was: a
   tablet is wide enough to show a 16:9 frame at a readable size with the
   copy beneath it. Phones no longer take this branch — they have their own
   cut of the film and their own composition below. */
@media (min-width: 641px) and (max-width: 860px) {
  .film-plane,
  .worlds {
    inset: auto 0 auto 0;
    top: clamp(56px, 11vh, 108px);
    height: auto;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    /* The plate is an object on the stage, so it gets an edge and a shadow
       under it. Without them it reads as a hole cut in the black. */
    box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.9);
  }
  /* THE PLATE STAYS WHOLE AND THE GROUND COMES TO MEET IT. Growing the
     plate to full-bleed at the resolve was the first attempt, and it
     cropped the film's own end card: the words baked into that frame are
     laid out for 16:9 and a portrait cover-crop cuts them in half. So the
     plate keeps its shape and the STAGE crosses to paper instead. By the
     end the phone is looking at an ivory card on an ivory ground, the two
     become one surface, and the section releases into a page painted the
     same colour — which is the desktop effect reached the other way round.
     The plate's own edge and shadow have to leave with the black, or a
     rectangle is left outlined on the paper. */
  .film-plane::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    box-shadow: inset 0 0 0 1px rgba(246, 243, 236, 0.09);
    opacity: calc(1 - var(--resolve, 0));
  }
  .film-plane {
    box-shadow: 0 24px 60px -30px rgba(0, 0, 0, calc(0.9 * (1 - var(--resolve, 0))));
  }

  /* The stage crosses from its near-black to the paper as the film
     resolves, so the black never sits between the end card and the page.
     The flat colour is declared first: if a browser cannot do color-mix it
     keeps the stage it always had rather than losing the declaration. */
  .film-stage { background: #0d0b09; }
  @supports (background: color-mix(in srgb, red 50%, blue)) {
    .film-stage {
      background: color-mix(in srgb, var(--paper) calc(var(--resolve, 0) * 100%), #0d0b09);
    }
  }
  /* The scrim no longer has to protect type that sits over the picture,
     because no type does. It only keeps the plate from floating on pure
     black: a soft fall under it, nothing across it. It also has to be gone
     by the end card, or the ivory arrives with a shadow across its foot. */
  .film-scrim {
    opacity: calc(1 - var(--resolve, 0));
    background: linear-gradient(to top,
      rgba(13, 11, 9, 0.92) 0%, rgba(13, 11, 9, 0.72) 26%,
      rgba(13, 11, 9, 0) 52%);
  }

  /* The copy takes the lower half, aligned to the top of its own space so
     a three-line chapter and a one-line chapter start at the same place
     instead of drifting up and down the screen as the sequence plays. */
  .film-copy {
    inset: auto 0 0 0;
    height: 52%;
    align-items: start;
    padding: clamp(18px, 3.5vh, 34px) var(--gutter)
             calc(clamp(64px, 11vh, 96px) + var(--safe-b, 0px));
  }
  .film-ch h3 { font-size: clamp(2.15rem, 8.6vw, 3.1rem); margin-block: 0.85rem 0.7rem; }
  .film-ch p  { font-size: clamp(1.06rem, 4.3vw, 1.28rem); max-width: 34ch; }
  .film-ch .plate { font-size: 0.76rem; }

  /* The world's name and line move to the same lower half, so the plate
     above stays a clean picture. `place-items: center` would put them back
     over it. */
  .worlds-copy {
    inset: auto 0 0 0;
    height: 52%;
    align-content: start;
    padding: clamp(18px, 3.5vh, 34px) var(--gutter)
             calc(clamp(64px, 11vh, 96px) + var(--safe-b, 0px));
  }
  .world-name { font-size: clamp(2.6rem, 12.5vw, 4.2rem); }
  .world-line { font-size: clamp(1.1rem, 4.4vw, 1.4rem); }
  /* Its scrim was built to protect centred type ON the image; with the
     copy below, the picture can be left alone. */
  .world::after {
    background: linear-gradient(to top,
      rgba(23, 20, 18, 0.5) 0%, rgba(23, 20, 18, 0.12) 40%, rgba(23, 20, 18, 0.2) 100%);
  }

  /* The rail sits under the copy, not under the frame. */
  .film-rail { bottom: calc(clamp(24px, 4.5vh, 40px) + var(--safe-b, 0px)); }
}


/* ── THE FILM ON A PHONE ──────────────────────────────────────────────
   FULL BLEED, because the phone has its own cut of the film: a 9:16 file
   that already contains the whole 16:9 frame, centred, with the surround
   made from the film's own light (see the portrait render in the notes).
   So the video can simply fill the screen the way it does on a desktop —
   nothing is cropped by doing so, because the cropping question was
   answered in the encode rather than in the CSS.

   That also puts the composition back where the desktop has it: the copy
   over the picture at the foot of the frame, with the scrim under it. The
   phone now gets the same shot the desktop gets. */
@media (max-width: 640px) {
  /* `inset: 0` then `top: auto` is not "full height", it is "no top edge":
     the box then sizes to its content and the video came out 391x694 inside
     an 844 stage. Both edges have to be set for the plane to fill. */
  .film-plane,
  .worlds {
    inset: 0;
    height: auto;
    aspect-ratio: auto;
    box-shadow: none;
  }
  .film-plane::after { display: none; }

  /* The scrim protects type over a moving picture again, so it is the
     desktop's scrim, weighted a little harder because a phone's copy sits
     over a larger share of the frame. */
  .film-scrim {
    background: linear-gradient(to top,
      rgba(13,11,9,0.94) 0%, rgba(13,11,9,0.82) 26%,
      rgba(13,11,9,0.42) 56%, rgba(13,11,9,0.3) 100%);
  }

  .film-copy {
    inset: 0;
    height: auto;
    align-items: end;
    padding: 0 var(--gutter) calc(clamp(78px, 15vh, 130px) + var(--safe-b, 0px));
  }
  .film-ch h3 { font-size: clamp(2.15rem, 9vw, 3.2rem); margin-block: 0.8rem 0.7rem; }
  .film-ch p  { font-size: clamp(1.06rem, 4.4vw, 1.3rem); max-width: 30ch; }
  .film-ch .plate { font-size: 0.76rem; }

  /* THE WORLD'S WORDS SIT AT THE FOOT, WHERE THE FILM'S DO. Centred, they
     landed in the middle of the contained picture and needed a scrim heavy
     enough to put the picture out — the frame came back almost black. At
     the foot they need only the bottom of the frame darkened, which is the
     same arrangement the chapters use, so the two kinds of copy in this
     section now sit in the same place. */
  .worlds-copy {
    inset: 0;
    height: auto;
    place-items: end center;
    align-content: end;
    text-align: center;
    padding: 0 var(--gutter) calc(clamp(78px, 15vh, 130px) + var(--safe-b, 0px));
  }
  .world-name { font-size: clamp(2.8rem, 15vw, 4.6rem); }
  .world-line { font-size: clamp(1.08rem, 4.6vw, 1.4rem); }

  /* THE NINE INDUSTRY STILLS ARE 16:9 AND THERE IS NO PORTRAIT CUT OF
     THEM, so they get the same treatment the video got, done in CSS: the
     picture is CONTAINED so none of it is lost, and the space around it is
     filled by a scaled, dimmed, blurred copy of itself. One element per
     world, only ever one world visible, and the blur is static — it is
     rasterised once when the world arrives, not per frame. */
  .world img { object-fit: contain; position: relative; z-index: 1; }
  .world::before {
    content: ""; position: absolute; inset: 0;
    background: var(--w-img) center / cover no-repeat;
    transform: scale(1.12);
    filter: blur(22px) brightness(0.66) saturate(0.75);
  }
  .world::after { z-index: 2; }

  /* The scrim under the world copy has to come back too, for the same
     reason: the words are over the picture again. */
  .world::after {
    background: linear-gradient(to top,
      rgba(23,20,18,0.88) 0%, rgba(23,20,18,0.62) 22%,
      rgba(23,20,18,0.16) 48%, rgba(23,20,18,0.08) 100%);
  }

  .film-rail { bottom: calc(clamp(24px, 4.5vh, 40px) + var(--safe-b, 0px)); }
}

/* Landscape on a phone is a different problem: the height is tiny, so the
   plate would eat the whole screen. There the original full-bleed frame is
   correct, and only the copy needs to come down. */
@media (max-width: 860px) and (orientation: landscape) {
  .film-plane, .worlds {
    inset: 0; top: auto; height: auto; aspect-ratio: auto; box-shadow: none;
  }
  .film-copy, .worlds-copy { inset: 0; height: auto; align-items: end; align-content: end; }
  .worlds-copy { place-items: center; }
  .film-scrim {
    background: linear-gradient(to top, rgba(13,11,9,0.9) 0%, rgba(13,11,9,0.62) 34%,
                rgba(13,11,9,0.3) 66%, rgba(13,11,9,0.36) 100%);
  }
}

/* Under reduced motion the film becomes a readable stack, not a scene. */
.film[data-calm="true"] .film-stage { position: relative; height: auto; padding-block: var(--scene); }
.film[data-calm="true"] .worlds { position: relative; inset: auto; height: 46vh; }
.film[data-calm="true"] .worlds-copy { position: relative; inset: auto; padding-block: 2.4rem; }
.film[data-calm="true"] .world-say { grid-area: auto; opacity: 1; margin-inline: auto; }
.film[data-calm="true"] .film-copy { position: relative; inset: auto; display: grid; gap: 2.4rem; padding-block: 2rem; }
.film[data-calm="true"] .film-ch { grid-area: auto; opacity: 1; }

/* ═══ 05 · HOW WE BUILD ════════════════════════════════════════════════
   The stages are nodes on a drawn line, not cells in a table. The wires are
   generated from the nodes' measured positions, so the diagram holds at any
   width and re-draws on resize. */
.process-head { max-width: 18ch; margin-top: 1.1rem; }
.process .duo { margin-top: clamp(1.2rem, 3vh, 1.9rem); }

.process-map { position: relative; margin-top: clamp(3rem, 8vh, 5.5rem); }
.process-wires {
  position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: visible; pointer-events: none;
}
.process-wires .wire {
  stroke: var(--ink-soft); stroke-width: 1.25; stroke-linecap: round;
  opacity: 0.55;
}

.steps {
  position: relative; z-index: 1;
  display: grid; gap: clamp(2.4rem, 4vw, 2.6rem);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.step { position: relative; will-change: transform, opacity; }

/* The stages step down and back up across the row. Without the offset the
   connectors would be a straight horizontal rule between four dots, which
   is a table with a line through it, not a path. */
@media (min-width: 641px) {
  .step:nth-child(2) { margin-top: clamp(2rem, 5vw, 4.2rem); }
  .step:nth-child(3) { margin-top: clamp(1rem, 2.4vw, 2rem); }
  .step:nth-child(4) { margin-top: clamp(3rem, 7vw, 6rem); }
}

/* The node is the thing the wire arrives at: a hairline ring that fills
   with the accent once its stage is live. */
.step-node {
  display: grid; place-items: center;
  width: 15px; height: 15px; border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--paper);
  margin-bottom: clamp(1.1rem, 2.6vh, 1.7rem);
}
.step-node i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--red);
  transform: scale(0);
  transition: transform var(--dur-ui) var(--ease-ui);
}
.step[data-on="true"] .step-node i { transform: scale(1); }
.step[data-on="true"] .step-node { border-color: var(--red-wash); }

.step-n { display: block; font-family: var(--f-display); font-size: 1.035rem;
          color: var(--ink-mid); letter-spacing: 0.06em; }
.step h3 { font-size: clamp(1.438rem, 2.3vw, 1.955rem); margin-block: 0.5rem 0.4rem; }
.step p  { color: var(--ink-2); font-size: 1.092rem; }

@media (max-width: 640px) {
  .steps { grid-template-columns: 1fr; gap: 2rem; }
  .process-wires { display: none; }
}

/* ═══ 07 · WHY US ══════════════════════════════════════════════════════ */
.usp { text-align: center; }
.usp .plate { margin-bottom: clamp(1.6rem, 4vh, 2.6rem); }
/* Was max-width: 22ch, which took a 62-character sentence and stacked it
   into four. The statement is meant to land as one line: the container
   opens up and the size is tied to the viewport instead, so it sets on a
   single line from about 1200px and wraps to two below that rather than
   breaking into a column of fragments. `balance` keeps the wrap even when
   it does happen. */
.usp-head {
  max-width: none; margin-inline: auto;
  font-size: clamp(2.012rem, 3.507vw, 3.795rem);
  text-wrap: balance;
}
.usp-list { margin-block: clamp(2.4rem, 6vh, 4rem); display: grid; gap: 0.2rem; }
.usp-list li {
  font-family: var(--f-display); font-style: italic; font-weight: 400;
  font-size: clamp(1.208rem, 2.415vw, 2.012rem); line-height: 1.5; color: var(--ink-2);
}
.usp-close { margin-inline: auto; color: var(--ink); font-style: normal; }

/* ═══ 08 · THE CRAFT ═══════════════════════════════════════════════════ */
/* The sentence is now two authored lines rather than one wrapping one, so
   the old max-width that governed where it broke is gone; `.craft-say`
   owns the measure. */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: clamp(1.4rem, 3vw, 2.4rem);
  margin-top: clamp(2.4rem, 6vh, 4rem);
  padding-top: clamp(1.6rem, 4vh, 2.6rem);
  border-top: 1px solid var(--line);
}
.stat-num {
  /* Bodoni has no tabular figures — an animated counter set in it jitters.
     The figures stay in the grotesk at a light weight, tabular. */
  font-family: var(--f-sans); font-weight: 300; font-variant-numeric: tabular-nums;
  font-size: clamp(2.99rem, 6.9vw, 5.75rem); line-height: 1; letter-spacing: -0.03em;
}
.stat-num sup { font-size: 0.4em; vertical-align: super; color: var(--red); margin-left: 0.06em; }
.stat-label { font-size: 0.9rem; letter-spacing: 0.2em; text-transform: uppercase;
              color: var(--ink-3); margin-top: 0.9rem; }
.constants {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: clamp(1.4rem, 3vw, 2.4rem); margin-top: clamp(2.4rem, 6vh, 4rem);
}
.constants li { border-top: 1px solid var(--line); padding-top: 1rem; }
.constants b { display: block; font-family: var(--f-display); font-weight: 400;
               color: var(--ink-3); font-size: 1.035rem; margin-bottom: 0.5rem; }
.constants h3 { font-size: 1.265rem; margin-bottom: 0.3rem; }
.constants p { color: var(--ink-2); font-size: 1.035rem; }

/* ═══ 09 · CLOSING ═════════════════════════════════════════════════════ */
.closing {
  min-height: 92vh; min-height: 92svh; display: grid; place-items: center; text-align: center;
  padding-block: var(--scene);
  /* NO BACKGROUND. This was a paper-to-paper-2-to-paper gradient painted by
     the section itself, which is exactly the thing that makes a site read
     as a deck: a band of slightly different ivory with two hard edges,
     sitting on top of a ground that was already being interpolated
     underneath it. The ground is the only thing on this site allowed to
     carry colour, and it now reaches this section on its own. */
}
.closing-in { will-change: transform; }
.closing .plate { margin-bottom: clamp(1.6rem, 4vh, 2.6rem); }
/* The gap is not optional. Fraunces has a materially deeper descender than
   the Didone this was drafted against, and with no margin the `y` of
   "story" landed on top of the line underneath. */
.closing-head { max-width: 18ch; margin-inline: auto;
                margin-bottom: clamp(1.4rem, 3.4vh, 2.4rem); }
.closing-sub { margin: clamp(1.4rem, 3vh, 2rem) auto 0; }

/* ═══ 10 · CONTACT ═════════════════════════════════════════════════════ */
.tell-grid { display: grid; gap: clamp(2.4rem, 6vw, 5rem); grid-template-columns: 1fr; }
@media (min-width: 940px) { .tell-grid { grid-template-columns: 1fr 1fr; align-items: start; } }
.tell-say h2 { margin-block: 1.1rem 1.4rem; }
.tell-reach { margin-top: 1.6rem; font-size: 1.035rem; color: var(--ink-3); }
.tell-reach a { color: var(--ink); border-bottom: 1px solid var(--line); }

.form { display: grid; gap: 1.05rem; }
.field { display: grid; gap: 0.45rem; }
/* `display:grid` above outranks the `hidden` attribute's UA `display:none`,
   so the conditional field would show before it was asked for. */
.field[hidden] { display: none; }
.field label { font-size: 0.83rem; letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3); }
.field input, .field select, .field textarea {
  background: transparent; border: 0; border-bottom: 1px solid var(--line);
  padding: 11px 2px; color: var(--ink); font-size: 1.127rem; border-radius: 0;
  transition: border-color 0.4s var(--ease);
}
.field textarea { resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-bottom-color: var(--red); }
.field input::placeholder, .field textarea::placeholder { color: var(--ink-3); opacity: 0.75; }
.field select { appearance: none; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: right 10px center, right 5px center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.form .btn { justify-self: start; margin-top: 0.7rem; }
.form-status { font-size: 0.977rem; color: var(--red); min-height: 1.2em; }
.form-status[data-ok="true"] { color: var(--ink-2); }
.hp { position: absolute; left: -9999px; }

/* ═══ FINALE + FOOT ════════════════════════════════════════════════════ */
/* The closing statement, at the scale a closing statement earns. */
.finale { display: grid; place-items: center; padding-block: clamp(88px, 15vh, 176px); }
/* Block, not flex. As a flex row the heart was a sibling of the whole
   sentence, so once the words filled the line it wrapped onto its own
   line underneath. Inline, it sits immediately after the full stop, which
   is where a mark like this belongs. */
.finale-line {
  display: block; max-width: 22ch; text-align: center; text-wrap: balance;
  font-family: var(--f-display); font-variation-settings: var(--vs-head);
  font-size: clamp(2.185rem, 5.29vw, 4.14rem);
  font-weight: var(--w-head); letter-spacing: var(--tr-head); line-height: 1.08;
}
.heart { display: inline-block; line-height: 0; margin-left: 0.3em; }
.heart svg {
  width: clamp(16px, 1.9vw, 26px); height: auto; color: var(--red);
  transform-origin: 50% 60%;
  animation: sys-beat 1.9s var(--ease-io) infinite;
}
/* Two beats and a rest, not a steady pulse. A heart that throbs evenly
   reads as a loading spinner; the pause is what makes it a heartbeat. */
@keyframes sys-beat {
  0%, 100% { transform: scale(1); }
  7%       { transform: scale(1.17); }
  14%      { transform: scale(1); }
  21%      { transform: scale(1.10); }
  28%      { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .heart svg { animation: none; } }

.foot { border-top: 1px solid var(--line); padding-block: clamp(36px, 6vh, 60px); }
.foot-in { display: flex; flex-wrap: wrap; gap: 1.4rem 2.4rem; align-items: baseline; }
.foot-mark { font-size: 1.15rem; }
.foot-nav { display: flex; flex-wrap: wrap; gap: 1.2rem; }
.foot-nav a { font-size: 0.828rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3); }
.foot-nav a:hover { color: var(--ink); }
.foot-love { margin-left: auto; font-size: 0.92rem; color: var(--ink-3); font-style: italic;
             font-family: var(--f-display); }

/* ═══ CURSOR ═══════════════════════════════════════════════════════════
   A DOT. Nothing else.

   What was here: a heart-shaped cursor built from a mask, with a three-
   layer glow, and a canvas trail of two red ribbons behind it. All of it
   is gone at the client's instruction — no heart, no tail, no glow, no
   canvas. `motion/trail.js` is no longer imported and the `.trail` layer
   no longer exists.

   What replaces it is the smallest thing that still reads as a considered
   cursor: an 8px dot in the site's ink at two-thirds strength, which sits
   quietly on paper and never competes with a headline. It keeps the eased
   follow (that motion was asked for and is unchanged) and it keeps label
   mode, which is the one place it is allowed to become something else.
   Desktop only, and enabled from JS so a touch device never sees it. */
.cursor { display: none; }
@media (hover: hover) and (pointer: fine) {
  body[data-cursor] .cursor {
    display: grid; place-items: center;
    opacity: 0;
    position: fixed; top: 0; left: 0; z-index: 250;
    width: 8px; height: 8px; border-radius: 50%;
    background: rgba(23, 20, 18, 0.62); pointer-events: none;
    transform: translate(-50%, -50%);
    transition: width 0.4s var(--ease), height 0.4s var(--ease),
                background 0.4s var(--ease), opacity 0.25s linear,
                border-radius 0.4s var(--ease);
  }
  body[data-cursor="live"] .cursor { opacity: 1; }
  /* The one exception: over a labelled target it becomes a bubble with a
     word in it. Kept, because it is functional rather than decorative. */
  body[data-cursor] .cursor[data-mode="label"] {
    width: 86px; height: 86px; background: var(--ink);
    border-radius: 50%;
  }
  .cursor span {
    font-size: 0.667rem; letter-spacing: 0.18em; text-transform: uppercase;
    color: var(--paper); opacity: 0; transition: opacity 0.3s;
    white-space: nowrap;
  }
  .cursor[data-mode="label"] span { opacity: 1; }
}

/* ═══ REVEAL BASE ══════════════════════════════════════════════════════
   Elements start hidden only when JS is there to bring them back. */
body[data-boot="ready"] [data-reveal],
body[data-boot="done"]  [data-reveal] { opacity: 0; transform: translateY(18px); }
/* Must out-specify the hiding rule above — `body[data-boot] [data-reveal]`
   is (0,2,1), so a bare `[data-reveal].is-in` at (0,2,0) loses and nothing
   ever appears. Scoping the visible state to body the same way settles it. */
body[data-boot] [data-reveal].is-in { opacity: 1; transform: none;
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease); }
.split-line { display: block; overflow: hidden; }
.split-line > span { display: block; }
.split-line .sw { display: inline-block; }
/* Promotion is applied by the tween and released on arrival, see reveal.js.
   A standing will-change would leave every word on its own composited layer
   for the life of the page. */
.split-line.is-armed .sw { will-change: transform, opacity; }
.split-line.is-done { overflow: visible; }

/* ── The two-tone sub ─────────────────────────────────────────────────
   A statement and its answer, where the emphasis is carried by colour
   rather than by size. Muted line, then full-strength line. */
.duo { display: grid; gap: 0.1rem; max-width: 58ch; }
.duo b { font-weight: 400; color: var(--ink-mid); }
.duo strong { font-weight: 400; color: var(--ink); }
.duo b, .duo strong {
  font-family: var(--f-display); font-style: italic;
  font-size: clamp(1.15rem, 1.897vw, 1.587rem); line-height: 1.38; text-wrap: balance;
}
.duo--center { margin-inline: auto; text-align: center; }

/* ═══ PHONE ════════════════════════════════════════════════════════════
   Not a stripped-down version — the same scenes with the depth budget
   spent differently. Type minimums are re-tuned because a Didone runs
   wider per character than the grotesk the clamps were drafted against. */
@media (max-width: 860px) {
  .nav-index, .nav-cta { display: none; }
  .burger { display: block; }
  /* The plate was inset into a card here, which left the hero as a picture
     floating in a page with empty ivory above and below it. The hero is
     full bleed at every width; the veil protects the type, not a margin. */
}
@media (max-width: 640px) {
  :root {
    --t-hero:    clamp(3.105rem, 14.95vw, 4.485rem);
    --t-display: clamp(2.415rem, 11.04vw, 3.45rem);
    --t-head:    clamp(2.012rem, 9.2vw, 2.76rem);
    --scene:     clamp(64px, 9vh, 96px);
    --tr-plate:  0.32em;
  }
  .hero { align-items: flex-end; }
  /* Measured, not guessed: at 320px the column is 280px and a 2.8rem floor
     breaks "to be remembered." onto a second line. Viewport-relative from
     here down, no floor. */
  .hero-head { --t-hero: clamp(2.3rem, 13.225vw, 3.22rem); }
  /* The cue was hidden here because it used to sit in the bottom right
     corner, where a phone has no room for it. Centred in the open band it
     has room at any width, and the brief asks for it on both — so it
     stays, one notch smaller and a little higher, because a phone's free
     band is shorter than a desktop's. */
  /* A phone's hero is one full-width column of copy running to the bottom
     edge, so there is no room below the buttons and the cue takes the
     quiet band above the eyebrow instead — still on the axis, still the
     lowest thing in the empty half of the screen. Anchoring it to the
     bottom here would put SCROLL on top of the call to action. */
  .hero-cue { font-size: 0.632rem; letter-spacing: 0.42em; }
  .hero-cue::after { height: 44px; }
  .lede, .sub { max-width: 100%; }
  /* THE WORK IS THE POINT ON A PHONE TOO. 78vw left a strip of paper down
     both sides of every screenshot; 90vw makes the shot the thing you see
     when the section arrives, and the rail still shows the edge of the
     next card so the drag stays discoverable. The name gets more air under
     the shot for the same reason it does on the wheel: the two should not
     read as one block. */
  .work-card { width: 90vw; }
  .work-rail { gap: 18px; }
  .work-meta { margin-top: 1.5rem; }
  .film-track { height: 400vh; }
  .film-copy { padding-bottom: clamp(64px, 12vh, 110px); }
  .foot-love { margin-left: 0; }
}

/* ═══ REDUCED MOTION ═══════════════════════════════════════════════════
   Everything still arrives; nothing travels. The film becomes a still. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
      transition-duration: 0.12s !important; scroll-behavior: auto !important; }
  [data-reveal], body[data-boot] [data-reveal] { opacity: 1 !important; transform: none !important; }
  .film-track { height: 150vh; }
}

/* 05a was an overlay on the laptop screen in the film. Removed: nothing
   is drawn over the clip. */

/* ═══ THE STANDARD ═══════════════════════════════════════
   The industry line, restored to a scene. It carries real vertical space
   again, the sentence is set at display scale on two lines, and the two
   halves behave differently on purpose: see initStandard() in scenes.js.

   No band, no mask, no loop, and nothing here is nowrap. The section is
   simply given room, which is the only thing it ever actually needed. */
.craft { padding-block: clamp(120px, 20vh, 250px); }
.craft-in { display: grid; justify-items: center; text-align: center; }
.craft .plate { margin-bottom: clamp(2.4rem, 6vh, 4.4rem); }

/* No measure here, for the same reason as .idea-say: a `ch` on the h2
   resolves against its inherited body size, not the 90px its lines are
   actually set in, and a 16ch cap broke a four-word line across three. */
.craft-say { display: grid; gap: 0.08em; margin: 0; width: 100%; }
.craft-l {
  display: block;
  font-family: var(--f-display);
  font-size: clamp(2.53rem, 7.36vw, 6.44rem);
  line-height: 1.02;
  letter-spacing: var(--tr-display);
  /* Measured in the line's own size, so each clause holds one line on any
     desktop or tablet and balances onto two only where a phone forces it. */
  max-width: min(100%, 22ch); margin-inline: auto;
  text-wrap: balance;
}
/* The half that moves. It is the roman, at display weight, in full ink. */
.craft-l--drift {
  font-variation-settings: var(--vs-hero);
  font-weight: var(--w-display);
  color: var(--ink);
  will-change: transform;
}
/* The half that does not. Italic, quieter, and deliberately given no
   `will-change` and no transform of any kind: it is the constant in the
   sentence and it is the constant on the screen. */
.craft-l--fixed {
  font-variation-settings: var(--vs-mid);
  font-style: italic; font-weight: 450;
  letter-spacing: -0.004em;
  color: var(--ink-2);
}

/* The figures get their grid back. The `.craft.strip` overrides that
   flattened them into one inline row are gone with the strip. */
.craft .stats, .craft .constants { width: 100%; text-align: left; }
.craft .stats { margin-top: clamp(3.4rem, 9vh, 6rem); }

/* Centred sentence, left-aligned data. The switch is intentional: the
   statement is a piece of art direction and the figures underneath are a
   table, and pretending a table is art direction is how a luxury page
   starts looking like a pitch deck. */
@media (max-width: 640px) {
  .craft .stats, .craft .constants { text-align: center; }
  .craft .constants li { justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .craft-l--drift { transform: none !important; }
}

/* ═══ WHAT A PHONE DOES NOT RENDER ════════════════════════════════════
   Measured, not guessed. On a 4x-throttled phone the whole document was
   scrolling at 30fps (33.3ms median, stalls to 350ms), and an inventory of
   the live page said why:

     · 3 full-viewport FIXED layers with `mix-blend-mode` (multiply x2,
       soft-light x1). Every frame, the compositor re-blends the entire
       screen three times. This is the single most expensive thing here.
     · 8 SVG `feTurbulence` + `feDisplacementMap` filters on the drawings,
       over 131 paths, re-rasterised as they move.
     · 27 running animations, 19 of them the label shimmer — a moving
       gradient on `background-clip: text`, which is a repaint per frame
       per label.
     · 110 elements carrying `will-change`, each a candidate compositor
       layer on a device with a fraction of the memory bandwidth.

   None of that is wrong on a desktop, where it costs nothing anyone can
   feel. On a phone it is the difference between a page that moves with
   your finger and one that does not. So the phone gets the same PICTURE
   with none of the per-frame work: the paper's tone is baked into plain
   alpha instead of blend modes, the drawings keep their line and lose the
   filter, the ambient loops stop, and nothing stands on `will-change`.

   Scoped to coarse pointers rather than to a width, because this is about
   what the DEVICE can composite, not how wide the window is. */
@media (pointer: coarse) {
  /* 1 · No blend modes. The three layers keep their texture through alpha
     alone; the values were re-picked against the blended originals so the
     sheet reads the same, not merely similar. */
  .paper-grain  { mix-blend-mode: normal; opacity: 0.06; }
  .paper-craze  { mix-blend-mode: normal; opacity: 0.035; }
  .paper-crush  { mix-blend-mode: normal; opacity: 0.16; }
  /* The crush layer's second element is four long gradients over the whole
     viewport for a bend that is barely visible on a 390px screen. */
  .paper-crush::after { display: none; }

  /* 2 · The drawings keep their line and lose the turbulence. The wander
     is what makes them read as drawn rather than as icons, and it is worth
     a lot on a desktop; on a phone the drawings are cropped fragments at
     low opacity and the filter is invisible at any honest reading of it. */
  .draw svg { filter: none; }

  /* 3 · The glows are radial gradients behind a blur. The gradient is
     already soft; the blur is the part that costs. */
  .glow { filter: none; }

  /* 4 · The ambient loops stop. Every one of them is decoration on a page
     that is being scrolled with a thumb, and the label shimmer alone was
     nineteen simultaneous text repaints. */
  .plate > span::before, .plate > span::after,
  .sheen::before, .lume::before, .lume::after,
  .paper-grain, .glow, .heart svg, .hero-cue::after {
    animation: none !important;
  }

  /* 5 · Nothing stands on `will-change`. GSAP still arms it for the
     duration of a tween where it matters; what this removes is the
     hundred-odd elements declaring it permanently in the stylesheet, each
     one asking for a compositor layer that is never released. */
  .mark-i, .marks, .draw, .film-plane, .film-ch, .world, .world-say,
  .scene-in, .hero-line.is-armed > span,
  .idea-beat, .work-rail, .cf-card {
    will-change: auto;
  }

  /* 6 · The nav pill's backdrop blur. A blurred backdrop under a fixed
     element is re-computed against everything scrolling behind it. */
  .nav-cta { -webkit-backdrop-filter: none; backdrop-filter: none;
             background: var(--paper-2); }
}
