/*
  Hero — pinned, monotonic scroll sequence. All values are tokens
  from tokens.css; the only non-token numbers here are the layout
  geometry needed to reconstruct the layered artwork's own coordinate
  space (verified against the source art — see js/hero.js header) and
  the cover's trim ratio, which is itself a token (--cover-ratio-*).

  Structure (back to front — matches assets/cover/layer_positions.json's
  z_order_back_to_front, split across two independently-scaled groups
  so shrinking one can never reintroduce bars in the other):
    .hero                    tall scroll track (sets pin distance)
      .bg-fixed              position:fixed, full-viewport, z-index:-1
                              — the persistent backdrop. Lives OUTSIDE
                              .hero-pin on purpose: .hero-pin only
                              exists for the hero's own scroll distance
                              (GSAP tears its pin down once the hero
                              ends), but this needs to keep rendering,
                              fixed to the viewport, for the rest of
                              the page. Being a plain (untransformed)
                              descendant of .hero — never of .hero-pin —
                              is what keeps "position:fixed" actually
                              fixed: a transformed ancestor would
                              silently rescope it to that ancestor's
                              box instead of the viewport. z-index:-1
                              (not DOM order) is what keeps it behind
                              .hero-pin during the hero AND behind the
                              plain, unpositioned page content below —
                              content with no z-index of its own would
                              otherwise still paint above a z-index:0/
                              auto fixed element, negative z-index is
                              required for a true "behind everything"
                              layer.
        .hero-stage            bg + corner haze — ONE continuous scene,
                                sized to always COVER the viewport
                                (100vmax), so there is never a
                                pillarbox/letterbox gap. bg itself
                                carries its own scale-up (computed from
                                its total drift across the WHOLE page)
                                so its edges never enter frame while it
                                parallaxes. Driven by exactly one
                                ScrollTrigger (buildSceneDrift, hero.js)
                                spanning top-of-hero to bottom-of-page —
                                not the hero's own timeline at all, and
                                never rebuilt or restarted partway down;
                                the hero's own pinned timeline
                                (hero-main) never touches this element,
                                so there's exactly one authority over
                                its transform, for the life of the page.
          .hero-layer--bg
          .haze-1 … .haze-4     (the four canvas corners — the "dust",
                                  strong/fast parallax)
      .hero-pin              the pinned 100vh viewport window
        .hero-engine           disk + magnetar + all jet pieces + the
                                cover's text overlay + cover_nature.png
                                — ONE rigid group, sized and scaled
                                independently of the stage (matches
                                cover_art.jpeg's contained composition;
                                see hero.js for why the scale is
                                JS-measured once). Everything inside
                                shares ONE parallax transform (set on
                                .hero-engine itself in hero.js) — an
                                unmoving true-center settle (no residual
                                drift — see hero.js), the still center
                                the dust rushes past.
          .hero-layer--jet-lower  (behind the disk)
          .hero-layer--disk
          .hero-layer--magnetar   (in front of the disk, at center)
          .hero-layer--jet-upper  (crosses in front of the disk)
          .jetglow-1 / .jetglow-2 (top / bottom jet-base flares — in
                                    front of their own beam at both
                                    ends; don't overlap the disk, so
                                    sitting this late doesn't affect it)
          .hero-layer--overlay    (Nature masthead + cover text — the
                                    Beat-3 reveal. Shares
                                    .hero-layer--cover-box, below, with
                                    .hero-layer--nature — SAME box, so
                                    their text (same 880×1168 canvas)
                                    sits at identical pixels)
          .hero-layer--nature     (cover_nature.png, the real printed
                                    cover — fades in on TOP of overlay,
                                    inside the SAME .hero-engine group,
                                    not a separate top-level element —
                                    see hero.js for why)
        .hero-frame-outline    a hairline white wireframe on the
                                cover's trim rectangle, drawn concurrent
                                with the overlay and faded out again
                                once the crossfade has held. Replaces
                                four opaque closing bars + four corner
                                patches: nothing is masked out, so the
                                background and dust stay visible to the
                                viewport edge right through the reveal
        .hero-copy             title / dek
        .hero-statements       three Beat-2 captions, shown one at a
                                time between the title's exit and the
                                assembly window opening (see hero.js)
        .hero-scrollcue
        .hero-caption
*/

.hero {
  position: relative;
  /* Pin length — 700vh (100vh pinned viewport + 600vh of scroll), up
     from 460vh. Every hero-scoped tween (buildScrollSequence) is
     scrubbed linearly across whatever this is, so this number sets
     what one unit of that timeline is worth in actual scrolling:
     at 600vh, its 0.01 == 6vh. The extra 240vh over the old value is
     spent entirely on the title and the Beat-2 statements holding
     still for longer — hero.js rescaled Beat 3 to compensate, so the
     cover assembly and resolve still occupy the same ~147vh they did
     before. This number does NOT affect the dust's speed at all —
     SCENE_DRIFT/buildSceneDrift (hero.js) is driven by the whole
     page's scroll length, completely independent of this value,
     precisely so the two can be tuned separately. */
  height: 700vh;
  /* No background here — and this isn't just redundant with
     .bg-fixed's own, it's actively required to be absent. .hero is
     position:relative (z-index:auto), which stacks as a "positioned,
     stack-level-0" box at the nearest ancestor stacking context —
     since nothing between .hero and the root establishes one, that's
     the root itself. .bg-fixed, with its own negative z-index AND its
     own stacking context (position:fixed always creates one), paints
     at the EARLIER "negative stack level" step of that same root
     order — one step before stack-level-0 boxes like .hero. So an
     opaque background painted here would land on top of .bg-fixed's
     entire content, hiding bg_environment/dust completely, even
     though .bg-fixed is z-index:-1 and .hero looks like a plain
     ancestor. (Found by exactly this: bg_environment was rendering,
     decoded, fully opaque, correctly positioned — and still
     invisible, until this line was identified as the culprit.) */

  /* The Nature cover's own trim ratio — used to size the closing
     frame bars, and (via --cover-ratio-w/h below) to place the text
     overlay at the exact crop it represents. */
  --frame-w: min(92vw, calc(72vh * var(--cover-ratio-w) / var(--cover-ratio-h)));
  --frame-h: min(72vh, calc(92vw * var(--cover-ratio-h) / var(--cover-ratio-w)));

  /* The leftover margin outside that frame on each axis — shared by
     the closing bars AND the four corner-rounding patches below, so
     the frame wireframe sits exactly on that rectangle. */
  --frame-margin-x: calc((100vw - var(--frame-w)) / 2);
  --frame-margin-y: calc((100vh - var(--frame-h)) / 2);
}

/* ---------------------------------------------------------------
   The persistent backdrop — background + dust, fixed to the
   viewport, behind the entire page (hero through footer). Its own
   box carries no transform, ever — only .hero-stage inside it does —
   so it stays truly viewport-fixed rather than getting rescoped to a
   transformed ancestor. overflow:hidden replaces the clipping
   .hero-pin used to provide for the (deliberately oversized,
   100vmax) .hero-stage now that the two are no longer nested.
   opacity/is-ready mirrors .hero-pin's own reveal-once-decoded gate
   (hero.js toggles both together) so background and hero content
   never appear out of step while images are still decoding.
------------------------------------------------------------------ */
.bg-fixed {
  position: fixed;
  inset: 0;
  overflow: hidden;
  z-index: -1;
  background: var(--bg);
  opacity: 0;
}

.bg-fixed.is-ready {
  opacity: 1;
  transition: opacity var(--duration-slow) var(--ease-weighty);
}

.hero-pin {
  position: relative;
  z-index: 1; /* explicit, so it never depends on auto-stacking order
                 relative to .bg-fixed's negative z-index */
  width: 100%;
  height: 100vh;
  overflow: hidden;
  /* No background here anymore — .bg-fixed supplies it now, for the
     whole page, not just the hero. */
  opacity: 0; /* revealed by hero.js once every layer has decoded */
}

.hero-pin.is-ready {
  opacity: 1;
  transition: opacity var(--duration-slow) var(--ease-weighty);
}

/* ---------------------------------------------------------------
   Stage — bg + haze only. 100vmax is exactly max(100vw, 100vh), the
   classic "cover a viewport with a square" trick: at ANY aspect
   ratio this square is large enough to fill the screen with no
   pillarboxing, with no JS measurement needed. Scroll parallax
   (hero.js) only ever translates it a little from here — it never
   resizes, so it can never expose a bar.
------------------------------------------------------------------ */
.hero-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vmax;
  height: 100vmax;
  will-change: transform;
}

.hero-layer {
  position: absolute;
  pointer-events: none;
  user-select: none;
  will-change: transform, opacity;
}

.hero-layer--bg {
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Scale is set in hero.js (computeEdgeSafeScale), not here — it's
     computed from the layer's actual max parallax displacement, not
     a guessed constant, and applying it via GSAP alongside the same
     element's scroll-driven transform avoids a separate CSS transform
     fighting the JS one. See hero.js for the formula and reasoning. */
}

.hero-layer--haze {
  width: 36%;
  height: auto;
  opacity: 0.62;
  /* Modest legibility-preserving lift on top of the opacity bump below
     — reads as slightly more present dust, not a brighter light source
     competing with text passing behind it. */
  filter: brightness(1.22);
  /* Several of the wisp PNGs are cropped by their own canvas edge
     rather than fading fully to transparent — feather every wisp's
     own bounding box so that crop never reads as a hard rectangle
     against the dark sky. Radius stays just under the 50%
     center-to-edge distance (48%, not the original 60%) so the fade
     still finishes — alpha 0 — before the img's own rectangular edge,
     which is what removes the hard line. But the opaque core now
     holds out to 65% of THAT radius (~31% of the box, matching the
     original 30%) instead of 20% — the first pass shrank the core
     as well as the radius, which was the actual mistake: it was
     quietly eating the wisp's own mid-frame dust (the part reaching
     toward the page's center, the "looking through fog" effect),
     not just the hard-cropped outer edge. Now only the outer ~17% of
     the box (31%→48%) is the feather zone; everything inside that is
     back to full strength. Corners still fade out earliest, same as
     always — they're farther from center than the ellipse reaches on
     any axis. */
  -webkit-mask-image: radial-gradient(ellipse 48% 48% at 50% 50%, #000 65%, transparent 100%);
  mask-image: radial-gradient(ellipse 48% 48% at 50% 50%, #000 65%, transparent 100%);
}

/* Hand-placed in the four canvas corners per layer_positions.json's
   haze_corners note (~36% × ~50% each) — not registered to the art.
   These are the "dust": strong, fast, individually-directed parallax
   (hero.js) is what sells the scene's motion. Opacity nudged up from
   the original 0.38-0.45 in two small passes — still corner-anchored
   and behind all page content (.bg-fixed, z-index:-1), just easier to
   read as drifting dust rather than barely-there texture. */
.haze-1 { left: -2%;  top: -2%;    width: 36%; opacity: 0.62; transform: rotate(-6deg); }
.haze-2 { right: -2%; top: -2%;    width: 36%; opacity: 0.60; transform: rotate(8deg) scaleX(-1); }
.haze-3 { left: -2%;  bottom: -2%; width: 36%; opacity: 0.68; transform: rotate(4deg) scaleY(-1); }
.haze-4 { right: -2%; bottom: -2%; width: 36%; opacity: 0.62; transform: rotate(-10deg) scale(-1); }

/* ---------------------------------------------------------------
   Engine — disk + magnetar + jets + the cover text overlay, one
   rigid group. Its own square, sized independently of .hero-stage —
   shrinking this can never shrink or letterbox the stage. The base
   CSS size here is deliberately the FRAME-MATCHING size (frame-w
   divided by the cover's own aspect ratio) rather than a viewport-
   relative guess: that makes "scale: 1" the exact size at which the
   text overlay's crop (see .hero-layer--overlay) fills --frame-w
   precisely, with no measurement needed for Beat 3's registration.
   Beat 1's "small, contained, ~35% of viewport width" look is then
   a single JS-measured scale-up applied on top of this — see
   computeEngineStartScale() in hero.js — which the scroll timeline
   eases back down to 1 across the whole scroll (monotonic).
------------------------------------------------------------------ */
.hero-engine {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(var(--frame-w) * var(--cover-ratio-h) / var(--cover-ratio-w));
  height: calc(var(--frame-w) * var(--cover-ratio-h) / var(--cover-ratio-w));
  will-change: transform;
}

.hero-layer--jetglow {
  /* top/bottom jet-base flares — layer_positions.json verbatim for
     position/size. These are ambient glow, not the beam itself:
     stacked at full opacity under jet_upper/jet_lower they summed to
     flat white. Dimmed to read as faint atmosphere instead. */
  left: 25%;
  width: 50%;
  height: 15.62%;
  object-fit: contain;
  opacity: 0.35;
}
.jetglow-1 { top: 0; }
.jetglow-2 { top: 84.38%; }

.hero-layer--jet-lower,
.hero-layer--jet-upper {
  /* both centered on the same vertical axis (left 48.44% + width
     3.12% = 50%) */
  left: 48.44%;
  width: 3.12%;
  object-fit: contain;
}
.hero-layer--jet-lower { top: 60.94%; height: 39.06%; }
.hero-layer--jet-upper { top: 0;      height: 53.12%; }

.hero-layer--disk {
  /* verified by cross-correlation against cover_art.jpeg, then
     confirmed against layer_positions.json: 12.5%/34.38% of the
     square canvas at 68.75%/37.5% of its size */
  left: 12.5%;
  top: 34.38%;
  width: 68.75%;
  height: 37.5%;
  object-fit: contain;
  /* disk_foreground.png's alpha isn't quite zero right at its top
     edge (a faint ~1-2/255 residual, presumably a canvas-crop
     artifact) — invisible against the bright disk/jet, but reads as
     a hard seam against open dark sky. A hairline top feather (2.5%
     of the box) hides that without touching the disk itself. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 2.5%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 2.5%, #000 100%);
}

.hero-layer--magnetar {
  /* glowing core + dipole field lines, in front of the disk, at its
     center — layer_positions.json verbatim */
  left: 43.75%;
  top: 45.31%;
  width: 12.5%;
  height: 7.81%;
  object-fit: contain;
}

/* ---------------------------------------------------------------
   .hero-layer--cover-box — the ONE shared position/size rule for
   BOTH cover_text_overlay.png and cover_nature.png. They're the same
   880×1168 canvas (the overlay is cover_nature.png with the artwork
   erased — same crop, same pixels, same text), so putting them both
   through this identical rule, as siblings inside the SAME
   .hero-engine, is what guarantees their text lines up exactly: no
   per-element registration math, nothing to drift out of sync,
   nothing to verify beyond "do both elements use this one class" —
   their getBoundingClientRect()s are identical by construction, not
   by coincidence.
     width%  = cover-ratio-w / cover-ratio-h   (the crop keeps this
               fraction of the canvas's width)
     left%   = (1 - width%) / 2                 (crop is centered)
     top/height% = 0 / 100                       (no vertical crop)
   Because .hero-engine's own box is sized so scale:1 makes this
   exactly --frame-w wide (see .hero-engine above), the overlay's
   transparent disk-shaped hole lands exactly on the live disk at
   every moment, automatically, in both Beat 1/2 (small, scaled up)
   and the Beat-3 settle (scale 1, filling the frame) — and
   cover_nature.png, sharing the same box, lands exactly over that
   same disk position once it fades in on top.
------------------------------------------------------------------ */
.hero-layer--cover-box {
  top: 0;
  height: 100%;
  left: calc((100% - 100% * var(--cover-ratio-w) / var(--cover-ratio-h)) / 2);
  width: calc(100% * var(--cover-ratio-w) / var(--cover-ratio-h));
  object-fit: fill;
}

.hero-layer--overlay {
  opacity: 0; /* GSAP-driven: 0 → 1 once during assembly, then left
                 at 1 for good — see hero.js */
}

.hero-layer--nature {
  opacity: 0; /* GSAP-driven: 0 → 1 during the tail crossfade, on top
                 of the (already fully opaque) overlay above */
  border-radius: var(--radius-cover);
}

/* ---------------------------------------------------------------
   The cover's trim edge, as a hairline wireframe.

   This replaces what used to be four opaque --bg bars closing in from
   the viewport edges (plus four radial-gradient patches to round the
   window they left). That version cropped to the cover by painting
   everything outside it black, which killed the scene: the dust and
   background vanished behind solid margins exactly at the moment the
   page transitions into the sections below, and the cover read as a
   cut rather than as part of one continuous scene. Drawing only the
   border — at precisely the rectangle those bars used to define, same
   --frame-margin-* insets, same --radius-cover corners — keeps the
   whole backdrop live edge to edge while still registering the trim.
   One element instead of eight, and the corner rounding is now just
   border-radius rather than four gradient patches.
------------------------------------------------------------------ */
.hero-frame-outline {
  position: absolute;
  left: var(--frame-margin-x);
  right: var(--frame-margin-x);
  top: var(--frame-margin-y);
  bottom: var(--frame-margin-y);
  border: 1px solid var(--text-bright, #fff);
  border-radius: var(--radius-cover);
  opacity: 0; /* GSAP-driven, Beat 3 */
  pointer-events: none;
  will-change: transform, opacity;
}

/* ---------------------------------------------------------------
   Copy
------------------------------------------------------------------ */
.hero-copy {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-inline: var(--content-pad);
  will-change: transform, opacity;
}

.hero-copy::before {
  content: '';
  position: absolute;
  inset: -10% -10%;
  background: radial-gradient(ellipse at center, var(--scrim) 0%, transparent 65%);
  z-index: -1;
}

.hero-title {
  max-width: 16ch;
  font-size: var(--step-5);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  /* Plain white, full stop. The literal fallback in the var() is load
     bearing: if --text-bright ever fails to resolve, an unresolvable
     var() makes `color` invalid-at-computed-value-time and the element
     INHERITS instead — landing back on the body's off-white, which is
     exactly the "still grey" this kept regressing to. */
  color: var(--text-bright, #fff);
}

.hero-dek {
  max-width: 46ch;
  margin-top: var(--space-4);
  font-size: var(--step-0);
  font-weight: var(--weight-light);
  line-height: var(--leading-normal);
  color: var(--text-bright, #fff);
}

/* Entrance for both lines: a quick rise into place, TRANSFORM ONLY.
   Nothing here touches colour or opacity, so at every instant of the
   animation — including before it starts, if it stalls partway, or if
   it never runs at all — the text is already fully opaque white. The
   dek follows the title by a beat. */
@media (prefers-reduced-motion: no-preference) {
  .hero-title {
    animation: hero-text-rise var(--duration-fast) var(--ease-weighty) both;
  }

  .hero-dek {
    animation: hero-text-rise var(--duration-fast) var(--ease-weighty) 0.12s both;
  }
}

@keyframes hero-text-rise {
  from { transform: translateY(0.45em) scale(0.985); }
  to   { transform: none; }
}

/* The CHIRPS acronym letters within the dek — bold, same color as the
   surrounding text (not --text, so it doesn't also read as an accent
   color change, just weight). */
.hero-dek strong {
  font-weight: var(--weight-bold);
  color: inherit;
}

/* ---------------------------------------------------------------
   Beat-2 statements — three captions shown one at a time over the
   drifting dust, after the title has lifted away and before the
   cover overlay/bars start assembling (see hero.js for the exact
   scroll timing). Stacked directly on top of one another (each its
   own inset:0 box) rather than laid out in flow, so animating one
   in/out never shifts the others' centered position.
------------------------------------------------------------------ */
.hero-statements {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

/* The legibility scrim belongs to each STATEMENT, not to the container.
   It used to sit on .hero-statements::before — and because that
   container is a later sibling of .hero-copy at the SAME z-index (3),
   its whole stacking context (its z-index:-1 scrim included) painted
   on top of .hero-copy. The result was a permanent 55%-black veil over
   the title and dek during Beat 1, when no statement is on screen at
   all: white title text composited down to about rgb(118) — grey. No
   amount of setting `color` on the title could win against a layer
   painted over it. Living on .hero-statement instead, the scrim
   inherits that statement's GSAP-driven opacity, so it exists only
   while its own statement is actually being shown. */
.hero-statement::before {
  content: '';
  position: absolute;
  inset: -10% -10%;
  background: radial-gradient(ellipse at center, var(--scrim) 0%, transparent 65%);
  z-index: -1;
}

.hero-statement {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--content-pad);
  opacity: 0; /* GSAP-driven, Beat 2 only — matches the Beat-1 rest
                 state before hero.js's initial gsapLib.set() runs,
                 and (with no JS/reduced-motion) keeps these three out
                 of the static Beat-1 render below; see
                 .hero-statements--static for that path's own copy. */
  will-change: transform, opacity;
}

/* Shared with the reduced-motion static fallback below, so the
   wording reads identically whether it's scroll-driven or static. */
.hero-statement-text {
  max-width: 30ch;
  margin: 0;
  text-align: center;
  font-size: var(--step-3);
  font-weight: var(--weight-light);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}

/* ---------------------------------------------------------------
   Scroll cue
------------------------------------------------------------------ */
.hero-scrollcue {
  position: absolute;
  left: 50%;
  bottom: var(--space-5);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  transform: translateX(-50%);
  color: var(--text-dim);
  will-change: transform, opacity;
}

.hero-scrollcue__line {
  width: 1px;
  height: var(--space-5);
  background: linear-gradient(to bottom, transparent, var(--text-dim) 45%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-scrollcue__line {
    animation: hero-scrollcue-drift var(--duration-ambient-slow) var(--ease-ambient) infinite;
  }
}

@keyframes hero-scrollcue-drift {
  0%, 100% { opacity: 0.4; transform: scaleY(0.85); }
  50%      { opacity: 1;   transform: scaleY(1); }
}

.hero-scrollcue__label {
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* ---------------------------------------------------------------
   Caption — the Nature cover credit line, Beat 3 payoff only.
------------------------------------------------------------------ */
.hero-caption {
  position: absolute;
  left: 50%;
  bottom: var(--space-5);
  z-index: 4;
  transform: translateX(-50%);
  opacity: 0;
  will-change: transform, opacity;
  margin: 0;
}

.hero-caption a {
  font-size: var(--step--1);
  color: var(--text-dim);
  letter-spacing: var(--tracking-normal);
}

.hero-caption a:hover,
.hero-caption a:focus-visible {
  color: var(--accent);
}

/* ---------------------------------------------------------------
   Reduced motion: no pin, no scroll-jacking, no ambient loop. The
   scene, then the resolved cover, in normal document flow.
------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .hero {
    height: auto;
  }
  .hero-pin {
    position: relative;
    height: 100vh;
  }
  .hero-scrollcue__line {
    animation: none;
  }
}

.hero-static-resolve {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .hero-static-resolve {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding-block: var(--space-7);
    background: var(--bg);
  }

  /* Same three Beat-2 statements as the animated sequence, just
     always visible and unanimated — this whole block replaces the
     scroll-jacked journey rather than sitting at a position within
     it, so there's no single "scroll progress" for them to fade
     against; showing all three, plainly, is the static equivalent. */
  .hero-statements--static {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    max-width: 46ch;
    padding-inline: var(--content-pad);
  }

  .hero-cover-frame--static {
    width: var(--frame-w);
    height: auto;
    aspect-ratio: var(--cover-ratio-w) / var(--cover-ratio-h);
    box-shadow: 0 var(--space-2) var(--space-6) var(--scrim);
  }

  .hero-cover-frame--static img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .hero-caption--static {
    position: static;
    transform: none;
    opacity: 1;
  }
}

/* ---------------------------------------------------------------
   Mobile degradation — fewer layers, less movement (movement
   amounts themselves are dialed down in hero.js).
------------------------------------------------------------------ */
@media (max-width: 700px) {
  .haze-2,
  .haze-4 {
    display: none;
  }
  .hero-title {
    max-width: 13ch;
  }
}
