/* ═══════════════════════════════════════════════════════════════════════════
   bm-analyzeshell-aug1 · a new shell on the REAL /analyze/ page

   ⛔ THE SKIN ROUTE IS DEAD FOR THIS PAGE AND IT WAS MEASURED, NOT ARGUED.
   /analyze/ is a forked Figma React app with 236kB of generated Tailwind, so an
   override sheet reached nothing:

                    nav             text sizes 10-18       h1
      live      cream pill      [10,11,12,13,14,15]      38px
      skinned   forest band  [10,11,12,13,14,15,16]      38px

   Only the nav colour moved. Owner: "its litteraly the same how it was nothing new".

   ⛔ AND THE STANDALONE REBUILD WAS ALSO WRONG: it was a dead lookalike with no
   engine. Owner: "make sure it has bloodmarker.com/analyzer engine and shit".

   So this loads ON the real page. The fork's hero is hidden and a new one takes its
   place, wired to the page's OWN pickFile() / pickCamera() / handleFile(), its own
   #fileInput, and its own scan screen. Every byte of the parse, the unit conversion,
   the thin-panel guard and the /report/ hand-off runs untouched underneath.
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  --sh-canvas:#FBFAF6; --sh-paper:#fff; --sh-ink:#14201B; --sh-mid:#55635C;
  --sh-hair:rgba(20,32,27,.13); --sh-band:#0E3623; --sh-brand:#15803d;
  --sh-tint:#F0FDF4; --sh-low:#B22234;
  --sh-ease:cubic-bezier(.5,0,0,1); --sh-dur:.7s; --sh-r:4px;

  /* ⛔⛔ EVERY REVEAL ON THIS PAGE READS ITS TRAVEL, DURATION AND STAGGER FROM
     THESE THREE, AND FROM NOWHERE ELSE. That is the entire reason they exist:
     reduced motion then becomes three new numbers in one place rather than a
     separate block that turns the page off. Do not hard-code a px or an ms into
     a reveal rule. */
  /* ⚠️ .7s and cubic-bezier(.5,0,0,1) are KILO'S OWN VALUES, read off their computed
     styles, not chosen. Their per-line stagger is 20ms across 24 lines; ours runs
     26ms, near their 20ms: at 45ms a ten-line section took 450ms just to START
     its last line, which is latency stacked on top of the scroll glide and reads as
     lag rather than as sequence. --sh-rise applies only to blocks that cannot be
     line-split; split text travels exactly one line height, by definition. */
  --sh-rise:28px; --sh-rdur:.7s; --sh-stag:26ms; --sh-bstag:55ms; --sh-accd:.5s;
}

/* ⛔⛔⛔ REDUCE THE MOTION. DO NOT REMOVE IT. THIS COST FIVE ROUNDS.
   The owner's Mac has Reduce Motion ON, so the old block here (opacity:1 and
   transform:none, both !important) meant he saw a completely static page while I
   kept re-measuring in a headless browser at the default no-preference and telling
   him it worked. Measured, scrolling the same page:

       reducedMotion = no-preference   16 distinct opacity states
       reducedMotion = reduce           1 state — everything already at rest

   ⚠️ And kilo.co, the reference he keeps sending, animates IDENTICALLY under both
   settings (motion in 7 of 26 scroll frames either way) — it ignores the flag
   outright. So on his screen kilo moved and this page was frozen.

   The spec asks for REDUCED motion, not for no motion. Short travel, quick curve,
   tight stagger: the reveal still happens, and nobody gets 44px of lurch. If exact
   kilo parity is ever wanted, delete this block — but that takes the page away from
   readers who set the switch for vestibular reasons, so it is a deliberate call,
   never a default.

   ⚠️ ANY BROWSER CHECK OF THIS PAGE MUST RUN AT reducedMotion:'reduce'. Testing at
   the default is testing a machine nobody is using.

   ⚠️ REVISED once the reveal became a masked LINE reveal: the line timings are now
   left at kilo's full values even under reduce, and only --sh-rise is cut. A line
   travels exactly one line-height and is clipped by its own mask the whole way, so
   there is no large-area movement to soften — the thing the flag exists to prevent
   is not present. What does move freely is the unmasked block slide, so that is what
   gets shortened. Reduce what actually lurches, not everything with a duration. */
@media(prefers-reduced-motion:reduce){
  :root{ --sh-rise:14px; --sh-bstag:60ms; --sh-accd:.3s; }
}

/* ⛔ HIDDEN AT PARSE TIME, not when the script decides. Gating this behind a class
   the JS added after a 9s wait meant the deployed hero was on screen for nine
   seconds first. */
/* ⛔⛔ THE HERO STAYS. IT IS NOT HIDDEN, MOVED, OR CLONED.
   I tried three ways to transplant its animation into a new box: hide and clone
   (React laid it out at zero size, so nothing to copy), wait then build (the
   deployed page was on screen for nine seconds first), park it offscreen (still
   never laid out). All three were solving a problem the owner never set.

   He asked for two things: "i jsut wanted that uplaod window and told you how to
   improve text and shit". So the upload window is left exactly as deployed, with
   its own phone-hover, scan-line, progress-fill and text-pulse running in place,
   and only the WORDS around it change. Nothing to transplant, nothing to race,
   and the animation is real because it was never touched. */
body{ background:var(--sh-canvas); }

/* ── masthead, identical to the blog ── */
nav{
  position:fixed; top:0; left:0; right:0; height:58px; z-index:60;
  display:flex; align-items:center; gap:26px; padding:0 26px;
  background:var(--sh-band) !important; border:0;
  border-bottom:1px solid rgba(255,255,255,.14);
  backdrop-filter:none; -webkit-backdrop-filter:none; box-shadow:none;
}
nav a{ color:rgba(255,255,255,.72) !important; font-size:16px; text-decoration:none; }
nav a:hover{ color:#fff !important; }
nav .logo{ color:#fff !important; font-weight:700 !important; font-size:17px !important;
  margin-right:auto; display:flex; align-items:center; gap:10px; }
nav .logo svg circle{ fill:#fff !important; }
nav .logo svg path{ fill:var(--sh-band) !important; }
nav .logo::after{
  content:"· reads your whole blood test"; font-family:'Overpass Mono',monospace;
  font-size:10.5px; letter-spacing:.09em; text-transform:uppercase;
  color:rgba(255,255,255,.5); font-weight:400;
}
@media(max-width:760px){
  nav{ height:52px; padding:0 15px; gap:14px; }
  nav .logo::after{ display:none; }
}

/* ── the shell's own sections: one idea per screen ── */
.sh{ padding:clamp(64px,12vh,130px) 26px; border-bottom:1px solid var(--sh-hair);
  font:400 18px/1.62 'Satoshi',system-ui,sans-serif; color:var(--sh-ink); }
.sh .in{ max-width:780px; margin:0 auto; }
.sh .eye{ margin:0 0 15px; font-family:'Overpass Mono',monospace; font-size:11px;
  letter-spacing:.14em; text-transform:uppercase; color:var(--sh-mid); }
.sh h1{ margin:0 0 20px; font-size:clamp(34px,7vw,60px); font-weight:700;
  letter-spacing:-.035em; line-height:1.02; text-wrap:balance; color:var(--sh-ink); }
.sh h2{ margin:0 0 18px; font-size:clamp(26px,4.6vw,38px); font-weight:700;
  letter-spacing:-.03em; line-height:1.1; text-wrap:balance; color:var(--sh-ink); }
.sh .lead{ margin:0 0 30px; font-size:20px; line-height:1.5; color:var(--sh-mid); max-width:30em; }
.sh p{ margin:0 0 15px; max-width:34em; }
.sh .micro{ font-size:13px; color:var(--sh-mid); }
#shHero{ padding-top:clamp(56px,10vh,110px); }

/* ── the door: the real drop zone, restyled ── */
.shdrop{ margin-top:6px; border:1.5px dashed rgba(21,128,61,.42); border-radius:var(--sh-r);
  background:var(--sh-paper); padding:clamp(32px,5.5vw,52px) 26px; text-align:center;
  transition:border-color .3s var(--sh-ease), background .3s var(--sh-ease); }
.shdrop.hot{ border-color:var(--sh-brand); background:var(--sh-tint); }
.shdrop b{ display:block; font-size:22px; font-weight:700; letter-spacing:-.02em; margin-bottom:6px; }
.shdrop .sub{ margin:0 auto; max-width:26em; font-size:16px; color:var(--sh-mid); }
.shbtns{ display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:22px; }
.shbtn{ appearance:none; border:0; border-radius:999px; padding:15px 26px;
  font:700 16px/1 'Satoshi',sans-serif; background:var(--sh-brand); color:#fff; cursor:pointer;
  transition:background .3s var(--sh-ease), transform .3s var(--sh-ease); }
.shbtn:hover{ background:#116632; transform:translateY(-1px); }
.shbtn.ghost{ background:var(--sh-paper); color:var(--sh-ink); border:1px solid var(--sh-hair); }

/* ⛔ THE DROP MARK IS THE PAGE'S OWN, not a new drawing. Same blood drop and same
   fill motion the scan screen uses, so the thing you press and the thing that runs
   are visibly one object. */
.shmark{ width:46px; height:60px; margin:0 auto 18px; display:block; }
.shmark .o{ fill:none; stroke:rgba(21,128,61,.34); stroke-width:1.5; }
.shmark .f{ fill:rgba(21,128,61,.5); transform:translateY(34px); }
@keyframes shfill{ 0%{transform:translateY(34px)} 55%{transform:translateY(9px)} 100%{transform:translateY(34px)} }
.shdrop .shmark .f{ animation:shfill 2.6s cubic-bezier(.45,0,.55,1) infinite; }
@media(prefers-reduced-motion:reduce){ .shdrop .shmark .f{ animation:none; transform:translateY(16px); } }

/* ── the combination, DEMONSTRATED ── */
.shcombo{ display:grid; gap:1px; background:var(--sh-hair); border:1px solid var(--sh-hair);
  border-radius:var(--sh-r); overflow:hidden; margin-top:28px; }
.shpane{ background:var(--sh-paper); padding:26px 22px; }
.shpane .k{ font-family:'Overpass Mono',monospace; font-size:11px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--sh-mid); margin:0 0 15px; }
.shrow{ display:flex; justify-content:space-between; align-items:baseline; gap:10px;
  padding:10px 0; border-top:1px solid var(--sh-hair);
  transition:opacity .5s var(--sh-ease), transform .5s var(--sh-ease); }
.shrow:first-of-type{ border-top:0; }
.shrow .n{ font-weight:600; font-size:16px; }
.shrow .v{ font-variant-numeric:tabular-nums; font-weight:700; }
.shrow .v em{ font-style:normal; font-weight:400; color:var(--sh-mid); font-size:13px; margin-left:5px; }
.shflag{ font-family:'Overpass Mono',monospace; font-size:10px; letter-spacing:.08em;
  padding:3px 7px; border-radius:999px; background:#FDF1F2; color:var(--sh-low); }

/* the arithmetic happens on screen */
.shsum{ margin-top:18px; padding-top:16px; border-top:1px solid var(--sh-hair);
  opacity:0; transform:translateY(10px);
  transition:opacity .6s var(--sh-ease) .35s, transform .6s var(--sh-ease) .35s; }
.shcombo.go .shsum{ opacity:1; transform:none; }
.shsum .eq{ font-family:'Overpass Mono',monospace; font-size:13px; color:var(--sh-mid);
  margin:0 0 8px; }
/* ⛔ DIRECT CHILD. `.shsum b` also matched the <b> that opens the explanation
   paragraph, so "It changes which number you are watching." rendered at 38px as a
   display heading wrapped around the body copy. Only the number is the number. */
.shsum>b{ font-size:38px; font-weight:700; letter-spacing:-.035em; font-variant-numeric:tabular-nums;
  line-height:1; }
.shsum>b span{ font-size:16px; font-weight:400; color:var(--sh-mid); letter-spacing:0; }
.shsum .why{ margin:12px 0 0; font-size:16px; line-height:1.5; }
.shcombo .shrow{ opacity:.35; }
.shcombo.go .shrow{ opacity:1; }

/* ── the accordion, in place of the swipeable cards ───────────────────────────
   ⛔ THE CAROUSEL IS GONE AND ITS THREE STRUCTURAL BUGS WENT WITH IT: the shared
   height floor that had to be measured in JS, the action strip pinned to that
   measured foot, and the last card sitting off the right edge of the screen. None
   of them can recur here, because a row is as tall as its own content.

   Read off kilo's `.section-f-cards` at 1440x900, which is what he keeps pointing
   at: header at 64px uppercase in the left half with a 17-word paragraph offset
   three columns right, then FULL-BLEED rows, one open on the accent field, the rest
   collapsed to a single line. 130 words for the whole section.

   ⚠️ FULL BLEED, TEXT STILL ON THE RAIL. `.shacc` cancels the section's 26px gutter
   so the colour field reaches both screen edges, and `.shacc-in` puts the text back
   on exactly the same 780px centred rail as `.sh .in`. Getting only the first half
   of that gives you a wide card, not a band. */
.shacc-s{ padding-bottom:0; border-bottom:0; }

/* header: asymmetric on purpose, the way the reference sets it
   ⛔ `.sh .shacc-t`, TWO classes. `.sh .in{margin:0 auto}` is also two classes and
   this element carries `in`, so a bare `.shacc-t{margin-bottom}` tied on specificity
   and lost on source order — measured gap 0, the green field touching the headline.
   Third time this file has been bitten by the same thing in one session. */
.sh .shacc-t{ display:grid; gap:20px 40px; margin:0 auto clamp(46px,8vh,96px); }
@media(min-width:820px){
  .sh .shacc-t{ grid-template-columns:1fr 300px; align-items:start; }
  .sh .shacc-e{ padding-top:.55em; }
}
/* ⛔ `.sh .shacc-h`, not `.shacc-h`. It is an h2 and `.sh h2` is class+type, which
   outranks a bare class — the same specificity trap that has already bitten `.shsum
   b` and `.shcard p.shact` in this file. Two classes beats it without !important. */
.sh .shacc-h{
  margin:0; text-transform:uppercase; text-wrap:balance;
  font-size:clamp(27px,4vw,46px); line-height:.96; font-weight:500;
  letter-spacing:-.035em; color:var(--sh-ink);
}
.sh .shacc-e{ margin:0; font-size:17px; line-height:1.5; color:var(--sh-mid); }

.shacc{ margin:0 -26px; border-top:1px solid var(--sh-hair); }
.shacc-in{ width:min(780px, calc(100% - 52px)); margin-inline:auto; }

.shacc-i{
  border-bottom:1px solid var(--sh-hair);
  transition:background-color var(--sh-accd) var(--sh-ease),
             border-color var(--sh-accd) var(--sh-ease);
}
/* the one saturated field this section gets, and it moves with the reader */
.shacc-i.open{ background:var(--sh-band); border-bottom-color:var(--sh-band); }
.shacc-i:not(.open):hover{ background:rgba(20,32,27,.035); }

.shacc-b{
  display:block; width:100%; margin:0; padding:0; border:0; background:none;
  font:inherit; color:inherit; text-align:left; cursor:pointer;
}
.shacc-b .shacc-in{ display:flex; align-items:baseline; gap:20px;
  padding-block:clamp(19px,2.4vh,28px); }
.shacc-n{
  flex:0 0 auto; width:clamp(44px,5.6vw,64px); font-style:normal;
  font-family:'Overpass Mono',monospace; font-variant-numeric:tabular-nums;
  font-size:clamp(19px,2.1vw,28px); letter-spacing:-.02em; color:var(--sh-mid);
}
.shacc-b b{
  flex:1 1 auto; font-size:clamp(18px,2.4vw,28px); font-weight:500;
  line-height:1.16; letter-spacing:-.025em; color:var(--sh-ink);
}
.shacc-i.open .shacc-n{ color:rgba(255,255,255,.5); }
.shacc-i.open .shacc-b b{ color:#fff; }

/* ⚠️ TWO PROPERTIES, TWO OWNERS, AND THAT IS THE POINT. `transform` on this mark
   belongs to `.open` (it rotates to a close mark) and `opacity` belongs to `.on`
   (the scroll reveal). If the reveal also set transform they would overwrite each
   other and the mark would snap straight whenever a row scrolled back into view. */
.shacc-x{
  position:relative; flex:0 0 auto; align-self:center; width:13px; height:13px;
  transition:transform var(--sh-accd) var(--sh-ease);
}
.shacc-x::before, .shacc-x::after{
  content:""; position:absolute; background:var(--sh-mid);
  transition:background-color var(--sh-accd) var(--sh-ease);
}
.shacc-x::before{ left:0; right:0; top:6px; height:1px; }
.shacc-x::after{ top:0; bottom:0; left:6px; width:1px; }
.shacc-i.open .shacc-x{ transform:rotate(45deg); }
.shacc-i.open .shacc-x::before, .shacc-i.open .shacc-x::after{ background:rgba(255,255,255,.6); }

/* ⚠️ 0fr to 1fr, NOT a max-height guess. Nothing has to be measured, nothing goes
   stale when the copy or the width changes, and there is no arbitrary number in the
   file waiting to be wrong on somebody's screen. */
.shacc-p{
  display:grid; grid-template-rows:0fr;
  transition:grid-template-rows var(--sh-accd) var(--sh-ease);
}
.shacc-i.open .shacc-p{ grid-template-rows:1fr; }
.shacc-c{ overflow:hidden; }
.shacc-c .shacc-in{ display:block; padding-bottom:clamp(24px,3.2vh,36px);
  padding-left:calc(clamp(44px,5.6vw,64px) + 20px); }
.shacc-d{ display:block; max-width:33em; font-size:17px; line-height:1.55;
  color:rgba(255,255,255,.74); }
/* the one number per row: the science vibe, without the worksheet */
.shacc-f{
  display:inline-block; margin-top:18px; padding:7px 13px;
  font-family:'Overpass Mono',monospace; font-size:11px; letter-spacing:.09em;
  text-transform:uppercase; color:#fff;
  border:1px solid rgba(255,255,255,.3); border-radius:999px;
}
@media(max-width:600px){
  .shacc-c .shacc-in{ padding-left:0; }
  .shacc-b .shacc-in{ gap:14px; }
  .shacc-n{ width:36px; }
  .shacc-d{ font-size:16px; }
}

/* an "in range" flag must not look like a warning */
.shflag.ok{ background:rgba(20,32,27,.05); color:var(--sh-mid); }

/* ── facts ── */
.shfacts{ display:grid; grid-template-columns:repeat(auto-fit,minmax(148px,1fr)); gap:1px;
  background:var(--sh-hair); border:1px solid var(--sh-hair); border-radius:var(--sh-r);
  overflow:hidden; margin-top:30px; }
.shfact{ background:var(--sh-canvas); padding:22px 18px; }
.shfact b{ display:block; font-size:32px; font-weight:700; letter-spacing:-.035em; line-height:1;
  font-variant-numeric:tabular-nums; }
.shfact span{ display:block; margin-top:7px; font-size:13px; color:var(--sh-mid); line-height:1.35; }

/* ── the band ── */
.sh.band{ background:var(--sh-band); border:0; }
.sh.band h2, .sh.band .eye{ color:#fff; }
.sh.band .eye{ color:rgba(255,255,255,.5); }
.sh.band .lead, .sh.band p{ color:rgba(255,255,255,.72); }
.sh.band .shbtn{ background:#fff; color:var(--sh-band); }
.sh.band .fine{ margin-top:24px; padding-top:18px; border-top:1px solid rgba(255,255,255,.16);
  font-size:14px; color:rgba(255,255,255,.5); max-width:40em; }

/* ⛔ THE REVEAL IS DEFINED ONCE, DOWN IN bm-bigfeel-aug1, AND NOWHERE ELSE.
   There used to be a second definition here and a third in a reduced-motion block
   at the bottom of the file. Three copies of the same rule is how the bottom one
   silently won and switched the whole page off. Do not re-add one here. */

/* ── the cloned live widget, given room inside the new box ────────────────────
   ⚠️ It arrives with the fork's absolute positioning, so it needs a sized frame or
   it collapses to nothing. 466px is its own measured height. */
.shwrap{ position:relative; width:100%; max-width:560px; height:466px; margin:0 auto 6px;
  opacity:0; transition:opacity .6s var(--sh-ease); }
.shwrap.ready{ opacity:1; }
.shwrap .shwidget{ position:absolute; inset:0; }
@media(max-width:760px){ .shwrap{ height:390px; } }

/* ── the deployed hero, sitting under the new masthead ────────────────────────
   ⚠️ Its top spacing was built for the old floating pill, which sat over the page
   rather than in it. Against a 58px fixed band the h1 was cut off by 21px on
   desktop and only escaped on mobile because that layout starts lower. */
/* ⛔⛔ NEVER PAD THE HERO TO CLEAR THE NAV. I did, and it cost 32px off a flex
   child: the upload frame went 468px on live to 436px here, which is exactly why
   the green "Pattern:" row started getting sliced in half. I then spent three
   rounds adding min-heights to fix a crop I had caused, and made it worse each
   time (77px -> 167px) because I was fighting the fork's own flex maths.

   ⚠️ DIAGNOSE BY COMPARING TO LIVE FIRST. One measurement — live frame 468 vs
   mockup 436 — would have found this immediately instead of three rounds of
   guessing at overrides.

   The nav is fixed, so the PAGE gets the offset, not a component inside it. */
body{ padding-top:58px; }
@media(max-width:760px){ body{ padding-top:52px; } }

/* ── its buttons, in the system's language ───────────────────────────────────
   Owner: "button design doesnt match the whole design you build". Right: the
   fork ships a full-width 46px slab at 12px radius with a tick icon in it, and
   everything else on the page is now a pill that says what it does and nothing
   more. The tick was decorating a promise rather than reporting a state. */
#premiumHero button[class*="rounded-xl"],
#premiumHero button[class*="w-full"][class*="h-["]{
  border-radius:999px !important;
  height:auto !important;
  padding:16px 30px !important;
  font-size:16px !important;
  font-weight:700 !important;
  width:auto !important;
  min-width:min(340px,100%);
  display:inline-flex !important;
  align-items:center; justify-content:center;
  margin:0 auto;
  box-shadow:none !important;
  transition:background .3s var(--sh-ease), transform .3s var(--sh-ease) !important;
}
#premiumHero button[class*="rounded-xl"]:hover{
  background:#116632 !important; transform:translateY(-1px);
}
/* the tick inside it goes: a button is not a checklist */
#premiumHero button[class*="rounded-xl"] svg{ display:none !important; }
/* and the button needs its own line to be centred rather than stretched */
#premiumHero button[class*="rounded-xl"]{ align-self:center; }

/* ═══════════════════════════════════════════════════════════════════════════
   bm-bigfeel-aug1 · motion you can actually see, and colour that breaks the page

   Owner: "text doesnt appear in animated way like in kilo so we loose this big
   company feeling ... should be all maybe different collors or something ... we are
   trying to build big feeling bro".

   ⚠️ THE ANIMATION WAS ALREADY RUNNING AND THAT WAS THE PROBLEM. Measured: 2 of 3
   blocks fire, 0.7s transitions, all correct. It was a 16px fade, which is so timid
   it does not register as motion at all. Motion that cannot be felt costs the same
   as motion that is absent and buys nothing.

   Kilo's travel is large and slow on one curve. So: 40px of travel, a slight scale,
   and 110ms between siblings instead of 70 so the sequence is legible as a sequence
   rather than a single event.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ⛔⛔ THE MOTION IS TRANSFORM-LED, NOT A FADE, AND THAT IS MEASURED OFF KILO.
   Sampled every kilo.co scroll frame: 0 elements at partial opacity, 1 to 6
   elements mid-transform. They MOVE type at full opacity. We were cross-fading,
   which is why "text doesnt fall or appear" was an accurate description even on
   the frames where our animation was provably running.

   So opacity resolves in 220ms, near the start, and the remaining ~560ms of travel
   happens with the type fully visible. You watch it arrive instead of watching it
   materialise. */
/* ── TEXT: a masked LINE reveal. This is the reference, reverse-engineered ──────
   ⛔ THE FADE WAS NEVER THE POINT AND I SHIPPED TWO VERSIONS OF IT.
   Pulled off kilo.co's own DOM, not inferred from watching it:

       <p class="js-split-l js-split-w" style="--line-total:6">
         <span class="line-h">              overflow:hidden, display:block
           <span class="line" data-index="0">   display:inline-block
                                                transform:translateY(25.9px)
                                                opacity:1  <- NEVER TOUCHED
                                                transition:transform .7s
                                                            cubic-bezier(.5,0,0,1)
                                                transition-delay:.22s + 20ms*index

   25.9px is exactly one line-height, so every line starts fully BELOW its own mask
   and rises out of it. Nothing fades, at all, anywhere. That is why sampling their
   scroll found 0 elements at partial opacity and up to 6 mid-transform, and why
   "text doesnt fall or appear" was the correct description of what we had: a block
   that dissolved instead of lines that arrive.

   ⚠️ The travel is ONE LINE HEIGHT, not a distance I get to choose. Bigger is not
   stronger here; the mask is what makes it read, and a line that starts 44px below
   a 26px mask just appears late. */
:is(.shseq,.shlines,.shacc-i) .line-h{
  display:block; overflow:hidden;
  /* descenders (g, y, p) would be sliced by the mask without this pair */
  padding-bottom:.14em; margin-bottom:-.14em;
}
:is(.shseq,.shlines,.shacc-i) .line{
  display:inline-block; transform:translateY(110%);
  transition:transform var(--sh-rdur) var(--sh-ease);
  transition-delay:calc(60ms + var(--i) * var(--sh-stag));
  will-change:transform;
}
:is(.shseq,.shlines,.shacc-i).on .line{ transform:none; }

/* ── NON-TEXT BLOCKS: transform only, same curve, no cross-fade ──
   The table, the facts grid and the buttons cannot be line-split, so they slide on
   the same curve. A 180ms opacity ramp only stops them showing in their offset
   pre-state; it is not the effect. */
.shseq>*:not(.line-h){ opacity:0; transform:translateY(var(--sh-rise)); }
.shseq.on>*:not(.line-h){
  opacity:1; transform:none;
  transition:opacity .18s linear, transform var(--sh-rdur) var(--sh-ease);
}
/* a split element is a mask stack, so it must never be offset or faded itself */
.shseq>*.issplit{ opacity:1 !important; transform:none !important; }
.shseq.on>*:not(.line-h):nth-child(1){ transition-delay:0ms }
.shseq.on>*:not(.line-h):nth-child(2){ transition-delay:calc(var(--sh-bstag) * 1) }
.shseq.on>*:not(.line-h):nth-child(3){ transition-delay:calc(var(--sh-bstag) * 2) }
.shseq.on>*:not(.line-h):nth-child(4){ transition-delay:calc(var(--sh-bstag) * 3) }
.shseq.on>*:not(.line-h):nth-child(n+5){ transition-delay:calc(var(--sh-bstag) * 4) }

/* the big number lands last and lands hard, because it is the point of the section */
.shsum{ transform:translateY(calc(var(--sh-rise) * .6)); }
.shcombo.go .shsum{
  transform:none;
  transition:opacity .22s linear calc(var(--sh-stag) * 4),
             transform var(--sh-rdur) var(--sh-ease) calc(var(--sh-stag) * 4);
}
.shcombo .shrow{ transform:translateY(calc(var(--sh-rise) * .5)); }
.shcombo.go .shrow{ transform:none;
  transition:opacity .22s linear, transform var(--sh-rdur) var(--sh-ease); }
.shcombo.go .shrow:nth-child(2){ transition-delay:calc(var(--sh-stag) * 1) }
.shcombo.go .shrow:nth-child(3){ transition-delay:calc(var(--sh-stag) * 2) }
.shcombo.go .shrow:nth-child(4){ transition-delay:calc(var(--sh-stag) * 3) }

/* ── the accordion's own reveal ───────────────────────────────────────────────
   Owner, about the version before this one: "its not animated in that kilo way,
   when you scroll down these slides are static". Every row is its own split
   container AND its own observer target, so the titles rise out of their masks one
   row after another as you scroll past, and again on the way back up.

   ⛔ EACH ELEMENT HAS EXACTLY ONE OWNER. `on` (the scroll reveal) owns the opacity
   and the offset. `open` (the click) owns the row's background, the panel height
   and the rotation of the close mark. They never both write the same property on
   the same element — the two-owners bug has already cost this file the hero split
   and then `go` on the carousel, and it is not happening a third time.
   ⚠️ Note `.shacc-x` in particular: `on` gives it opacity ONLY, because `open`
   already owns its transform. */
.shacc-i .shacc-n{ opacity:0; transform:translateY(var(--sh-rise)); }
.shacc-i.on .shacc-n{ opacity:1; transform:none;
  transition:opacity .18s linear, transform var(--sh-rdur) var(--sh-ease); }
.shacc-i .shacc-x{ opacity:0; }
.shacc-i.on .shacc-x{ opacity:1; transition:opacity .3s linear .12s; }
.shacc-i .shacc-d, .shacc-i .shacc-f{ opacity:0; transform:translateY(var(--sh-rise)); }
.shacc-i.on .shacc-d, .shacc-i.on .shacc-f{ opacity:1; transform:none;
  transition:opacity .22s linear, transform var(--sh-rdur) var(--sh-ease); }
.shacc-i.on .shacc-d{ transition-delay:calc(var(--sh-stag) * 2); }
.shacc-i.on .shacc-f{ transition-delay:calc(var(--sh-stag) * 4); }

/* ── colour, so the page has chapters instead of one long cream scroll ────────
   ⛔ ONE saturated field, not five. Kilo breaks its page with a single full-bleed
   block; more than one and the effect is gone because nothing stands out any more. */
.sh.band{
  background:var(--sh-band); border:0; color:#fff;
  padding:clamp(84px,15vh,160px) 26px;
}
/* ⛔ !important, AND IT IS NOT OPTIONAL. bloodmarker.com/assets/override.css ships
   `h1, h2, article h1, article h2 { color: var(--bm-ink) !important }`, so this
   headline computed rgb(17,17,17) — near black on the forest field, effectively
   invisible. Caught by putting the accordion and this band in one screenshot; every
   rule below it was already !important for the same reason, this one had been
   missed. Found while building the accordion, not caused by it. */
.sh.band h2{ color:#fff !important; font-size:clamp(30px,5.4vw,46px); }
.sh.band .eye{ color:rgba(255,255,255,.5); }
.sh.band .lead, .sh.band p{ color:rgba(255,255,255,.74); }
.sh.band .shbtn{ background:#fff; color:var(--sh-band); }

/* ── the facts, on the page rather than in a black box ────────────────────────
   ⛔ HE SAID "this color is bad" AND MY OWN RULE, 12 LINES ABOVE, ALREADY SAID WHY:
   "ONE saturated field, not five." I then put a second dark slab 200px from the
   first, in a different dark:

       .sh.band   rgb(14,54,35)   forest
       .shfacts   rgb(20,32,27)   near-black   <- neither the same nor deliberately different

   Two darks that are almost the same read as a mistake, because that is what it was.
   ⚠️ And the reference does not do this at all: kilo's own stat numbers compute to
   `background: rgba(0,0,0,0)` — no panel, no card, numbers sitting on the page with
   the type doing the work. So the slab goes and the forest band is once again the
   only saturated field on the page. */
.shfacts{ background:transparent; border:0; border-top:1px solid var(--sh-hair);
  border-radius:0; gap:0; }
.shfact{ background:transparent; color:var(--sh-ink);
  border-bottom:1px solid var(--sh-hair); padding:20px 22px 20px 0; }
.shfact b{ color:var(--sh-ink); font-size:40px; letter-spacing:-.04em; }
.shfact span{ color:var(--sh-mid); }
@media(min-width:700px){
  /* vertical hairlines between columns, none on the outer edges */
  .shfact + .shfact{ border-left:1px solid var(--sh-hair); padding-left:22px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   bm-onelanguage-aug1 · the hero speaks the same language as the sections

   Owner, on the hero screenshot beside the section under it: "this design and this
   is some sort of mismatch". Correct, and it is two whole systems on one page: the
   hero is the Figma fork (soft 12/16px cards, coloured lozenges, a tick row) and
   everything below it is the editorial skin already approved for the blog (mono
   eyebrows, hairlines, 4px corners). Measured 8 distinct corner radii inside the
   hero alone.

   ⚠️ RESTYLE, NEVER REBUILD. Every node here belongs to the React app, so this only
   changes how it looks. Nothing is removed, reordered or re-parented, and the
   widget's own animations keep running underneath.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⛔⛔ EVERY RULE THAT OVERRIDES THE FORK LIVES IN @layer theme, AND !important
   ALONE IS NOT ENOUGH. THIS IS THE SINGLE MOST EXPENSIVE THING IN THIS FILE.

   /analyze/ is Tailwind v4, which declares `@layer properties, theme, base,
   components, utilities`. Cascade layers INVERT for important declarations: an
   !important inside any layer beats an !important written outside all layers,
   whatever the specificity. So `#premiumHero div[class*="rounded-2xl"]` with
   !important — an id, an attribute and a type selector — lost outright to a bare
   `.rounded-2xl` in the utilities layer. Measured: rule present in the sheet, the
   ONLY matching rule for that property, and still computing to the fork's 16px.

   ⚠️ That is why the earlier note in shell.js concluded "CSS could not win this one"
   and reached for inline styles. It was the wrong conclusion from the right
   observation. CSS wins fine from `theme`, which is declared before `utilities`, and
   earlier layers take priority for important declarations.

   ⚠️ DEBUG NOTE: enumerating document.styleSheets shows only the winning-looking
   rule and hides the competing one, because CSSLayerBlockRule needs its own
   recursion. Check for layers FIRST when an !important override does nothing. */
@layer theme{

/* corners: the fork's soft cards come to the system's 4px */
#premiumHero div[class*="rounded-2xl"],
#premiumHero div[class*="rounded-xl"]{ border-radius:var(--sh-r) !important; }

/* ⛔ THE PILL RADII INSIDE THE WIDGET STAY ROUND. rounded-full is on 24 elements
   and most of them are the progress bars and the drop mark, which are shapes rather
   than containers. Squaring those breaks the animation he asked to keep. */

/* the HIGH / LOW / OPTIMAL flags, in the same mono the rest of the page uses */
#premiumHero span[class*="text-[9px]"][class*="uppercase"]{
  font-family:'Overpass Mono',monospace !important;
  font-size:9.5px !important; letter-spacing:.1em !important;
  border-radius:2px !important; font-weight:400 !important;
}

/* ── hero type, at the reference's proportions ────────────────────────────────
   ⛔ MEASURED, kilo's h1 against ours. He said the hero "takes so much space":

                    kilo        ours
       size         40px        60px    (clamp 34-60)
       line-height  48px 1.2    61px 1.02
       weight       500         700
       column       581px       580px   <- the only thing that already matched

   Their headline is two thirds the size and two weights lighter in the same column
   width. That is the whole difference: ours was shouting, and a 60px/1.02/700
   headline in a 580px column can only break into three cramped lines.

   ⚠️ THE HERO IS A FIXED-HEIGHT FLEX PARENT, so type size here is exactly what stole
   32px from the upload window before. Re-verify the widget floor after any change on
   this block, at every width. */
#premiumHero h1{
  font-size:clamp(30px,3.4vw,42px) !important;
  line-height:1.18 !important;
  font-weight:500 !important;
  letter-spacing:-.022em !important;
  text-wrap:balance;
}
#premiumHero p.shlead{
  font-size:17px !important; line-height:1.5 !important; max-width:34em;
}
/* ⚠️ The tablet band is the tightest: the section is still fixed-height but the
   column is narrow, so the same copy costs more lines. Measured the review card
   4px past the clip at 768 with the desktop leading. Trim the leading here rather
   than lower the widget floor, because the floor is what protects the Pattern row. */
@media(max-width:1023px){
  #premiumHero h1{ line-height:1.1 !important; }
  #premiumHero p.shlead{ font-size:16px !important; line-height:1.42 !important; }
}
/* ⛔ AND THE SAME FOR SHORT WINDOWS, WHICH IS THE AXIS I KEPT FORGETTING.
   The hero is sized in vh, so a 700px-tall window has 200px less to give and every
   pixel of my copy is taken off the upload box. Measured against live at 1280x700,
   child by child:

                        mine   live
       text block        156    136    +20
       CTA                56     46    +10
       upload box        240    268    -28   <- exactly what the two above cost it

   and the Pattern row cropped 41px against live's 10px. Not a new bug so much as my
   design changes never being paid for at this height. Give the 28px back. */
@media(max-height:820px){
  #premiumHero h1{ line-height:1.05 !important; }
  #premiumHero p.shlead{ font-size:15px !important; line-height:1.4 !important; }
}

/* the tick row reads as a spec line, not three sales ticks
   ⛔ SCOPED TO THE TICK ROW BY ITS CONTAINER, NOT BY FONT SIZE. Matching on
   `text-[11.5px]` alone also caught the marker names inside the widget and the
   privacy line, so LDL Cholesterol, Vitamin D, Hemoglobin A1c and Ferritin all
   rendered as UPPERCASE MONO. A size class is not a role. Target the thing. */
#premiumHero div[class*="gap-6"] span[class*="text-[11.5px]"]{
  font-family:'Overpass Mono',monospace !important;
  font-size:10px !important; letter-spacing:.09em; text-transform:uppercase;
  color:var(--sh-mid) !important;
}

/* ⛔ THE REVIEW CARD STAYS. I proposed removing it as template placeholder data and
   the owner corrected me: the reviews are real. So it is restyled into the system,
   not deleted, and no wording inside it is touched. */
#premiumHero div[class*="rounded-xl"][class*="shrink-0"][class*="overflow-hidden"]{
  border-color:var(--sh-hair) !important;
}
#premiumHero div[class*="rounded-xl"][class*="shrink-0"] span[class*="text-[10px]"]{
  font-family:'Overpass Mono',monospace !important; letter-spacing:.07em;
}

}  /* ── end @layer theme ── */

/* ⛔⛔ THE UPLOAD WINDOW GETS A FLOOR, BECAUSE IT IS A flex:1 CHILD OF A
   FIXED-HEIGHT SECTION AND THEREFORE PAYS FOR EVERY WORD ABOVE IT.

   That is the real mechanism behind the cropped green "Pattern:" row, and I got it
   wrong twice by calling it a padding bug. Measured on the ancestor chain at 1136:

       .relative.rounded-2xl (flex:1)   mock 436   live 468    <- 32px gone
       SECTION.lg:w-[58%]               mock 844   live 844    <- identical
       at 768 and below                 mock 298   live 298    <- identical

   The section height never changed. My longer headline and lead simply took 32px
   out of the only child allowed to shrink, and the Pattern row is that child's LAST
   row, so it is always the first thing sliced.

   ⚠️ It does not reproduce at every width, because the Pattern string only wraps to
   two lines at some of them. A single-width "no crop" check is worthless and I
   published one. Verify at 390/430/768/1024/1136/1440 with the row forced to wrap.

   The floor is the live measurement, so the box cannot be squeezed by whatever the
   copy above it does next.

   ⛔ IT IS A FLOOR, NOT A TARGET, AND IT IS DELIBERATELY NOT 468.
   Live measures 468 and the obvious move is to demand exactly that. It is wrong. The
   section is a fixed 844px and every other child is shrink-0:

       15  eyebrow      136  headline + lead      [widget, flex:1]
       56  CTA           17  spec row             100  review card

   so forcing the widget up to 468 pushes the total past 844, and the section is
   lg:overflow-hidden — the review card at the bottom is what would get clipped
   instead. I would have traded one crop for another. The 10px gap to live is my CTA
   being 56px where the fork's is 46px, and that is a deliberate design change.

   452 sits above the 434 that was actually cropping and below the 458 the layout
   settles at, so it never fights the flex maths and only engages if the copy above
   grows again. ⚠️ Raising it means shrinking something else first, not just typing
   a bigger number. That is the mistake this comment exists to prevent.

   ⚠️ !important is required: the fork's own flex-1 rule sets min-height:0 and beat
   the first version of this outright, so it computed to 0px and protected nothing.
   A dead safety net is worse than none, because the next person trusts it.

   ⛔⛔ AND THE FIXED FLOOR IS GONE, BECAUSE IT BECAME THE BUG.
   A `min-height:452px` cannot work here: the section height tracks the VIEWPORT
   (`calc(100vh - …)`), so on a short window the floor holds the box open and shoves
   the CTA and the review card out of a section that clips. He photographed exactly
   that, the buy button sliced in half. Measured, mine against live:

                        section   box   CTA        review
       mine 1280x800      744     452   ok         ⛔ clipped 96
       mine 1280x700      644     452   ⛔ 69       ⛔ clipped 196
       mine 1024x620      620     452   ⛔ 76       ⛔ clipped 204
       LIVE 1280x800      744     368   ok         ok
       LIVE 1280x700      644     268   ok         ok
       LIVE 1024x620      620     244   ok         ok

   ⚠️ Live simply lets the box shrink, and nothing below it is ever clipped. That is
   the correct behaviour and the floor was fighting it.

   ⚠️ I ONLY EVER TESTED AT ONE WINDOW HEIGHT (900). Every width from 390 to 1440
   passed while six viewport heights were broken. On a page whose hero is sized in
   vh, HEIGHT IS A TEST DIMENSION, not a constant.

   The floor is unnecessary now anyway: it was guarding against my old hero copy
   squeezing the box, and that copy is now shorter than the fork's own, so the box
   comes out at or above live's at every size. Verified rather than assumed. */

