/* ============================================================
   SparkPlug v3 — scroll story layer
   Choreography grammar from the TeraWulf audit
   (sparkplug-scroll/scroll-animation-audit.md): sticky-sandwich
   pinning, letter reveals 0.18→1, clamp() triggers, scrub-lag
   smoothing, clean mobile/reduced-motion amputation.
   Recolored to this site's system: white canvas, #171717 ink,
   #5e5e5e granite, #D4752E copper, Satoshi display.

   PARKED: every rule belonging to the hero dolly (the runway height,
   the sticky pane, the pre-render box, the paint-park and the bloom)
   is scoped under `body.hero-zoom`. That class is added by scroll.js
   only when its HERO_ZOOM flag is true, so with the flag off this file
   contributes NOTHING to the hero — it falls back to the ordinary
   padded, auto-height section in styles.css. The CTA sticky beat, the
   #services flip and the asterisk zoom-through are unscoped on purpose:
   they predate the zoom pass and are not part of it.
   ============================================================ */

.ch{display:inline-block;will-change:opacity}

/* the set piece is amputated by default; desktop opts in */
.zoomthru{display:none}

@media (min-width:768px) and (prefers-reduced-motion:no-preference){

  /* ── story beat 1: the hero becomes a sticky sandwich, and the
     sandwich is a camera dolly into the laptop screen.
     Runway geometry ported 1:1 from mercury-scroll-audit.md §3:
       section 250vh  ·  sticky pane 100vh  ·  runway 150vh
     Here the pane is 100vh - 72px (it sits under the fixed nav), so
     the section is 250vh - 72px and the pin distance lands on exactly
     150vh — Mercury's 1350px at a 900px viewport, to the pixel. ── */
  body.hero-zoom .hero{height:calc(250vh - 72px);padding-top:0;padding-bottom:0}
  body.hero-zoom .hero > .shell{
    position:sticky;top:72px;
    height:calc(100vh - 72px);
    align-content:center;
  }
  /* Mercury needs no will-change: its zoom is a <video>, already its own
     compositor layer. A DOM scale has to ask for the promotion — but only
     while the runway is live. scroll.js adds it on the trigger's onToggle
     and strips it again on the way out, so the page isn't holding a
     multi-megabyte texture for a section nobody is looking at. */
  body.hero-zoom .hero__copy{will-change:opacity}
  body.hero-zoom .float{will-change:transform,opacity}

  /* ══════════ FIX 1 - pre-render the screen at zoom resolution ══════════
     The laptop was going soft because the layer is rasterised at its layout
     size and then stretched: measured, the headline glyphs were rastered at
     40px and displayed at 118px - 0.34x of display resolution.

     .lap__screen is container-type:inline-size and essentially every rule in
     the eight slides is authored in cqw, so making the inner .ms box --lz
     times larger re-lays-out AND re-rasterises all of it at that size -
     real text at 118px, not a stretched 40px bitmap. Scaling .ms straight
     back down by 1/--lz leaves the resting appearance identical, and .ms is
     absolutely positioned so none of this touches layout outside the screen.
     The dolly then scales UP from an already-high-resolution raster.

     --lz is set per viewport by scroll.js as ZOOM / min(devicePixelRatio, 2),
     so the raster lands at ~1:1 with the display at the deepest point and a
     retina display doesn't pay for it twice. */
  body.hero-zoom .ms{
    inset:auto;top:0;left:0;
    width:calc(100% * var(--lz,1));
    height:calc(100% * var(--lz,1));
    transform:scale(calc(1 / var(--lz,1)));
    transform-origin:0 0;
    container-type:inline-size;
  }

  /* cqw sizing scales itself; these are the only lengths in the eight slides
     authored in px, so they have to be multiplied back or a 1px rule would
     render at 1/--lz of a pixel and a 24px pill radius would lose its round.
     9999px radii are excluded - already fully round at any scale. */
  body.hero-zoom .lap .dsg--velvet .dg-cta{border-radius:calc(24px * var(--lz,1))}
  body.hero-zoom .lap .msD-gl{filter:blur(calc(2.6px * var(--lz,1)))}
  body.hero-zoom .lap .dsg--showroom .dg-ghost{border:calc(1px * var(--lz,1)) solid #494949}
  body.hero-zoom .lap .a2-pips i{height:calc(1.5px * var(--lz,1))}
  body.hero-zoom .lap .dsg--biolab .dg-cta{border:calc(1px * var(--lz,1)) solid #cef79e;border-radius:calc(6px * var(--lz,1))}
  body.hero-zoom .lap .c3-stats{border-top:calc(1px * var(--lz,1)) solid rgba(201,203,190,0.18)}
  body.hero-zoom .lap .dsg--literary .dg-nav{border-bottom:calc(1px * var(--lz,1)) solid #dee2de}
  body.hero-zoom .lap .f4-row{border-top:calc(1px * var(--lz,1)) solid #dee2de}
  body.hero-zoom .lap .m4{border:calc(1px * var(--lz,1)) solid #dee2de}
  body.hero-zoom .lap .f5-input{border:calc(1px * var(--lz,1)) solid rgba(255,255,255,0.1)}
  body.hero-zoom .lap .f5-panel{border:calc(1px * var(--lz,1)) solid rgba(255,255,255,0.08)}
  body.hero-zoom .lap .f5-code{border:calc(1px * var(--lz,1)) solid rgba(255,255,255,0.07)}
  body.hero-zoom .lap .f5-logos{border-top:calc(1px * var(--lz,1)) solid rgba(255,255,255,0.09)}
  body.hero-zoom .lap .dsg--pastoral .dg-cta{border-radius:calc(100px * var(--lz,1))}
  body.hero-zoom .lap .dsg--pastoral .dg-ghost{border:calc(1px * var(--lz,1)) solid #c9c7b4;border-radius:calc(100px * var(--lz,1))}
  body.hero-zoom .lap .r8-plus{border:calc(2px * var(--lz,1)) solid #000}
  body.hero-zoom .lap .m8{border:calc(2px * var(--lz,1)) solid #000}
  body.hero-zoom .lap .r8-qr{border:calc(2px * var(--lz,1)) solid #000}
  body.hero-zoom .lap .r8-marq{border-top:calc(2px * var(--lz,1)) solid #000}

  /* ══════════ FIX 2 - nothing but transform/opacity during the runway ══════
     51 element-animations inside the laptop animate background, clip-path,
     filter, stroke-dashoffset, top/left - all of which repaint, now on a
     surface --lz times larger. They are parked for the length of the runway
     and resume after. The slide shuffle itself is opacity and is NOT in this
     set, so the eight designs keep cycling through the whole zoom. */
  body.hero-zoom .ms.is-zooming .js-paint-anim{animation-play-state:paused !important}

  /* ══════════ FIX 3 - the copper flare at the crossover ══════════
     mix-blend-mode:screen is the whole trick. Over the dark laptop screen it
     blooms copper; over the white canvas the deck stands on, screen-blending
     against white is an identity, so it costs nothing and tints nothing. It
     lights the handoff and then puts itself out as white takes the frame.
     Built by scroll.js, never in markup, so mobile and reduced motion never
     get the node. */
  body.hero-zoom .zoom-bloom{
    position:fixed;inset:0;z-index:3;
    pointer-events:none;opacity:0;
    mix-blend-mode:screen;
    background:radial-gradient(58% 46% at 50% 52%,
      rgba(212,117,46,0.34) 0%,
      rgba(212,117,46,0.16) 40%,
      rgba(212,117,46,0.04) 62%,
      rgba(212,117,46,0) 76%);
  }

  /* ── final beat: the CTA holds on screen ── */
  .cta{height:150vh;padding-top:0;padding-bottom:0}
  .cta > .shell{
    position:sticky;top:72px;
    height:calc(100vh - 72px);
    display:flex;flex-direction:column;justify-content:center;
  }

  /* ── alternate the copy side so the page breathes ── */
  #services .section__head{margin-left:auto;text-align:right}
  #services .section__head .eyebrow{justify-content:flex-end}
  #services .section__lede{margin-left:auto}

  /* ── the signature: asterisk zoom-through ── */
  .zoomthru{
    display:block;position:relative;height:300vh;z-index:5;
  }
  .zt-sticky{
    position:sticky;top:0;height:100vh;overflow:hidden;
  }
  .zt-back{
    position:absolute;inset:0;
    background:
      radial-gradient(58% 58% at 50% 44%,rgba(212,117,46,0.16) 0%,rgba(212,117,46,0.05) 52%,transparent 74%),
      #fdf0e6;
    display:grid;place-content:center;text-align:center;
  }
  /* faint living texture: video at the very back of .zt-back,
     heavy white wash above it (strongest where the copy sits),
     then .zt-back-inner paints on top in DOM order */
  .zt-video{
    position:absolute;inset:0;width:100%;height:100%;
    object-fit:cover;
    pointer-events:none;
  }
  .zt-wash{
    position:absolute;inset:0;pointer-events:none;
    background:
      radial-gradient(58% 58% at 50% 44%,rgba(212,117,46,0.12) 0%,rgba(212,117,46,0.04) 52%,transparent 74%),
      radial-gradient(72% 62% at 50% 46%,rgba(255,255,255,0.92) 0%,rgba(255,255,255,0.88) 55%,rgba(255,255,255,0.85) 100%);
  }
  .zt-back-inner{position:relative;will-change:transform,opacity}
  .zt-eyebrow{
    font-family:ui-monospace,"Cascadia Mono",Consolas,monospace;
    font-size:0.8rem;letter-spacing:0.2em;text-transform:uppercase;
    color:var(--copper,#D4752E);margin-bottom:1.1rem;
  }
  .zt-big{
    font-weight:700;
    font-size:clamp(2.6rem,5.2vw,4.6rem);
    line-height:1.02;letter-spacing:-0.03em;
    color:var(--charcoal,#171717);
    text-wrap:balance;
  }
  .zt-cloud{
    position:absolute;top:-12%;bottom:-12%;width:68%;
    background:radial-gradient(50% 50% at 50% 50%,rgba(255,255,255,0.95) 0%,rgba(255,255,255,0.55) 55%,transparent 78%);
    filter:blur(18px);
    will-change:transform;pointer-events:none;
  }
  .zt-cloud--l{left:-22%}
  .zt-cloud--r{right:-22%}
  .zt-plate{
    position:absolute;inset:0;width:100%;height:100%;
    transform-origin:50% 50%;
    will-change:transform;
  }
  .zt-fill{
    position:absolute;left:50%;top:50%;
    height:48vh;aspect-ratio:1;
    transform:translate(-50%,-50%);
    will-change:opacity;
  }
}
