/* The pinned video sections: SIMPLE OPERATION and Slit-Scan Technology.
 *
 * They are one component — the same block in Figma, the same markup here — so everything
 * below is written against `.opscroll` and both instances get it. Only the copy and the
 * video differ.
 *
 * A 200vh section with a sticky viewport-height stage inside it. The video card starts
 * filling the band below the header and shrinks into a centred 16:9 card as the reader
 * scrolls; the copy rises under it once the card is most of the way there.
 *
 * One custom property comes from js/operation-scroll.js:
 *
 *   --operation-progress  0 -> 1 across the section's own scroll
 *
 * plus four measured values it writes per layout: --op-k0 (the scale the card starts at),
 * --op-ty0 and --op-copy-ty0 (where each starts), and --op-reserve when the copy needs more
 * room than the floor below allows. Everything else is derived here. Defaults resolve to
 * the finished, centred state, so the sections render correctly with no JavaScript.
 */

.opscroll {
  --p: var(--operation-progress, 0);
  --op-vh: 100vh;
  --op-inset: 30px;
  --op-pad-top: 96px;
  --op-pad-bottom: 40px;
  --op-gap: 80px;
  /* Height the copy needs below the card, so the card is never sized so tall that the
     title is pushed off the bottom of the sticky stage. A floor, not the final word:
     js/operation-scroll.js measures the copy and raises this when it needs more, which is
     what lets one component carry both a two-line lead and a four-line one. */
  --op-reserve: 400px;
  --op-radius: 30px;
  --op-card-w-end: min(954px, calc(100vw - var(--op-inset) * 2), calc((var(--op-vh) - var(--op-reserve)) * 16 / 9));
  /* Interpolates from the measured start scale to 1. */
  --op-k: calc(1 + (var(--op-k0, 1) - 1) * (1 - var(--p)));
  --op-copy-k: var(--op-k);
  /* Three windows cut out of the one progress, so the copy arrives in stages after the
     card rather than with it. */
  --op-pt: clamp(0, calc((var(--p) - 0.35) / 0.5), 1);
  --op-title-p: clamp(0, calc((var(--p) - 0.47) / 0.22), 1);
  --op-lead-p: clamp(0, calc((var(--p) - 0.58) / 0.2), 1);
  position: relative;
  z-index: 0;
  height: 200vh;
}

/* The mirror leaves 50px under each of these; the sticky stage butts straight up against
   its neighbours instead, so the section owns the full screen while it is pinned. */
.point__2,
.point__4 {
  margin-bottom: 0;
}

/* Mobile browsers change 100vh as their chrome hides, which would resize the card
   mid-scroll. */
@supports (height: 100svh) {
  .opscroll {
    --op-vh: 100svh;
  }
}

.opscroll-sticky {
  position: sticky;
  top: 0;
  height: var(--op-vh);
  overflow: hidden;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-direction: column;
  flex-direction: column;
  -ms-flex-align: center;
  align-items: center;
  -ms-flex-pack: center;
  justify-content: center;
  padding-top: var(--op-pad-top);
  padding-bottom: var(--op-pad-bottom);
}

/* The card is always laid out at its final size and scaled back to where it started, so
   the growth is a compositor transform and the layout never changes. The radius is divided
   by the scale so it stays visually constant while the card grows. */
.opscroll-card {
  position: relative;
  z-index: 1;
  width: var(--op-card-w-end);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #000;
  border-radius: calc(var(--op-radius) / var(--op-k));
  transform: translateY(calc(var(--op-ty0, 0px) * (1 - var(--p)))) scale(var(--op-k));
  transform-origin: 50% 50%;
  will-change: transform;
}

.opscroll-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Scaled with the card rather than independently, so the two move as one object. Origin at
   the top, so the copy grows away from the card instead of into it. */
.opscroll-copy {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1250px;
  margin-top: var(--op-gap);
  padding: 0 30px;
  text-align: center;
  opacity: var(--op-pt);
  transform: translateY(calc(var(--op-copy-ty0, 0px) * (1 - var(--p)))) scale(var(--op-copy-k));
  transform-origin: 50% 0;
  will-change: opacity, transform;
}

/* fit-content + auto margins rather than a block: the text-shadow needs the box to end
   where the text does.

   The `min(…vw, …vh)` on both type sizes is what keeps the card from collapsing on a short
   window. The stage gives the copy whatever height it asks for and the card takes what is
   left, so on a 1512x620 laptop Slit-Scan's four-line lead at its full 26px left the card
   at 160x90. The vh term only binds once the window is wider than about 1.7:1 — at
   1512x900 both of these still resolve to the design's 94px and 26px. */
.opscroll .opscroll-title {
  width: -webkit-fit-content;
  width: fit-content;
  margin-inline: auto;
  font-size: clamp(2.6rem, min(6.2vw, 12vh), 9.4rem);
  font-weight: 700;
  line-height: normal;
  text-align: center;
  color: #fff;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
}

.opscroll-lead {
  width: -webkit-fit-content;
  width: fit-content;
  margin-inline: auto;
  margin-top: 10px;
  font-size: clamp(1.2rem, min(1.72vw, 3.2vh), 2.6rem);
  font-weight: 500;
  line-height: 1.8;
  color: #f0f0f0;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
}

/* The line masks. Scrubbed against scroll rather than transitioned, hence
   `transition: none` — a transition here would fight the scrub on every frame. */
.launch-reveal {
  overflow: hidden;
}

.launch-line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.04em;
}

.launch-line > span {
  display: block;
  opacity: var(--op-title-p);
  transform: translate3d(0, calc((1 - var(--op-title-p)) * 1.05em), 0);
  filter: blur(calc((1 - var(--op-title-p)) * 8px));
  transition: none;
  will-change: opacity, transform, filter;
}

.launch-reveal--delay .launch-line > span {
  opacity: var(--op-lead-p);
  transform: translate3d(0, calc((1 - var(--op-lead-p)) * 1.2em), 0);
  filter: blur(calc((1 - var(--op-lead-p)) * 6px));
}

/* Phones: a shorter section, a smaller card, and a copy block that grows less than the
   card so the title is legible for more of the move. */
@media only screen and (max-width: 767.99px) {
  .opscroll {
    --op-inset: 16px;
    --op-pad-top: 56px;
    --op-pad-bottom: 32px;
    --op-gap: 40px;
    --op-reserve: 240px;
    --op-card-w-end: min(560px, calc((100vw - 32px) * 0.8), calc((var(--op-vh) - var(--op-reserve)) * 16 / 9));
    --op-copy-k: calc(1 + (var(--op-k) - 1) * 0.45);
    height: 180vh;
  }

  .opscroll-copy {
    padding: 0 16px;
  }
}

/* Reduced motion: nothing pinned, nothing scaled, everything at its finished value. */
@media (prefers-reduced-motion: reduce) {
  .opscroll {
    height: auto;
    --op-k: 1;
    --op-copy-k: 1;
    --op-pt: 1;
    --op-card-w-end: min(954px, calc(100vw - var(--op-inset) * 2));
  }

  .opscroll-sticky {
    position: static;
    height: auto;
    padding-top: 80px;
    padding-bottom: 80px;
  }

  .opscroll-card {
    border-radius: var(--op-radius);
    transform: none;
  }

  .opscroll-copy {
    opacity: 1;
    transform: none;
  }

  .launch-line > span,
  .launch-reveal--delay .launch-line > span {
    opacity: 1;
    transform: none;
    filter: none;
  }
}
