/* =====================================================================
   PEPSTRAL — hero assay sequence
   Loaded AFTER site.css so it can override the flat hero. Audited from
   opxy.mengto.here.now (10 Oct 2026), which pins a tall section with a
   sticky 100svh stage and drives a camera between authored stations from
   per-section scroll progress. That page renders a 650k-triangle GLTF
   through Three.js with bloom and depth of field; a storefront whose
   organic traffic is the whole point cannot carry ~700 KB of module JS,
   so the ARCHITECTURE is borrowed and the subject is what this brand
   actually owns: the assay. The vial travels, the RP-HPLC trace draws,
   the peak integrates, and lot RETA10-0803's real numbers resolve.
   Adds no library: GSAP and Lenis are already on the page.

   Nothing here applies until js/hero-seq.js adds .heroseq-on to <html>,
   so no-JS and reduced-motion readers get today's hero untouched.

   Custom properties written per frame by the engine, all read with a
   fallback: an invalid custom-property chain resolves to the property's
   INITIAL value silently, which once rendered five tint layers fully on.
     --ap   0..1 progress through the whole pinned section
     --b1..--b4  eased 0..1 window for each beat
     --vx --vy --vs  the vial's station, damped (px, px, scale)
     --mx --my  pointer parallax, damped (-1..1)
   ===================================================================== */

/* ---------- inert by default -----------------------------------------
   The engine adds .heroseq-on only where the sequence can actually run:
   JS on, a window wide and tall enough, and a hero whose copy fits the
   capped stage. Everywhere else these layers must not exist at all —
   without this the readout would render as loose text under the hero. */
.heroseq_atm,.heroseq_grid,.heroseq_reticle,.heroseq_scan,.heroseq_readout{display:none}
.heroseq-on .heroseq_atm,.heroseq-on .heroseq_grid,.heroseq-on .heroseq_reticle,
.heroseq-on .heroseq_scan{display:block}
.heroseq-on .heroseq_readout{display:block}
.heroseq_fill,.heroseq_peak{opacity:0}

/* ---------- the pin ----------------------------------------------------
   .heroseq is the tall track; .hero becomes its sticky stage. Overflow must
   stay OFF the track: an overflow value on any ancestor of a sticky
   element silently kills the stick. .hero keeps its own overflow:hidden
   from site.css, which is what clips the atmosphere to the stage.        */
/* Everything the pinned layout needs is CSS, with no value JS has to supply, so
   the first paint IS the final layout. The previous version centred the content
   in a 100svh box and padded for a JS-measured nav height; because the class only
   landed once the engine booted, that re-centring moved .hero_inner 197px down at
   255ms and scored CLS 0.0936 on the homepage, against 0 on the deploy before it.
   The inline script in <head> (build.mjs) now adds .heroseq-on before paint, and
   .hero_inner keeps exactly the padding and position it has without the class.
   The nav covers the top of the stage only once it is actually stuck, at p ~ 0.19,
   by which point beat 1 has taken the copy away — so reserving space for the nav
   bought nothing and cost a layout shift on the site's most important page. */
.heroseq-on .heroseq{position:relative;height:240vh}
.heroseq-on .heroseq .hero{position:sticky;top:0;height:100svh;align-content:start}
/* .hero_inner is deliberately left alone. Padding it down to clear the sticky
   nav was tried and reverted: it bought 166px of clearance for content that is
   already leaving, and paid for it by pushing the CTAs below the fold at rest on
   a 1440x900 laptop. The hero at scroll 0 is worth more than the hold at p=0, so
   beat 1 instead starts at p=0 (see BEATS in js/hero-seq.js) and the copy is
   already fading the moment the stage parks under the nav. */

/* ---------- beat 1 · the copy band hands over -------------------------
   Borrowed from the target verbatim in spirit: the outgoing copy is gone
   by mid-handover, so two beats' words never share a frame.             */
.heroseq-on .hero_copy{
  opacity:calc(1 - var(--b1,0));
  transform:translate3d(0, calc(var(--b1,0) * -4.5vh), 0);
  transition:none}
/* once it has left it stops catching the pointer, so the search box
   underneath an invisible layer can never swallow a click */
.heroseq-on .hero_copy[data-gone]{visibility:hidden}

/* ---------- the vial · a damped station, not a scrub ------------------
   The target damps the camera toward each station rather than wiring it
   straight to scroll; that single choice is most of why it reads as a
   film and not a scrubber. The engine damps --vx/--vy/--vs and CSS only
   composites them, so the vial keeps gliding after the wheel stops.     */
.heroseq-on .hero_vial{z-index:4}
.heroseq-on .hero_vial_img{
  translate:calc(var(--vx,0) * 1px + var(--mx,0) * 10px) calc(var(--vy,0) * 1px + var(--my,0) * 8px);
  scale:var(--vs,1);
  will-change:translate,scale}
/* site.css sweeps a looping highlight across the glass. At rest that reads as
   glass; at station scale it is a 100px grey bar crossing the stage, so it
   leaves with the copy band and the vial becomes the only subject. */
.heroseq-on .hero_sheen{opacity:calc(1 - var(--b1,0));transition:none}

/* the pool of brand light grows and warms with the vial */
.heroseq-on .hero_vial_pool{
  translate:calc(var(--vx,0) * 1px) calc(-50% + var(--vy,0) * 1px);
  scale:calc(1 + var(--b2,0) * .22);
  opacity:calc(1 - var(--b4,0) * .45)}

/* ---------- the reticle · beat 2 --------------------------------------
   Four corner brackets closing on the vial. An instrument converging on
   a sample, which is the literal claim in the badge above it.           */
.heroseq-on .heroseq_reticle{
  position:absolute;left:0;top:0;
  /* the brackets frame the vial itself: --vw/--vh are the image's measured box
     in px and --vs is the station scale, so they track it through the travel.
     A fixed square sat across the middle of the glass and put its label over
     the label that is already printed on the vial. */
  width:calc(var(--vw,240) * 1px * var(--vs,1));
  height:calc(var(--vh,320) * 1px * var(--vs,1));
  translate:calc(var(--vcx,0) * 1px - 50% + var(--vx,0) * 1px + var(--mx,0) * 10px) calc(var(--vcy,0) * 1px - 50% + var(--vy,0) * 1px + var(--my,0) * 8px);
  z-index:5;pointer-events:none;
  opacity:calc(var(--b2,0) * (1 - var(--b4,0)));
  /* the brackets start wide and close to a hairline margin as the beat runs */
  --spread:calc(.55rem + (1 - var(--b2,0)) * 2.6rem)}
.heroseq_reticle i{position:absolute;width:1.5rem;height:1.5rem;border:1.5px solid var(--teal-2);opacity:.9}
.heroseq_reticle i:nth-child(1){top:calc(var(--spread) * -1);left:calc(var(--spread) * -1);border-right:0;border-bottom:0}
.heroseq_reticle i:nth-child(2){top:calc(var(--spread) * -1);right:calc(var(--spread) * -1);border-left:0;border-bottom:0}
.heroseq_reticle i:nth-child(3){bottom:calc(var(--spread) * -1);left:calc(var(--spread) * -1);border-right:0;border-top:0}
.heroseq_reticle i:nth-child(4){bottom:calc(var(--spread) * -1);right:calc(var(--spread) * -1);border-left:0;border-top:0}

/* ---------- the scan · one pass down the glass, scrubbed --------------
   site.css already runs a scan once on load as a keyframe. This is the
   scrolled one: its position IS the beat, so the reader drives it.      */
.heroseq-on .heroseq_scan{
  position:absolute;left:0;top:0;width:min(34vh,17rem);height:min(44vh,22rem);
  translate:calc(var(--vcx,0) * 1px - 50% + var(--vx,0) * 1px) calc(var(--vcy,0) * 1px - 50% + var(--vy,0) * 1px);
  z-index:6;pointer-events:none;overflow:hidden;
  opacity:calc(var(--b2,0) * (1 - var(--b2,0)) * 4)}
.heroseq_scan::after{content:"";position:absolute;left:0;right:0;height:2px;
  top:calc(var(--b2,0) * 100%);
  background:linear-gradient(90deg,transparent,var(--teal-2),transparent);
  box-shadow:0 0 18px 2px rgba(37,205,238,.55)}

/* the certificate card hands over to the readout plate rather than sitting
   beside it repeating the same lot. Same rule as the copy band in beat 1. */
.heroseq-on .hero_proof{opacity:calc(1 - var(--b3,0));transition:none}
.heroseq-on .hero_proof[data-gone]{visibility:hidden}

/* ---------- the trace · beat 3 ----------------------------------------
   site.css draws .hero_trace once on load across the bottom at 50%
   opacity. Here it becomes the subject: it rises off the floor, grows to
   own the lower band, and the stroke re-reveals with the beat.          */
.heroseq-on .hero_trace{
  height:calc(clamp(120px,22vh,210px) + var(--b3,0) * 26vh);
  opacity:calc(.5 + var(--b3,0) * .5);
  z-index:3;
  transition:none}
.heroseq-on .hero_trace_line{stroke-width:calc(1.4px + var(--b3,0) * .7px)}
/* site.css draws the trace once on load. Killing that outright to make room for
   the scrub would cost a good moment for nothing, so the engine only hands the
   stroke over at the start of beat 3 — a CSS animation overrides the declared
   stroke-dashoffset, so the two cannot share the property without this class. */
.heroseq-on .hero_trace_line.is-scrub{
  animation:none;
  stroke-dasharray:2600;
  stroke-dashoffset:calc(2600 * (1 - var(--b3,0)))}
/* the integrated area under the curve, wiped left to right */
.heroseq-on .heroseq_fill{fill:url(#heroseqGrad);opacity:calc(var(--b3,0) * .8);
  clip-path:inset(0 calc(100% - var(--b3,0) * 100%) 0 0)}
/* the quantified peak: a marker on the one peak the purity number describes */
.heroseq-on .heroseq_peak{opacity:calc(max(0, var(--b3,0) * 1.6 - .6) * (1 - var(--b4,0) * .5))}
.heroseq_peak line{stroke:var(--gold);stroke-width:1;stroke-dasharray:3 4;vector-effect:non-scaling-stroke}
.heroseq_peak text{fill:var(--gold);font:600 7px var(--f-body);letter-spacing:.1em}

/* ---------- the readout · beats 3-4 ----------------------------------
   An instrument plate, not a card: hairline top rule, uppercase channel
   labels, tabular figures. Every value is lot RETA10-0803 out of
   src/coa.json, which is the same record the certificate link serves.   */
.heroseq-on .heroseq_readout{
  position:absolute;left:var(--gutter);bottom:clamp(1.2rem,4vh,3rem);z-index:7;
  width:min(26rem,calc(100% - var(--gutter) * 2));
  pointer-events:none;
  opacity:var(--b3,0);
  transform:translate3d(0, calc((1 - var(--b3,0)) * 2rem), 0)}
.heroseq_readout > .k{display:flex;align-items:center;gap:.5rem;
  font-size:var(--t-xs);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--teal-2);
  padding-bottom:.6rem;border-bottom:1px solid rgba(255,255,255,.16)}
.heroseq_readout > .k i{width:7px;height:7px;border-radius:50%;background:var(--teal-2);flex:none}
.heroseq_big{display:flex;align-items:baseline;gap:.5rem;margin:.9rem 0 .2rem}
.heroseq_big b{font-family:var(--f-display);font-weight:500;font-size:clamp(2.6rem,6vw,4.2rem);line-height:.9;
  letter-spacing:-.03em;color:#fff;font-variant-numeric:tabular-nums}
.heroseq_big span{font-size:var(--t-xs);color:#93a3b5;letter-spacing:.04em;max-width:17ch;line-height:1.35}
/* the three channels resolve one at a time inside beat 4's window */
.heroseq_rows{display:grid;gap:.4rem;margin-top:1rem;font-size:var(--t-sm);font-variant-numeric:tabular-nums}
.heroseq_rows div{display:flex;justify-content:space-between;gap:1rem;color:#c9d3de;
  padding-bottom:.4rem;border-bottom:1px solid rgba(255,255,255,.09);
  opacity:var(--r,0);transform:translate3d(calc((1 - var(--r,0)) * -.6rem),0,0)}
.heroseq_rows div b{color:#fff;font-weight:600}
.heroseq_rows div:nth-child(1){--r:clamp(0, calc(var(--b4,0) * 3), 1)}
.heroseq_rows div:nth-child(2){--r:clamp(0, calc(var(--b4,0) * 3 - .7), 1)}
.heroseq_rows div:nth-child(3){--r:clamp(0, calc(var(--b4,0) * 3 - 1.4), 1)}
.heroseq_cert{display:inline-flex;align-items:center;gap:.5rem;margin-top:1rem;
  font-size:var(--t-sm);font-weight:600;color:#fff;
  padding:.6rem 1rem;border-radius:var(--r-pill);background:rgba(37,205,238,.14);
  border:1px solid rgba(37,205,238,.4);
  opacity:clamp(0, calc(var(--b4,0) * 2.2 - 1.2), 1);
  transform:translate3d(0, calc((1 - clamp(0, calc(var(--b4,0) * 2.2 - 1.2), 1)) * .8rem), 0)}
.heroseq-on .heroseq_readout[data-live]{pointer-events:auto}
.heroseq_cert:hover{background:rgba(37,205,238,.22)}

/* ---------- atmosphere + instrument grid ------------------------------
   The grid is the only decoration and it earns its place by carrying the
   trace's own axis: 12 columns across a 1200-unit viewBox is one gridline
   per 100 units of retention time.                                      */
.heroseq-on .heroseq_grid{position:absolute;inset:0;z-index:1;pointer-events:none;
  opacity:calc(var(--b2,0) * .5 * (1 - var(--b4,0) * .6));
  background-image:linear-gradient(90deg,rgba(37,205,238,.14) 1px,transparent 1px);
  background-size:calc(100% / 12) 100%;
  mask-image:linear-gradient(180deg,transparent,#000 45%,#000 85%,transparent)}
.heroseq-on .heroseq_atm{position:absolute;inset:0;z-index:1;pointer-events:none;
  width:100%;height:100%;opacity:.75}

/* ---------- the scroll cue retires once the sequence starts ---------- */
.heroseq-on .hero_scroll{opacity:calc(1 - var(--b1,0) * 2);transition:none}

/* ---------- phones · no pin -------------------------------------------
   Under 900px the hero already stacks the vial above the certificate and
   there is no spare height to travel through, so the track collapses to
   one screen and the stage stops sticking. The engine also skips the
   canvas and the pointer parallax below this width, so a phone runs the
   page it ran yesterday with no extra work per frame.                   */
@media (max-width:1099px){
  .heroseq-on .heroseq .hero{position:static;height:auto;min-height:0}
  .heroseq-on .heroseq{height:auto !important}
  .heroseq-on .hero_copy{opacity:1;transform:none}
  .heroseq-on .hero_vial_img,.heroseq-on .hero_vial_pool{translate:none;scale:1}
  .heroseq-on .heroseq_reticle,.heroseq-on .heroseq_scan,.heroseq-on .heroseq_grid,
  .heroseq-on .heroseq_atm,.heroseq-on .heroseq_readout{display:none}
  .heroseq-on .hero_trace{height:clamp(120px,22vh,210px);opacity:.5}
  .heroseq-on .hero_trace_line{stroke-dashoffset:0;stroke-width:1.4px}
}

/* ---------- motion is a nicety; the assay is not ----------------------
   The audited page only switches off its film grain and its reveals for
   prefers-reduced-motion and leaves the 3D scene running. Here the pin,
   the travel and the canvas are all off and the readout is simply shown
   in its resolved state, so the same facts are on the page either way.  */
@media (prefers-reduced-motion:reduce){
  .heroseq-on .heroseq .hero{position:static;height:auto}
  .heroseq-on .heroseq{height:auto !important}
  .heroseq-on .hero_copy{opacity:1;transform:none}
  .heroseq-on .hero_vial_img,.heroseq-on .hero_vial_pool{translate:none;scale:1}
  .heroseq-on .heroseq_reticle,.heroseq-on .heroseq_scan,
  .heroseq-on .heroseq_atm,.heroseq-on .heroseq_grid{display:none}
  .heroseq-on .hero_trace{height:clamp(120px,22vh,210px);opacity:.5}
  .heroseq-on .hero_trace_line{stroke-dashoffset:0}
  .heroseq-on .heroseq_readout{position:static;opacity:1;transform:none;width:auto;margin-top:2rem;pointer-events:auto}
  .heroseq-on .heroseq_rows div{opacity:1;transform:none}
  .heroseq-on .heroseq_cert{opacity:1;transform:none}
  .heroseq-on .heroseq_fill,.heroseq-on .heroseq_peak{opacity:0}
}
