/* ==========================================================================
   Page sections
   Scroll-driven sections read a --p progress variable (0 to 1) written by
   js/progress.js. All motion stays on transform / opacity / clip-path.
   ========================================================================== */

/* ---- Hero -------------------------------------------------------------- */

.hero {
  position: relative;
  height: 100svh;
}

/* Full bleed at rest. As --p climbs, the frame draws inward from every edge
   and hands the screen over to the stage beneath it. */
.hero__media {
  position: absolute;
  inset: 0;
  overflow: hidden;
  clip-path: inset(
    calc(var(--p, 0) * 7%)
    calc(var(--p, 0) * 9%)
    calc(var(--p, 0) * 24%)
    calc(var(--p, 0) * 9%)
  );
  will-change: clip-path;
}

.hero__still {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(calc(1 + var(--p, 0) * 0.08));
}

/* The backdrop is a light, pastel photograph, so the white type needs a
   scrim to sit on. Weighted toward the centre, where the wordmark lands. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 68% 46% at 50% 50%, rgba(24, 22, 21, 0.58), transparent 72%),
    linear-gradient(180deg, rgba(24, 22, 21, 0.38), rgba(24, 22, 21, 0.06) 38%, rgba(24, 22, 21, 0.5));
}

.hero__center {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(0.5rem, 0.3rem + 0.8vw, 1.1rem);
  padding-inline: var(--gutter);
  text-align: center;
  color: var(--veil);
  transform: scale(calc(1 - var(--p, 0) * 0.28));
  will-change: transform;
}

.hero__mark {
  font-family: var(--font-display);
  font-size: clamp(2.75rem, 0.8rem + 8vw, 9rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: 0.06em;
  text-indent: 0.06em;
  text-transform: uppercase;
}

.hero__tag {
  font-family: var(--font-display);
  font-size: clamp(0.85rem, 0.72rem + 0.75vw, 1.5rem);
  font-weight: 300;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.hero__tag em { font-style: italic; text-transform: lowercase; }

.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(1.25rem, 0.8rem + 1.6vw, 2.5rem);
  translate: -50% 0;
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  color: var(--veil);
  opacity: calc(1 - var(--p, 0) * 3);
}

.hero__scroll .stem {
  width: 1px;
  height: clamp(2rem, 1.4rem + 1.8vw, 3.5rem);
  background: linear-gradient(var(--veil), transparent);
  transform-origin: top;
  animation: stem 2.6s var(--ease-in-out) infinite;
}

@keyframes stem {
  0%   { transform: scaleY(0); opacity: 0; }
  35%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; }
}

/* ---- Sculpture stage ----------------------------------------------------
   Three layers share one grid cell so they can overlap: type behind the
   form, the turning form, then type in front of it. js/stage.js writes
   --scale / --ty / --o onto each layer.

   The flat, stacked version is the base. Pinning is layered on top only
   when scripting is available, since nothing else can drive it.
   -------------------------------------------------------------------------- */

.stage {
  position: relative;
  text-align: center;
  padding-block: var(--section-tight);
}

.stage__type {
  width: 100%;
  padding-inline: var(--gutter);
  font-size: var(--text-display-sm);
}

.stage__form img {
  width: min(100%, 22rem);
  height: auto;
  margin: var(--section-tight) auto;
}

.js .stage {
  height: 420svh;
  padding-block: 0;
}

.js .stage__inner {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  place-items: center;

  /* place-items centres an item INSIDE its track; it does not centre the
     track. A track sized by content wider than the container starts at the
     container's left edge and grows right, which pushed the sculpture off
     the right of the screen. This centres the track itself, so any overflow
     is even on both sides. */
  justify-content: center;
  overflow: hidden;
}

.js .stage__type,
.js .stage__form { grid-area: 1 / 1; }

.js .stage__type {
  /* Its own ramp rather than --text-display, which only reaches its 7.5rem
     ceiling past a 1625px window and so topped out around 108px on a normal
     desktop. This one lands on 120px at about 1400px wide and holds there.
     The svh cap stays because the type shares one pinned screen with the
     sculpture: 13.5svh lets 120px through from a 889px-tall window up, and
     still shrinks the type on anything shorter. */
  font-size: min(clamp(2.6rem, 0.5rem + 8vw, 7.5rem), 13.5svh);
  opacity: var(--o, 1);
  /* scale sits to the right of translate, so it is applied to the glyphs
     first and the travel distance stays independent of it. Default 1, so
     the front layer is untouched. */
  transform: translate3d(0, var(--ty, 0rem), 0) scale(var(--s, 1));
  will-change: transform, opacity;
}

.js .stage__type--back {
  z-index: 1;
  /* Set at the size it STARTS at (300px), then scaled DOWN to 200px by
     stage.js. Deliberately that way round: this layer is promoted with
     will-change, and scaling a promoted layer UP resamples its bitmap and
     the type goes soft, while scaling down does not. */
  font-size: min(clamp(3rem, 0.5rem + 20.9vw, 18.75rem), 33svh);
}
.js .stage__type--front { z-index: 3; --o: 0; }

/* The closing line is pinned to the bottom of the shared cell rather than
   centred in it, so it reads as sitting under the sculpture. It takes the
   same size as the line in front, which is larger than the gap under the
   lotus — so it does ride over the base. That is the trade for matching
   the line above it, and it is the front-most layer, so it stays legible. */
.js .stage__type--tail {
  z-index: 4;
  --o: 0;
  align-self: end;
  /* The display face is set at a 0.94 line-height, so Thai lower vowels
     drop ~0.3em past the bottom of the line box. The em term buys that
     back, or .stage__inner's overflow clips the vowel in คุณ. */
  padding-bottom: calc(clamp(0.5rem, 1.5svh, 1.25rem) + 0.35em);
}

.js .stage__form {
  z-index: 2;
  height: 100svh;
  display: grid;
  place-items: center;
  transform: translate3d(0, var(--ty, 12%), 0) scale(var(--scale, 0.8));
  will-change: transform;
}

/* js/stage.js swaps the still for a canvas once frames start landing, so the
   two have to sit identically. */
.stage__form > [hidden] {
  /* The base img display rule overrides the browser's hidden default.
     Keep the inactive surface out of the grid during the canvas handoff. */
  display: none !important;
}

.js .stage__form img,
.js .stage__form canvas {
  grid-area: 1 / 1;
  position: relative;
  z-index: 1;

  /* Sized by HEIGHT on purpose — the ground shadow below is positioned from
     the frame being 94% of the stage height, so switching to width-based
     sizing would move the contact line out from under the sculpture.

     The cap is what keeps it on a phone: the frame is 0.749 wide per unit
     tall, so an unbounded 94svh came out 572px across inside a 375px screen.
     115vw bounds the rendered width to ~86vw and leaves room for the push-in
     to 1.3x at the end of the scroll. */
  height: min(94svh, 115vw);
  width: auto;
  max-width: none;
  margin: 0;
}

.js .stage__form { position: relative; }

/* Ground shadow. It lives inside .stage__form, so it scales and travels with
   the sculpture, but its own pixels never change. A `filter: drop-shadow`
   would track the silhouette more exactly and re-blur from the alpha channel
   on every scrubbed frame — precisely the per-frame cost the canvas exists
   to avoid. */
.js .stage__form::after {
  content: "";
  position: absolute;
  left: 50%;

  /* Sits on the contact line, not on the edge of the picture. The alpha
     channel puts the base of the sculpture 8.9% above the bottom of the
     frame, and the frame is 94% of the stage height and centred, which
     lands the base 14.4% above the bottom of .stage__form. Half the
     ellipse height is subtracted so it straddles that line. */
  bottom: 11.3%;
  z-index: 0;
  width: 52%;
  aspect-ratio: 6 / 1;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(24, 22, 21, 0.28),
    rgba(24, 22, 21, 0.1) 58%,
    transparent 100%
  );
}

/* Phones and other touch devices get the flat, stacked stage: no pin, no
   scrub, and js/stage.js holds no decoded bitmaps. This condition MUST match
   the `scrubs` test in js/stage.js — if CSS keeps pinning while JS declines
   to build the canvas, the sculpture scales but never turns. */
/* ---- Flattened stage -----------------------------------------------------
   `.stage--flat` is added by js/stage.js when it decides NOT to scrub, so the
   layout and the scrubber can never disagree.

   This used to be `@media (pointer: coarse)`, which made the call in CSS
   while JS made its own separately. When the two disagreed the section still
   pinned and scaled from CSS with no canvas behind it — a sculpture that
   zoomed but never turned, and nothing in the console.

   Touch is no longer a reason to flatten: phones get a smaller frame set
   instead (see data-frames-mobile), so only reduced-motion and a missing
   canvas land here now.
   -------------------------------------------------------------------------- */

.js .stage--flat {
  height: auto;
  padding-block: var(--section-tight);
}

.js .stage--flat .stage__inner {
  position: static;
  height: auto;
  display: block;
  overflow: visible;
}

.js .stage--flat .stage__type,
.js .stage--flat .stage__form {
  grid-area: auto;
  opacity: 1 !important;
  transform: none !important;
}

.js .stage--flat .stage__form {
  height: auto;
  margin-block: var(--section-tight);
}

.js .stage--flat .stage__form img,
.js .stage--flat .stage__form canvas {
  height: auto;
  width: min(100%, 22rem);
  margin-inline: auto;
}

.js .stage--flat .stage__form::after { display: none; }

/* A phone still pins, just over a shorter run — 420svh of pinned scroll is a
   long commitment on a small screen. */
@media (max-width: 48rem) {
  .js .stage:not(.stage--flat) { height: 260svh; }
}

/* ---- Statement bands ----------------------------------------------------- */

.statement {
  text-align: center;
  display: grid;
  justify-items: center;
  gap: clamp(1.5rem, 1rem + 2.4vw, 3.5rem);
}

.statement__body {
  display: grid;
  justify-items: center;
  gap: clamp(1.5rem, 1rem + 2vw, 3rem);
}

/* The pantheon heading exists to introduce the rail directly beneath it, so
   it closes the gap rather than keeping full section rhythm. An id selector
   because this is one specific block, not a reusable modifier. */
#deities { padding-bottom: 20px; }

/* ---- Triptych (horizontal pinned process) --------------------------------- */

.triptych {
  position: relative;
  height: 300svh; /* JS re-measures this from the rail's actual overflow. */
}

.triptych__track {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.triptych__rail {
  display: flex;
  gap: clamp(1.5rem, 1rem + 3vw, 5rem);
  padding-inline: var(--gutter);
  transform: translate3d(calc(var(--shift, 0px) * -1), 0, 0);
  will-change: transform;
}

/* The width is bounded by viewport HEIGHT as well as width: a 9:16 plate is
   1.78x as tall as it is wide, and the old 40rem ceiling would have made a
   1138px card inside a 100svh track. */
.step {
  flex: none;
  width: min(clamp(11rem, 8rem + 14vw, 20rem), 36svh);
  display: grid;
  gap: clamp(0.75rem, 0.5rem + 0.9vw, 1.25rem);
  align-content: center;
}

/* The plate's link has to fill it, or .plate img's 100% height resolves
   against an inline box instead of the plate. */
.step__plate > a {
  display: block;
  width: 100%;
  height: 100%;
}

.step__title a { transition: color var(--dur-fast) var(--ease-out); }
.step__title a:hover { color: var(--gold); }

/* ---- Per-card shot slider ---------------------------------------------- */

.shot-nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  z-index: 1;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  color: var(--ink);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.shot-nav--prev { left: 0.6rem; }
.shot-nav--next { right: 0.6rem; }

.step:hover .shot-nav,
.shot-nav:focus-visible { opacity: 1; }

.shot-nav:hover {
  background: var(--gold);
  color: var(--veil);
}

/* Hover never fires on a touch screen, so there they simply stay out. */
@media (hover: none) {
  .shot-nav { opacity: 1; }
}

.shot-nav svg {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.step__shot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.4em;
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  color: var(--gold);
}

.step__shot-i { font-variant-numeric: tabular-nums; }

.step__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--line-gold);
}

.step__title {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 1rem + 2.6vw, 3.4rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

.step__title em { font-style: italic; text-transform: lowercase; }

.step__plate { aspect-ratio: 9 / 16; }

.step p {
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ink-soft);
}

/* On narrow screens the rail stops pinning and stacks instead. */
@media (max-width: 48rem) {
  .triptych { height: auto !important; }

  .triptych__track {
    position: static;
    height: auto;
    display: block;
    padding-block: var(--section-tight);
  }

  .triptych__rail {
    transform: none !important;
    flex-direction: column;
    gap: clamp(3rem, 2rem + 5vw, 5rem);
  }

  /* Full width would make each 9:16 card as tall as the phone itself, so
     the stack is capped and centred instead. */
  .step {
    width: 100%;
    max-width: 17rem;
    margin-inline: auto;
  }
}

/* ---- Story (two column editorial) ---------------------------------------- */

.story {
  display: grid;
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: start;
}

@media (min-width: 60rem) {
  .story { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); }
  .story__aside { position: sticky; top: calc(var(--header-h) + 3rem); }
}

.story__actions { margin-top: clamp(2rem, 1rem + 2.4vw, 3rem); }

/* ---- Marquee -------------------------------------------------------------- */

.marquee {
  display: flex;
  overflow: hidden;
  user-select: none;
  padding-block: clamp(1.5rem, 1rem + 2vw, 3rem);
  border-block: 1px solid var(--line-gold);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

.marquee__row {
  flex: none;
  display: flex;
  gap: clamp(1.5rem, 1rem + 2vw, 3rem);
  padding-right: clamp(1.5rem, 1rem + 2vw, 3rem);
  animation: slide 38s linear infinite;
}

.marquee:hover .marquee__row { animation-play-state: paused; }

.marquee__row span {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 1.2rem + 4.4vw, 6rem);
  font-weight: 300;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.16;
  white-space: nowrap;
}

.marquee__row span:nth-child(odd) { opacity: 0.45; }

@keyframes slide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100%, 0, 0); }
}

/* ---- Closing -------------------------------------------------------------- */

.closing {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: clamp(1.25rem, 0.8rem + 1.6vw, 2.25rem);
  padding-block: clamp(5rem, 3rem + 9vw, 13rem);
}
