/* ============================================================================
   descent-scrub.css — the photoreal descent layer.
   Companion to js/descent-scrub.js. Frames from cinematic/build_sequence.py.

   STACKING IS DOM ORDER, DELIBERATELY. Nothing here sets z-index. The GL canvas
   is inserted with stageEl.insertBefore(canvas, stageEl.firstChild), so the
   order inside .sc__stage ends up: .sc__gl, .dsc, .sc__space. The descent
   therefore paints over the storm and UNDER the plates, and the plates keep
   their own stacking context untouched — adding a z-index to .sc__space would
   have created a new one around the preserve-3d subtree the camera drives.

   TWO STATES AND NOTHING IN BETWEEN:
     no .sc-live   the scene never started (no JS, reduced motion, GL failed).
                   .dsc is a plain block image in the flow: the arrival's last
                   frame, which is the frame that carries the whole idea.
     .sc-live      the scene is running. The still is dropped, and IF the script
                   accepted the environment it adds .is-live and paints frames.
                   If it declined (Save-Data, 2g, narrow, no manifest) nothing
                   is added, the layer stays empty, and the page is exactly the
                   page that shipped before this file existed.
   ========================================================================== */

/* ── STATIC: the poster, in flow ─────────────────────────────────────────── */
/* NO MARGIN, NO PADDING, NO MIN-HEIGHT ON THE WRAPPER. Every box property lives
   on the still instead, so an EMPTY .dsc is exactly zero pixels tall. It carried
   a bottom margin for one revision and cumulative layout shift on the page went
   from 0.0697 to 0.1142, because the margin was in the flow until js/camera.js
   added `sc-live` and took the layer out of it. Measured, then removed. */
.dsc {
  display: block;
  position: relative;
}
.dsc__still {
  margin: 0 0 clamp(2rem, 5vw, 4rem);
  background: var(--ink, #0a0a0a);
  /* revealed by js/descent-scrub.js only when the scene did NOT start, or by
     being inside <noscript>. Never present on a page whose scene is running, so
     it can never become the LCP element there. */
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* ── LIVE: the scrub layer over the stage ────────────────────────────────── */
.sc-live .dsc {
  position: absolute;
  inset: 0;
  margin: 0;
  overflow: clip;
  pointer-events: none;
  /* invisible until the script has a manifest AND the playhead is inside the
     span, so a declined environment never paints a black rectangle over the
     storm and a slow manifest never flashes one */
  visibility: hidden;
  background: none;
  will-change: opacity;
}
.sc-live .dsc.is-live.is-on { visibility: visible; }
/* belt and braces: the still is never INSERTED under .sc-live, and if one ever
   arrives anyway it is not shown */
.sc-live .dsc__still { display: none; }

.dsc__c {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  transform-origin: 50% 50%;   /* rewritten by fit() from the manifest panel */
  will-change: transform;
  backface-visibility: hidden;
}

/* ── REDUCED MOTION ──────────────────────────────────────────────────────── */
/* js/camera.js is never started under reduce so .sc-live is never added and the
   static block above is what renders. This is belt and braces for the case
   where the class is present anyway: no transform, no scrub, the poster. */
@media (prefers-reduced-motion: reduce) {
  .sc-live .dsc {
    position: relative;
    inset: auto;
    visibility: visible;
    opacity: 1 !important;
  }
  .sc-live .dsc__still { display: block; }
  .dsc__c { display: none; }
}

/* ══ THE SCROLL CUE ═══════════════════════════════════════════════════════
   Ethan: "need indicator that tells them to scroll".

   WHY IT IS NEEDED HERE AND NOWHERE ELSE ON THE SITE. These four doors open
   on a full-viewport frame with the title already at full opacity (the `who`
   plate holds a negative beat so the h1 is this page's LCP element and never
   animates in). Nothing moves until the reader scrolls, and there is no visible
   page edge to imply more below — so a still first frame reads as a finished
   page rather than the first frame of a descent. Every other page on the site
   opens on flowing text, which announces its own continuation.

   BONE, NOT RED. CINEMATIC-BRIEF.md §0 budgets --red for exactly three
   motivated appearances: the rooftop aviation light, the firm's real red
   staircase, and one UI indicator. That third one is already spent. A fourth
   red would break the count, so the cue is bone at low opacity -- present,
   never competing with the headline.

   IT CANNOT BECOME THE LCP ELEMENT. 11px of type and a 1px rule, so its paint
   area is negligible; mobile LCP is already 3428 ms on fast 4G and nothing
   added here is allowed to make that number worse. */
.dsc-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.25rem, 4vh, 2.5rem);
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  pointer-events: none;
  font: 500 .6875rem/1 var(--ui, system-ui, sans-serif);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--bone, #faf8f3);
  opacity: .62;
  /* Fades on the first scroll rather than snapping, and is removed from paint
     entirely once gone so it costs nothing for the rest of the descent. */
  transition: opacity .45s ease, visibility 0s linear .45s;
}

/* THE RULE THAT FALLS. The travelling segment reads as direction without the
   bounce-arrow cliche, and it is a transform on a 1px box -- compositor only,
   so it never shares the frame budget with the canvas the way an animated
   background would. */
.dsc-cue__rail {
  position: relative;
  width: 1px;
  height: 2.75rem;
  background: linear-gradient(to bottom,
    rgba(250, 248, 243, .30) 0%,
    rgba(250, 248, 243, .06) 100%);
  overflow: hidden;
}

.dsc-cue__rail::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 40%;
  background: var(--bone, #faf8f3);
  animation: dsc-cue-fall 1.9s cubic-bezier(.7, 0, .3, 1) infinite;
}

@keyframes dsc-cue-fall {
  0%   { transform: translateY(-100%); opacity: 0; }
  22%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { transform: translateY(250%);  opacity: 0; }
}

/* GONE ONCE THE READER HAS ANSWERED IT. A cue that keeps insisting after the
   instruction has been followed is noise. `visibility` is delayed by the
   transition so the fade is seen before the element leaves the paint tree. */
.dsc-cue[data-cue-done] {
  opacity: 0;
  visibility: hidden;
  transition: opacity .45s ease, visibility 0s linear .45s;
}

/* No scene, no descent, no cue -- on a page that fell back to the still there
   is nothing below to scroll THROUGH, and the cue would be a lie. */
:where(html:not(.sc-live)) .dsc-cue,
[data-descent-off] ~ .dsc-cue { display: none; }

/* Somebody who asked for less motion still needs to know the page continues,
   so the cue stays and only the travelling segment stops. */
@media (prefers-reduced-motion: reduce) {
  .dsc-cue__rail::after { animation: none; top: 30%; opacity: .85; }
  .dsc-cue { transition: opacity .01s; }
}
