/* ─────────────────────────────────────────────────────────────
   The ascent: the panels that sit over the film.

   A .panel is one keyframe. It is a tall block whose .panel__pin sticks to the
   viewport while the camera holds on that keyframe, on phones and desktop alike.
   ascent.js writes these custom properties while the film is running:

     on a panel   --d   distance of the camera from this keyframe (−0.6 … 0.6)
                  --x   0 → 1 as the panel leaves
                  --p   presence, 0 … 1 (drives the panel's scrim)
     on a cue     --r   0 → 1 as that element is revealed

   Without JavaScript, or with reduced motion, none of them are set: the panels
   are ordinary full-height sections and every word is simply visible.
   ───────────────────────────────────────────────────────────── */

.ascent { position: relative; z-index: 1; }

.panel { position: relative; }

/* Scroll target that lands the camera exactly on this panel's keyframe (used by #model). */
.panel__anchor { position: absolute; top: calc(50% - 50vh); }

.panel__pin {
  position: relative;
  isolation: isolate;
  overflow: hidden;                          /* each scrim stays inside its own panel */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;                 /* phones: copy sits low in the frame… */
  padding: calc(var(--nav-h) + 1.25rem) var(--gutter) max(2.25rem, calc(env(safe-area-inset-bottom) + 1.5rem));
}
.panel--m-top .panel__pin { justify-content: flex-start; }   /* …or high, where the shot leaves room */

.panel__body {
  position: relative;
  container-type: inline-size;
  width: 100%;
  max-width: 40rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(0.8rem, 2.1vh, 1.6rem);
}
.panel__body > * { max-width: 100%; }

/* Portrait: each panel's copy carries its own scrim. It hugs the text block — dark behind
   the words, fading out over --fade beyond them, and running off the screen edge the copy
   sits against — so its edge follows however much text the panel has, never a fixed
   half-screen band. Present only while the copy is (--p). */
.panel__body::before {
  --fade: 22vh;
  content: "";
  position: absolute;
  z-index: -1;
  left: calc(-1 * var(--gutter));
  width: 100vw;
  top: calc(-1 * var(--fade));
  bottom: -40vh;                                   /* off the bottom of the screen */
  opacity: var(--p, 1);
  pointer-events: none;
  background: linear-gradient(180deg, rgba(10, 9, 8, 0) 0, rgba(10, 9, 8, 0.78) var(--fade), rgba(10, 9, 8, 0.9) 100%);
}
.panel--m-top .panel__body::before {
  top: -40vh;                                      /* off the top of the screen */
  bottom: calc(-1 * var(--fade));
  background: linear-gradient(0deg, rgba(10, 9, 8, 0) 0, rgba(10, 9, 8, 0.78) var(--fade), rgba(10, 9, 8, 0.9) 100%);
}
.panel--opening .panel__body::before {
  background: linear-gradient(180deg, rgba(10, 9, 8, 0) 0, rgba(10, 9, 8, 0.62) var(--fade), rgba(10, 9, 8, 0.82) 100%);
}


/* The film: pin every panel for the length of its hold. */
@media (prefers-reduced-motion: no-preference) {
  .js .panel { height: 220vh; }
  .js .panel--opening { height: 150vh; }
  .js .panel__pin {
    position: sticky;
    top: 0;
    height: 100svh;
    height: 100dvh;                        /* follows mobile toolbars, so copy and scrim reach the real bottom */
    min-height: 0;
  }
}

/* Landscape composition: the structure owns the right of the frame, copy sits left, centred. */
@media (min-width: 900px) and (min-aspect-ratio: 4/5) {
  .panel__pin,
  .panel--m-top .panel__pin { justify-content: center; padding-bottom: 3rem; }
  .panel--opening .panel__pin { justify-content: flex-end; padding-bottom: 3.5rem; }
  .panel__body::before { display: none; }      /* the stage scrim does this job in landscape */

  .panel__body { width: min(42rem, 44%); max-width: none; }
  .panel__body--wide { width: min(60rem, 58%); }
}

/* ── Title choreography ───────────────────────────────────────
   Each cue is wiped in from its top edge and settles downward; the panel is
   wiped away the same direction — as if the structure were passing over it
   while the camera climbs.                                              */

.is-film .ascent [data-cue] {
  clip-path: inset(
    calc(var(--x, 0) * 112% - 6%) -4%
    calc((1 - var(--r, 1)) * 112% - 6%) -4%
  );
  transform: translate3d(0, calc((var(--r, 1) - 1 + var(--x, 0)) * 0.45em), 0);
}

/* Running text also fades, so a paragraph does not read as a hard shutter. */
.is-film .ascent .body[data-cue],
.is-film .ascent .lede[data-cue],
.is-film .ascent .schedule [data-cue],
.is-film .ascent .annotation-line[data-cue],
.is-film .ascent .scroll-cue[data-cue] {
  opacity: calc(var(--r, 1) * (1 - var(--x, 0)));
}

/* Depth: the type drifts a little against the picture as the camera passes. */
.is-film .panel__body {
  transform: translate3d(0, calc(var(--d, 0) * 4vh), 0);
}

/* ── Opening ────────────────────────────────────────────────── */

.scroll-cue {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: clamp(0.5rem, 3vh, 2rem);
  color: var(--muted);
  font-size: 0.625rem;
}
.scroll-cue::before {
  content: "";
  width: 2.5rem;
  height: 1px;
  background: var(--line);
}
html:not(.js) .scroll-cue { display: none; }

/* ── Leader lines (engineering callouts) ────────────────────────
   LABEL ────────────────────────■
   The line runs from the copy toward the structure and is drawn as the
   camera settles. --reach: how far across the viewport it travels (landscape). */

.leaders {
  display: flex;
  flex-direction: column;
  gap: clamp(0.45rem, 1.2vh, 0.8rem);
  width: 100%;
}

.leader {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  width: 100%;
  color: var(--fg);
  font-size: 0.6875rem;
  white-space: nowrap;
}
.leader__line {
  flex: 1 1 auto;
  min-width: 1.5rem;
  height: 1px;
  background: var(--line);
  transform-origin: left center;
  transform: scaleX(var(--r, 1));
}
.leader::after {                /* the node at the end of the line */
  content: "";
  width: 6px;
  height: 6px;
  margin-left: -0.9rem;
  background: var(--accent);
  flex: none;
}

@media (min-width: 900px) and (min-aspect-ratio: 4/5) {
  .leader { width: calc(var(--reach, 60vw) - var(--gutter)); max-width: none; }
}

/* ── Schedule: a numbered legend of technical annotations ───── */

.schedule {
  counter-reset: item;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.75rem, 1fr));
  gap: 0 1.25rem;
  width: 100%;
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  color: var(--fg);
}
.schedule li {
  counter-increment: item;
  display: flex;
  gap: 0.75em;
  padding-block: 0.5rem;
  border-top: 1px solid var(--hairline);
}
.schedule li::before {
  content: counter(item, decimal-leading-zero);
  color: var(--accent);
}

/* ── Closing line of the reveal ─────────────────────────────── */

.annotation-line--closing {
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  color: var(--accent);
}

/* ── Short screens: tighten the copy so every panel fits its pin ── */
@media (max-height: 700px) {
  .panel__body { gap: 0.7rem; }
  .ascent .body { font-size: 0.9375rem; line-height: 1.42; }
  .schedule li { padding-block: 0.35rem; }
}

/* Phones: the small monospace labels go up a step so they stay readable at arm's length. */
@media (max-width: 599px) {
  .scroll-cue, .leader, .schedule { font-size: 0.75rem; }
}
