/* Beat 03 — Position */

.beat-position { height: 500vh; }

.beat-position__stage {
  --gate-closed: 0;

  /* The width the name box needs, as a multiple of its own type size rather than a fixed px
     or a raw vw — that is what the requirement actually tracks, so it stays correct if the
     type scale moves.

     5.2em is set by "Anotherland", measured live at 5.15em (409px against 79px type at
     1280, 466px against 96px type from 1680 up). It is the longest UNBREAKABLE run on the
     beat, and that is the number that matters: a box narrower than its longest word does
     not wrap, it overflows OUTWARD past the gutter toward the screen edge. At 1280 a 349px
     box put "Anotherland" 4px from clipping off the viewport, which reads as a bug rather
     than as the intended tuck. */
  --name-w: calc(5.2 * var(--t-statement));

  /* The clear space between the card's border and the name beside it — the ONE number both
     names are placed from, so the two are equidistant from the card and that distance holds
     for every project.

     They used to be pinned to the screen gutter instead, which meant each name's own width
     decided its gap: measured live at 2560, Hendrick's/Anotherland sat 274.8px and 206.4px
     from the card, Sleeman/Money Can't Buy 350.0px and 212.1px. Unequal side to side, and
     moving as the gate cycles. Anchoring to the card is what makes it constant. */
  --name-gap: clamp(1rem, 2.2vw, 2.75rem);

  /* The closed gate's width — the single source of truth, used by the gate itself and by
     the reduced-motion stacklist below.

     It is DERIVED, not chosen: whatever is left of the viewport once both names have their
     clear space and their gutters. That is what makes it vary with screen width in the way
     that matters — the old `min(64vw, 1040px)` took a fixed 64% at every size, so the space
     left for the names collapsed far faster than the viewport did. At 1280 it left a 349px
     client name just 166px of clear room and covered the rest.

     Floored so the card keeps real presence on a small screen rather than shrinking to
     whatever the longest name demands, and capped at 1040px so it stops growing on large
     ones. Ordered min(cap, max(floor, derived)) rather than clamp() on purpose: clamp()'s
     minimum wins outright when it exceeds the maximum, which would let the floor push the
     gate past 1040px on an ultrawide display.

     Now that the names sit OUTSIDE the card rather than tucking behind it, the derived
     branch reserves the whole name plus its gap, not 88% of it. Floored at 34vw rather
     than 36vw: at 1512 the honest derivation asks for a 313px card, which is not a card,
     so the floor takes over and the widest names close the gap up instead of keeping it.
     The floor is what trades perfect clearance away to keep the card credible. */
  --gate-floor: 34vw;
  --gate-w: min(
    1040px,
    max(var(--gate-floor), 100vw - 2 * (var(--name-w) + var(--name-gap) + var(--gutter)))
  );

  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--c-aperture);
}

.position__surround { position: absolute; inset: 0; z-index: 0; }

/* The gate interpolates from full bleed to a centred 16:9 as --gate-closed goes 0 → 1. */
.position__gate {
  position: absolute;
  z-index: 10;
  left: 50%;
  top: 50%;
  /* Full bleed at --gate-closed 0, --gate-w at 1, linear between.
     Written as `a + (b - a) * t` rather than the algebraically identical
     `a - (a - b) * t`. That second form is what this was, and in Chromium 141 it computes
     to **0px whenever the factor is exactly 1** — which is every project centre, the only
     frames anyone actually stops on. Verified in isolation: with a literal 1 the same
     expression returns 0px, while dropping the nested min(), or using any factor other
     than 1, returns the correct width. Do not "simplify" it back. */
  width: calc(100vw + (var(--gate-w) - 100vw) * var(--gate-closed));
  aspect-ratio: 16 / 9;
  transform: translate(-50%, -50%);
  overflow: hidden;
  /* The gate is a Portalith too. Radius scales in with the close, so at full bleed it has
     none and the frame is the screen. */
  border-radius: calc(var(--portalith-radius) * var(--gate-closed));
  /* The card turns in real space. Perspective belongs to the container, not the card —
     put it on the element that rotates and every card gets its own vanishing point, which
     is what makes a flip look like a sprite scaling rather than an object turning. */
  perspective: 1600px;
}

.position__film, .position__film video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The corner brackets are gone — they read as a hard 90-degree angle on a frame whose
   whole language is the Portalith's radius. The ruler still carries the camera furniture. */
/* removed */ .x-gate__bracket--tl { top: 8px; left: 8px; border-right: 0; border-bottom: 0; }
/* removed */ .x-gate__bracket--tr { top: 8px; right: 8px; border-left: 0; border-bottom: 0; }
/* removed */ .x-gate__bracket--bl { bottom: 8px; left: 8px; border-right: 0; border-top: 0; }
/* removed */ .x-gate__bracket--br { bottom: 8px; right: 8px; border-left: 0; border-top: 0; }

/* The ruler is the project index — four marks, the current one lit Golden Blaze. */
.gate__ruler {
  position: absolute;
  right: -1.5rem;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
  opacity: var(--gate-closed);
}
.gate__ruler li {
  width: 14px;
  height: var(--gate-hairline);
  background: var(--c-ground);
  opacity: 0.4;
  transition: opacity 0.3s, background 0.3s, width 0.3s;
}
.gate__ruler li.is-current { width: 24px; background: var(--c-portalith); opacity: 1; }

/* Names run behind the gate: the gate is above them in z-order, so they are clipped by it
   without any clip-path arithmetic. */
.position__name {
  position: absolute;
  z-index: 5;
  top: 50%;
  transform: translateY(-50%);
  /* The same clear space the gate is sized from, so the box and the card are derived from
     one number and cannot drift apart. Multi-word project names wrap inside the clear zone
     and read in full; a single long word ("Anotherland") still overruns and tucks behind
     the gate, which is the intended grammar — the metadata row carries the full string. */
  max-width: var(--name-w);
  font-family: var(--f-display);
  font-size: var(--t-statement);
  line-height: 1.02;
  color: var(--c-ground);
  opacity: var(--gate-closed);
  pointer-events: none;
}
/* Below ~1366 the floor is what pushes a name off the screen rather than the type doing it:
   at 1280 a 34vw card left "Anotherland", the longest unbreakable name on the beat, hanging
   14.5px past the right edge. Measured, not guessed. The card gives way instead of the gap
   closing to nothing, so the clear space stays proportional at every width. */
@media (max-width: 1366px) {
  .beat-position__stage { --gate-floor: 30vw; }
}

/* Past a point the card cannot give back enough: "Anotherland" is one unbreakable word, so
   it cannot wrap out of trouble, and below ~1200 shrinking the card far enough to clear it
   would leave no card. The names take the reduction instead. Measured at 1152 the longest
   name is 386.7px against 71.4px type — 5.4em, not the 4.9em it runs at 1512, because the
   viewport shrinks faster than the clamped type does. */
@media (max-width: 1200px) {
  .position__name { font-size: calc(var(--t-statement) * 0.82); }
}

/* Placed from the CARD's border, not the screen's, and both left-aligned.
 *
 * Only one horizontal offset is set on each, so the absolutely-positioned box shrink-wraps
 * its text (capped by --name-w above). That is what makes the gap exact: the left name is
 * anchored by the edge that faces the card, so its ink ENDS --name-gap from the border
 * whatever the name is, and the right name's ink STARTS there. Setting both offsets would
 * stretch the box and hand the gap back to the text's own width, which is the bug.
 *
 * The left name's ragged left edge is the deliberate cost of left-aligning it: its start
 * now shifts per project, its finish does not. */
.position__name--left {
  right: calc(50% + var(--gate-w) / 2 + var(--name-gap));
  text-align: left;
}
.position__name--right {
  left: calc(50% + var(--gate-w) / 2 + var(--name-gap));
  text-align: left;
}

.position__meta {
  position: absolute;
  z-index: 20;
  inset: auto var(--gutter) 2.25rem;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0.4rem 1rem;
  color: var(--c-ground);
  font-size: var(--t-nav);
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  opacity: var(--gate-closed);
}
.position__meta [data-meta="year"] { text-align: right; }
.position__meta [data-meta="title"] { text-align: center; }
.position__meta [data-meta="count"] { text-align: right; }

/* Motion-on: the gate cycles through all four projects as the viewer scrolls, so this
   stays out of the way entirely. Motion-off gets it back below, legible and static. */
.position__stacklist { display: none; }

/* Reduced motion has no scrub, so a 500vh runway would become five viewport-heights of an
   unchanging frame. Collapse it to a single screen.

   Collapse to 100vh, never `auto`: every child of this stage is absolutely positioned and
   contributes no height, so `auto` flattens the whole composition into its padding. One
   viewport keeps resolve()'s composed still frame intact. */
:root[data-motion="reduced"] .beat-position { height: 100vh; }
:root[data-motion="reduced"] .beat-position__stage {
  position: relative;
  height: 100vh;
}
:root[data-motion="reduced"] .gate__ruler li { transition: none; }

/* The meta rail names one project (whichever show(0) landed on); the stacklist below
   names all four. Showing both was redundant, and that redundancy is exactly what put
   the stacklist on top of the gate's bottom edge at ordinary desktop heights — hide the
   rail so the stacklist has the space to sit below the gate instead of over it. */
:root[data-motion="reduced"] .position__meta { display: none; }

/* Spec §5 Position, reduced motion: "four projects as a stacked list with stills." Kept
   deliberately typographic — client × project names only, no stills, no gate rework — the
   simplest form that makes all four legible without a scrub.

   `top` is derived from the gate's own width/aspect-ratio expression (16:9 over the shared
   `--gate-w` the gate itself uses) plus a fixed clearance, rather than a fixed
   offset from the viewport bottom. A fixed offset only clears the gate at whichever
   viewport height it was tuned against — short viewports shrink the gap between them to
   nothing while the gate stays centred. Deriving the top edge from the gate's own bottom
   edge keeps the clearance positive at any height. */
:root[data-motion="reduced"] .position__stacklist {
  display: grid;
  gap: 0.5rem;
  position: absolute;
  z-index: 25;
  left: 50%;
  top: calc(50% + (var(--gate-w) * 9 / 16 / 2) + 1.5rem);
  transform: translateX(-50%);
  width: min(90vw, 26rem);
  padding: 1rem 1.5rem;
  background: rgb(17 17 17 / 0.55);
  border-radius: 4px;
  list-style: none;
  margin: 0;
}
:root[data-motion="reduced"] .position__stacklist li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  color: var(--c-ground);
  font-family: var(--f-sans);
  font-size: 0.85rem;
  line-height: 1.2;
}
:root[data-motion="reduced"] .position__stacklist li b { font-weight: 600; }
:root[data-motion="reduced"] .position__stacklist li span { opacity: 0.75; }


/* The shutter blink. The gate's film narrows and dims for a quarter of a second as the
   project changes, so the swap reads as an aperture closing and reopening rather than as a
   cut. Transform-origin is the centre, because the gate is the fixed point of the beat. */
.position__film {
  transform-style: preserve-3d;
  backface-visibility: hidden;
}

.position__film.is-blinking { animation: gate-blink 0.62s cubic-bezier(0.65, 0, 0.35, 1) both; }

/* The card turns edge-on, the film changes while it cannot be seen, and it turns back.
   position.js holds the source swap until the midpoint for exactly that reason — swapping
   on the way in shows the new project face-on before the turn has hidden anything.

   The slight scale down through the turn is what sells it as an object with thickness:
   a card rotating away from you also gets a little further away. */
@keyframes gate-blink {
  0%   { transform: rotateX(0deg) scale(1); }
  50%  { transform: rotateX(-88deg) scale(0.94); }
  100% { transform: rotateX(0deg) scale(1); }
}

:root[data-motion="reduced"] .position__film.is-blinking { animation: none; }


/* The surround's own swap. A full-bleed plate cannot blink like the gate does, so it fades
   through instead — same duration, so the two read as one shutter action rather than two
   things happening near each other. Opacity only: this element carries a live video and a
   pre-blurred still, and animating either of those channels would fight plane.js. */
.position__surround.is-swapping { animation: surround-swap 0.42s var(--ease-out) both; }

@keyframes surround-swap {
  0%   { opacity: 1; }
  38%  { opacity: 0.18; }
  100% { opacity: 1; }
}

:root[data-motion="reduced"] .position__surround.is-swapping { animation: none; }


/* Position arrives over the beat before it rather than after it: its contents start pushed
   down and rise into place as the section enters, on the same --seam the fade already uses.

   The translate goes on the stage's CHILDREN, never the stage. The stage is position:
   sticky, and a transform on a sticky element makes it a containing block and kills the
   pinning — which would take the gate with it. */
/* On the wrapper, never on the stage's children. The gate centres itself with its own
   transform, and a blanket rule here replaced it and parked the card in the bottom-right
   corner. One element moves; everything inside it keeps its own positioning. */
.position__enter {
  position: absolute;
  inset: 0;
  transform: translateY(calc((1 - var(--seam, 1)) * 18vh));
}

:root[data-motion="reduced"] .position__enter { transform: none; }


/* Every rectangle on this page is a Portalith, so every rectangle takes its radius. */
.position__film { border-radius: var(--portalith-radius); overflow: hidden; }
