/* ═══════════════════════════════════════════════════════════
   SCENE 02 — STATEMENT
   A single paragraph set large, lighting up word by word as the
   section passes the viewport.

   NOT PINNED, and NOT a full screen. The reveal is driven by the
   section's own passage up the viewport, so the words track the
   scroll from the moment the paragraph appears — there is no point
   at which the page stops and waits for the text to catch up.
   ═══════════════════════════════════════════════════════════ */

.st-stage {
  /* NO `position` DECLARATION HERE. .scene-stage is position:sticky
     and that is the entire pinning mechanism — the stage holds at
     the top of the viewport while the runway underneath supplies
     the scroll. This rule used to say `position: relative`, left
     over from when the section was not pinned and only needed a
     positioning context for the rings. Relative beat sticky, so the
     stage scrolled away with the page and the whole timeline played
     out above the fold: you could scroll the entire section and see
     neither the words light nor the film open, because both were
     happening off-screen. Sticky is itself a positioning context,
     so the rings never needed relative in the first place. */
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
  /* Full screen, because the section is pinned: it holds two beats
     — the paragraph, then the film opening — and a pin is what buys
     the scroll length for both. */
  display: grid;
  /* Centred — and with NO padding on .st-inner. The two together
     were what produced the runaway top gap earlier: the block was
     centred in the full screen and then pushed down again on top
     of that. Centring is now the only thing placing it, which is
     also what puts the copy on the same middle line as the panel. */
  align-content: center;
}

/* ── The ground ──────────────────────────────────────────
   The brief is "premium and minimal", and on a white page that
   means depth WITHOUT a shape: the moment any of this resolves
   into an object you can name, it has stopped being atmosphere
   and become decoration. The rings are the one exception — they
   are meant to be seen, which is why they are hairlines and why
   they run off the edge rather than sitting complete in frame. */
.st-stage::before,
.st-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* CONTAINED, ON PURPOSE. Both washes are centred vertically and
   sized so neither reaches the top or the bottom of the stage.

   That is the whole fix for the seam between this section and the
   one below. Two sections cannot be made to match at a shared edge
   by tuning their gradients toward each other — one is at 100% of
   its own box there and the other at 0% of its own, and those are
   different numbers. The only reliable answer is that BOTH are
   plain paper where they touch, which means neither gradient may
   reach an edge at all. The approach section below carries the
   same rule; keep them in step. */
.st-stage::before {
  background:
    radial-gradient(58% 54% at 74% 47%, rgba(201,160,99,.22) 0%, rgba(201,160,99,0) 66%),
    radial-gradient(48% 44% at 7% 53%, rgba(27,39,66,.05) 0%, rgba(27,39,66,0) 64%);
}

/* Generated noise, same as the loader's ground. feTurbulence is
   evaluated once when the image decodes, so there is no per-frame
   cost — and plain alpha, NOT a blend mode: overlay and soft-light
   both resolve to nothing against a near-white ground, which cost
   a whole pass to discover on the loader. */
.st-stage::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
  background-repeat: repeat;
  opacity: .04;
}

/* Two concentric hairline rings, cropped by the right edge. They
   fill the empty half of the section without putting anything in
   it that has to be read, and cropping is what keeps them
   atmosphere — a complete circle sitting in frame would read as a
   logo or a button. */
.st-ring {
  position: absolute;
  top: 50%;
  border-radius: 50%;
  border: var(--hair) solid rgba(201,160,99,.42);
  transform: translateY(-50%);
  z-index: 0;
  pointer-events: none;
}
.st-ring--a { right: -16rem; width: 58rem; height: 58rem; }
.st-ring--b {
  right: -4rem;
  width: 34rem; height: 34rem;
  border-color: rgba(201,160,99,.26);
}


.st-inner {
  position: relative;
  z-index: 1;
  align-self: center;
}
.st-body { grid-column: content-start / span 12; }
/* THE HOLD. The sentence finishes lighting the instant this section
   pins, so every pixel of runway after that is the finished
   statement sitting still on a full screen. That pause is the point:
   the section arrives, completes, and is allowed to be read for a
   beat before anything else moves.

   14vh — about one notch of the wheel. Long enough that the
   completed sentence registers as a held frame rather than a
   waypoint the scroll passes through, short enough that it never
   becomes a stretch where the visitor is scrolling and nothing is
   happening. It was 8vh, which at most screen heights is less than
   a single scroll: the crossing began on the same gesture that
   finished the sentence, so there was no hold at all.

   THIS NUMBER IS NOW THE GAP BEFORE THE PICTURE MOVES. The crossing
   starts at this section's 'bottom bottom' — the instant the pin
   releases — so this runway IS the pause between the statement
   completing and the picture setting off. Change it and you change
   that pause; nothing else needs touching, but nothing else will
   compensate either. See js/core/travel.js. */
.st-runway { height: 14vh; }
/* Proportionally shorter on a phone, where a viewport-height of
   hold is a much longer thumb gesture. */
/* Shorter on narrow screens, where a viewport-height of hold is a
   much longer thumb gesture than it is a wheel notch. */
@media (max-width: 991px) { .st-runway { height: 9vh; } }
@media (max-width: 767px) { .st-runway { height: 7vh; } }

/* ── The mark ────────────────────────────────────────────
   A drafting cross. Drawn rather than typed, because a glyph like
   this is present in some faces and absent in others and the
   section's opening image should not depend on a font fallback. */
.st-mark {
  display: block;
  /* Bigger than the plus was. At 3rem the rings and the travelling
     arcs collapsed into a smudge — the detail needs the room or it
     is detail nobody can see. */
  width: 4.6rem;
  height: 4.6rem;
  /* Close to the paragraph it introduces. The mark grew to 4.6rem
     to make its detail legible, and the old gap was set for a mark
     a third that size — at the new size it read as floating on its
     own rather than belonging to the text under it. */
  margin-bottom: 2.4rem;
  color: var(--gold);
}
.st-mark svg { width: 100%; height: 100%; display: block; }

/* ── The paragraph ───────────────────────────────────────
   Set in the body face, at size. The display face was the first
   instinct and the wrong one: this is thirty words of prose, and
   a display face stops being display type the moment it has to
   carry a paragraph. */
.st-para {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 4.4rem;
  line-height: 1.34;
  letter-spacing: -.012em;
  color: var(--ink);
  /* A measure, not the full column — long lines at this size read
     as body copy that happens to be large.

     THE CEILING IS THE PANEL, not the column. The picture's slot is
     36% of the content track, right-aligned, so its left edge sits
     at 64%. At 38ch the longest line ran right up against it with
     no gutter left; 32ch keeps a clear band of paper between the
     two. If the slot width in .st-frame changes, this has to move
     with it — there is nothing enforcing the relationship. */
  max-width: 32ch;
}

/* Each word carries its own opacity. Nothing above .st-para may
   set opacity — the two would multiply and the faint state would
   go invisible. */
.st-w { display: inline-block; }

@media (max-width: 1200px) {
  .st-para { font-size: 3.8rem; }
  .st-ring--a { width: 46rem; height: 46rem; right: -14rem; }
  .st-ring--b { width: 28rem; height: 28rem; }
}
@media (max-width: 991px) {
  .st-body { grid-column: content; }

  /* THE SENTENCE HAS TO FILL THE SCREEN IT IS HOLDING.

     This section pins for a full screen and centres its content in
     it. On a wide layout the photograph sits alongside and the two
     together take the frame; with the picture gone at this width
     the block is six short lines centred in a whole viewport, and
     what reads is not a held statement but a lot of empty paper
     with a paragraph in the middle of it.

     Sized up and given the full measure, so the sentence occupies
     the screen rather than floating in it. The fix is the type, not
     the padding — there is no padding to remove; the space is what
     centring short content in a tall box produces. */
  /* NO MEASURE CAP. 22ch was set when this section still had a
     photograph beside it and the paragraph had half the width to
     work in. With the picture gone the column is the full content
     width, and the cap only broke the sentence early and left a
     third of the line empty — measured at 882px, 428px of text in
     a 782px column. The column is the measure now. */
  .st-para { font-size: 3.6rem; max-width: none; }
  .st-mark { width: 3.8rem; height: 3.8rem; margin-bottom: 1.8rem; }

  /* NO PICTURE HERE AT THIS WIDTH.

     On a wide screen the paragraph takes twelve columns and the
     photograph sits in the space beside it. There is no such space
     on a narrow one: the paragraph takes the full content width,
     and the picture either lands on top of the words or pushes the
     sentence off a screen it is meant to fill.

     So the statement is words only here, and the photograph makes
     its first appearance in the next section as that section's
     background. js/core/travel.js takes the same branch — see the
     note on `narrow` there. */
  .st-media { display: none; }
}
@media (max-width: 767px) {
  .st-para { font-size: 2.9rem; max-width: none; line-height: 1.34; }
  .st-mark { width: 3.2rem; height: 3.2rem; margin-bottom: 1.7rem; }
  /* At this width the rules sit almost on the text, and the rings
     crowd it rather than sitting behind it. */
  .st-ring--b { display: none; }
  .st-ring--a { width: 32rem; height: 32rem; right: -12rem; }
}

/* ── The film ────────────────────────────────────────────
   The video lives in THIS section rather than one of its own: the
   paragraph is read and then the footage takes the frame, which is
   one idea and reads as one. Splitting it across two sections made
   the visitor scroll past a finished statement to reach a panel
   that then had to re-introduce itself.

   THE BOX GROWS AND THE FOOTAGE GROWS WITH IT. The first version
   held the video still behind a clip-path window that opened over
   it — technically neat, and wrong: the frame moved and the
   picture did not, so only the mask read as animating. Because the
   video is object-fit:cover inside a box that changes size, a
   wider box re-covers with more of the frame showing, so the
   picture opens out as the panel does.

   Anchored bottom-right with right/bottom pinned at 0 and only
   width/height animating, so it grows leftward and upward out of
   that corner. */
/* THE PANEL IS A GRID ITEM, not an absolutely positioned box sitting
   on invented percentages. It rides the page's own content track, so
   its right edge is content-end and — once open — its left edge is
   content-start: the same lines the paragraph and every other section
   align to. A hand-picked gutter in percent looks right at one width
   and drifts at every other.

   .st-media is the grid, .st-frame the item. */
.st-media {
  position: absolute;
  inset: 0;
  z-index: 2;
  /* stretch, so the single row fills the stage and the frame's
     percentage height has something definite to resolve against */
  align-content: stretch;
  pointer-events: none;
}

/* A SLOT, not a panel. It paints nothing and holds no image — it
   exists so the page's own grid decides where the travelling
   picture sits while this section holds the screen, and
   js/core/travel.js measures it. Change these numbers and the
   picture moves; there is no matching value in the JS. */
.st-frame {
  grid-column: content;
  justify-self: end;
  align-self: center;
  width: 36%;
  /* SQUARE, and the approach section's slot is square too. The
     picture travels between the two, so any difference in RATIO
     between them is a shape morph on the way across — it was going
     from 562x515 to 604x755, which reads as the image being
     stretched rather than carried. Same shape at both ends means
     the crossing is a move and a scale, nothing else. */
  aspect-ratio: 1;
  height: auto;
  visibility: hidden;
}


@media (max-width: 991px) {
  .st-frame { width: 52%; }
}
@media (max-width: 767px) {
  .st-frame { width: 64%; }
}

/* ── The mark, alive ─────────────────────────────────────
   Two loops at different periods, and that is the point: 16s and
   5.5s do not share a common multiple you would ever sit through,
   so the combined state never visibly repeats. One loop on its own
   reads as a looping animation; two out of phase read as something
   that is simply moving.

   The rotation is slow enough to be barely perceptible frame to
   frame, but a cross passing through 45 degrees becomes an ×, so
   glancing back at it a few seconds later it has plainly changed.

   ON THE <svg>, NOT ON .st-mark. GSAP animates the mark's own
   transform for the entrance and writes it inline, and an inline
   transform beats a stylesheet — a rotation declared on .st-mark
   would simply never run once the scene had mounted. Same trap as
   the hero title, the menu index and the panel hover. */
@keyframes st-turn   { to { transform: rotate(360deg); } }
@keyframes st-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}

.st-mark svg {
  animation:
    st-turn    16s linear        infinite,
    st-breathe 5.5s ease-in-out  infinite;
}

@media (prefers-reduced-motion: reduce) {
  .st-mark svg { animation: none; }
}

/* ── The mark, alive ─────────────────────────────────────
   A surveyor's target rather than a spinning plus. Six loops on
   six periods, none of them a multiple of another: 19s, 13s, 7s,
   5s, 6.4s, 3.1s. The combined state does not repeat for about
   four hours, so however long anyone watches it, it never lands
   in the same arrangement twice. That is the whole difference
   between something animated and something alive — a single loop,
   however elaborate, gives itself away in one cycle.

   THE MOTION IS ALL COUNTER-MOTION. The rings turn against each
   other, the arcs travel against their own rings, and the arms
   extend across the top of it. Nothing here moves with anything
   else, which is what makes it read as a mechanism.

   EVERY ANIMATION IS ON A CHILD, never on .st-mark. GSAP owns the
   mark's own transform for the entrance and writes it inline,
   which beats a stylesheet — a transform declared on .st-mark
   would silently never run. Fourth time in this codebase; see the
   panel hover for the same note. */
.st-mark svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* transform-box:fill-box makes `center` mean the element's own
   centre. Without it an SVG transform-origin resolves against the
   whole viewBox and everything orbits the corner. */
.st-mark svg * {
  transform-origin: center;
  transform-box: fill-box;
}

.m-ring { stroke-width: 1.1; }
/* One sixth of the circumference lit, travelling. The gap value is
   the remainder, so the arc is a real fraction of the ring rather
   than a length that has to be recomputed per radius. */
.m-ring--o {
  opacity: .5;
  stroke-dasharray: .17 .83;
  animation: m-spin 19s linear infinite, m-sweep 7s linear infinite;
}
.m-ring--i {
  opacity: .34;
  stroke-dasharray: .12 .88;
  animation: m-spin-r 13s linear infinite, m-sweep-r 5s linear infinite;
}

.m-cross { animation: m-spin 26s linear infinite; }
.m-arm {
  stroke-width: 1.25;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  /* One cycle, four delays. The arms are otherwise identical, so
     the stagger is the only thing making the cross unfold rather
     than blink. */
  animation: m-arm 6.4s ease-in-out infinite;
  animation-delay: calc(var(--i) * .38s);
}

.m-dot { animation: m-dot 3.1s ease-in-out infinite; }

@keyframes m-spin    { to { transform: rotate(360deg); } }
@keyframes m-spin-r  { to { transform: rotate(-360deg); } }
@keyframes m-sweep   { to { stroke-dashoffset: -1; } }
@keyframes m-sweep-r { to { stroke-dashoffset: 1; } }
@keyframes m-arm {
  0%,  10%  { stroke-dashoffset: 1; }
  40%, 60%  { stroke-dashoffset: 0; }
  90%, 100% { stroke-dashoffset: 1; }
}
@keyframes m-dot {
  0%, 100% { transform: scale(1);  opacity: 1; }
  50%      { transform: scale(.45); opacity: .45; }
}

/* A perpetual mechanism is exactly what this setting is for. */
@media (prefers-reduced-motion: reduce) {
  .st-mark svg * { animation: none; }
  .m-arm { stroke-dashoffset: 0; }
}

/* ── The mark, alive ─────────────────────────────────────
   A surveyor's target that never stops moving but is never
   illegible: the rings and the cross are always fully drawn, and
   what animates is a bright arc running round each ring, the
   cross turning under them, and the centre pulsing.

   THE FIRST VERSION ANIMATED THE GEOMETRY ITSELF — arms drawing
   out on a stagger, rings reduced to one travelling dash. Every
   individual moment of it was a fragment, so it read as debris.
   Motion belongs on top of a mark, not instead of one.

   Four periods, none a multiple of another (2.4s, 1.7s, 4.2s,
   1.3s), so the combined state does not settle into a visible
   loop. One cycle, however elaborate, always gives itself away.

   transform-box:view-box with a PERCENTAGE origin. Two traps here,
   both hit: fill-box resolves against each element's own bounding
   box, so a dashed arc orbits its own fragment instead of the
   centre of the target; and a length origin like 24px does not
   reliably mean 24 user units once the SVG is scaled — the cross
   was turning about a point up and left of centre. 50%% 50%% of the
   view-box is the middle of the drawing at any size.

   EVERY ANIMATION IS ON A CHILD, never on .st-mark. GSAP owns the
   mark's transform for the entrance and writes it inline, which
   beats a stylesheet — see the panel hover for the same note. */
.st-mark svg { width: 100%; height: 100%; display: block; overflow: visible; }
.st-mark svg * {
  transform-box: view-box;
  transform-origin: 50% 50%;
}

.m-track { stroke-width: 1; opacity: .22; }

/* An eighth of the ring, lit, travelling. pathLength="1" makes the
   dash a readable fraction rather than a number derived from each
   radius — change a radius and nothing here needs recomputing. */
.m-arc  { stroke-width: 1.4; }
.m-arc--o { stroke-dasharray: .13 .87; animation: m-run   2.4s linear infinite; }
.m-arc--i { stroke-dasharray: .18 .82; animation: m-run-r 1.7s linear infinite; }

.m-cross { stroke-width: 1.3; opacity: .85; animation: m-spin 4.2s linear infinite; }

.m-dot { animation: m-dot 1.3s ease-in-out infinite; }

@keyframes m-spin  { to { transform: rotate(360deg); } }
@keyframes m-run   { to { stroke-dashoffset: -1; } }
@keyframes m-run-r { to { stroke-dashoffset: 1; } }
@keyframes m-dot {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(.35); opacity: .4; }
}

/* A perpetual mechanism is exactly what this setting is for. */
@media (prefers-reduced-motion: reduce) {
  .st-mark svg * { animation: none; }
}

@media (max-width: 991px) {
  /* ── NOT PINNED, AND NOT A FULL SCREEN ────────────────
     A pin buys a held frame, and holding is only worth the scroll
     it costs when there is something to hold — on the wide layout
     that is the sentence lighting word by word beside a
     photograph. Here it is a paragraph on paper, and pinning it to
     a full screen produced exactly what it sounds like: six lines
     centred in a viewport, with a third of the screen empty above
     and below them.

     An ordinary section, sized to its own content with room top and
     bottom. The words still light as it arrives — that is scrubbed
     against the section's own passage and needs no pin. */
  .st-stage {
    position: static;
    height: auto;
    min-height: 0;
    padding: 11rem 0;
    align-content: start;
  }
  .st-runway { display: none; }
}
@media (max-width: 767px) {
  .st-stage { padding: 8.5rem 0; }
}
