@import url('/fonts/fonts.css?v=3');

/* ============================================================
   RECLAIM — design tokens
   Ported from Figma "Reclaim · Color" / "Reclaim · Scale"
   (file yFc9QSmqnTSk30F2ke0WpJ). Fluid values are solved so the
   curve passes exactly through the 390 and 1440 comps.
   ============================================================ */
:root {
  /* surfaces */
  --surface-canvas: #F7F5F2;
  --surface-card:   #FFFFFF;
  --surface-sand:   #EFEBE5;
  --surface-deep:   #1000C3;
  --surface-ink:    #111114;

  /* brand */
  --brand-blue:        #2E1BFF;
  --brand-blue-bright: #4800FF;
  --brand-blue-mid:    #4000FF;
  --brand-periwinkle:  #796DFF;
  --brand-tint:        #F1F0FF;   /* the Reclaim column in the pricing table */

  /* text — muted is darkened from the Figma #8A8A93, which measured 3.14:1 on the
     canvas and failed WCAG AA for normal text. #6A6A74 is 4.91:1. The footer sits on
     ink and needs the opposite correction, hence its own token at 8.00:1. */
  --text-primary:      #111114;
  --text-secondary:    #5A5A63;
  --text-muted:        #6A6A74;
  --text-on-ink:       #A8A8B2;
  --text-on-deep:      #FFFFFF;
  --text-on-deep-soft: #C3BCFF;

  /* lines */
  --line-soft:    #E4E2DE;
  --line-on-deep: #3F33CF;

  /* radii */
  --r-img: 16px;
  --r-card: 20px;
  --r-tile: 24px;
  --r-full: 999px;

  /* type ramp — Inter, sentence case, tight tracking */
  --t-display-xl: clamp(38px, calc(28.343px + 2.4762vw), 64px);
  --t-display-l:  clamp(30px, calc(24.800px + 1.3333vw), 44px);
  --t-display-m:  clamp(24px, calc(21.029px + 0.7619vw), 32px);
  --t-subhead:    clamp(19px, calc(17.886px + 0.2857vw), 22px);
  --t-body-l:     clamp(16px, calc(15.257px + 0.1905vw), 18px);
  --t-body:       16px;
  --t-body-s:     14px;
  --t-eyebrow:    clamp(12px, calc(11.629px + 0.0952vw), 13px);
  --t-mono-data:  clamp(14px, calc(13.629px + 0.0952vw), 15px);
  --t-mono-micro: 11px;
  --t-button:     15px;
  --t-cta:        clamp(46px, calc(31.143px + 3.8095vw), 86px);

  /* rhythm — ONE scale, solved through the same 390 / 1440 comps as the type ramp.
     Read it as a boundary system, not a padding system: adjacent block paddings
     STACK, so the gap a reader perceives between two sections is twice whatever
     each of them carries. Hence --section-pad is deliberately HALF a boundary.

                      390    1440
       --space-2xs      8       8
       --space-xs      12      16
       --space-s       16      24
       --space-m       20      32
       --space-l       28      48
       --space-xl      40      64
       --space-2xl     56      80   <- section padding-block

     The only two boundaries the page is allowed to use:
       major  = 2 x --space-2xl           = 112 / 160
                section <-> section, and content <-> band edge <-> content (80 + 80,
                split by the band's own colour edge)
       minor  = --space-m + --space-xl    =  60 /  96
                below the trust strip only — a rule between two sections, not a section

     The old --section-pad ran 64 -> 112px, so the boundary it produced ran 128 -> 225
     while --content-max had already capped at 1296. That is why the page read as
     over-padded at desktop and fine on mobile: 225px of air against a 1296px measure. */
  --space-2xs: 8px;
  --space-xs:  clamp(12px, calc(10.514px + 0.3810vw), 16px);
  --space-s:   clamp(16px, calc(13.029px + 0.7619vw), 24px);
  --space-m:   clamp(20px, calc(15.543px + 1.1429vw), 32px);
  --space-l:   clamp(28px, calc(20.571px + 1.9048vw), 48px);
  --space-xl:  clamp(40px, calc(31.086px + 2.2857vw), 64px);
  --space-2xl: clamp(56px, calc(47.086px + 2.2857vw), 80px);

  --gutter:      clamp(20px, calc(0.686px + 4.9524vw), 72px);
  --section-pad: var(--space-2xl);
  --content-max: 1296px;

  --ease: cubic-bezier(0.4, 0.14, 0.2, 1);
}

/* ============================================================
   scroll reveal
   ------------------------------------------------------------
   Every rule here is scoped to `.js`, which an inline <head> script adds. That is the
   whole safety design: with scripting off, blocked, or erroring before main.js runs,
   none of these selectors match and the page renders normally. A reveal system that
   hides by default and depends on JS to un-hide can blank an entire page, so it is
   built so the failure mode is "no animation" rather than "no content".

   Only opacity and translate are animated — both composited, so revealing a section
   costs no layout and no repaint. Nothing here changes box size, so there is no CLS.
   ============================================================ */
.js [data-reveal],
.js [data-reveal-stagger] > * {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}
.js [data-reveal].is-in,
.js [data-reveal-stagger].is-in > * { opacity: 1; transform: none; }
/* Stagger is CSS, not JS: the observer only ever toggles one class per group. */
.js [data-reveal-stagger].is-in > :nth-child(2) { transition-delay: .07s; }
.js [data-reveal-stagger].is-in > :nth-child(3) { transition-delay: .14s; }
.js [data-reveal-stagger].is-in > :nth-child(4) { transition-delay: .21s; }
.js [data-reveal-stagger].is-in > :nth-child(5) { transition-delay: .28s; }
.js [data-reveal-stagger].is-in > :nth-child(6) { transition-delay: .35s; }
/* MOTION POLICY under an OS reduce-motion setting — softened, not silenced.
   The original build disabled every effect under prefers-reduced-motion, which made
   the site read as completely broken on any machine where Windows "Animation
   effects" is off or iOS "Reduce Motion" is on — a very common power-user setting,
   and exactly how the client first saw their own site.
   The setting exists for vestibular safety, which is about CONTINUOUS or LARGE
   TRAVELLING motion. So: the looping helix stays a poster and reveals lose their
   travel — but one-shot, sub-5s, small-area effects (fade-ins, the glimmer sweep,
   the pill rotation, the count-up) are kept. WCAG 2.2.2 exempts auto-playing motion
   of five seconds or less. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal], .js [data-reveal-stagger] > * {
    transform: none;                    /* fade only — no vertical travel */
  }
}

/* ============================================================
   base
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--surface-canvas);
  color: var(--text-primary);
  font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; }

:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  background: var(--brand-blue); color: #fff;
  padding: 12px 20px; border-radius: var(--r-full);
  font-size: var(--t-button); font-weight: 500;
  transition: top .18s var(--ease);
}
.skip-link:focus { top: 8px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================================================
   type
   ============================================================ */
.display-xl, .display-l, .display-m, .subhead {
  font-weight: 500; margin: 0; text-wrap: balance;
}
.display-xl { font-size: var(--t-display-xl); line-height: 1.05; letter-spacing: -0.025em; }
.display-l  { font-size: var(--t-display-l);  line-height: 1.10; letter-spacing: -0.02em; }
.display-m  { font-size: var(--t-display-m);  line-height: 1.15; letter-spacing: -0.015em; }
.subhead    { font-size: var(--t-subhead);    line-height: 1.30; letter-spacing: -0.01em; }

.body-l { font-size: var(--t-body-l); line-height: 1.55; margin: 0; text-wrap: pretty; }
.body   { font-size: var(--t-body);   line-height: 1.60; margin: 0; text-wrap: pretty; }
.body-s { font-size: var(--t-body-s); line-height: 1.50; margin: 0; text-wrap: pretty; }

.eyebrow {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-micro);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--brand-blue); margin: 0;
}
.eyebrow--on-deep { color: var(--text-on-deep-soft); }
.mono-data {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-data); font-weight: 500; letter-spacing: 0.02em;
}
/* margin:0 is not cosmetic — .mono-micro is used on <p>, and without it the UA's
   1em margin (11px) was leaking into the page rhythm: it added 11px to the bottom
   of the "Half the cost" section and turned .compare__foot's 10px grid gap into 21. */
.mono-micro {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-micro); letter-spacing: 0.06em; text-transform: uppercase;
  margin: 0;
}
.muted { color: var(--text-secondary); }
.dim   { color: var(--text-muted); }

/* ============================================================
   layout
   ============================================================ */
.section { padding: var(--section-pad) var(--gutter); }
.section--hairline { border-top: 1px solid var(--line-soft); }
.wrap { max-width: var(--content-max); margin-inline: auto; }

/* Stacked, not side-by-side: the note sits UNDER the heading (client call — the
   side-by-side pushed subheads into a right column that read as disconnected).
   Notes are capped at two sentences editorially and 62ch physically. */
.section-head { display: block; margin-bottom: var(--space-l); }
.section-head__note { max-width: 62ch; margin-top: var(--space-s); }
.section-head .eyebrow { margin-bottom: var(--space-xs); }
.section-head--center { text-align: center; }
.tile__icon { width: 64px; height: 64px; object-fit: contain; margin-bottom: var(--space-xs); }
.section-head--center .section-head__note { margin-inline: auto; }

/* 2x2 for the four treatments — bento density rather than a 4-across strip, which
   would shrink each card below the point where the tablet render reads. */
/* One row of four (client call). At --content-max that's ~306px per card, which also
   resolves the earlier too-wide complaint without needing a cap. Collapses 4 -> 2 -> 1
   as the cards approach the point where the dose line can't breathe. */
.grid-cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-s); }
@media (max-width: 1100px) { .grid-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 1048px; margin-inline: auto; } }
@media (max-width: 640px) { .grid-cards { grid-template-columns: 1fr; max-width: 560px; } }

/* One gap token across both grids and both breakpoints — --space-s fluids 16 -> 24,
   which is what the old 24px/20px pair was approximating by hand. */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-s); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-s); }
@media (max-width: 1020px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 680px)  { .grid-3, .grid-4 { grid-template-columns: 1fr; } }

/* ============================================================
   buttons
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 52px; padding: 0 28px;
  border-radius: var(--r-full);
  font-size: var(--t-button); font-weight: 500; letter-spacing: -0.01em;
  cursor: pointer; white-space: nowrap;
  transition: background-color .18s var(--ease), color .18s var(--ease),
              transform .18s var(--ease), box-shadow .18s var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn--primary { background: var(--brand-blue); color: #fff; }
.btn--primary:hover { background: var(--brand-blue-mid); box-shadow: 0 8px 24px -8px rgba(46,27,255,.55); }
.btn--secondary { background: var(--surface-card); color: var(--brand-blue); border: 1px solid var(--line-soft); }
.btn--secondary:hover { border-color: var(--brand-blue); }
.btn--on-deep { background: #fff; color: var(--brand-blue); }
.btn--on-deep:hover { background: var(--brand-tint); }
.btn--block { width: 100%; }

/* ============================================================
   announcement + nav
   ============================================================ */
.announce {
  /* Flush full-bleed bar (client call — the inset pill read as detached on mobile).
     margin:0 is load-bearing: this is a <p>, and its UA margin escapes through
     <body> and paints a canvas band above the bar otherwise. */
  margin: 0;
  background: var(--surface-deep); color: var(--text-on-deep);
  text-align: center; padding: 9px var(--gutter);
  font-size: var(--t-body-s);
}
/* eonbio-style behaviour: merged with the page at rest; once scrolled it detaches
   into a floating glassy pill (blur + hairline + shadow, inset from the edges), and
   it HIDES on scroll-down / REVEALS on scroll-up (driven from main.js). All the
   float styles are layout-inert for the page: only the nav's own width and paint
   change, so content never shifts. */
.nav {
  /* Eon structure: links left, logo CENTRED, actions right. 1fr auto 1fr keeps the
     logo optically centred regardless of how the sides differ in width. */
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 24px;
  padding: 0 var(--gutter); height: 84px;
  background: var(--surface-canvas);
  border-bottom: 1px solid var(--line-soft);
  position: sticky; top: 0; z-index: 50;
  transition: transform .35s var(--ease), background-color .3s var(--ease),
              border-radius .3s var(--ease), margin .3s var(--ease),
              box-shadow .3s var(--ease), border-color .3s var(--ease), height .3s var(--ease);
}
.nav.is-float {
  margin-inline: clamp(10px, 2vw, 24px);
  height: 68px;
  transform: translateY(10px);
  background: rgba(247, 245, 242, .78);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-full);
  box-shadow: 0 12px 32px -18px rgba(17, 17, 20, .28);
  padding: 0 clamp(18px, 3vw, 34px);
}
.nav.is-hidden { transform: translateY(-120%); }
/* Same shadow-tree rule as .footer__logo — this one only ever LOOKED right because the
   default fill is black and the nav sits on canvas. Made explicit so a theme change
   can't silently break it. */
.nav__logo { height: 26px; width: auto; fill: var(--text-primary); }
.nav__home { justify-self: center; }
.nav__links { display: flex; gap: 28px; order: -1; }
.nav__links a { font-size: var(--t-button); font-weight: 500; }
.nav__links a:hover { color: var(--brand-blue); }
.nav__actions { display: flex; align-items: center; gap: 14px; justify-self: end; }
.nav__cta { font-size: var(--t-button); font-weight: 500; }
.nav__cta:hover { color: var(--brand-blue); }
/* outlined rounded-square icon buttons, per the Eon reference */
.nav__iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; flex: 0 0 auto;
  border: 1px solid var(--line-soft); border-radius: 12px;
  background: var(--surface-card);
  transition: border-color .2s var(--ease), color .2s var(--ease);
  color: var(--text-primary);
}
.nav__iconbtn:hover { border-color: var(--brand-periwinkle); color: var(--brand-blue); }
.nav__icon { width: 20px; height: 20px; }
.nav__toggle { display: none; }
.nav__icon--close { display: none; }
.nav__toggle[aria-expanded="true"] .nav__icon--menu { display: none; }
.nav__toggle[aria-expanded="true"] .nav__icon--close { display: block; }
@media (max-width: 900px) {
  .nav { height: 64px; gap: 10px; grid-template-columns: auto 1fr auto; }
  .nav__logo { height: 22px; }
  .nav__cta { display: none; }
  .nav__toggle { display: inline-flex; order: -2; }
  .nav__links {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; gap: 0; margin: 0;
    background: var(--surface-canvas);
    border-bottom: 1px solid var(--line-soft);
    padding: 8px var(--gutter) 20px;
  }
  .nav.is-float .nav__links {
    border: 1px solid var(--line-soft); border-radius: var(--r-card);
    margin-top: 8px;
  }
  .nav__links.is-open { display: flex; }
  .nav__links a { padding: 14px 0; border-top: 1px solid var(--line-soft); }
}

/* ============================================================
   hero
   ============================================================ */
/* Deliberately NOT inside .wrap: a max-width wrapper can't put anything against the
   viewport's left edge, and the helix has to bleed off it. The copy column
   re-establishes the page grid with its own margin instead.

   Fold budget: the announce bar and the sticky nav are both on screen at scrollTop 0,
   so both come out of the height. --hero-reveal deliberately leaves the top of the
   trust strip visible below the fold — that hairline IS the scroll cue, which is why
   there's no chevron (a bouncing arrow is a loud-DTC tell; this brand isn't that). */
/* --hero-reveal was 76px, sized when the strip below was a single line of text. The
   strip is now 203px of glass-icon cards, so 76 put the fold through the middle of
   them and cropped four icons. 46 lands it on the cards' rounded tops instead — still
   unmistakably "the page continues", without slicing artwork in half. */
:root { --nav-h: 88px; --announce-h: 43px; --hero-reveal: 46px; }
@media (max-width: 900px) { :root { --nav-h: 64px; } }

.hero {
  display: grid;
  grid-template-columns: clamp(320px, 44vw, 660px) minmax(0, 1fr);
  /* The cap lives INSIDE min-height via min(), not in a separate max-height.
     CSS applies max-height first and min-height second, so min-height always wins a
     conflict — `min-height: 100svh; max-height: 900px` on a tall window silently
     ignores the cap and the hero grows without limit. That is what made the header
     run to ~1600px on some browsers.
     No max-height at all now: if the copy itself ever exceeds the cap (large text
     settings, long translations) the section SHOULD grow rather than clip. */
  min-height: min(
    calc(100svh - var(--nav-h) - var(--announce-h) - var(--hero-reveal)),
    720px);
  /* Full-bleed on purpose: the render's ground carries a soft vignette (measured
     227,226,222 vs the page's 247,245,242), invisible only while the video runs off
     the viewport edge — capping the section exposed it as a grey rectangle. So the
     hero stays full-width and the CENTRING is done inside: the copy column is
     capped to half the content width and pushed against the centreline, so on wide
     screens copy + helix read as one centred pair while the helix keeps bleeding. */
  max-width: none; margin-inline: 0;
  padding: 0;
  overflow: clip;                 /* the helix runs past its track */
}

.hero__helix { position: relative; min-width: 0; }
/* The video carries a BAKED --surface-canvas ground (see tools/reclaim/encode-helix.sh
   for why it isn't an alpha format), so `cover` is safe: whatever it crops, the
   remaining frame still paints the exact page colour edge to edge. Centre-crop rather
   than edge-anchored because the column's aspect flips either side of the asset's
   0.826 — at 1440 it crops top/bottom, on a narrow tall panel it crops left/right, and
   the helix reads correctly cropped both ways since it already bleeds off its own
   frame edges. */
.hero__helix-media {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  max-width: none;                /* <video> isn't covered by the global img,svg reset */
  display: block;
  object-fit: cover;
  object-position: 50% 50%;
  pointer-events: none;
}

/* The two hero scenes stack in the same box; the vial one fades in on the
   "sex drive." phase using the identical 320ms/--ease as the headline word. */
.hero__vial-media { opacity: 0; transition: opacity .32s var(--ease); z-index: 1; }
/* The vial is a finite object with a cap and a base, unlike the helix, which is designed to bleed
   off the frame. `cover` cropped both ends on wide, short desktop windows (client, 2026-09-21), so
   the vial scene fits INSIDE its box instead. Its render carries the canvas colour as a baked
   ground and the element paints that same colour in the letterbox, so the frozen helix beneath is
   fully covered during the vial phase and the fit is invisible at any window shape. The compound
   selector outranks the mobile `object-fit: cover` rule further down. */
.hero__helix-media.hero__vial-media { object-fit: contain; background: var(--surface-canvas); }
.hero.is-ed .hero__vial-media { opacity: 1; }

.hero__copy {
  align-self: center; justify-self: start;
  display: grid; gap: 28px; justify-items: start;
  width: min(100%, 560px);
  margin-inline: clamp(28px, 4.4vw, 80px) var(--gutter);
  padding-block: 56px;
}
/* On wide screens the copy column starts at the viewport centre (the grid's first
   track is 44vw), so the copy is anchored just right of centre with the helix
   filling the left half — the balanced composition from the design comps. Cap the
   helix track at 50vw so the pair stays symmetric however wide the window goes. */
@media (min-width: 1500px) {
  .hero { grid-template-columns: minmax(0, 50vw) minmax(0, 1fr); }
  .hero__copy { margin-inline: clamp(48px, 5vw, 96px) var(--gutter); }
}
/* The eyebrow is load-bearing, not decoration: a vertically-centred stack with nothing
   above the headline floats, and in a regulated category it puts "prescription" on
   screen before the promise. */
.hero__copy .eyebrow { margin-bottom: -10px; }

/* ── rotating headline word ─────────────────────────────────
   The word crossfades between "hairline." and "ED."; the vial render slides in
   beside the word only during the ED phase. Driven from main.js (which also parks
   it on "hairline." under reduced motion — a forever-cycling headline is looping
   motion). The h1's aria-label carries the stable sentence. */
.hero__swap { display: inline-flex; align-items: center; gap: 0.14em; white-space: nowrap; }
.hero__word { display: inline-block; transition: opacity .32s var(--ease), transform .32s var(--ease); }
.hero__word.is-out { opacity: 0; transform: translateY(0.35em); }
/* Each product is a tiny sprite window (11x11 mini sheet) that SPINS for the whole
   time its word is on screen — pill with "hairline.", vial with "ED." Both stack in
   one box and FADE with exactly the word's transition (same duration, same ease,
   same vertical travel), so the swap reads as one gesture. */
.hero__objbox { position: relative; width: 0.92em; height: 0.92em; flex: 0 0 auto; }
.hero__obj {
  position: absolute; inset: 0;
  overflow: hidden; display: block;
  opacity: 0; transform: translateY(0.35em);
  transition: opacity .32s var(--ease), transform .32s var(--ease);
}
.hero__obj.is-on { opacity: 1; transform: none; }
.hero__obj-sheet {
  position: absolute; left: 0; top: 0;
  width: 1100%; height: 1100%;
  max-width: none; display: block;
}
.hero__ctas { display: flex; gap: 12px; flex-wrap: wrap; }

/* Mobile hero: a full-width helix BAND above the copy — restyled by design after
   both side-by-side variants disappointed (a `contain` ribbon floats in gutters, a
   `cover` ribbon crops most of the frame's width). In a WIDE box the asset gives up
   nothing: it already bleeds off its own top and bottom, so `cover` trims only
   vertically and every tablet stays whole. Copy keeps its full type scale below. */
@media (max-width: 1020px) {
  .hero {
    display: block;
    min-height: 0; max-width: none;
  }
  /* Height derived from WIDTH, not viewport units: svh resolves differently across
     browsers, and whenever the band's aspect dipped below the video's 0.826, cover
     flipped from width-constrained to height-constrained and cropped the spiral's
     sides (the Safari bug). aspect-ratio 1238/1200 keeps the band comfortably wider
     than the video's aspect at every size, so cover ALWAYS shows the full frame
     width and trims only the vertical bleed — identical composition everywhere. */
  .hero__helix { height: auto; aspect-ratio: 1238 / 1200; max-height: 540px; }
  .hero__helix-media { object-fit: cover; object-position: 50% 50%; }
  .hero__copy {
    width: auto; margin-inline: var(--gutter);
    padding-block: var(--space-l) var(--space-s); gap: var(--space-s);
  }
  .hero__ctas .btn { flex: 1 1 auto; }
}
@media (max-width: 680px) {
  /* same width-derived rule as above — no separate phone height */
  .hero__copy { padding-block: var(--space-m) var(--space-2xs); }
  .hero__copy .eyebrow { margin-bottom: -6px; }
  /* two-line lede on phones: the tail sentence is desktop-only colour */
  .hero__lede-more { display: none; }
}

.media-card {
  position: relative; overflow: hidden;
  border-radius: var(--r-tile);
  background: var(--surface-sand);
  min-height: 460px;
  display: flex; align-items: flex-end;
}
.media-card img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.media-card__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,.72) 0%, rgba(0,0,0,.05) 45%, rgba(0,0,0,0) 100%);
}
.media-card__body {
  position: relative; display: flex; align-items: center; gap: var(--space-s);
  padding: var(--space-l); width: 100%; color: #fff;
}
.media-card__body .eyebrow { color: var(--text-on-deep-soft); margin-bottom: 6px; }
.media-card__body > div { flex: 1 1 auto; min-width: 0; }
.media-card__arrow {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: var(--r-full);
  background: #fff; color: var(--brand-blue);
  display: grid; place-items: center; font-size: 20px;
  transition: transform .2s var(--ease);
}
.media-card:hover .media-card__arrow { transform: translateX(4px); }
@media (max-width: 680px) {
  .media-card { min-height: 380px; }
  .media-card__body { padding: var(--space-m); }
  .media-card__arrow { display: none; }
}

/* Leads the #how section since moving out of the hero — full width there, and taller
   than the old 456px-wide tile allowed. */
/* One step below the section boundary on purpose: the card leads INTO this section,
   so it has to read as part of it rather than as its own band. (Was a bespoke
   clamp(40px, 4.4vw, 72px) that resolved to 63.36px — --space-xl is 64.) */
/* ── two-panel: copy left, image right ─────────────────────────
   Replaces the full-width overlaid photo card. The old crop cut the top of his head
   off, which on a hair-loss site is the subject of the photograph. The media panel
   therefore carries the source's native 4:3 rather than a designed height, so `cover`
   is a no-op and NOTHING is cropped — no object-position guesswork to get wrong. */
/* ONE container, not two floating columns (client call — the open version read as
   disconnected). Copy padded on the left, image flush to the right edge; the card's
   own radius clips the image so no inner rounding is needed. The image keeps its
   native 4:3, which sets the card height, and the copy centres against it. */
.split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0; align-items: stretch;
  background: var(--surface-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-tile);
  overflow: hidden;
  margin-bottom: var(--space-xl);
}
.split__copy {
  display: grid; gap: var(--space-s); justify-items: start; align-content: center;
  padding: var(--space-l) var(--space-xl);
}
.split__copy .btn { margin-top: var(--space-2xs); }
.split__media {
  margin: 0;
  aspect-ratio: 4 / 3;                 /* == the source, so cover crops nothing */
  background: var(--surface-sand);
}
.split__media img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  /* Image first on narrow: it is the emotional hook and the copy reads better under
     it than above it once they stack. */
  .split__media { order: -1; }
  .split__copy { padding: var(--space-m); }
}

/* ============================================================
   trust strip
   ============================================================ */
/* NOT a moving marquee, deliberately:
   1. Nothing overflows — four items fit one row to 1240 and a 2x2 below it, so there
      is no hidden content for scrolling to reveal, which is a marquee's only job.
   2. WCAG 2.2.2 Pause/Stop/Hide is Level A and covers anything moving automatically
      for >5s alongside other content. prefers-reduced-motion does NOT satisfy it —
      2.2.2 wants an in-page control, so motion here costs a pause button living in
      the strip forever.
   3. Register — the same call already made against a scroll chevron on the hero.
   The presence asked for is bought with containers, glass renders and a 3x contrast
   jump instead. */
.trust {
  padding: var(--space-m) var(--gutter);
  border-block: 1px solid var(--line-soft);
}
/* .wrap sits on the LIST, not the band, so the hairlines stay full-bleed while the
   cards cap at --content-max and line up with the section heads below. */
/* Centred flex, not a 4-column grid: equal columns each left-aligned their content,
   so the SET sat left of centre (client: "my OCD"). Flex with a centred main axis and
   an even gap centres the group as a whole. */
.trust__list {
  /* margin-inline:auto is what actually centres the 1296px list on WIDE viewports —
     the earlier `margin: 0` (a <ul> reset) beat .wrap's auto margins and pinned the
     list to the left gutter, so at 1970px the whole row sat 265px left of centre. */
  list-style: none; margin: 0 auto; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: var(--space-s) clamp(24px, 4vw, 64px);
}
/* Containers OFF (client call via Loom 2026-08-13): icon + label pairs floating in
   one clean centred row, nothing boxed. */
.trust__item {
  display: flex; align-items: center; justify-content: center; gap: var(--space-xs);
  padding: 0;
}
/* Fixed 88px, never fluid: the glass renders lose their volumetric read below ~88px
   (same floor as .step__art), so the ICON is the constant and the GRID absorbs the
   responsive load — the column count drops before the icon ever does. 88 also sits
   just under .step__art's 98px minimum, so a step icon stays the larger of the two at
   every width and the hierarchy can't invert. */
.trust__icon { flex: 0 0 auto; width: 88px; height: 88px; object-fit: contain; }
/* --text-primary on --surface-card = 18.85:1, against 6.27:1 for the old
   --text-secondary on canvas. The complaint was perceptual weight, not compliance. */
.trust__label {
  color: var(--text-primary);
  font-size: var(--t-body); font-weight: 500;
  line-height: 1.3; letter-spacing: -0.01em;
  text-wrap: balance;
}
/* 4-up needs ~1040px of content width (18+88+12+109+18 per card, widest unbreakable
   word "FDA-approved" at 109px in the shipped Inter); that runs out near 1155px of
   viewport, so flip at 1240 with slack for the fallback face. */
/* Was calc(50% - 32px), which paired the badges two-up — correct for the four
   we had, but the FDA badge came out for the LegitScript review and three items
   wrapped 2 + 1 with a stranded third. Auto keeps them on one line while they
   fit and wraps them evenly when they do not. */
@media (max-width: 1240px) { .trust__item { flex: 0 1 auto; justify-content: center; } }
@media (max-width: 560px) {
  .trust__item { flex-direction: column; align-items: center; text-align: center; gap: 8px; }
}
/* The client's note: "too much padding from the usp marquee and this section".
   The strip is a RULE, not a section — one line of text between two hairlines — so a
   full section boundary under it reads as a hole rather than as breathing room. It
   drops one rung on the ladder: minor boundary = 2 x --space-l = 56 / 96, made of the
   strip's own --space-m below its text plus --space-xl above the next section's first
   element. Was 26 + 112 = 139 at 1440, against a 224 boundary everywhere else. */
.trust + .section { padding-top: var(--space-xl); }
/* Dropping to 14px takes the widest word to 95px, which is what lets the 2-up stay
   horizontal all the way down to 560 instead of needing a third column count. */
@media (max-width: 780px) { .trust__label { font-size: var(--t-body-s); } }

/* ============================================================
   how it works
   ============================================================ */
/* Bento: each step in the same container recipe as .trust__item / .tile / .mech,
   content centred. Gap drops from --space-xl to --space-s — bento cells sit close;
   the breathing room the old open layout needed now lives INSIDE the cards. */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-s); }
.step {
  position: relative;             /* anchors the mobile timeline dot */
  background: var(--surface-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  padding: var(--space-l) var(--space-m);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
/* Glass renders, not line icons, so they run far larger than the 42px a stroke icon
   would take — the optical detail (volumetric absorption, Fresnel edges) is invisible
   below ~88px.
   No tile: these are the blue-throughout renders, whose body measures 5.7-6.2:1 against
   the canvas, so they float unaided. The earlier near-white renders needed a dark tile
   because 81-92% of their area sat below the 3:1 floor. If the art is ever re-generated
   pale, the tile has to come back — check with tools/reclaim/measure-blue.mjs first. */
.step__art {
  position: relative; display: block;
  width: clamp(98px, calc(83.143px + 3.8095vw), 138px);
  aspect-ratio: 1;
  margin-bottom: var(--space-s);  /* moved off .step__icon */
}
.step__icon { width: 100%; height: 100%; object-fit: contain; }

/* ── the cascading glimmer ────────────────────────────────────
   A white duplicate of the icon, revealed through a narrow gradient band that
   travels across it, so the highlight follows the render's own alpha silhouette.
   That matters more than it sounds: these icons sit on the bare canvas with no
   tile, so a box-shaped sweep would show its own corners against #F7F5F2 and read
   as a rectangle sliding over the art.

   `filter` and `mask` are on SEPARATE boxes on purpose — WebKit has long-standing
   bugs when -webkit-mask and filter land on the same element (dropped masks, black
   fills). One extra <span> buys immunity from a whole class of Safari failure.

   The duplicate <img> carries an identical srcset/sizes, so it resolves to the same
   candidate the real icon already fetched and comes straight off the memory cache:
   one network request, zero extra bytes. */
.step__glimmer {
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
  /* Narrow band, soft shoulders. mask-size is 300%, so each 1% of the gradient is 3%
     of the icon — the 44-56% span here is a ~50px sweep on a 138px icon. Widening
     these stops reads as a wash over the whole icon rather than a specular glint. */
  -webkit-mask-image: linear-gradient(76deg, rgba(0,0,0,0) 44%, rgba(0,0,0,.28) 48%, rgba(0,0,0,1) 50%, rgba(0,0,0,.28) 52%, rgba(0,0,0,0) 56%);
          mask-image: linear-gradient(76deg, rgba(0,0,0,0) 44%, rgba(0,0,0,.28) 48%, rgba(0,0,0,1) 50%, rgba(0,0,0,.28) 52%, rgba(0,0,0,0) 56%);
  -webkit-mask-size: 300% 300%;    mask-size: 300% 300%;
  -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
  -webkit-mask-position: 118% 50%; mask-position: 118% 50%;
}
.step__glimmer img {
  display: block; width: 100%; height: 100%; object-fit: contain;
  filter: brightness(0) invert(1);   /* every opaque pixel -> white; alpha untouched */
}

/* Without mask support the gradient is ignored and the opacity keyframes alone would
   flash a solid white silhouette — considerably worse than no effect. Drop the layer
   rather than degrade to that. The glimmer carries no information, so a static icon
   is the correct fallback. */
@supports not ((-webkit-mask-image: linear-gradient(#000,#000)) or (mask-image: linear-gradient(#000,#000))) {
  .step__glimmer { display: none; }
}

/* Linear, deliberately not var(--ease): a strong ease-in-out loiters at both ends —
   which are off the icon and invisible — and races through the middle, which is the
   entire point of the effect. Real speculars travel at constant speed. */
.steps.is-lit .step__glimmer { animation: step-glimmer 900ms linear both; }
.steps.is-lit .step:nth-child(2) .step__glimmer { animation-delay: 200ms; }
.steps.is-lit .step:nth-child(3) .step__glimmer { animation-delay: 400ms; }

/* Replaying on hover needs a SEPARATE keyframes name. After the scroll cascade runs,
   `animation: step-glimmer ... both` stays applied to the element, so re-declaring the
   same animation on :hover is a no-op and nothing replays. A distinct name is a distinct
   animation, so it starts fresh on every pointer entry.
   Hover-capable pointers only — on touch, :hover latches after a tap and the glimmer
   would fire at a random moment and then stick. */
@media (hover: hover) and (pointer: fine) {
  /* Whole .step is the target, not just .step__art — a 138px icon is a mean target, and
     "point at the step, the icon catches the light" is the readable interaction. */
  .step:hover .step__glimmer { animation: step-glimmer-hover 900ms linear both; }
}

@keyframes step-glimmer {
  0%   { opacity: 0;   -webkit-mask-position: 118% 50%; mask-position: 118% 50%; }
  12%  { opacity: .75; }
  82%  { opacity: .75; }
  100% { opacity: 0;   -webkit-mask-position: -18% 50%; mask-position: -18% 50%; }
}
/* Same curve as the cascade so the two triggers read as one effect. */
@keyframes step-glimmer-hover {
  0%   { opacity: 0;   -webkit-mask-position: 118% 50%; mask-position: 118% 50%; }
  12%  { opacity: .75; }
  82%  { opacity: .75; }
  100% { opacity: 0;   -webkit-mask-position: -18% 50%; mask-position: -18% 50%; }
}

.step h3 { margin: 0 0 var(--space-xs); }
/* gap is inherited, not re-declared: --space-xl is already 40px at 390. */
@media (max-width: 900px) { .steps { grid-template-columns: 1fr; } }

/* ── mobile timeline ──────────────────────────────────────────
   Stacked steps get a rail on the left: a hairline with one dot per card. main.js
   keeps .is-active on whichever step is crossing the middle of the viewport; the
   active dot fills brand blue, the card's stroke lifts, and its glimmer re-runs.
   The glimmer rule is AFTER the .is-lit rules in the cascade order, so while a card
   is active it wins the `animation` property; class churn between cards restarts it. */
@media (max-width: 900px) {
  .steps { position: relative; padding-left: 30px; }
  .steps::before {
    content: ''; position: absolute; left: 5px; top: 14px; bottom: 14px;
    width: 2px; border-radius: 1px; background: var(--line-soft);
  }
  .step { transition: border-color .3s var(--ease); }
  .step::before {
    content: ''; position: absolute; left: -30px; top: 50%; translate: 0 -50%;
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--surface-canvas); border: 2px solid var(--line-soft);
    transition: background-color .3s var(--ease), border-color .3s var(--ease), box-shadow .3s var(--ease);
  }
  .step.is-active { border-color: var(--brand-periwinkle); }
  .step.is-active::before {
    background: var(--brand-blue); border-color: var(--brand-blue);
    box-shadow: 0 0 0 4px var(--brand-tint);
  }
  .step.is-active .step__glimmer { animation: step-glimmer 900ms linear both; }
}

/* ============================================================
   treatment cards
   ============================================================ */
/* One card, one surface: the gradient is painted on the CARD, starting saturated at
   the top and dissolving to white before the copy begins — per the client's comp.
   This replaces the old two-part construction (sand media panel over white body),
   whose junction read as "a line in the middle". The dome image is gone entirely. */
.card {
  display: flex; flex-direction: column;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--brand-periwinkle) 58%, #fff) 0%,
      color-mix(in srgb, var(--brand-periwinkle) 20%, #fff) 34%,
      var(--surface-card) 56%);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-tile);
  overflow: hidden;
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease);
}
.card:hover { border-color: #d5d2ff; box-shadow: 0 18px 40px -24px rgba(17,17,20,.25); transform: translateY(-2px); }

.card__media { position: relative; aspect-ratio: 416 / 300; }
.card__pill {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: 55%; aspect-ratio: 1;
  /* A clipping WINDOW. The sheet inside is an <img> moved by transform — NOT a
     background-image. The background-size/position percentage path mis-rendered on
     iOS Safari (huge zoomed fragments) while identical geometry was perfect in
     Chromium; composited transforms on an img are the one sprite mechanism WebKit
     has never got wrong. main.js injects the img and drives it. */
  overflow: hidden;
}
.card__pill-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.card__pill-sheet {
  position: absolute; left: 0; top: 0;
  /* 800% = the 8x8 HD sheets: two per product (frames 0-63 / 64-120), because a
     single 121-frame sheet caps cells at ~370px under iOS's ~16.7MP image-decode
     ceiling — the split lifts cells to 448px, each sheet staying under the limit. */
  width: 800%; height: 800%;
  max-width: none; display: block;
  will-change: transform;
}
.card__body { display: flex; flex-direction: column; gap: var(--space-xs); padding: var(--space-m); flex: 1 1 auto; }
.card__body h3 { margin: 0; }
/* margin:0 is load-bearing. This is a bare <p> and its parent is a FLEX column —
   flex items do not collapse margins, so the UA's 1em sat on top of the container's
   16px gap and produced 32px above and below the dose line where the card design
   asks for 16. Same leak class as .mono-micro and .stat__value. */
.card__dose { margin: 0; display: flex; flex-wrap: nowrap; align-items: baseline; gap: var(--space-2xs); white-space: nowrap; }
/* Ingredients on their own single line, FULL names, never truncated (client call).
   Lists that fit their card sit still; lists that overflow become a rolling marquee
   (main.js measures each and adds .is-marquee). The track is duplicated for a
   seamless loop, and the run pauses on hover so it can be read.
   Soft edge masks hide the wrap seam. */
.card__ingr {
  margin: -4px 0 0; white-space: nowrap; overflow: hidden;
  font-size: var(--t-mono-data);
}
.card__ingr-track { display: inline-block; }
.card__ingr.is-marquee {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.card__ingr.is-marquee .card__ingr-track {
  /* Continuous: the only thing between the end of the list and its repeat is the
     same " · " separator that sits between the ingredients (appended by main.js),
     so the loop reads as one endless list rather than list-gap-list. */
  animation: ingr-roll var(--ingr-dur, 12s) linear infinite;
}
.card:hover .card__ingr.is-marquee .card__ingr-track { animation-play-state: paused; }
@keyframes ingr-roll { to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: reduce) {
  .card__ingr.is-marquee .card__ingr-track { animation-duration: calc(var(--ingr-dur, 12s) * 2); }
}
.card__desc { flex: 1 1 auto; }
.card__rule { height: 1px; background: var(--line-soft); }
.card__foot { display: flex; align-items: center; gap: var(--space-xs); }
.card__foot .price { flex: 1 1 auto; }
/* A glass render, matching the step/USP/trust icon family — not a disc with a glyph. */
.card__arrow {
  width: 44px; height: 44px; object-fit: contain;
  transition: transform .2s var(--ease);
}
.card:hover .card__arrow { transform: translateX(4px); }

/* The rotation is driven by JS (a requestAnimationFrame frame-stepper in main.js),
   not CSS keyframes. The two-axis steps() approach was rebuilt after it broke twice:
   once when a fill-mode parked the sheet on a blank cell, and once when the sheet
   geometry changed under immutable-cached files. The stepper walks the 11x11 sheet
   cell by cell from JS, so there is exactly one source of truth for frame order,
   timing, replay and rest state. */
@media (prefers-reduced-motion: reduce) {
  .card:hover { transform: none; }
  .btn:hover { transform: none; }
  .card:hover .card__arrow { transform: none; }
}

/* ============================================================
   half the cost — comparison table
   ============================================================ */
/* Bento'd: the table lives inside a max-width card, centred. Full content-width gave
   the four rows acres of empty tracking between columns; 880px is enough for the
   longest row plus the two price columns without the dead air. */
.compare-shell {
  max-width: 880px; margin-inline: auto;
  background: var(--surface-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-tile);
  padding: var(--space-l) var(--space-l) var(--space-m);
  overflow: hidden;
}
.compare {
  /* separate, NOT collapse: border-collapse:collapse discards border-radius on cells,
     and the tinted columns need rounded ends. border-spacing puts an even gutter
     between the three columns (the tinted pills were touching); the negative inline
     margins cancel the outer spacing so the table still aligns with the shell. */
  border-collapse: separate; border-spacing: var(--space-s) 0;
  width: calc(100% + 2 * var(--space-s));
  margin-inline: calc(-1 * var(--space-s));
}
/* The tinted column is one visual pill inside the card: round its first and last
   cells with the nested radius (--r-img, per the radii scale). */
.compare tbody tr:first-child .col-us { border-top-left-radius: var(--r-img); border-top-right-radius: var(--r-img); }
.compare tbody tr:last-child .col-us { border-bottom-left-radius: var(--r-img); border-bottom-right-radius: var(--r-img); }
.compare th, .compare td { text-align: left; vertical-align: middle; }
.compare thead th {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-micro); font-weight: 400;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted); padding: 0 var(--space-m) var(--space-s) 0;
}
.compare tbody th, .compare tbody td { padding: var(--space-s) var(--space-m) var(--space-s) 0; border-top: 1px solid var(--line-soft); }
.compare tbody th { font-weight: 400; padding-left: 0; }
.compare .t-name { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: var(--t-mono-data); font-weight: 500; }
.compare .t-sub { color: var(--text-muted); font-size: var(--t-body-s); }
.compare .col-them {
  background: var(--surface-sand);
  padding-left: var(--space-m);
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: var(--t-mono-data);
}
/* The value columns are self-contained pills, but they are still TABLE columns — the
   row rules stay so the grid reads, drawn in a neutral ink-alpha that works on both
   the sand and the tint ground. Only the FIRST row's rule is dropped, so no line cuts
   across the rounded top corners. The vertical hairline dividers go — the containers
   do the column separation. */
.compare tbody .col-them, .compare tbody .col-us {
  border-top: 1px solid rgba(17, 17, 20, .08);
  border-left: 0;
}
.compare tbody tr:first-child .col-them,
.compare tbody tr:first-child .col-us { border-top: 0; }
.compare tbody tr:first-child .col-them { border-top-left-radius: var(--r-img); border-top-right-radius: var(--r-img); }
.compare tbody tr:last-child .col-them { border-bottom-left-radius: var(--r-img); border-bottom-right-radius: var(--r-img); }
.compare .col-us {
  padding-left: var(--space-m);
  background: var(--brand-tint); color: var(--brand-blue);
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: var(--t-mono-data); font-weight: 500;
}
.compare .col-us .t-sub { color: var(--brand-blue); opacity: .8; font-family: Inter, sans-serif; }
.compare thead .col-them, .compare thead .col-us { background: none; border-left: 0; color: var(--text-muted); }
.compare__foot { text-align: center; margin-top: var(--space-l); display: grid; gap: var(--space-2xs); justify-items: center; }

/* stacked cards on narrow screens — a 3-col table is unreadable at 390 */
@media (max-width: 760px) {
  /* The desktop machinery — border-spacing gutters, negative-margin compensation,
     per-corner radii on the tinted columns — must all reset here: on display:block
     elements it turns into stray margins and clipped corners, which is exactly the
     broken phone layout reported 2026-08-13. */
  .compare, .compare tbody, .compare tr, .compare th, .compare td { display: block; width: 100%; }
  .compare { margin-inline: 0; border-spacing: 0; }
  .compare thead { display: none; }
  .compare tbody tr {
    border: 1px solid var(--line-soft); border-radius: var(--r-img);
    overflow: hidden; margin-bottom: var(--space-s);
    padding: var(--space-2xs);
    background: var(--surface-card);
  }
  .compare tbody th { border-top: 0; padding: var(--space-s); }
  /* value cells become inset rounded chips with an even gap; all four corners round,
     overriding the desktop first/last-row radius split */
  .compare tbody td {
    border-top: 0; padding: var(--space-s);
    border-radius: var(--r-img) !important;
    margin-top: var(--space-2xs);
  }
  .compare tbody td::before {
    content: attr(data-label); display: block;
    font-family: 'IBM Plex Mono', ui-monospace, monospace;
    font-size: var(--t-mono-micro); letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted); margin-bottom: 4px;
  }
  .compare .col-them, .compare .col-us { border-left: 0; padding-left: var(--space-s); }
}

/* ============================================================
   science
   ============================================================ */
/* stretch, not start: the photo and the three boxes are one bento row and must share
   a bottom edge. The image DROPS its fixed aspect-ratio — the mech column is the
   height authority and the photo cover-fills whatever that is; with a fixed ratio the
   two columns could never agree. */
.science__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-s); align-items: stretch; }
/* The img is ABSOLUTE inside the figure so it contributes zero intrinsic height to
   the grid track — otherwise the photo's natural height competes with the mech column
   and the taller of the two wins, which is exactly the mismatch being fixed. */
.science__img { position: relative; border-radius: var(--r-tile); overflow: hidden; min-height: 320px; }
.science__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Boxed rather than hairline-separated. Same recipe as .trust__item and .tile — white
   on canvas, 1px --line-soft, --r-card — so the page keeps ONE container vocabulary
   instead of inventing a treatment per section.
   1fr rows: the three boxes split the column height equally, so the row's height is
   set by the column as a whole and every box bottom lines up. */
.mech-list { display: grid; gap: var(--space-s); grid-template-rows: repeat(3, 1fr); }
.mech {
  background: var(--surface-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  padding: var(--space-m);
}
.mech__head { display: flex; align-items: center; gap: var(--space-xs); margin-bottom: var(--space-2xs); }
.mech__head .idx { color: var(--brand-blue); }
.mech h3 { margin: 0; }
@media (max-width: 900px) { .science__row { grid-template-columns: 1fr; } }

/* ============================================================
   full-bleed image bands
   ============================================================ */
.band { position: relative; overflow: hidden; padding: var(--section-pad) var(--gutter); color: #fff; }
.band > img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0;
}
.band__scrim { position: absolute; inset: 0; z-index: 1; }
.band .wrap { position: relative; z-index: 2; }
.band--evidence .band__scrim { background: rgba(0,0,0,.58); }
.band--cta .band__scrim {
  background: color-mix(in srgb, var(--surface-deep) 70%, transparent);
  mix-blend-mode: multiply;
}
.band--cta .band__scrim2 { position: absolute; inset: 0; z-index: 1; background: rgba(5,0,46,.16); }

/* Bento tiles on the dark band: translucent ink cards with hairline borders, so the
   photograph still reads through and the tiles feel cut from the band rather than
   pasted onto it. Same radius family as the light bento cells. */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-s); margin: var(--space-l) 0; }
.stat__label { text-align: left; }
/* Outlined, not filled (client call via Loom follow-up): a white stroke on the
   photo band, no background. */
.stat {
  background: none;
  border: 1px solid rgba(255,255,255,.38);
  border-radius: var(--r-card);
  padding: var(--space-m);
  text-align: left;
}
/* tabular-nums is load-bearing, not typographic taste: the value counts up from 0, and
   proportional digits change width as it climbs, which would shove the label under it
   around for the whole animation. */
/* margin:0 is the biggest single margin leak on the page: this is a bare <p>, so the
   UA's 1em resolves against --t-display-xl and lands at 64px top AND bottom, silently
   inflating every gap in the evidence band. */
.stat__value {
  margin: 0;
  font-size: var(--t-display-xl); font-weight: 500; line-height: 1.05; letter-spacing: -.025em;
  /* tabular-nums is load-bearing, not typographic taste: the value counts up from 0,
     and proportional digits change width as it climbs, which would shove the label
     under it around for the whole animation. */
  font-variant-numeric: tabular-nums;
}
.stat__label { opacity: .82; margin-top: var(--space-xs); }
@media (max-width: 860px) {
  .stats { grid-template-columns: 1fr; gap: var(--space-s); }
  /* Same outlined rounded container as desktop (client call, superseding an earlier
     "bare on mobile" note) — the white stroke/no-fill treatment inherits; only the
     alignment centres. */
  .stat { text-align: center; }
  .stat__label { text-align: center; max-width: 28ch; margin-inline: auto; }
}

/* ── CTA split: one seamless black container ──────────────────
   Same construction as the assessment .split — copy beside media, media on top on
   mobile. The copy half is the video's exact ground (#000, measured from the render
   corners), so there is no visible seam between the halves and the timelapse gets
   its spotlight rather than carrying a headline. */
.cta-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  background: #000; color: var(--text-on-deep);
  border-radius: var(--r-tile); overflow: hidden;
}
.cta-split__copy {
  display: grid; gap: var(--space-m); align-content: center; justify-items: start;
  padding: var(--space-xl);
}
.cta-split__copy p { opacity: .88; }
.cta-split__media { margin: 0; position: relative; min-height: 380px; }
.band__video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 900px) {
  .cta-split { grid-template-columns: 1fr; }
  /* media first — the video is the hook. A 4:3 panel with `contain`: the video
     letterboxes inside it, but the bars are #000 on #000 — invisible — so the panel
     gets real presence while the full frame always shows, uncropped. */
  .cta-split__media { order: -1; min-height: 0; aspect-ratio: 4 / 3; }
  .cta-split__media .band__video { object-fit: contain; }
  .cta-split__copy { padding: var(--space-l) var(--space-m) var(--space-l); }
}

/* ============================================================
   included / faq / isi / footer
   ============================================================ */
.tile {
  background: var(--surface-card); border: 1px solid var(--line-soft);
  border-radius: var(--r-card); padding: var(--space-m); display: grid; gap: var(--space-xs); align-content: start;
}
.tile h3 { margin: 0; }

/* Capped like .compare-shell — full content width left the Q&A rows stretched thin. */
.faq { max-width: 880px; margin-inline: auto; }
.faq__item { border-top: 1px solid var(--line-soft); }
.faq__q {
  width: 100%; display: flex; align-items: center; gap: var(--space-s);
  padding: var(--space-m) 0; cursor: pointer; text-align: left;
}
.faq__q > span:first-child { flex: 1 1 auto; }
.faq__mark { color: var(--brand-blue); font-size: var(--t-subhead); line-height: 1; }
.faq__a { padding: 0 0 var(--space-m); max-width: 860px; }
.faq__item[data-open='false'] .faq__a { display: none; }

/* .isi and .footer sit outside <main> and never had .section, so they carried their
   own hand-set padding (72 / 88) and produced the page's two odd boundaries — 184 and
   160 against 224 everywhere else. They now take --space-2xl like every section. */
.isi { background: var(--surface-sand); padding: var(--space-2xl) var(--gutter); }
/* Bento'd: each drug's safety copy in its own card. White-on-sand keeps the cards in
   the page's one container recipe while the band still reads as a distinct zone. */
/* Was a fixed 1fr 1fr for two drug entries. The ED actives were added as a third,
   which left it alone on a second row at half width. auto-fit tracks the count so
   adding or removing a drug entry never needs a rule change. */
.isi__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: var(--space-s); margin: var(--space-s) 0; }
.isi__cols > div {
  background: var(--surface-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  padding: var(--space-m);
}
@media (max-width: 860px) { .isi__cols { grid-template-columns: 1fr; } }

.footer { background: var(--surface-ink); color: var(--text-on-ink); padding: var(--space-2xl) var(--gutter) var(--space-xl); }
.footer__top { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: var(--space-l); }
/* fill goes on the ELEMENT, not on a descendant selector. <use> clones the symbol into
   a shadow tree that descendant selectors cannot cross, so `.footer__logo path` matched
   zero nodes and the whole lockup rendered default black — invisible on the ink footer.
   `fill` is an inherited SVG property, so setting it here does reach the shadow content. */
.footer__logo { height: 34px; width: auto; margin-bottom: var(--space-s); fill: #fff; }
.footer .footer-h {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-micro); font-weight: 400; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-on-deep-soft); margin: 0 0 var(--space-xs);
}
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-xs); }
.footer a:hover { color: #fff; }
/* Compliance blocks (LegitScript): two long-form paragraphs between the link
   columns and the legal line. Two columns on desktop, stacked on mobile; the text
   sits at --text-on-ink for legibility on the ink ground (8:1) and drops to a
   comfortable measure so the state list doesn't run edge to edge. */
.footer__compliance {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-l);
  margin-top: var(--space-xl); padding-top: var(--space-l);
  border-top: 1px solid rgba(255,255,255,.12);
}
.footer__block p { margin: 0; line-height: 1.6; }
.footer__block a { text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 860px) { .footer__compliance { grid-template-columns: 1fr; gap: var(--space-m); } }

.footer__legal {
  display: flex; flex-wrap: wrap; gap: var(--space-xs) var(--space-m); align-items: center;
  border-top: 1px solid #2a2a30; margin-top: var(--space-xl); padding-top: var(--space-m);
}
.footer__legal p { margin: 0; flex: 1 1 420px; }
/* LegitScript approval seal: served from their CDN so it always reflects live certification */
.footer__seal { flex: 0 0 auto; display: block; line-height: 0; }
.footer__seal img { width: 73px; height: 79px; }
@media (max-width: 900px) { .footer__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__top { grid-template-columns: 1fr; } }

/* ── evidence band, single figure ──────────────────────────────────────────
   The two trial figures were removed for the LegitScript review
   (Lauren, 2026-08-21). .stats is a hard 3-column grid, so the one
   remaining item — a timeline expectation, not an efficacy statistic — would
   otherwise sit in the left third of a centred band. */
.stats--single { grid-template-columns: minmax(0, 560px); justify-content: center; }
.stats--single .stat { text-align: center; }
.stats--single .stat__label { text-align: center; }

/* ── legal / company pages ─────────────────────────────────────────────────
   /privacy/, /terms/, /telehealth-consent/, /shipping-returns/, /about/,
   /contact/ — generated by tools/reclaim/build-legal-pages.mjs. Long-form
   prose, so the column is capped by MEASURE (ch), not by --content-max: at
   1440 the 1296px wrap would run ~150 characters a line, roughly twice the
   comfortable reading width. */
.legal { padding: var(--section-pad) var(--gutter); }
.legal__head {
  padding-bottom: var(--space-l);
  margin-bottom: var(--space-l);
  border-bottom: 1px solid var(--line-soft);
}
.legal__head h1 { margin: var(--space-2xs) 0 var(--space-s); max-width: 18ch; }
.legal__head .body-l { max-width: 56ch; margin: 0 0 var(--space-m); }
.legal__head .mono-micro { margin: 0; }
.legal__head .eyebrow a { color: inherit; text-decoration: none; }
.legal__head .eyebrow a:hover { text-decoration: underline; }

.legal__body { max-width: 62ch; }
.legal__body h2 {
  font-size: var(--t-subhead); font-weight: 600; letter-spacing: -.01em;
  margin: var(--space-l) 0 var(--space-xs);
}
.legal__body h2:first-child { margin-top: 0; }
.legal__body p,
.legal__body li { font-size: var(--t-body); line-height: 1.65; color: var(--text-secondary); }
.legal__body p { margin: 0 0 var(--space-s); }
.legal__body ul { margin: 0 0 var(--space-s); padding-left: 1.25em; }
.legal__body li { margin-bottom: var(--space-2xs); }
.legal__body strong { color: var(--text-primary); font-weight: 600; }
.legal__body a { color: var(--brand-blue); text-decoration: underline; text-underline-offset: 2px; }

/* The emergency notice has to survive skim-reading — it is the one block on
   these pages a reader may need before they need anything else. */
.legal__callout {
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--brand-blue);
  border-radius: var(--r-img);
  background: var(--surface-card);
  padding: var(--space-s) var(--space-m);
  margin: 0 0 var(--space-m) !important;
}

/* Fields still awaiting client data. Deliberately conspicuous: a support phone
   number that silently reads as blank on a compliance page is worse than one
   that announces it is missing. */
.legal__tbc {
  display: inline-block;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: var(--t-mono-micro);
  text-transform: uppercase; letter-spacing: .06em;
  padding: 2px 8px; border-radius: var(--r-full);
  background: var(--surface-sand); color: var(--text-muted);
  border: 1px dashed var(--line-soft);
}

/* ── pharmacy locations (LegitScript, Lauren 2026-08-24) ───────────────────
   Physical address + phone for each dispensing pharmacy. Spans both compliance
   columns and then splits three ways, so each pharmacy is one scannable unit
   rather than a paragraph a reviewer has to parse. Sits inside
   .footer__compliance on purpose: build-legal-pages.mjs extracts that block, so
   this ships on all seven pages rather than the homepage alone. */
.footer__block--wide { grid-column: 1 / -1; }
.footer__pharmacies {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-m);
}
.footer__pharmacies li {
  font-size: var(--t-body-s); line-height: 1.6; color: var(--text-on-ink);
}
.footer__pharmacies strong {
  display: block; color: #FFF; font-weight: 600; margin-bottom: 2px;
}
/* Tel links are the one thing here a visitor may actually action, so they get
   the affordance; the address around them stays plain. */
.footer__pharmacies a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.footer__pharmacies a:hover { color: #FFF; }
@media (max-width: 860px) { .footer__pharmacies { grid-template-columns: 1fr; gap: var(--space-s); } }

/* ============================================================
   /hard-4x/  — ED product page (tools/reclaim/build-hard4x.mjs)
   Inherits every token above. Layout, copy and type follow the client's Figma redesign
   "Frame 4" (file yFc9QSmqnTSk30F2ke0WpJ, node 60:207, 2026-09-16): hero, photo strip,
   comparison on the logo-mark field, performance band, numbers, graph, product.
   This block is authored in tools/reclaim/hard4x.css and appended here.
   ============================================================ */

/* ── page type: the comp's display face is Neue Haas Grotesk Text Pro 700. That face is
   installed here on a Linotype DESKTOP licence (fsType 4, no web embedding), so the page
   sets the same sizes and tracking in Inter 700 (the site's variable Inter already carries
   the weight) until a web licence exists. Eyebrows move to Space Mono (OFL, self-hosted). */
.h4x .display-xl, .h4x .display-l { font-weight: 700; }
.h4x .eyebrow { font-family: 'Space Mono', ui-monospace, monospace; letter-spacing: .06em; }

/* section rhythm from the comp: 92px above and below the photo strip and the graph, 72px
   around the comparison, heads 12px eyebrow gap, 24px to the dek, 48px to the content */
.h4x-sec--actives, .h4x .h4x-sec#science { padding-block: clamp(56px, calc(35.9px + 3.9vw), 92px); }
.h4x-sec--actives { padding-inline: min(var(--gutter), 64px); }
.h4x-sec--actives .wrap { max-width: 1312px; }
.h4x-sec--compare { padding-block: clamp(48px, calc(39.1px + 2.29vw), 72px); }
.h4x-head { margin-bottom: var(--space-l); }
.h4x-head .eyebrow { margin-bottom: 12px; }
.h4x-head .display-l { max-width: 680px; margin-inline: auto; }
.h4x-head .body-l { max-width: 820px; margin: 24px auto 0; letter-spacing: -.02em; }

/* hero: full-bleed film loop (Figma 57:1965). The video is black-and-white with one brand-blue light. The section fills the first
   screen below the nav (client call 2026-09-16, "more full screen"); cover crops the film to fit. The scrim is the frame's own gradient (solid black at
   the left edge, gone by 46%) PLUS a soft ellipse under the lower copy: the Figma gradient
   alone was drawn over the still, and on the moving loop the lit sheets pass behind the
   body copy and micro-row (measured worst case 3.4:1 and 1.3:1). With the glass pill doing
   part of that work, alpha .45 models body 7.4:1 and micro-on-glass 6.0:1 over the 23s loop. */
.hero--h4x {
  --h4x-hero-ground: #0A0A0D;
  grid-template-columns: minmax(0, 1fr);
  position: relative; isolation: isolate;
  background: var(--h4x-hero-ground);
  min-height: max(600px, calc(100svh - var(--nav-h) - var(--announce-h)));
}
.hero--h4x .hero__helix { position: absolute; inset: 0; z-index: 0; }
/* the couple sit right of centre; bias the crop that way when the box is taller than 16:9 */
.hero--h4x .hero__helix-media { object-position: 62% 40%; }
.hero--h4x .hero__helix::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(48% 46% at 24% 76%, rgba(0, 0, 0, .45) 40%, rgba(0, 0, 0, 0) 100%),
    linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0) 46%);
}
/* comp stack: eyebrow 12 · headline 24 · dek 16 · buttons 16 · trust pill, at x=64 */
.hero--h4x .hero__copy {
  position: relative; z-index: 1; grid-column: 1;
  width: min(100%, 610px); gap: 16px;
  margin-inline: clamp(20px, 4.45vw, 64px) var(--gutter);
}
.hero--h4x .hero__copy .eyebrow { font-size: 14px; margin-bottom: -4px; color: #CCD1FF; }

/* 92px at 1440, 46px at 390; -3px tracking at 92 */
.hero--h4x .display-xl {
  color: #F5F5F5; font-size: clamp(46px, calc(28.91px + 4.381vw), 92px);
  line-height: 1; letter-spacing: -.0326em; margin-bottom: 8px;
}
/* Cycling headline word (sex drive / dominance / desire / strength / endurance / vitality). No glow (client call
   2026-09-16). The word sits on its own line after the <br>, so width changes never reflow the
   first line. */
.hero--h4x .h4x-word {
  display: inline-block;
  transition: opacity .32s var(--ease), transform .32s var(--ease), filter .32s var(--ease);
}
.hero--h4x .h4x-word.is-out { opacity: 0; transform: translateY(.3em); filter: blur(3px); }
@media (prefers-reduced-motion: reduce) { .hero--h4x .h4x-word { transition: none; } }
.hero--h4x .body-l { color: #EFEBE5; max-width: 560px; letter-spacing: -.02em; }
.hero--h4x .h4x-micro { color: #F8F6F4; }
/* Liquid-glass pill under the trust claims (Figma 48:1814: #181818 @ 20%, radius 14,
   10/20 padding, 24 gap, GLASS effect). REST exposes the effect's type but none of its
   parameters, so this is a CSS rendering of it: backdrop blur + a touch of saturation so
   the brand blue survives the frost, the 20% tint, and a 1px rim brighter toward the
   top-left via a masked gradient border. */
.hero--h4x .h4x-micro {
  position: relative;
  width: fit-content; max-width: 100%;
  padding: 10px 20px; gap: 6px 24px;
  border-radius: 14px;
  background: rgba(24, 24, 24, .2);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 10px 30px -14px rgba(0, 0, 0, .6);
}
.hero--h4x .h4x-micro::before,
.h4x-stat::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, .42) 0%, rgba(255, 255, 255, .08) 38%, rgba(255, 255, 255, .06) 62%, rgba(255, 255, 255, .22) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
/* no backdrop-filter support: denser tints keep the glass readable */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero--h4x .h4x-micro { background: rgba(24, 24, 24, .55); }
  .h4x-stat { background: linear-gradient(180deg, rgba(0, 0, 0, .78) 60.5%, rgba(46, 27, 255, .78) 106%); }
}
.hero--h4x .btn--secondary { border-color: #E4E2DE; }
/* trust pill sits between the dek and the buttons (client call 2026-09-16); a little more air before the buttons */
.hero--h4x .hero__ctas { margin-top: 8px; }
@media (min-width: 1021px) and (max-width: 1200px) { .hero--h4x .h4x-micro { background: rgba(24, 24, 24, .5); } }

/* Tablet and phone: the film runs the FULL height of the hero, behind the copy (client
   call 2026-09-16). The copy is pushed down by a reveal gap so the couple show clear at
   the top, and the scrim runs vertically: clear over the couple, darkening behind the copy.
   The faces stay between ~45% and ~75% of the frame's width across the loop, so the window
   is centred near 62% of the frame (72% position on tablets, 66% on phones). */
@media (max-width: 1020px) {
  .hero--h4x { display: block; min-height: 0; }
  .hero--h4x .hero__helix { position: absolute; inset: 0; aspect-ratio: auto; max-height: none; }
  .hero--h4x .hero__helix-media { object-position: 72% 50%; }
  .hero--h4x .hero__helix::after {
    inset: 0;
    background: linear-gradient(180deg, rgba(10, 10, 13, 0) 0%, rgba(10, 10, 13, 0) 20%, rgba(10, 10, 13, .6) 36%, rgba(10, 10, 13, .82) 48%, rgba(10, 10, 13, .88) 100%);
  }
  .hero--h4x .hero__copy { width: auto; margin-inline: var(--gutter); padding-top: clamp(220px, min(58vw, calc(100svh - var(--nav-h) - var(--announce-h) - 460px)), 520px); padding-bottom: var(--space-l); }
}
@media (max-width: 680px) {
  .hero--h4x .hero__helix-media { object-position: 66% 50%; }
  .hero--h4x .hero__copy { padding-top: clamp(250px, 72vw, 360px); }
  .hero--h4x .hero__copy .eyebrow { font-size: 12px; }
  /* One line at every phone width (client call). Every dimension of the row is in ems, so
     its natural width is a constant number of ems (measured in Chromium with Inter: see
     the divisor), and the font scales to the copy column's own width via container units.
     justify-self: stretch is load-bearing: the base hero copy is justify-self: start, which
     Chromium honours in block layout as shrink-to-fit, and inline-size containment makes the
     content contribute nothing, so the column collapsed to 0px wide. */
  .hero--h4x .hero__copy { container-type: inline-size; justify-self: stretch; }
  .hero--h4x .h4x-micro {
    flex-wrap: nowrap; white-space: nowrap; letter-spacing: -0.005em;
    font-size: min(var(--t-body-s), calc((100vw - 2 * var(--gutter) - 17px) / 32.9));
    font-size: min(var(--t-body-s), calc((100cqi - 2px) / 32.9));
    padding: .68em .9em; gap: .5em;
  }
  /* phones: small text-colour dots only BETWEEN the claims (client call) */
  .hero--h4x .h4x-micro li { gap: .5em; }
  .hero--h4x .h4x-micro li::before { content: "\2022"; width: auto; height: auto; border-radius: 0; background: none; color: inherit; flex: none; font-size: .6em; }
  .hero--h4x .h4x-micro li:first-child::before { content: none; }
}

/* hero micro-row: the trust strip's claims, one line, no icons */
.h4x-micro { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px var(--space-s); font-size: var(--t-body-s); color: var(--text-muted); }
.h4x-micro li { display: flex; align-items: center; gap: 8px; }
.h4x-micro li::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--brand-blue); }

/* ── photo strip: the four actives (Figma 60:11). One rounded strip of four touching cards,
   2px blue-to-white seams between them (each card's outside stroke, clipped by the strip),
   a bottom-weighted black gradient under the copy. The photographs are the client's
   Midjourney set; js/h4x-motion.js drifts each one inside its card on scroll. ───────── */
.h4x-cards {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-radius: var(--r-tile); overflow: hidden; isolation: isolate;
}
.h4x-card {
  position: relative; overflow: hidden; aspect-ratio: 328 / 327;
  display: flex; align-items: flex-end; padding: 24px 32px;
  background: var(--surface-ink);
}
/* taller than the card by 30% above and below: the parallax margin (js/h4x-motion.js reads it) */
.h4x-card > img { position: absolute; left: 0; top: -30%; width: 100%; height: 160%; object-fit: cover; z-index: 0; }
.h4x-card::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 32%, rgba(0, 0, 0, .55) 58%, rgba(0, 0, 0, .86) 100%);
}
.h4x-card + .h4x-card::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 2px; z-index: 2;
  background: linear-gradient(180deg, #707CFF, #F7F7F7);
}
.h4x-card__text { position: relative; z-index: 2; display: grid; }
.h4x-card__role {
  margin: 0; color: #CCD1FF;
  font: 400 11px/1.6 'Space Mono', ui-monospace, monospace; letter-spacing: .06em; text-transform: uppercase;
}
.h4x-card__name { margin: 0; color: #fff; font-size: 22px; font-weight: 500; line-height: 1.3; letter-spacing: -.01em; }
.h4x-card__desc { margin: 8px 0 0; color: #DED6CA; font-size: 14px; line-height: 1.5; letter-spacing: -0.5px; text-wrap: pretty; }
@media (max-width: 1020px) {
  .h4x-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .h4x-card:nth-child(odd)::before { display: none; }
  .h4x-card { padding: 20px 24px; }
}
@media (max-width: 560px) {
  .h4x-cards { grid-template-columns: 1fr; }
  .h4x-card { aspect-ratio: 4 / 3; }
  .h4x-card + .h4x-card::before { display: none; }
}

/* ── comparison on the logo-mark field (Figma 60:63) ──────────── */
.h4x-sec--compare { position: relative; isolation: isolate; overflow: hidden; }
/* 15% taller above and below than the section: its parallax margin */
.h4x-pattern { position: absolute; left: 0; top: -15%; z-index: -1; width: 100%; height: 130%; display: block; pointer-events: none; }
.h4x-compare-shell {
  max-width: 976px; margin: 0 auto;
  background: var(--surface-card); border: 1px solid var(--line-soft); border-radius: var(--r-tile);
  padding: 32px 48px; overflow-x: auto;
}
.h4x-compare { width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; min-width: 640px; }
.h4x-compare th, .h4x-compare td {
  height: 70px; padding: 0 24px; text-align: left; vertical-align: middle;
  border-top: 1px solid var(--line-soft);
}
.h4x-compare th:first-child { padding-left: 0; }
.h4x-compare thead th {
  border-top: 0;
  font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 11px; font-weight: 400;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.h4x-compare tbody th { font-size: 14px; font-weight: 500; color: var(--text-primary); }
.h4x-compare td { font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 15px; color: var(--text-secondary); }
/* the Hard 4x column: tint, a hairline of brand blue at 12% down both sides, rounded ends */
.h4x-compare .is-us {
  background: var(--brand-tint); color: var(--brand-blue); font-weight: 500;
  box-shadow: inset 1px 0 rgba(46, 27, 255, .12), inset -1px 0 rgba(46, 27, 255, .12);
}
.h4x-compare thead th.is-us { border-radius: 16px 16px 0 0; box-shadow: inset 1px 1px rgba(46, 27, 255, .12), inset -1px 0 rgba(46, 27, 255, .12); }
.h4x-compare tbody .is-us { border-top-color: rgba(46, 27, 255, .12); }
.h4x-compare tbody tr:last-child .is-us { border-radius: 0 0 16px 16px; box-shadow: inset 1px -1px rgba(46, 27, 255, .12), inset -1px 0 rgba(46, 27, 255, .12); }
.h4x-yes { color: var(--brand-blue); font-weight: 600; }
.h4x-no { color: var(--text-muted); }
.h4x-compare__foot {
  margin: 0; padding-top: 24px; color: var(--text-muted);
  font: 400 11px/1.55 'IBM Plex Mono', ui-monospace, monospace;
}
/* CTA under the comparison shell (client call 2026-09-25); above the logo-mark field */
.h4x-compare__cta { position: relative; z-index: 1; display: flex; justify-content: center; margin-top: var(--space-l); }
@media (max-width: 840px) {
  .h4x-compare { table-layout: auto; }
  .h4x-compare td, .h4x-compare thead th { white-space: nowrap; }
  .h4x-compare tbody th, .h4x-compare thead th:first-child { position: sticky; left: 0; z-index: 2; background: var(--surface-card); box-shadow: 1px 0 0 var(--line-soft); }
}
/* the phone switch only exists at 640px and below */
.h4x-vs { display: none; }
/* Phones: a head-to-head. Hard 4x against ONE active, picked with the switch above the table, so
   the table fits the screen: no sideways scroll, no sticky column, no labels squeezed onto three
   lines. Columns: 1 feature · 2 Hard 4x · 3 sildenafil · 4 tadalafil · 5 vardenafil. */
@media (max-width: 640px) {
  .h4x-compare-shell { padding: 16px 16px 20px; overflow: visible; }
  .h4x-vs {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; margin: 0 0 12px;
    padding: 4px; border-radius: var(--r-full); background: var(--surface-canvas); border: 1px solid var(--line-soft);
  }
  .h4x-vs button {
    appearance: none; border: 0; border-radius: var(--r-full); background: none; cursor: pointer;
    height: 36px; padding: 0 6px; font: 500 13px/1 Inter, system-ui, sans-serif; color: var(--text-secondary);
    transition: background-color .25s var(--ease), color .25s var(--ease), box-shadow .25s var(--ease);
  }
  .h4x-vs button[aria-pressed="true"] { background: var(--surface-card); color: var(--brand-blue); box-shadow: 0 1px 3px rgba(17, 17, 20, .12), inset 0 0 0 1px rgba(46, 27, 255, .16); }
  .h4x-vs button:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 2px; }
  .h4x-compare { min-width: 0; table-layout: fixed; }
  .h4x-compare-shell .h4x-compare th, .h4x-compare-shell .h4x-compare td { height: 58px; padding: 0 10px; white-space: normal; }
  .h4x-compare-shell .h4x-compare tbody th, .h4x-compare-shell .h4x-compare thead th:first-child { position: static; box-shadow: none; width: 38%; padding-left: 4px; }
  .h4x-compare-shell .h4x-compare tbody th { font-size: 13px; line-height: 1.3; }
  .h4x-compare-shell .h4x-compare td { font-size: 14px; }
  .h4x-compare-shell .h4x-compare thead th { height: 46px; font-size: 10px; line-height: 1.3; }
  .h4x-compare tr > :nth-child(n+3) { display: none; }
  [data-vs="3"] .h4x-compare tr > :nth-child(3),
  [data-vs="4"] .h4x-compare tr > :nth-child(4),
  [data-vs="5"] .h4x-compare tr > :nth-child(5) { display: table-cell; animation: h4x-vs-in .35s var(--ease) both; }
  .h4x-compare__foot { padding: 16px 4px 0; }
}
@keyframes h4x-vs-in { from { opacity: 0; transform: translate3d(8px, 0, 0); } to { opacity: 1; transform: none; } }

/* ── get started band (Figma 63:369) ──────────────────────────
   The comp's image fill is the graded window still, cropped to its right 72% (imageTransform
   x .278); the web files are pre-cropped to exactly that. Copy starts at x=681 of 1440 and is
   vertically centred; the comp's black gradient darkens the LEFT 45% at 65%. */
.h4x-perform {
  display: grid; align-items: center;
  min-height: clamp(560px, 56.4vw, 812px);
  padding: 50px 0; background: #000;
}
/* bands: 15% parallax margin above and below */
.h4x-perform > img, .h4x-stats > img, .band--close > img { top: -15%; height: 130%; }
.h4x-perform > img { top: -3%; height: 106%; object-position: 50% 50%; }
.h4x-perform .band__scrim { background: linear-gradient(90deg, rgba(0, 0, 0, .65) 0%, rgba(0, 0, 0, 0) 45.5%); }
.h4x-perform::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; z-index: 2;
  background: linear-gradient(90deg, rgba(153, 153, 153, .8), rgba(255, 255, 255, .8));
}
.h4x-perform__copy {
  position: relative; z-index: 2;
  margin: 0 var(--gutter) 0 47.3%; max-width: 695px;
  display: grid; gap: clamp(24px, 3.26vw, 47px); justify-items: start;
}
.h4x-perform__h2 {
  margin: 0; color: #F5F5F5; font-weight: 700; text-wrap: balance;
  font-size: var(--t-display-xl); line-height: 1.05; letter-spacing: -.047em;
}
.h4x-perform .body-l { color: #EFEBE5; max-width: 560px; letter-spacing: -.02em; }
/* Below 1020 the photograph runs the full height of the band behind the copy (client call
   2026-09-16), like the hero on phones. The band is tall enough that the kiss sits above the
   headline; the scrim stays clear over the window and darkens only where the copy sits. */
@media (max-width: 1020px) {
  .h4x-perform { min-height: clamp(700px, 200vw, 960px); align-items: end; padding: 0 0 var(--space-xl); }
  .h4x-perform > img { top: -3%; height: 106%; object-position: 12% 50%; }
  .h4x-perform .band__scrim { background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0) 44%, rgba(0, 0, 0, .7) 62%, rgba(0, 0, 0, .86) 100%); }
  .h4x-perform__copy { margin: 0 var(--gutter); }
}
@media (max-width: 560px) { .h4x-perform > img { object-position: 7% 50%; } }

/* ── in numbers (Figma 63:2007) ───────────────────────────────
   Desaturated still (the comp's saturation -1, pre-baked) with a brand-blue rise from the
   bottom edge; four glass tiles, #000 → #2E1BFF at 44%, radius 25, 32px apart. */
.h4x-stats { padding: 32px; background: #000; }
.h4x-stats > img { object-position: 50% 40.5%; }
.h4x-stats .band__scrim { background: linear-gradient(0deg, rgba(46, 27, 255, .67) 0%, rgba(46, 27, 255, 0) 56.3%); }
.h4x-stats__list {
  position: relative; z-index: 2; list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 64px;
}
.h4x-stat {
  container-type: inline-size;
  position: relative; min-height: 263px; border-radius: 25px; padding: 32px 16px;
  display: grid; align-content: center; justify-items: center; gap: 16px; text-align: center;
  background: linear-gradient(180deg, rgba(0, 0, 0, .44) 60.5%, rgba(46, 27, 255, .44) 106%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
}
.h4x-stat__num {
  margin: 0; color: #E6E6E6; font-weight: 400; white-space: nowrap;
  font-size: min(clamp(44px, calc(20.6px + 5.24vw), 96px), 36cqi); line-height: .8; letter-spacing: -.05em;
}
.h4x-stat__unit { font-size: .667em; margin-left: .1em; }
/* the base reset makes every svg display:block, which dropped the star under the number */
.h4x-stat__star { display: inline-block; width: .5em; height: .5em; margin-left: .1em; fill: currentColor; vertical-align: .08em; }
.h4x-stat__label { margin: 0; max-width: 264px; color: #fff; font-size: 18px; font-weight: 500; line-height: 20px; letter-spacing: -.012em; text-wrap: balance; }
@media (max-width: 1020px) {
  .h4x-stats { padding: var(--space-l) var(--gutter); }
  .h4x-stats__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-s); }
  .h4x-stat { min-height: 220px; }
}
@media (max-width: 560px) {
  .h4x-stats__list { gap: 12px; }
  .h4x-stat { min-height: 176px; padding: 20px 10px; gap: 10px; border-radius: 20px; }
  .h4x-stat__label { font-size: 14px; line-height: 17px; }
}

/* ── the onset / duration graph ───────────────────────────── */
.h4x-graph {
  display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: var(--space-l); align-items: center;
  background: var(--surface-card); border: 1px solid var(--line-soft); border-radius: var(--r-tile);
  padding: var(--space-l);
}
.h4x-graph__plot { min-width: 0; }
.h4x-graph__svg { width: 100%; height: auto; display: block; overflow: visible; }
.h4x-graph__grid line { stroke: var(--line-soft); stroke-width: 1; stroke-dasharray: 3 5; }
.h4x-graph__grid .h4x-graph__base { stroke: var(--text-muted); stroke-dasharray: none; opacity: .45; }
.h4x-graph__ticks text { font: 400 12px Inter, system-ui, sans-serif; fill: var(--text-muted); text-anchor: middle; }
/* Hard 4x is the only coloured line. The single actives are grey dashes — the
   argument is "one at a time" versus "all four", not a brand fight. */
.h4x-graph__line { fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.h4x-graph__line--single { stroke: #A8A8B2; stroke-dasharray: 7 8; }
.h4x-graph__line--h4x { stroke: var(--brand-blue); stroke-width: 5; }
/* Draw-on via a growing clip window rather than stroke-dashoffset, because the
   single-active lines are themselves dashed and the two dasharrays would fight.
   Singles draw first; the Hard 4x line lands last, on its own, slower. */
.h4x-graph__line { clip-path: inset(-10px 100% -10px 0); transition: clip-path 1.5s cubic-bezier(.4,.14,.2,1); }
.h4x-graph__line[data-line="sild"] { transition-delay: .2s; }
.h4x-graph__line[data-line="tada"] { transition-delay: .4s; }
.h4x-graph__line[data-line="h4x"]  { transition-duration: 1.9s; transition-delay: 1.05s; }
.is-drawn .h4x-graph__line { clip-path: inset(-10px 0 -10px 0); }
.h4x-graph__dot--h4x { fill: var(--brand-blue); stroke: #fff; stroke-width: 2.5; opacity: 0; transition: opacity .4s var(--ease); }
.is-drawn .h4x-graph__dot--h4x { opacity: 1; transition-delay: 2.65s; }
/* Pills sit ON their curves (Figma 63:2201): white with a hairline, occluding the dashes
   beneath; Hard 4x's is black-to-blue with a blue-to-grey edge, above its end dot. */
.h4x-graph__label { opacity: 0; transition: opacity .5s var(--ease); }
.h4x-graph__label rect { fill: var(--surface-card); stroke: var(--line-soft); stroke-width: 1; }
.h4x-graph__label text { font: 500 12px Inter, system-ui, sans-serif; fill: var(--text-secondary); text-anchor: middle; }
.h4x-graph__label--h4x rect { fill: url(#h4x-pill-fill); stroke: url(#h4x-pill-edge); }
.h4x-graph__label--h4x text { fill: #fff; }
.is-drawn .h4x-graph__label { opacity: 1; }
.is-drawn .h4x-graph__label[data-for="vard"] { transition-delay: 1.15s; }
.is-drawn .h4x-graph__label[data-for="sild"] { transition-delay: 1.35s; }
.is-drawn .h4x-graph__label[data-for="tada"] { transition-delay: 1.55s; }
.is-drawn .h4x-graph__label[data-for="h4x"]  { transition-delay: 2.7s; }
.h4x-graph__key { display: grid; gap: var(--space-xs); align-content: center; }
.h4x-graph__keyhead { margin: 0; display: flex; align-items: center; gap: 10px; font-weight: 600; }
.h4x-graph__keyhead--single { margin-top: var(--space-s); font-weight: 500; color: var(--text-muted); }
.h4x-graph__swatch { width: 22px; height: 4px; border-radius: 2px; background: #A8A8B2; }
.h4x-graph__swatch--h4x { background: var(--brand-blue); height: 5px; }
.h4x-graph__bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; font-size: var(--t-body-s); color: var(--text-secondary); }
.h4x-graph__bullets li { display: flex; gap: 10px; align-items: flex-start; }
.h4x-graph__bullets li::before { content: ""; flex: 0 0 auto; width: 14px; height: 14px; margin-top: 3px; border-radius: 50%; background: var(--brand-tint); border: 1px solid var(--brand-periwinkle); }
.h4x-graph__key .body-s { margin: 0; }
/* CTA under the graph (client call 2026-09-21: replaces the footnote that sat here) */
.h4x-graph__cta { display: flex; justify-content: center; margin-top: var(--space-l); }
@media (max-width: 1180px) { .h4x-graph { grid-template-columns: 1fr; } }
@media (max-width: 900px) { .h4x-graph { padding: var(--space-m); } }
/* Under ~600px the in-plot pills would be unreadable at viewBox scale, and the key
   already carries every name — so the pills go and the ticks and strokes grow in
   viewBox units to compensate for the scale-down. */
@media (max-width: 600px) {
  .h4x-graph__labels { display: none; }
  .h4x-graph__dot--h4x { r: 10; stroke-width: 4; }
  .h4x-graph__ticks text { font-size: 26px; }
  .h4x-graph__line { stroke-width: 5; } .h4x-graph__line--h4x { stroke-width: 9; }
}

/* ── product tile ─────────────────────────────────────────── */
.h4x-product {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  background: var(--surface-ink); color: var(--text-on-deep);
  border-radius: var(--r-tile); overflow: hidden;
}
.h4x-product__media {
  margin: 0; display: grid; place-items: center; min-height: 400px; padding: var(--space-l);
  background: radial-gradient(ellipse at 50% 62%, rgba(121,109,255,.30), transparent 62%);
}
/* The spinner is the treatment card's player wearing no card: same
   [data-spin] > .card > .card__pill contract main.js drives, with .card's
   gradient, border, hover-lift and shadow zeroed so only the vial shows. */
.h4x-product__card { width: min(100%, 520px); background: none; border: 0; border-radius: 0; box-shadow: none; }
.h4x-product__card:hover { transform: none; box-shadow: none; border-color: transparent; }
.h4x-product__card .card__media { aspect-ratio: 1; }
.h4x-product__card .card__pill { width: 86%; }
.h4x-product__media > .card, .h4x-product__media > .h4x-actmap { grid-area: 1 / 1; }

/* The four actives called out around the vial (client request 2026-09-16, after medvi's QUAD
   list). The map is a square box stacked on the vial card in the same grid cell, so its
   percentage coordinates track the render at every width. Each leader is one SVG polyline
   (underline + connector to a dot on the glass) drawn at a fixed y; the name sits ABOVE that
   line and the role BELOW it, so type size never shifts where a line lands. Font sizes are in
   container units of the square, so the labels scale with the vial. */
.h4x-actmap { position: relative; width: min(100%, 520px); aspect-ratio: 1; container-type: inline-size; pointer-events: none; }
.h4x-actmap__lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.h4x-actmap__lines polyline { fill: none; stroke: rgba(195, 188, 255, .42); stroke-width: .2; }
.h4x-actmap__lines circle { fill: var(--brand-periwinkle); stroke: var(--surface-ink); stroke-width: .5; }
.h4x-actmap__list { list-style: none; margin: 0; padding: 0; }
.h4x-actmap__item { position: absolute; display: block; width: 34%; }
.h4x-actmap__item--l { left: -6%; }
.h4x-actmap__item--r { right: -6%; text-align: right; }
.h4x-actmap__item--apo { top: 13%; }
.h4x-actmap__item--var { top: 30%; }
.h4x-actmap__item--sil { top: 60%; width: 32%; }
.h4x-actmap__item--tad { top: 86%; width: 36%; }
.h4x-actmap__name {
  position: absolute; bottom: 100%; left: 0; right: 0; padding-bottom: .7em;
  display: flex; align-items: center; gap: .6em; white-space: nowrap;
  font: 400 clamp(10px, 2.35cqi, 12px)/1.2 'Space Mono', ui-monospace, monospace;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-on-deep);
}
.h4x-actmap__item--r .h4x-actmap__name { justify-content: flex-end; }
.h4x-actmap__icon { width: 1.5em; height: 1.5em; flex: none; fill: none; stroke: var(--brand-periwinkle); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.h4x-actmap__role { display: block; padding-top: .45em; color: var(--text-on-ink); font-size: clamp(12px, 2.75cqi, 14px); line-height: 1.3; }
/* Stacked panel: no room beside the vial, so the map becomes a 2x2 list under it. */
@media (max-width: 900px) {
  .h4x-product__media > .h4x-actmap { grid-area: auto; }
  .h4x-actmap { position: static; width: 100%; aspect-ratio: auto; container-type: normal; }
  .h4x-actmap__lines { display: none; }
  /* a thin cross divides the four (client call 2026-09-16), fading at its ends; it grows out from the
     centre when the diagram appears */
  .h4x-actmap__list { position: relative; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0; margin-top: 8px; }
  .h4x-actmap__list::before, .h4x-actmap__list::after { content: ""; position: absolute; pointer-events: none; transition: transform 1s cubic-bezier(.65, 0, .25, 1) .2s; }
  .h4x-actmap__list::before { left: 50%; top: 0; bottom: 0; width: 1px; background: linear-gradient(180deg, rgba(195, 188, 255, 0), rgba(195, 188, 255, .38) 30%, rgba(195, 188, 255, .38) 70%, rgba(195, 188, 255, 0)); }
  .h4x-actmap__list::after { top: 50%; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, rgba(195, 188, 255, 0), rgba(195, 188, 255, .38) 22%, rgba(195, 188, 255, .38) 78%, rgba(195, 188, 255, 0)); }
  .js .h4x-actmap__list::before { transform: scaleY(0); }
  .js .h4x-actmap__list::after { transform: scaleX(0); }
  .h4x-actmap.is-mapped .h4x-actmap__list::before, .h4x-actmap.is-mapped .h4x-actmap__list::after { transform: none; }
  .h4x-actmap__item, .h4x-actmap__item--sil, .h4x-actmap__item--tad { position: static; width: auto; text-align: left; padding: 16px 16px 16px 0; }
  /* right column reads from the right, mirroring the desktop diagram */
  .h4x-actmap__item:nth-child(even) { padding: 16px 0 16px 20px; text-align: right; }
  .h4x-actmap__item:nth-child(even) .h4x-actmap__name { justify-content: flex-end; }
  .h4x-actmap__item .h4x-actmap__name { position: static; padding-bottom: 4px; justify-content: flex-start; font-size: 11px; }
  .h4x-actmap__role { padding-top: 0; font-size: 14px; }
}
.h4x-product__copy { display: grid; gap: var(--space-s); align-content: center; justify-items: start; padding: var(--space-xl); }
.h4x-product__copy > * { margin: 0; }
.h4x-product__price { font-size: var(--t-display-xl); line-height: 1; letter-spacing: -.02em; font-weight: 500; }
.h4x-product__from { font-size: var(--t-body); font-weight: 400; opacity: .7; margin-right: .3em; vertical-align: middle; }
/* compare-at price (client call 2026-09-21): struck through beside the current price */
.h4x-product__was { font-size: var(--t-display-m); font-weight: 400; opacity: .55; margin-right: .25em; vertical-align: middle; text-decoration: line-through; text-decoration-thickness: 2px; }
.h4x-product__per { font-size: var(--t-display-m); font-weight: 400; opacity: .7; }
.h4x-product__spec { color: var(--text-on-deep-soft); }
.h4x-product__list { list-style: none; padding: 0; display: grid; gap: 8px; }
.h4x-product__list li { display: flex; gap: 10px; align-items: flex-start; opacity: .92; }
.h4x-product__list li::before {
  content: ""; flex: 0 0 auto; width: 18px; height: 18px; margin-top: 3px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23796DFF' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E") center / contain no-repeat;
}
.h4x-product__note { color: rgba(255, 255, 255, .62); max-width: 42ch; }
@media (max-width: 900px) {
  .h4x-product { grid-template-columns: 1fr; }
  .h4x-product__media { min-height: 0; padding: var(--space-m); background: radial-gradient(ellipse 72% 48% at 50% 36%, rgba(121, 109, 255, .30), rgba(121, 109, 255, 0) 100%); }
  .h4x-product__copy { padding: var(--space-l) var(--space-m); }
}

/* ── closing band ─────────────────────────────────────────── */
.band--h4x { min-height: clamp(440px, 58vw, 760px); display: grid; align-items: end; }
.band--h4x > img { object-position: 50% 40%; }
.band--close { align-items: center; }
.band--close .band__scrim { background: rgba(17,17,20,.55); }
.h4x-close { display: grid; gap: var(--space-m); justify-items: center; text-align: center; }
.h4x-close h2, .h4x-close p { margin: 0; }
.h4x-close p { max-width: 40ch; color: rgba(255, 255, 255, .9); }

.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }


/* ── presentational scrolling (js/h4x-motion.js, client call 2026-09-16) ──
   Lenis's own stylesheet, plus the site-wide html{scroll-behavior:smooth} switched off
   while Lenis drives the scroll: two smoothers stacked fight each other and anchor
   jumps overshoot. Every rule is inert until Lenis adds its classes to <html>. */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent], .lenis [data-lenis-prevent-wheel], .lenis [data-lenis-prevent-touch],
.lenis [data-lenis-prevent-vertical], .lenis [data-lenis-prevent-horizontal] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }
html.lenis.lenis-smooth { scroll-behavior: auto !important; }
/* Scrubbed elements also carry data-reveal, for visitors with reduced motion. With motion
   on, the scrub owns their transform and opacity, so the one-shot fade must not run too:
   its .55s transition would make every scrubbed frame trail the scroll. */
.h4x-motion [data-scrub][data-reveal],
.h4x-motion [data-scrub][data-reveal-stagger] > * { opacity: 1; transform: none; transition: none; }
.h4x-motion .hero--h4x .hero__helix { will-change: transform; }
/* Same reason, for the product panel: it is transform-scrubbed on every scroll tick while it
   clips a rounded, taller-than-viewport box around the vial's WebGL canvas. Un-promoted,
   Chrome re-rasterises that box tile by tile as it travels, and a half-painted tile reads as a
   hard horizontal line across the vial mid-scroll (client, 2026-09-18: "linear wipe on the
   product image"). Promoted, the compositor moves the finished texture instead of repainting it. */
.h4x-motion .h4x-product,
.h4x-motion .h4x-product__media { will-change: transform; }
.h4x-product__card .card__pill-canvas { transform: translateZ(0); }

/* ============================================================
   motion pass (client call 2026-09-16: "all elements beautifully animated")
   Scroll-scrubbed motion lives in js/h4x-motion.js; these are the time-based pieces.
   ============================================================ */

/* hero intro: the film settles in from a slight push, then the copy arrives line by line */
@keyframes h4x-intro { from { opacity: 0; transform: translate3d(0, 28px, 0); filter: blur(8px); } to { opacity: 1; transform: none; filter: none; } }
@keyframes h4x-film-in { from { opacity: 0; transform: scale(1.08); } to { opacity: 1; transform: none; } }
.h4x-motion .hero--h4x .hero__helix-media { animation: h4x-film-in 1.8s cubic-bezier(.2, .7, .1, 1) both; }
.h4x-motion .hero--h4x .hero__copy > * { animation: h4x-intro 1.1s cubic-bezier(.2, .7, .1, 1) both; }
.h4x-motion .hero--h4x .hero__copy > :nth-child(1) { animation-delay: .25s; }
.h4x-motion .hero--h4x .hero__copy > :nth-child(2) { animation-delay: .37s; }
.h4x-motion .hero--h4x .hero__copy > :nth-child(3) { animation-delay: .52s; }
.h4x-motion .hero--h4x .hero__copy > :nth-child(4) { animation-delay: .64s; }
.h4x-motion .hero--h4x .hero__copy > :nth-child(5) { animation-delay: .76s; }

/* the actives diagram arrives only after the vial's one turn (js/h4x-page.js adds .is-mapped
   on main.js's pill:spun). Nothing of it shows during the spin. Then, as a clear second beat:
   the diagram fades up, each dot pops onto the glass, its leader draws out to the label, and
   each label glides in from its own side. */
.js .h4x-actmap { opacity: 0; transition: opacity .9s var(--ease); }
.js .h4x-actmap__lines circle { transform-box: fill-box; transform-origin: center; transform: scale(0); transition: transform .7s cubic-bezier(.3, 1.7, .5, 1); }
.js .h4x-actmap__lines polyline { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.3s cubic-bezier(.65, 0, .25, 1); }
.js .h4x-actmap__item { opacity: 0; transition: opacity 1s var(--ease), transform 1.1s cubic-bezier(.2, .7, .1, 1); }
.js .h4x-actmap__item--l { transform: translate3d(-28px, 0, 0); }
.js .h4x-actmap__item--r { transform: translate3d(28px, 0, 0); }
.h4x-actmap.is-mapped { opacity: 1; }
.h4x-actmap.is-mapped .h4x-actmap__lines circle { transform: scale(1); }
.h4x-actmap.is-mapped .h4x-actmap__lines polyline { stroke-dashoffset: 0; }
.h4x-actmap.is-mapped .h4x-actmap__item { opacity: 1; transform: none; }
.h4x-actmap.is-mapped circle:nth-of-type(1) { transition-delay: .25s; }
.h4x-actmap.is-mapped circle:nth-of-type(2) { transition-delay: .5s; }
.h4x-actmap.is-mapped circle:nth-of-type(3) { transition-delay: .75s; }
.h4x-actmap.is-mapped circle:nth-of-type(4) { transition-delay: 1s; }
.h4x-actmap.is-mapped polyline:nth-of-type(1) { transition-delay: .45s; }
.h4x-actmap.is-mapped polyline:nth-of-type(2) { transition-delay: .7s; }
.h4x-actmap.is-mapped polyline:nth-of-type(3) { transition-delay: .95s; }
.h4x-actmap.is-mapped polyline:nth-of-type(4) { transition-delay: 1.2s; }
.h4x-actmap.is-mapped .h4x-actmap__item:nth-child(1) { transition-delay: 1.1s; }
.h4x-actmap.is-mapped .h4x-actmap__item:nth-child(2) { transition-delay: 1.35s; }
.h4x-actmap.is-mapped .h4x-actmap__item:nth-child(3) { transition-delay: 1.6s; }
.h4x-actmap.is-mapped .h4x-actmap__item:nth-child(4) { transition-delay: 1.85s; }

/* graph key: the legend and bullets follow the lines in */
.js .h4x-graph__key > *, .js .h4x-graph__bullets li { opacity: 0; transform: translate3d(14px, 0, 0); transition: opacity .6s var(--ease), transform .6s var(--ease); }
.is-drawn .h4x-graph__key > *, .is-drawn .h4x-graph__bullets li { opacity: 1; transform: none; }
.is-drawn .h4x-graph__key > :nth-child(1) { transition-delay: .9s; }
.is-drawn .h4x-graph__bullets li:nth-child(1) { transition-delay: 1.05s; }
.is-drawn .h4x-graph__bullets li:nth-child(2) { transition-delay: 1.17s; }
.is-drawn .h4x-graph__bullets li:nth-child(3) { transition-delay: 1.29s; }
.is-drawn .h4x-graph__bullets li:nth-child(4) { transition-delay: 1.41s; }
.is-drawn .h4x-graph__key > :nth-child(3) { transition-delay: 1.6s; }
.is-drawn .h4x-graph__key > :nth-child(4) { transition-delay: 1.72s; }

/* counting numbers keep their width while they climb */
.h4x-count { font-variant-numeric: tabular-nums; }

/* hover depth: photographs lean in, tiles and review cards lift (independent transform
   properties, so they compose with the scroll-driven transforms instead of replacing them) */
.h4x-card > img { transition: scale .9s cubic-bezier(.2, .7, .1, 1); }
.h4x-card:hover > img { scale: 1.05; }
.h4x-review { transition: translate .45s var(--ease), box-shadow .45s var(--ease); }
.h4x-review:hover { translate: 0 -6px; box-shadow: 0 28px 50px -30px rgba(17, 17, 20, .35); }
@media (prefers-reduced-motion: reduce) {
  .h4x-card:hover > img { scale: none; }
  .h4x-review:hover { translate: none; }
}

/* ── reviews ticker (medvi-style) ─────────────────────────── */
.h4x-reviews { padding-block: clamp(56px, calc(35.9px + 3.9vw), 92px); }
/* ghost headlines (reviews, actives): a very light word behind the top edge of a card row, sized
   so the phrase fits the screen, with about 70% of it showing above the cards. --ghost is the
   type size AND the box height (line-height 1), so the spacing math can use it directly. */
.h4x-ghost-host { position: relative; overflow: hidden; isolation: isolate; --ghost: clamp(64px, 13.5vw, 220px); }
.h4x-ghost-host .h4x-head { margin-bottom: calc(var(--ghost) * .7 + 14px); }
.h4x-ghost-stage { position: relative; }
/* on the dark performance band: white at low opacity, above the scrim and under the copy, sitting on
   the band's bottom edge with about 70% showing; on phones it sits just above the headline */
.h4x-perform .h4x-ghost--dark { z-index: 1; top: auto; bottom: calc(var(--ghost) * -.3); translate: -50% 0; color: rgba(255, 255, 255, .075); }
@media (max-width: 1020px) { .h4x-perform .h4x-ghost--dark { bottom: auto; top: 57%; translate: -50% -50%; } }
.h4x-ghost {
  position: absolute; left: 50%; top: calc(var(--ghost) * -.2); z-index: -1; margin: 0; translate: -50% -50%;
  white-space: nowrap; pointer-events: none; user-select: none;
  font-weight: 700; font-size: var(--ghost); line-height: 1; letter-spacing: -.055em;
  color: #EDEAE4;
}
.h4x-marquee {
  position: relative; overflow: hidden; padding-block: 10px 24px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  cursor: grab; touch-action: pan-y;
}
.h4x-marquee.is-dragging { cursor: grabbing; }
.h4x-marquee.is-dragging .h4x-review { pointer-events: none; }
.h4x-marquee__track { list-style: none; margin: 0; padding: 0 var(--gutter); display: flex; gap: 24px; width: max-content; will-change: transform; }
.h4x-review {
  flex: none; width: clamp(280px, 25vw, 360px);
  display: grid; align-content: start; gap: 12px; padding: 28px;
  background: var(--surface-card); border: 1px solid var(--line-soft); border-radius: var(--r-card);
  box-shadow: 0 18px 40px -32px rgba(17, 17, 20, .28);
  user-select: none;
}
.h4x-review__who { display: flex; align-items: center; gap: 12px; }
.h4x-review__avatar {
  width: 44px; height: 44px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--brand-tint); color: var(--brand-blue); border: 1px solid rgba(46, 27, 255, .14);
  font: 400 13px/1 'Space Mono', ui-monospace, monospace; letter-spacing: .04em;
}
.h4x-review__name { margin: 0; font-size: 15px; font-weight: 500; }
.h4x-review__stars { margin: 3px 0 0; display: flex; gap: 2px; }
.h4x-review__stars svg, .h4x-rating__row svg { width: 15px; height: 15px; flex: none; fill: var(--brand-blue); }
/* heading rating: a grey row of five with the brand-blue row laid over it, clipped to the score */
.h4x-rating { margin: 20px 0 0; display: inline-flex; align-items: center; gap: 10px; font-size: var(--t-body-s); color: var(--text-secondary); }
.h4x-rating__stars { position: relative; display: inline-flex; }
.h4x-rating__row { display: inline-flex; gap: 3px; }
.h4x-rating__row svg { width: 20px; height: 20px; fill: #D9D6D0; }
.h4x-rating__row--fill { position: absolute; left: 0; top: 0; overflow: hidden; }
.h4x-rating__row--fill svg { fill: var(--brand-blue); }
.h4x-rating__score { font-weight: 600; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.h4x-review__title { margin: 4px 0 0; font-size: 17px; font-weight: 600; line-height: 1.35; letter-spacing: -.01em; text-wrap: balance; }
.h4x-review__body { margin: 0; font-size: 15px; line-height: 1.6; color: var(--text-secondary); text-wrap: pretty; }
.h4x-reviews__note { margin: var(--space-s) auto 0; padding-inline: var(--gutter); text-align: center; color: var(--text-muted); font: 400 11px/1.55 'IBM Plex Mono', ui-monospace, monospace; }
/* CTA under the reviews (client call 2026-09-21) */
.h4x-reviews__cta { display: flex; justify-content: center; margin-top: var(--space-m); padding-inline: var(--gutter); }
/* the FAQ's safety pointer is a real link now that the page carries no safety block of its own */
.h4x .faq__a a { text-decoration: underline; text-underline-offset: 2px; }
/* reduced motion: a swipeable row instead of a ticker */
.h4x-marquee.is-static { overflow-x: auto; scroll-snap-type: x mandatory; -webkit-mask-image: none; mask-image: none; cursor: auto; }
.h4x-marquee.is-static .h4x-review { scroll-snap-align: start; }
@media (max-width: 560px) {
  .h4x-review { width: 82vw; padding: 22px; }
  .h4x-marquee__track { gap: 14px; }
}
