/* ==========================================================================
   Ascent Labs — design system
   Palette: golden off-white ground, black-and-gold product as the only contrast
   Type: Inter (matches the app and the geometric wordmark)
   Composition: Apple-inspired — one idea per screen, generous air, product-led
   ========================================================================== */

:root {
  /* ground */
  --sand:      #EDE8D9;   /* sampled from the hero footage backdrop */
  --sand-deep: #E4DCC9;
  --cream:     #F7F4EC;
  --paper:     #FCFAF5;

  /* ink */
  --ink:       #1A1815;
  --ink-2:     #46403A;
  --muted:     #6E665C;
  --faint:     #9A9184;

  /* accent — the one gold, used once per surface */
  --gold:      #C9A055;
  --gold-deep: #9D7840;
  --gold-tint: rgba(201, 160, 85, 0.14);

  --line:      rgba(26, 24, 21, 0.10);
  --line-2:    rgba(26, 24, 21, 0.06);

  --ok:        #2F7A4F;
  --danger:    #B3452F;

  /* shape */
  --r-sm: 12px;
  --r:    20px;
  --r-lg: 30px;
  --r-xl: 40px;
  --pill: 999px;

  /* spacious scale */
  --s-1: 8px;  --s-2: 16px; --s-3: 24px; --s-4: 36px;
  --s-5: 56px; --s-6: 88px; --s-7: 128px;

  --shadow-1: 0 1px 2px rgba(26,24,21,.04), 0 8px 24px rgba(26,24,21,.05);
  --shadow-2: 0 2px 6px rgba(26,24,21,.05), 0 24px 60px rgba(26,24,21,.09);

  --ease: cubic-bezier(.22,.61,.36,1);
  --nav-h: 70px;   /* room to breathe around the larger wordmark */
  /* The content gutter, as a token rather than a literal, because the catalog's
     filter row bleeds past it by exactly this much to scroll edge-to-edge */
  --wrap-pad: clamp(24px, 4vw, 48px);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Any element given a display value overrides the UA's [hidden] rule, which
   silently un-hides it. That shipped once with the checkout empty state
   rendering on top of a populated order. Make hidden mean hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--sand);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

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

/* ---------- type scale ---------- */
.eyebrow {
  font-size: 12px; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--gold-deep);
}
h1, h2, h3 { letter-spacing: -.028em; line-height: 1.05; font-weight: 700; }
h1 { font-size: clamp(40px, 6.6vw, 104px); }
h2 { font-size: clamp(30px, 4.4vw, 62px); }
h3 { font-size: clamp(19px, 1.7vw, 25px); letter-spacing: -.02em; line-height: 1.25; }
.lead { font-size: clamp(17px, 1.5vw, 22px); color: var(--muted); line-height: 1.55; max-width: 60ch; }
.small { font-size: 14px; color: var(--muted); }
.tabular { font-variant-numeric: tabular-nums; }

/* ---------- layout ---------- */
.wrap { max-width: 1240px; margin: 0 auto; padding: 0 var(--wrap-pad); }
.sec { padding: var(--s-7) 0; position: relative; }
.sec--cream { background: var(--cream); }
.sec--paper { background: var(--paper); }
.sec--tight { padding: var(--s-6) 0; }
/* Ink, in a page that otherwise alternates cream and off-white. The app is a
   different kind of thing from everything around it, and inserting another
   cream or paper band would have left it reading as one more product row. */
.sec--ink { background: var(--ink); color: rgba(247, 244, 236, .74); }
.sec--ink h2 { color: var(--cream); }
.sec--ink .eyebrow { color: var(--gold); }
.sec--ink .lead { color: rgba(247, 244, 236, .74); }

/* ---- app section ---- */
.app-icon { width: 84px; height: 84px; border-radius: 20px; display: block;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .38); }
.app-points { display: grid; gap: 10px; margin-top: var(--s-2); }
.app-point { display: grid; grid-template-columns: auto 1fr; gap: 10px;
  align-items: start; font-size: 14.5px; line-height: 1.5; }
.app-point svg { width: 17px; height: 17px; margin-top: 2px; color: var(--gold); flex: none; }
.app-cta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: var(--s-3); }
/* Placeholder until the app ships. Apple's badge may only be used for an app
   that is actually on the store, and the artwork has to be their file,
   unmodified — so this is deliberately NOT a lookalike. */
.app-soon { display: inline-flex; align-items: center; gap: 9px;
  padding: 12px 18px; border: 1px solid rgba(247, 244, 236, .26);
  border-radius: var(--pill); font-size: 14px; font-weight: 600;
  color: var(--cream); }
.app-soon::before { content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--gold); flex: none; }
.app-badge { height: 52px; width: auto; display: block; }
.app-note { font-size: 12.5px; color: rgba(247, 244, 236, .5); }

/* ---- live date on the phone ----
   Offsets are percentages of the capture, so the overlay tracks the image at
   any width. cqw sizes the type against the image box rather than the viewport;
   without a container the text would drift out of scale on every resize.

   Measured from assets/img/app.webp (928x1152):
     headline left edge   x=139   -> 14.98%
     cap top / baseline   y=367 / 406
     cap height           40px    -> 55px Inter at 928 wide -> 5.93cqw
     ink colour           rgb(33,31,27)
     surface behind it    rgb(250,248,244), stdev 0.0 across the whole band */
.app-shot { position: relative; container-type: inline-size; }
.app-date {
  position: absolute;
  /* The cover starts LEFT of the ink and pads the text back to 14.98%. Starting
     it exactly on the first ink pixel left a hairline of the baked "T" showing,
     because that glyph's antialiasing begins a fraction before x=139. */
  left: 13.9%;
  padding-left: 1.08%;
  top: 30.30%;
  /* out to the card's right edge, so a long date has the room the app gives it */
  width: 71.58%;
  box-sizing: border-box;
  height: 6.2%;
  /* The fill STOPS before the "+" button. The box has to stay wide enough for a
     long date, but the button's soft shadow reaches down to y=379 over
     x=704-803 — a full-width fill erased 30px of it and left the button
     looking cut off. Hard stop at x=665: past the baked ink (ends 631), clear
     of the shadow (starts 704). No visible edge, because the fill colour is
     the background colour. */
  background: linear-gradient(to right,
    rgb(250, 248, 244) 0 80.72%, rgba(250, 248, 244, 0) 80.72%);
  display: flex;
  align-items: center;
  overflow: hidden;
  pointer-events: none;
}
.app-date b {
  display: inline-block;
  white-space: nowrap;
  font-size: 5.93cqw;
  font-weight: 700;
  /* -0.011, not the -0.02 the site's headings use: overlaid on the capture at
     1:1 the tighter value ran the string 9px short over 492px, which showed as
     drift by the last word. 9px / 16 gaps / 55px = 0.010em back. */
  letter-spacing: -0.011em;
  line-height: 1;
  color: rgb(33, 31, 27);
  /* renderAppDate() shrinks from the left when a locale runs long — Spanish
     spells out "miércoles, 24 de septiembre" where English has "Wednesday,
     September 24" */
  transform-origin: left center;
}
.sec--ink .row-media { box-shadow: 0 24px 60px rgba(0, 0, 0, .45); }
.center { text-align: center; }
.center .lead { margin-inline: auto; }
.stack > * + * { margin-top: var(--s-3); }

/* ==========================================================================
   Age / research-use gate
   Motion per Emil Kowalski: modals keep transform-origin centre (they aren't
   anchored to a trigger); entry starts at scale(.95) not 0 because nothing in
   the real world appears from nothing; ease-out on entry so the first frame
   moves immediately; exit runs ~60% of entry so dismissal feels responsive.
   Only transform/opacity/filter animate.
   ========================================================================== */
:root {
  --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
}

.gate { position: fixed; inset: 0; z-index: 300; display: grid; place-items: center;
        padding: clamp(16px, 4vw, 40px); }
.gate[hidden] { display: none; }

.gate-backdrop {
  position: absolute; inset: 0;
  background: rgba(26, 24, 21, 0.42);
  /* the blur itself animates in, which is what sells the depth */
  backdrop-filter: blur(0px) saturate(100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  opacity: 0;
  transition: opacity 420ms var(--ease-out-strong),
              backdrop-filter 520ms var(--ease-out-strong),
              -webkit-backdrop-filter 520ms var(--ease-out-strong);
}
.gate.is-open .gate-backdrop {
  opacity: 1;
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
}

.gate-card {
  position: relative;
  width: min(480px, 100%);
  padding: clamp(28px, 5vw, 44px);
  border-radius: 28px;
  text-align: center;

  /* liquid glass: translucent warm pane, blurred backdrop, bright inner edge */
  background: linear-gradient(165deg, rgba(252,250,245,.86), rgba(237,232,217,.72));
  backdrop-filter: blur(30px) saturate(180%);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  border: 1px solid rgba(255,255,255,.6);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(26,24,21,.05),
    0 30px 80px rgba(26,24,21,.22),
    0 4px 16px rgba(26,24,21,.08);

  opacity: 0;
  transform: scale(.95) translateY(10px);
  filter: blur(4px);
  transform-origin: center;              /* modals are not trigger-anchored */
  transition: opacity 300ms var(--ease-out-strong),
              transform 420ms var(--ease-out-strong),
              filter 340ms var(--ease-out-strong);
}
.gate.is-open .gate-card {
  opacity: 1; transform: none; filter: blur(0px);
}
/* exit is faster than entry */
.gate.is-closing .gate-card {
  opacity: 0; transform: scale(.97) translateY(4px); filter: blur(3px);
  transition-duration: 190ms, 220ms, 180ms;
}
.gate.is-closing .gate-backdrop {
  opacity: 0;
  backdrop-filter: blur(0px); -webkit-backdrop-filter: blur(0px);
  transition-duration: 220ms, 240ms, 240ms;
}

.gate-mark { width: 46px; margin: 0 auto 20px; display: block; opacity: .9; }
.gate-card h2 { font-size: clamp(24px, 3.4vw, 32px); margin-bottom: 12px; }
.gate-lead { font-size: 15.5px; color: var(--muted); line-height: 1.55; margin-inline: auto; max-width: 42ch; }

.gate-list { list-style: none; text-align: left; margin: 26px 0 28px; display: grid; gap: 14px; }
.gate-list li { display: flex; gap: 11px; align-items: start; font-size: 14.5px; line-height: 1.5; color: var(--ink-2); }
.gate-list svg { width: 17px; height: 17px; flex: none; margin-top: 2px; color: var(--gold-deep); }
.gate-list strong { color: var(--ink); font-weight: 600; }

.gate-actions { display: grid; gap: 10px; }
.gate-actions .btn { width: 100%; }
.gate-fine { margin-top: 16px; font-size: 12.5px; color: var(--faint); }

/* staggered content reveal, 40ms apart, decorative only */
.gate.is-open .gate-mark,
.gate.is-open .gate-card h2,
.gate.is-open .gate-lead,
.gate.is-open .gate-list,
.gate.is-open .gate-actions,
.gate.is-open .gate-fine { animation: gate-in 420ms var(--ease-out-strong) backwards; }
.gate.is-open .gate-mark      { animation-delay: 90ms; }
.gate.is-open .gate-card h2   { animation-delay: 130ms; }
.gate.is-open .gate-lead      { animation-delay: 170ms; }
.gate.is-open .gate-list      { animation-delay: 210ms; }
.gate.is-open .gate-actions   { animation-delay: 250ms; }
.gate.is-open .gate-fine      { animation-delay: 290ms; }
@keyframes gate-in { from { opacity: 0; transform: translateY(8px); } }

body.gate-locked { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  /* keep the fade, drop the movement and the blur */
  .gate-card { transform: none; filter: none; transition: opacity 200ms ease; }
  .gate.is-closing .gate-card { transform: none; filter: none; }
  .gate-backdrop { transition: opacity 200ms ease; }
  .gate.is-open .gate-mark, .gate.is-open .gate-card h2, .gate.is-open .gate-lead,
  .gate.is-open .gate-list, .gate.is-open .gate-actions, .gate.is-open .gate-fine { animation: none; }
}

/* ---------- nav ---------- */
.nav {
  position: fixed; inset: 0 0 auto 0; height: var(--nav-h); z-index: 100;
  display: flex; align-items: center;
  background: rgba(237,232,217,.72);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  border-bottom: 1px solid transparent;
  transition: background .3s var(--ease), border-color .3s var(--ease);
}
.nav.is-stuck { background: rgba(237,232,217,.92); border-bottom-color: var(--line-2); }
.nav .wrap { display: flex; align-items: center; gap: var(--s-3); width: 100%; }
.brand { display: flex; align-items: center; gap: 10px; margin-right: auto; }
/* 30px CSS = 60px on a 2x display, still under the asset's native 72px height,
   so it stays crisp. The wordmark carries a small COMPOUNDS line that was
   illegible at 19px. */
.brand img { height: 30px; width: auto; filter: invert(1) brightness(.22) contrast(1.7); }
/* Apple-style link row: small, light, evenly spaced, no pill. The only
   feedback is a colour shift — restraint is the point. */
.nav-links { display: flex; gap: clamp(18px, 3vw, 40px); }
.nav-links a {
  font-size: 13px; font-weight: 400; letter-spacing: -.01em;
  color: rgba(26,24,21,.72);
  padding: 8px 0; position: relative;
  transition: color .22s var(--ease);
}
.nav-links a:hover { color: var(--ink); }
/* hairline that grows from the centre on hover */
.nav-links a::after {
  content: ""; position: absolute; left: 50%; right: 50%; bottom: 2px; height: 1px;
  background: var(--ink); opacity: 0;
  transition: left .26s var(--ease), right .26s var(--ease), opacity .26s var(--ease);
}
.nav-links a:hover::after { left: 0; right: 0; opacity: .55; }
.nav-cta {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--ink); color: var(--cream);
  padding: 10px 18px; border-radius: var(--pill); min-height: 44px;
  font-size: 14px; font-weight: 600;
  transition: transform .2s var(--ease), background .2s var(--ease);
}
.nav-cta:hover { transform: translateY(-1px); background: #2A2723; }
.nav-cta .count {
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: var(--pill);
  background: var(--gold); color: var(--ink);
  font-size: 11px; font-weight: 700; display: grid; place-items: center;
}
/* ---------- language toggle ----------
   A two-segment control rather than a dropdown: with only two languages a
   menu hides the choice behind a click. Stays in the bar at every width,
   including mobile where the link row collapses into the burger. */
.lang {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; border-radius: var(--pill);
  background: rgba(26, 24, 21, .05);
  border: 1px solid rgba(26, 24, 21, .07);
}
.lang button {
  position: relative;
  padding: 5px 11px; border-radius: var(--pill);
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  color: var(--muted); background: transparent;
  transition: color .2s var(--ease), background .3s var(--ease);
}
/* the pill stays visually small inside a 70px nav; the tap area is extended
   past it so each segment still clears 44px vertically */
.lang button::after { content: ""; position: absolute; inset: -9px 0; }
.lang button:hover { color: var(--ink); }
.lang button[aria-pressed="true"] {
  color: var(--ink); background: var(--cream);
  box-shadow: 0 1px 3px rgba(26, 24, 21, .09);
}

/* ---------- burger: two lines that morph into an X ----------
   Two-stage timing, the way Apple's does it: on open the lines first slide
   together, then rotate. On close they unrotate first, then separate. The
   delay swap between the two states is what makes it feel considered rather
   than a single spin. */
.burger { display: none; width: 44px; height: 44px; place-items: center; color: var(--ink); }
.burger-box { position: relative; width: 18px; height: 11px; display: block; }
.burger-line {
  position: absolute; left: 0; right: 0; height: 1.5px; border-radius: 2px;
  background: currentColor;
  transition: top .19s var(--ease) .19s, transform .24s var(--ease) 0s;
}
.burger-line:nth-child(1) { top: 0; }
.burger-line:nth-child(2) { top: 9.5px; }
.burger[aria-expanded="true"] .burger-line {
  transition: top .19s var(--ease) 0s, transform .24s var(--ease) .19s;
  top: 4.75px;
}
.burger[aria-expanded="true"] .burger-line:nth-child(1) { transform: rotate(45deg); }
.burger[aria-expanded="true"] .burger-line:nth-child(2) { transform: rotate(-45deg); }

/* ---------- full-screen mobile menu ---------- */
.mobile-menu {
  position: fixed; inset: var(--nav-h) 0 0 0; z-index: 95;
  background: rgba(237,232,217,.94);
  backdrop-filter: saturate(180%) blur(22px);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  padding: clamp(20px, 6vw, 48px);
  overflow-y: auto;
  opacity: 0; visibility: hidden;
  transition: opacity .32s var(--ease), visibility .32s var(--ease);
}
.mobile-menu.open { opacity: 1; visibility: visible; }
.mobile-menu nav { display: flex; flex-direction: column; }
.mobile-menu a {
  font-size: clamp(26px, 7vw, 38px); font-weight: 600; letter-spacing: -.03em;
  color: var(--ink);
  padding: 18px 0; min-height: 56px;
  border-bottom: 1px solid var(--line-2);
  opacity: 0; transform: translateY(14px);
  transition: opacity .42s var(--ease), transform .42s var(--ease);
}
.mobile-menu.open a {
  opacity: 1; transform: none;
  /* stagger, keyed off --i on each link */
  transition-delay: calc(var(--i) * 45ms + 90ms);
}
body.menu-open { overflow: hidden; }

/* ---------- hero ----------
   Height comes from the IMAGE on desktop, not the viewport, so the hero is
   shown whole rather than cropped to fit. At 16:9 that lands at 99% of a 16:9
   display and 86-89% of a squarer laptop — the sliver of the next section
   showing under it is a feature, not a gap.

   max-height caps it so an ultrawide short window (2560x1080 would want a
   1429px hero) falls back to a centred cover crop instead of pushing the
   product below the fold. */
#hero { position: relative; }
#hero-pin {
  position: relative;
  overflow: hidden; background: var(--sand);
}
/* The footage is 16:9 and the vial reaches the frame edges as it falls, so
   cropping to fill would cut the product off. Instead the frame lives in a
   wrapper sized to its own 16:9 ratio (so the image is never cropped at all),
   and the wrapper's edges are feathered — the footage backdrop is a gradient,
   so it does NOT match --sand exactly and a hard letterbox edge would show. */
/* No feather on the top edge: the footage's top row is a warm tan (190,165,125)
   against a sand page, so a gradient just reads as haze. A crisp boundary reads
   as a deliberate image panel instead.

   Height is driven by --frame-h rather than the footage's 16:9 ratio, so the
   panel can absorb leftover screen height instead of leaving it as dead space.

   Crop stays centred at 50%: measuring only the product (excluding its soft
   shadow, which trails right to the frame edge) puts the standing vial at
   x41.4-58.5%, dead centre, and the fallen vial at x31.6-73.4%. A centred crop
   therefore centres the standing vial and still contains the fallen one. */
.hero-frame {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--frame-h, 56.25vw);
  /* own stacking context: without it the still's z-index escapes and paints
     over the hero copy */
  z-index: 0; isolation: isolate;
  /* clips the parallax drift; the image is scaled slightly so drifting never
     exposes an edge */
  overflow: hidden;
}
.hero-frame img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  object-fit: cover; object-position: 50% 50%;
}
/* No transform. A parallax drift was tried and removed: the source is 1.79:1
   in a 1.6:1 box, so object-fit:cover already fits it vertically with zero
   headroom — any scale to create drift room cropped the cap and the base off
   the product. On a hero whose whole job is the product, that is the wrong
   trade for motion nobody asked to see. */
#hero-still { z-index: 1; }

/* Desktop: full-bleed, copy overlays the image.
   Keyed to width, not aspect ratio. The old `min-aspect-ratio: 16/9` gate
   missed every common desktop below 1.778 (1440x900 and 1280x800 are 1.6), so
   the frame rendered at 56.25vw and left a sand strip under the nav. */
@media (min-width: 721px) {
  /* relative, so the frame's own aspect ratio drives the hero's height and the
     image is never cropped; the copy and scrim still overlay it via inset:0 */
  .hero-frame {
    position: relative; inset: auto;
    width: 100%; height: auto;
    aspect-ratio: 5504 / 3072;
    max-height: 100vh; max-height: 100dvh;
  }
  .hero-frame img { object-position: 50% 50%; }
  /* The vial lands at roughly x40-60% of the viewport once the frame is
     full-bleed; unconstrained, the headline ran ~387px straight across the
     product. The caps below keep the copy clear of it from 721px up to ultrawide.
     ID prefix because the base `.hero-beat h1` rule is later in the file and
     would otherwise win on source order (ch also resolves far wider than it
     looks: 14ch was 897px here). */
  #hero-pin .hero-beat h1 {
    font-size: clamp(40px, 4.6vw, 76px);
    max-width: min(430px, 26vw);
  }
  #hero-pin .hero-beat .lead { max-width: min(340px, 22vw); }
  /* --muted on the new hero measured 2.03:1 at its worst pixel, against a 4.5
     floor for body text — the old pale hero was hiding a real failure. --ink-2
     only reached 3.68:1, so the lead takes full --ink here. Hierarchy is
     carried by size and weight instead of colour (66px/700 against 21.6px/400),
     which is the stronger typographic move anyway.

     Scoped to this breakpoint: below 721px the copy sits ABOVE the image on the
     sand page, where --muted has plenty of contrast and is the right weight. */
  #hero-pin .hero-beat .lead { color: var(--ink); }
}

/* No wash. This laid 50% sand over the outer 28% of the frame to feather the
   image into the page, but measured against the new hero it does not achieve
   that and costs real depth: the vial's shadow went #D8B587 -> #E2CEB0, and the
   bottom edge only reached #D8C4A6 against a #FCFAF5 next section — still a
   delta of R36/G54/B79, so nothing was actually being blended. It was hazing
   the image for no gain. A crisp boundary reads as a deliberate image panel,
   which is the same reasoning already applied to the frame's top edge. */
.hero-scrim { display: none; }

/* NO negative margin here. This used to be -24vh to close the gap left by the
   pinned hero lifting as you scrolled. That animation was removed with the
   scroll sequence and LIFT_VH no longer exists in site.js, but the margin
   stayed — silently pulling this section up over the bottom 24% of the hero and
   cutting the vial's base off at every viewport. */
.hero-copy { position: absolute; inset: 0; display: grid; align-content: center;
  padding: 0 clamp(20px, 5vw, 96px); pointer-events: none; z-index: 2; }
.hero-beat { position: absolute; inset: 0; display: grid; align-content: center;
  padding: inherit; opacity: 0; }
.hero-beat h1 { max-width: 14ch; }
.hero-beat .lead { margin-top: var(--s-3); max-width: 32ch; }
.hero-beat .eyebrow { margin-bottom: var(--s-2); }
.scroll-cue {
  position: absolute; bottom: 28px; left: 50%; transform: translateX(-50%);
  font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--muted); transition: opacity .4s var(--ease);
}
.scroll-cue::after {
  content: ""; display: block; width: 1px; height: 22px; margin: 10px auto 0;
  background: linear-gradient(to bottom, var(--faint), transparent);
}

/* ---------- bento ---------- */
.bento {
  display: grid; gap: var(--s-2);
  grid-template-columns: repeat(6, 1fr);
}
.card {
  background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: var(--s-4);
  display: flex; flex-direction: column; gap: var(--s-2);
  position: relative; overflow: hidden;
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-1); }
.card--img { padding: 0; }
.card--img img { width: 100%; height: 100%; object-fit: cover; }
.card--dark { background: var(--ink); color: var(--cream); border-color: transparent; }
.card--dark .lead, .card--dark .small { color: rgba(247,244,236,.66); }
.card--dark .eyebrow { color: var(--gold); }
.span-6 { grid-column: span 6; } .span-4 { grid-column: span 4; }
.span-3 { grid-column: span 3; } .span-2 { grid-column: span 2; }
/* two rows tall: lets a portrait image fill its cell without cropping */
.card.tall { grid-row: span 2; min-height: 520px; }
.card .metric { font-size: clamp(38px, 4.6vw, 62px); font-weight: 700; letter-spacing: -.04em; line-height: 1; }
.card .metric span { color: var(--gold-deep); }
.card-media { min-height: 260px; }

/* ---------- media rows (Apple-style alternating) ---------- */
.row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 5vw, 80px); align-items: center; }
.row--flip > .row-media { order: -1; }
.row-media { border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-1); }
/* intrinsic width/height on the tags reserves the exact box, so the row
   never collapses to 0 height while the image is still lazy-loading */
.row-media img { width: 100%; height: auto; }

/* ---------- chips / filters ---------- */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  padding: 9px 16px; border-radius: var(--pill);
  border: 1px solid var(--line); background: transparent;
  font-size: 14px; font-weight: 500; color: var(--ink-2);
  /* 44px is the WCAG 2.5.5 floor; at 40 these landed at 42 with padding */
  transition: all .2s var(--ease); min-height: 44px;
}
.chip:hover { border-color: var(--ink); }
.chip[aria-selected="true"],
.chip.is-on { background: var(--ink); border-color: var(--ink); color: var(--cream); }

/* result count: sits between the filter chips and the grid */
#count { margin: var(--s-3) 0; }

/* ---------- product grid ---------- */
.shop-search {
  display: flex; align-items: center; gap: 10px;
  max-width: 480px; margin: 0 0 var(--s-2);
  padding: 0 14px; min-height: 52px;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--pill);
  transition: border-color .18s var(--ease);
}
.shop-search:focus-within { border-color: var(--gold); }
.shop-search > svg { width: 19px; height: 19px; color: var(--muted); flex: none; }
.shop-search input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font: inherit; font-size: 16px; color: var(--ink); padding: 14px 0;
}
/* 16px, not smaller: iOS zooms the whole page when a focused input is under
   16px, which on a search field means the page jumps on every tap */
.shop-search input::placeholder { color: var(--faint); }
.shop-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
#cat-search-clear {
  width: 32px; height: 32px; display: grid; place-items: center; flex: none;
  border: 0; background: none; color: var(--muted); cursor: pointer;
  border-radius: 50%; opacity: 0; pointer-events: none;
  transition: opacity .15s var(--ease), background .15s var(--ease);
}
.shop-search input:not(:placeholder-shown) ~ #cat-search-clear { opacity: 1; pointer-events: auto; }
#cat-search-clear:hover { background: var(--line-2); color: var(--ink); }
#cat-search-clear svg { width: 15px; height: 15px; }
.shop-empty { max-width: 46ch; color: var(--muted); padding: var(--s-4) 0; }

/* ---------- catalog page: sticky control bar ----------
   63 cards is roughly 7,000px of page. Search and filters that scroll away
   after the first screen are search and filters you have to scroll back up to
   reach, so the whole control group pins under the nav instead.

   Every rule here is scoped to body[data-page="shop"]. The home page renders
   the same #filters markup through the same renderFilters(), but it only ever
   shows a three-card preview — pinning there would be chrome with nothing left
   to control. */
body[data-page="shop"] .shop-intro { max-width: 60ch; margin-bottom: var(--s-4); }

body[data-page="shop"] .shop-bar {
  position: sticky; top: var(--nav-h); z-index: 20;
  padding: 14px 0; margin-bottom: var(--s-2);
  /* Translucent so cards visibly pass underneath — the cue that the bar is
     pinned rather than simply sitting there. The fallback below matters: with
     no backdrop-filter support this must be opaque, or the grid scrolls
     straight through the filter labels. */
  background: var(--cream);
  border-bottom: 1px solid var(--line-2);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  body[data-page="shop"] .shop-bar {
    background: color-mix(in srgb, var(--cream) 82%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.3);
    backdrop-filter: blur(14px) saturate(1.3);
  }
}
body[data-page="shop"] .shop-bar-inner {
  display: flex; align-items: center; gap: var(--s-2);
}
body[data-page="shop"] .shop-search {
  margin: 0; flex: 0 0 300px; min-height: 46px;
}
body[data-page="shop"] .shop-search input { padding: 10px 0; }

/* One line, scrolled — not wrapped. Nine chips wrap to three rows on a phone,
   which would leave the pinned bar taller than the first row of cards it is
   meant to sit above. A chip sliced by the right edge is its own affordance:
   it says the list continues without needing an arrow or a fade. */
body[data-page="shop"] .shop-bar .chips {
  flex: 1 1 auto; min-width: 0;
  flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
  /* the chips carry a 1px border; without this their outline is shaved off */
  padding-block: 1px;
}
body[data-page="shop"] .shop-bar .chips::-webkit-scrollbar { display: none; }
body[data-page="shop"] .shop-bar .chip { flex: none; }

body[data-page="shop"] #count { margin: 0 0 var(--s-2); }

/* A little more air between cards than the home preview's three get, since
   here they tile four across and eight deep */
body[data-page="shop"] .grid-p { gap: 20px; }

@media (max-width: 860px) {
  /* Below this the search field and nine chips cannot share a line without
     the chips being reduced to a peephole, so they stack */
  body[data-page="shop"] .shop-bar-inner { display: block; }
  body[data-page="shop"] .shop-search { flex: none; max-width: none; margin-bottom: 10px; }
  body[data-page="shop"] .shop-bar { padding: 12px 0; }
  /* Now that the chips own a full row, let them scroll past the gutter so the
     row reads as continuing off-screen rather than ending at an inset */
  body[data-page="shop"] .shop-bar .chips {
    margin-inline: calc(var(--wrap-pad) * -1);
    padding-inline: var(--wrap-pad);
  }
}

.grid-p { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: var(--s-2); }
.p {
  background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r);
  padding: var(--s-3); display: flex; flex-direction: column; gap: 10px;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease), border-color .3s var(--ease);
}
.p:hover { transform: translateY(-2px); box-shadow: var(--shadow-1); border-color: var(--line); }
.p-top { display: flex; justify-content: space-between; align-items: start; gap: 10px; }
.p-name { font-size: 17px; font-weight: 600; letter-spacing: -.015em; }
.p-size { font-size: 12px; font-weight: 600; color: var(--gold-deep); letter-spacing: .06em; }
/* Muted, not gold: gold in this slot means "this is the vial you are buying",
   and a count is not a vial. The chips below carry the actual choice. */
.p-size--multi { color: var(--muted); letter-spacing: .04em; }
.p-cat {
  font-size: 10.5px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); background: rgba(26,24,21,.045);
  padding: 4px 9px; border-radius: var(--pill); white-space: nowrap;
}
.p-desc { font-size: 14px; color: var(--muted); line-height: 1.5; flex: 1; }
.p-more {
  display: inline-flex; align-items: center; gap: 6px; align-self: start;
  font-size: 13px; font-weight: 600; color: var(--gold-deep);
  min-height: 44px; padding: 6px 0;
  transition: gap .2s var(--ease);
}
.p-more:hover { gap: 10px; }
.p-more svg { width: 13px; height: 13px; }
/* Vial sizes, on the 7 compounds that have more than one. Sized down from the
   filter chips deliberately: these are a secondary control inside a card, and
   at the filter chips' 44px a three-size compound would push Add below the
   fold on a phone. 38px clears WCAG 2.5.8 Target Size (Minimum, AA, 24px)
   with room to spare; it does not meet 2.5.5 (AAA, 44px), which the filter
   chips do. */
.p-sizes { display: flex; flex-wrap: wrap; gap: 6px; }
.p-size-chip {
  min-height: 38px; padding: 8px 13px; border-radius: var(--pill);
  border: 1px solid var(--line); background: transparent; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; letter-spacing: .01em;
  color: var(--ink-2); font-variant-numeric: tabular-nums;
  transition: background .18s var(--ease), border-color .18s var(--ease), color .18s var(--ease);
}
.p-size-chip:hover { border-color: var(--ink); }
.p-size-chip[aria-checked="true"] { background: var(--ink); border-color: var(--ink); color: var(--cream); }

.p-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 4px; }
.p-price { font-size: 20px; font-weight: 700; letter-spacing: -.02em; }
.p-price small { font-size: 12px; font-weight: 500; color: var(--faint); }
.btn-add {
  min-height: 44px; padding: 11px 16px; border-radius: var(--pill);
  background: var(--ink); color: var(--cream); font-size: 14px; font-weight: 600;
  display: inline-flex; align-items: center; gap: 7px;
  transition: transform .18s var(--ease), background .2s var(--ease);
}
.btn-add:hover { transform: translateY(-1px); background: #2A2723; }
.btn-add:active { transform: scale(.97); }
.btn-add.is-added { background: var(--ok); }

/* ---------- research-use notices at point of purchase ---------- */
.p-ruo {
  font-size: 11px; font-weight: 500; letter-spacing: .04em;
  color: var(--faint); line-height: 1.45;
  padding-top: 10px; margin-top: 2px;
  border-top: 1px solid var(--line-2);
}
.sheet .p-ruo { border-top: 0; padding-top: 0; font-size: 12px; }

.ruo-check {
  display: flex; gap: 10px; align-items: start;
  font-size: 12.5px; line-height: 1.45; color: var(--ink-2);
  padding: 12px 14px; border-radius: var(--r-sm);
  background: rgba(26,24,21,.035); border: 1px solid transparent;
  cursor: pointer; transition: border-color .2s var(--ease), background .2s var(--ease);
}
.ruo-check:hover { background: rgba(26,24,21,.055); }
.ruo-check input { margin-top: 1px; width: 16px; height: 16px; accent-color: var(--ink); flex: none; cursor: pointer; }
.ruo-check strong { font-weight: 600; color: var(--ink); }
/* a short shake when someone tries to send without confirming */
.ruo-check.nudge { animation: ruo-nudge 380ms var(--ease-out-strong); border-color: var(--danger); }
@keyframes ruo-nudge {
  0%,100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}
.btn.is-disabled { opacity: .42; cursor: not-allowed; }
.btn.is-disabled:hover { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .ruo-check.nudge { animation: none; }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 50px; padding: 14px 28px; border-radius: var(--pill);
  font-size: 16px; font-weight: 600; letter-spacing: -.01em;
  transition: transform .2s var(--ease), background .2s var(--ease), border-color .2s var(--ease);
}
.btn--primary { background: var(--ink); color: var(--cream); }
.btn--primary:hover { transform: translateY(-2px); background: #2A2723; }
.btn--ghost { border: 1px solid var(--line); color: var(--ink); }
.btn--ghost:hover { border-color: var(--ink); transform: translateY(-2px); }
.btn--gold { background: var(--gold); color: var(--ink); }
.btn--gold:hover { transform: translateY(-2px); background: #D4AC63; }
.btn svg { width: 17px; height: 17px; }
.cta-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: var(--s-2); }
.cat-more { display: flex; justify-content: center; margin-top: var(--s-4); }
.cat-more .btn[hidden] { display: none; }

/* crawlable but off-screen (never display:none, which drops it from indexing) */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- featured strip (top of catalog) ---------- */

/* ---------- compound detail sheet ---------- */
.sheet {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 200;
  width: min(560px, 100%); background: var(--paper);
  display: flex; flex-direction: column;
  transform: translateX(101%); transition: transform .38s var(--ease);
  box-shadow: var(--shadow-2);
}
.sheet.open { transform: none; }
.spec-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s-2); padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line-2);
}
.spec .k { font-size: 10.5px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); }
.spec .v { font-weight: 600; font-size: 15px; margin-top: 4px; word-break: break-word; }
.sizes { display: flex; flex-wrap: wrap; gap: 10px; }
.no-data {
  border: 1px dashed var(--line); border-radius: var(--r);
  padding: var(--s-3); color: var(--muted); font-size: 14.5px; line-height: 1.55;
}

/* ---------- reviews ---------- */
.rv { background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--r); padding: var(--s-3); }
.rv-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.rv-av {
  width: 40px; height: 40px; border-radius: var(--pill); background: var(--gold-tint);
  display: grid; place-items: center; font-weight: 700; font-size: 15px; color: var(--gold-deep);
}
.rv-txt { font-size: 15px; line-height: 1.6; color: var(--ink-2); }
.rv-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.rv-tag { font-size: 11px; font-weight: 600; letter-spacing: .05em; color: var(--gold-deep);
  background: var(--gold-tint); padding: 4px 9px; border-radius: var(--pill); }

/* ---------- explore by research area ----------
   One card that swaps its contents step to step, rather than four screens.
   The reader stays in the same place on the page the whole way through. */
.ex-card {
  background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: clamp(24px, 4vw, 44px);
  box-shadow: var(--shadow-1);
}
.ex-progress {
  font-size: 12px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--faint); margin-bottom: var(--s-2);
}
.ex-progress:empty { display: none; }
.ex-q { font-size: clamp(19px, 2vw, 25px); font-weight: 600; letter-spacing: -.02em;
  line-height: 1.3; margin-bottom: var(--s-3); max-width: 40ch; }
.ex-nav { display: flex; flex-wrap: wrap; gap: 10px; margin-top: var(--s-4); }
.ex-warn { margin-top: 12px; font-size: 14px; color: var(--danger); }

/* step 2: one question block per selected area */
.ex-group + .ex-group { margin-top: var(--s-3); padding-top: var(--s-3); border-top: 1px solid var(--line-2); }
.ex-group-t { font-size: 12px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold-deep); margin-bottom: 6px; }
.ex-group-q { font-size: 15px; color: var(--muted); margin-bottom: 12px; }

/* step 3: evidence level */
.ex-opts { display: grid; gap: 12px; }
.ex-opt {
  display: grid; gap: 5px; text-align: left; padding: 18px 20px;
  border: 1px solid var(--line); border-radius: var(--r);
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.ex-opt:hover { border-color: var(--ink); }
.ex-opt.is-on { border-color: var(--ink); background: var(--cream); }
.ex-opt-t { font-size: 16px; font-weight: 600; }
.ex-opt-d { font-size: 14px; color: var(--muted); line-height: 1.5; }

/* step 4: results */
.ex-res-t { margin-bottom: 8px; }
.ex-res-l { font-size: 15px; color: var(--muted); max-width: 56ch; margin-bottom: var(--s-4); }
.ex-res-sec + .ex-res-sec { margin-top: var(--s-4); }
.ex-res-sec .eyebrow { margin-bottom: 14px; }

.ex-item { border: 1px solid var(--line-2); border-radius: var(--r); padding: var(--s-3); background: var(--cream); }
.ex-item + .ex-item { margin-top: 12px; }
.ex-item-h { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.ex-item-h h4 { font-size: 18px; font-weight: 700; letter-spacing: -.02em; }
.ex-tier {
  font-size: 11px; font-weight: 600; letter-spacing: .05em;
  padding: 4px 9px; border-radius: var(--pill);
}
.ex-tier--clinical { color: var(--gold-deep); background: var(--gold-tint); }
.ex-tier--community { color: var(--muted); background: var(--line-2); }
.ex-sum { font-size: 15px; line-height: 1.6; color: var(--ink-2); }

.ex-more {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 14px;
  font-size: 14px; font-weight: 600; color: var(--gold-deep); min-height: 44px;
}
.ex-more svg { width: 16px; height: 16px; transition: transform .25s var(--ease); }
.ex-more.is-open svg { transform: rotate(180deg); }
.ex-det { margin-top: 6px; font-size: 14.5px; line-height: 1.65; color: var(--ink-2); }
.ex-det[hidden] { display: none; }
.ex-src-t { margin-top: 16px; font-size: 12px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--faint); }
.ex-src { list-style: none; margin-top: 8px; display: grid; gap: 6px; }
.ex-src a { font-size: 14px; color: var(--gold-deep); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px; }
.ex-src a:hover { color: var(--ink); }

.ex-empty { border: 1px dashed var(--line); border-radius: var(--r); padding: var(--s-3); }
.ex-empty-t { font-weight: 600; margin-bottom: 4px; }

.ex-legal {
  margin-top: var(--s-4); padding: 16px 18px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
.ex-legal p { font-size: 12.5px; line-height: 1.55; color: var(--muted); }

/* ---------- faq ---------- */
.faq { border-top: 1px solid var(--line-2); }
.faq-i { border-bottom: 1px solid var(--line-2); }
.faq-q {
  width: 100%; text-align: left; display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) 0; font-size: clamp(16px, 1.4vw, 19px); font-weight: 600;
  letter-spacing: -.015em; min-height: 56px;
}
.faq-q .ic { margin-left: auto; flex: none; width: 22px; height: 22px; color: var(--gold-deep);
  transition: transform .3s var(--ease); }
.faq-i[open] .faq-q .ic { transform: rotate(45deg); }
.faq-a { padding: 0 0 var(--s-3); color: var(--muted); font-size: 16px; line-height: 1.65; max-width: 78ch; }

/* ---------- cart drawer ---------- */
.scrim {
  position: fixed; inset: 0; z-index: 190; background: rgba(26,24,21,.45);
  backdrop-filter: blur(3px); opacity: 0; pointer-events: none; transition: opacity .3s var(--ease);
}
.scrim.open { opacity: 1; pointer-events: auto; }
.cart {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 200;
  width: min(430px, 100%); background: var(--paper);
  display: flex; flex-direction: column;
  transform: translateX(101%); transition: transform .38s var(--ease);
  box-shadow: var(--shadow-2);
}
.cart.open { transform: none; }
.cart-h { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-3); border-bottom: 1px solid var(--line-2); }
.cart-h h3 { margin-right: auto; }
.icon-btn { width: 44px; height: 44px; display: grid; place-items: center; border-radius: var(--pill);
  transition: background .2s var(--ease); }
.icon-btn:hover { background: rgba(26,24,21,.055); }
.icon-btn svg { width: 20px; height: 20px; }
/* The scroller is the wrapper, not the item list, so the notes below the items
   scroll with them instead of being pinned. */
.cart-scroll { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
.cart-body { flex: 1 0 auto; padding: var(--s-3); display: flex; flex-direction: column; gap: var(--s-2); }
.cart-notes { padding: 0 var(--s-3) var(--s-3); display: grid; gap: var(--s-1); }
/* Grid, not flex, so the plan block can span the whole row underneath the item
   instead of sharing the middle column with the name and quantity. minmax(0,1fr)
   rather than 1fr: a flex/grid track sized to its content refuses to shrink
   below it, which pushed the price column off a phone screen. */
/* Two columns, not three. The price moved into the main column beside the
   name, which lets the name use the full width instead of being squeezed by a
   price column that only ever held one short line. */
.ci { display: grid; grid-template-columns: auto minmax(0, 1fr);
  column-gap: 14px; row-gap: 12px; align-items: start;
  padding-bottom: var(--s-2); border-bottom: 1px solid var(--line-2); }
.ci-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.ci-actions { display: flex; justify-content: space-between; align-items: center;
  gap: 12px; margin-top: 8px; }
.ci:last-child { border-bottom: 0; }
/* product thumbnail: the vial sits on a soft sand tile so the transparent
   PNG reads as a product shot rather than floating on the drawer */
.ci-thumb {
  flex: none; width: 62px; height: 76px; border-radius: var(--r-sm);
  background: linear-gradient(165deg, #F3EEE3, var(--sand-deep));
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; padding: 7px 6px;
}
/* max-* rather than a percentage height: the source is a tall 120x285 vial and
   a % height resolves against the flex area, not the box, so it overflowed and
   got clipped. Capping both axes keeps the whole vial inside the tile. */
.ci-thumb img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto; display: block;
}
.ci-main { min-width: 0; }
.ci-name { font-weight: 600; font-size: 15px; }
.ci-meta { font-size: 13px; color: var(--muted); }
.ci-price { font-size: 15px; font-weight: 700; color: var(--ink); white-space: nowrap; }
.ci-price small { font-size: .74em; font-weight: 500; opacity: .55; }
/* Recedes until wanted. Removing a line is recoverable and rare; permanent red
   beside the price competed with the number the reader is actually there for. */
.ci-rm { min-height: 44px; padding: 0 2px; font-size: 12.5px; color: var(--faint);
  background: none; border: 0; cursor: pointer; transition: color .18s ease; }
.ci-rm:hover { color: var(--danger); }
.ci-rm:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 4px; }

/* ---- purchase option, per cart line ----
   Two named choices, each with its own price, so the 10% is a comparison the
   reader can make rather than a claim they have to take on faith.

   ONE container, not three. Earlier this was two separately-bordered cards
   plus a loose stepper plus two captions — four outlines and three control
   shapes stacked under a product that already has a bordered quantity pill.
   Now a single bordered group with hairline dividers, and the interval nested
   inside the option it belongs to. */
/* No margin-top: the grid row-gap already separates this from the block above,
   and the two together were doubling the spacing. */
/* A seventh of the old tint. Across a full-width row the 14% read as a
   highlighter stripe; the radio and the border already carry the selection,
   so the fill only has to group the row with its detail below. */

/* Same tint as the selected row above it and no border of its own, so the two
   read as one block rather than a card with something bolted underneath. The
   left padding lines the control up with the option label, not the radio. */
.ci-detail { background: rgba(201, 160, 85, 0.07); padding: 0 12px 12px 38px;
  display: grid; gap: 8px; }
.ci-every { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--muted); }
/* Mirrors the quantity pill above it — same border, radius and 44px tap floor
   — because it is the same kind of control and should not look like a second,
   unrelated one. */
.ci-step { display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-radius: var(--pill); background: var(--paper); }
.ci-step button { width: 40px; height: 40px; display: grid; place-items: center;
  font-size: 17px; color: var(--ink-2); border: 0; background: none; cursor: pointer; }
.ci-step button:hover:not(:disabled) { color: var(--ink); }
/* Dimmed rather than removed at the bounds: a control that vanishes reads as a
   bug, one that greys out reads as a limit. */
.ci-step button:disabled { color: var(--line); cursor: default; }
/* value and unit as one centred unit, so "30 days" reads as a phrase instead
   of a number and a word with a gap between them */
.ci-step-v { display: inline-flex; align-items: baseline; gap: 3px; }
.ci-step input { width: 2.2ch; padding: 0; border: 0; background: none;
  font: inherit; font-size: 14px; font-weight: 700; color: var(--ink);
  text-align: right; -moz-appearance: textfield; }
/* the native spinners duplicate the − / + buttons and shrink the typing area */
.ci-step input::-webkit-outer-spin-button,
.ci-step input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ci-step input:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 4px; }
.ci-step-u { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }

/* ---- recurring summary, cart and checkout ----
   A hairline and alignment rather than a filled box. The recurring amount is a
   continuation of the total directly above it — same label-left/price-right
   grammar, one weight quieter — so it belongs to that block instead of sitting
   beside it as a grey slab. It stays adjacent to the total, which is what
   makes it hard to miss; the box was doing nothing the position was not. */
/* Quieter than the option group's gold: two gold emphases stacked in one
   footer compete, and the price the reader is agreeing to should win. */
.cart-saved { color: var(--gold-deep); font-weight: 600; font-size: 12.5px; }

/* Catalogue hint. Advertises the option where price is compared; the choice
   itself is made in the cart, so this stays text and not a second button. */
.qty { display: inline-flex; align-items: center; gap: 2px; border: 1px solid var(--line); border-radius: var(--pill); }
/* 44px is the tap-target floor. The visual pill stays compact — padding does
   the work, not a bigger circle — so the control looks the same and stops
   being mis-tapped. */
.qty button { width: 44px; height: 44px; display: grid; place-items: center; font-size: 17px; color: var(--ink-2); }
.qty button:hover { color: var(--ink); }
.qty span { min-width: 24px; text-align: center; font-size: 14px; font-weight: 600; }
/* Tighter gap than the rest of the drawer: this block is now four short rows
   that belong together, not four separate sections. */
.cart-f { padding: var(--s-3); border-top: 1px solid var(--line-2); display: grid; gap: 12px; }
.cart-total { display: flex; justify-content: space-between; align-items: baseline; font-size: 15px; }
.cart-total b { font-size: 25px; letter-spacing: -.02em; }
.empty { text-align: center; color: var(--muted); padding: var(--s-5) 0; }
.empty svg { width: 42px; height: 42px; color: var(--faint); margin: 0 auto var(--s-2); }

/* ---------- footer ---------- */
footer { background: var(--ink); color: rgba(247,244,236,.72); padding: var(--s-6) 0 var(--s-4); }
footer h4 { color: var(--cream); font-size: 13px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; margin-bottom: var(--s-2); }
footer a:hover { color: var(--cream); }
.f-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: var(--s-4); }
.f-brand img { height: 24px; margin-bottom: var(--s-2); }
.f-list { display: grid; gap: 9px; font-size: 15px; }
.f-bottom { margin-top: var(--s-5); padding-top: var(--s-3); border-top: 1px solid rgba(247,244,236,.12);
  display: flex; flex-wrap: wrap; gap: var(--s-2); justify-content: space-between; font-size: 13px; }
/* the site's one explicit legal notice: readable, not shouted, and visually
   set apart from marketing copy so it reads as a legend rather than a claim */
.disclaimer {
  border: 1px solid rgba(247,244,236,.18); border-radius: var(--r);
  padding: var(--s-3); font-size: 13.5px; line-height: 1.65; margin-top: var(--s-4);
  max-width: 92ch;
}
.disclaimer strong { color: var(--cream); font-weight: 600; }

/* ---------- wa float ---------- */
.wa {
  position: fixed; right: 20px; bottom: 20px; z-index: 150;
  width: 54px; height: 54px; border-radius: var(--pill);
  background: var(--ink); color: var(--cream);
  display: grid; place-items: center; box-shadow: var(--shadow-2);
  transition: transform .25s var(--ease);
}
.wa:hover { transform: scale(1.06); }
.wa svg { width: 26px; height: 26px; }

/* ---------- reveal on scroll ---------- */
.rise { opacity: 0; transform: translateY(22px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.rise.in { opacity: 1; transform: none; }

/* ---------- responsive ---------- */
@media (max-width: 1000px) {
  .f-grid { grid-template-columns: 1fr 1fr; }
  .span-4, .span-3, .span-2 { grid-column: span 3; }
  .row { grid-template-columns: 1fr; }
  .row--flip > .row-media { order: 0; }
}
@media (max-width: 720px) {
  /* Mobile runs its own spacing scale rather than the desktop one squeezed
     down. At 88px a section's own padding was 21% of a 844px viewport, so two
     stacked sections put 176px of dead air between them — the single biggest
     reason the layout read as "adapted". Every consumer of these tokens (section
     padding, card padding, heading blocks, footer) retunes from this one place. */
  :root { --s-4: 28px; --s-5: 40px; --s-6: 56px; --s-7: 72px; }
  /* Stacked buttons share one edge. Letting each size to its own label is
     fine side by side, but once they wrap onto separate lines the ragged
     right edge reads as an accident rather than a choice. */
  .cta-row { flex-direction: column; align-items: stretch; }
  .cta-row .btn { width: 100%; }
  .ex-nav .btn { flex: 1 1 100%; }
  /* Footer links and the wordmark were 24px tall — fine to read, too small to
     hit. Padding rather than min-height so the list keeps its rhythm. */
  .f-list a { padding: 10px 0; }
  .brand { padding: 10px 0; }
  body { font-size: 16px; }
  .sec { padding: var(--s-6) 0; }
  .nav-links { display: none; }
  .burger { display: grid; }
  /* the toggle stays; only its padding tightens to buy room for the CTA */
  .lang button { padding: 5px 9px; font-size: 11px; }

  .bento { grid-template-columns: repeat(2, 1fr); }
  .span-6, .span-4, .span-3, .span-2 { grid-column: span 2; }
  /* stop spanning rows once the grid is only two columns wide */
  .card.tall { grid-row: auto; min-height: 400px; }
  .f-grid { grid-template-columns: 1fr; }
  /* the hero footage is 16:9; in portrait anchor it low and lift the copy above */
  #hero { height: 100vh; height: 100dvh; }
  /* Portrait: type sits ABOVE the footage, never over it.
     The frame is bled 9% past each edge so it renders taller (66.4vw instead of
     56.25vw). That absorbs the leftover vertical space rather than leaving it as
     a gap, and the 9% side crop is safe — the vial never reaches the frame edge
     even lying down. --frame-h keeps the copy and the cue locked to it. */
  /* Tall phones left ~33% of the screen empty when the panel was locked to the
     footage's 16:9 height. Driving it off vh instead lets the image absorb that
     space; the 63% crop offset keeps the vial inside the narrower window. */
  /* 52vh: dropping the eyebrow shortened the copy, and without this the freed
     height just reappeared as a bigger gap above the panel */
  /* 52vh left ~122px of dead air between the lead and the image while the
     headline sat only 38px under the nav — the slack was pooling in the wrong
     place. The image absorbs it instead of the gap, which also means a bigger
     product in a product-led hero. The lower bound protects short phones,
     where there is no slack to give away. */
  /* A fixed vh fraction cannot work here: tall phones have slack to give the
     image, short ones have none. 52vh pooled 122px of dead air under the lead
     on a 844px phone; 58vh fixed that but squeezed an SE down to 9px.
     So solve for the gap instead of the image. The copy block below the nav is
     ~245px + 4.5vh of top padding, and we want a constant ~75px of separation
     before the image, which leaves the image 95vh - 315px. The 62vh ceiling is
     the safety net for when the copy runs long (Spanish wraps further), and the
     240px floor protects the smallest screens. */
  /* 128vw ceiling: the portrait frame crops the 16:9 footage horizontally, and
     the taller the frame the harder it zooms. Past ~1.28x the viewport width
     the window narrows below the fallen vial's 41.8% span and clips its cap.
     Only bites on large phones, where it costs ~20px of image. */
  /* The pin needs an explicit height here. On desktop .hero-frame is
     position:relative and gives the pin its height, but in portrait the frame
     is absolute (anchored to the bottom, copy above it) and so are the copy and
     cue — with nothing in flow the pin collapsed to zero and the whole hero
     rendered blank. */
  #hero-pin {
    height: 100vh; height: 100dvh;
    --frame-h: clamp(240px, min(calc(95vh - 315px), 62vh, 126vw), 580px);
  }

  /* Base stays centred — correct for the standing vial and for the sharp still.
     heroInit() pans the sequence right across the fall; see site.js. */
  /* The copy owns the band between the nav and the image panel, and is centred
     in it. Bottom-aligning dumped all the leftover height above the text as an
     unplanned ~91px gap under a fixed nav; centring makes the space symmetric,
     which reads as a decision rather than a mistake, and it adapts to any
     viewport height instead of only working at one. */
  /* only the outer band is inset; .hero-beat fills it. Insetting both nests the
     offset twice and pushes the copy up behind the nav. */
  .hero-copy {
    top: var(--nav-h);
    bottom: var(--frame-h);
    height: auto;
    align-content: center;
    padding-top: 0;
    padding-bottom: 0;
  }
  /* Deliberately asymmetric now, and in the correct direction: the copy belongs
     to the top zone so it sits close under the nav, while the larger gap below
     separates it from the image panel. Symmetric spacing was safer than the old
     accidental 91px, but it left too much air above the headline. */
  .hero-beat {
    inset: 0;
    align-content: start;
    padding-top: clamp(24px, 4.5vh, 46px);
    padding-bottom: 0;
  }
  /* a hero headline should carry the frame: bigger type also absorbs slack,
     so less of it ends up as empty space */
  .hero-beat h1 { font-size: clamp(38px, 12vw, 76px); max-width: 13ch; }
  .hero-beat .lead { font-size: 17px; max-width: 27ch; margin-top: var(--s-3); }
  .hero-beat .eyebrow { margin-bottom: var(--s-2); }
  /* the page now moves from the first pixel of scroll, so the cue is redundant
     and it crowded the last line of the lead */
  .scroll-cue { display: none; }
  .hero-beat h1 { max-width: 15ch; }
  .hero-beat .lead { max-width: 30ch; }
  /* no scrim needed now that nothing overlaps; just a whisper at the edges */
  .hero-scrim {
    background: radial-gradient(ellipse 140% 110% at 50% 78%, transparent 66%, rgba(237,232,217,.75) 100%);
  }}
/* Four items now compete for one row. Below ~420px the wordmark is the one
   that loses, so pin it and take the space out of the gaps and the CTA. */
@media (max-width: 420px) {
  .nav .wrap { gap: 10px; }
  .brand { flex: 0 0 auto; }
  .brand img { height: 24px; }
  .nav-cta { padding: 9px 13px; font-size: 13px; }
  .lang { padding: 2px; }
  .lang button { padding: 5px 7px; }
}
/* Below ~360px the burger is the one that gets squeezed under its 44px tap
   target, so the order button drops to its count badge. The badge was already
   carrying the state; the word was the redundant half. */
@media (max-width: 360px) {
  .nav .wrap { gap: 8px; padding-left: 14px; padding-right: 14px; }
  .nav-cta > span:not(.count) { display: none; }
  .nav-cta { padding: 9px 11px; }
  .burger { flex: 0 0 44px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  #hero { height: 100vh; }
  #hero-pin { position: relative; }
  .hero-beat:first-of-type { opacity: 1 !important; }
  .hero-beat:not(:first-of-type) { display: none; }
  .scroll-cue { display: none; }
  .rise { opacity: 1; transform: none; }
}

/* the way into 63 compounds, from the catalog header */
.cat-explore {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  font-size: 15px; font-weight: 600; color: var(--gold-deep);
}
.cat-explore svg { width: 17px; height: 17px; transition: transform .2s var(--ease); }
.cat-explore:hover svg { transform: translateX(3px); }

/* the explorer on its own page: no hero above it, so it needs top clearance
   for the fixed nav and it carries the page heading rather than a section one */
.ex-page { padding-top: calc(var(--nav-h) + var(--s-5)); min-height: 70vh; }
/* Checkout and the research-area pages are documents, not landing pages. The
   global h1 ramps to 104px, which buried the form it sits above. */
.ex-page h1 { font-size: clamp(30px, 3.4vw, 46px); }

/* evidence options are checkboxes now, so they show their state as one */
.ex-opt { grid-template-columns: auto 1fr; align-items: start; gap: 14px; }
.ex-opt-body { display: grid; gap: 5px; }
.ex-opt-check {
  width: 22px; height: 22px; border-radius: 7px; margin-top: 1px;
  border: 1.5px solid var(--line); display: grid; place-items: center;
  color: transparent; transition: all .2s var(--ease);
}
.ex-opt-check svg { width: 14px; height: 14px; }
.ex-opt.is-on .ex-opt-check { background: var(--ink); border-color: var(--ink); color: var(--cream); }
.ex-hint { font-size: 13px; color: var(--faint); margin-top: 2px; }

/* ---------- verification band ----------
   Ink surface: the gold data marks measure 2.37:1 on cream, below the 3:1
   floor for non-text marks, and pass on ink. One series, so no legend — the
   caption names it. */
.q-card {
  background: var(--ink); color: var(--cream);
  border-radius: var(--r-lg); padding: clamp(20px, 4vw, 40px);
  box-shadow: var(--shadow-1);
}
.q-cap { display: flex; flex-wrap: wrap; gap: 6px var(--s-2); align-items: baseline; margin-bottom: var(--s-3); }
.q-cap .eyebrow { color: var(--gold); }
.q-summary { color: rgba(247,244,236,.66); }
.q-plot svg { width: 100%; height: auto; overflow: visible; }
.q-axis { stroke: rgba(247,244,236,.22); stroke-width: 1; }
.q-tick line { stroke: rgba(247,244,236,.22); stroke-width: 1; }
.q-tick text { fill: rgba(247,244,236,.5); font-size: 11px; text-anchor: middle;
  font-variant-numeric: tabular-nums; }
.q-thr line { stroke: rgba(247,244,236,.45); stroke-width: 2; stroke-dasharray: 3 4; }
.q-thr text { fill: rgba(247,244,236,.72); font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-anchor: middle; text-transform: uppercase; }
/* 2px surface ring so stacked dots stay countable where they overlap */
.q-dot { fill: var(--gold); stroke: var(--ink); stroke-width: 2; }
.q-dot--held { fill: none; stroke: var(--gold); stroke-width: 2; stroke-dasharray: 2 2; }

.q-details { margin-top: var(--s-3); border-top: 1px solid rgba(247,244,236,.14); padding-top: var(--s-2); }
.q-details summary { cursor: pointer; font-size: 14px; font-weight: 600;
  color: var(--gold); min-height: 44px; display: flex; align-items: center; }
.q-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 14px; }
.q-table th, .q-table td { text-align: left; padding: 8px 10px 8px 0;
  border-bottom: 1px solid rgba(247,244,236,.1); }
.q-table th { font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(247,244,236,.5); font-weight: 600; }
.q-table td { color: rgba(247,244,236,.82); }

.q-floor { margin-top: var(--s-4); max-width: 62ch; }
/* 10x10, so "out of a hundred" is the grid's shape and needs no explaining.
   The 3px gutter is the surface showing through — it separates the cells
   without drawing a border around each one. */
.q-units { display: grid; grid-template-columns: repeat(10, 1fr); gap: 3px;
  width: min(100%, 260px); margin: 4px 0 var(--s-2); }
.q-units i { aspect-ratio: 1; border-radius: 2px; background: var(--cream); }
.q-units i.is-v { background: var(--gold); }
/* a fractional floor (99.5) splits its cell instead of rounding to a whole one */
.q-units i.is-p { background: linear-gradient(to right,
  var(--gold) 0 var(--p), var(--cream) var(--p)); }

.q-key { display: flex; flex-wrap: wrap; gap: 6px var(--s-2);
  color: rgba(247, 244, 236, .6); margin-bottom: 10px; }
.q-key-i { display: inline-flex; align-items: center; gap: 7px; }
/* the key carries a label beside each swatch, so the two classes are never
   distinguished by colour alone */
.q-key-i i { width: 11px; height: 11px; border-radius: 2px;
  background: var(--cream); flex: none; }
.q-key-i i.is-v { background: var(--gold); }
.q-floor-note { color: rgba(247, 244, 236, .66); }

.q-legend { display: flex; align-items: center; gap: 8px; margin-top: 10px;
  color: rgba(247,244,236,.6); }
.q-legend[hidden] { display: none; }
.q-legend svg { width: 14px; height: 14px; flex: none; overflow: visible; }

/* ---------- dual currency ----------
   Both figures always shown; the leading one takes the weight, the second
   drops to a muted line beneath so a price still scans as one number. */
.fx { display: inline-flex; flex-direction: column; line-height: 1.2; }
.fx > :first-child { font-weight: inherit; }
.fx > :last-child { font-size: .78em; font-weight: 500; color: var(--muted); }
.fx small { font-weight: 500; opacity: .62; }
.fx--inline { flex-direction: row; flex-wrap: wrap; gap: 0 6px; align-items: baseline; }
.fx--inline > :last-child { font-size: .88em; }
/* The total's second line is the sum actually charged to the card. Secondary,
   but not shrunk to a footnote: Colombian consumer rules expect the amount
   debited to be legible before purchase, not discovered on the statement. */
.fx--total > :last-child { font-size: .8em; }
.cart .fx > :last-child, .cart .fx small { color: var(--muted); }
.ci--na .ci-name, .ci--na .ci-meta { opacity: .55; }
.ci-na { color: var(--faint); font-weight: 500; font-size: 12.5px; }
.cart-blocked { margin-top: 6px; color: var(--faint); line-height: 1.5; }

.co-fixed { display: block; padding: 12px 0; color: var(--ink); font-weight: 500; }

.policy { max-width: 68ch; margin-top: 40px; }
.policy h2 { margin: 34px 0 10px; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.policy p { margin: 0; color: var(--muted); line-height: 1.75; }
.policy-note { margin-top: 40px !important; padding: 18px 20px; border: 1px solid var(--line);
  border-radius: 12px; font-size: 13.5px; color: var(--faint); }

.cart-banner { display: block; width: 100%; height: 132px; object-fit: cover;
  object-position: center 42%; border-bottom: 1px solid var(--line); }
/* The drawer's own tick, which the checkout page used to own. Sits above the
   button it gates, with room to be tapped without hitting the button. */
.cart-f .ruo-check { display: flex; gap: 10px; align-items: flex-start; margin: 4px 0 12px;
  font-size: 12.5px; line-height: 1.5; color: var(--muted); cursor: pointer; }
.cart-f .ruo-check input { margin-top: 2px; flex: none; width: 16px; height: 16px; accent-color: var(--ink); }

.fx-note { margin-top: 8px; color: var(--faint); font-size: 12.5px; line-height: 1.5; }

/* ---------- research explorer -> catalogue ---------- */
.ex-shop {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 12px;
  font-size: 14px; font-weight: 600; color: var(--gold-deep); min-height: 44px;
}
.ex-shop svg { width: 16px; height: 16px; transition: transform .2s var(--ease); }
.ex-shop:hover svg { transform: translateX(3px); }

/* Brief orientation cue when arriving from a research link — a ring rather
   than a colour change, so it reads as "here" and not as a new status. */
.p.is-focused {
  box-shadow: 0 0 0 2px var(--gold), var(--shadow-2);
  transition: box-shadow .3s var(--ease);
}

/* ---------- checkout ---------- */
.co-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s-4); align-items: start; }
.co-form { display: grid; gap: var(--s-3); }
.co-set { border: 1px solid var(--line-2); border-radius: var(--r); padding: var(--s-3); }
.co-set legend { padding: 0 8px; font-size: 12px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold-deep); }
.co-field { display: grid; gap: 6px; margin-top: var(--s-2); }
.co-field:first-of-type { margin-top: 0; }
.co-field label { font-size: 14px; font-weight: 600; }
.co-field input, .co-field select, .co-field textarea {
  width: 100%; min-height: 48px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper); font: inherit; color: var(--ink);
  transition: border-color .2s var(--ease);
}
.co-field textarea { min-height: 72px; resize: vertical; }
.co-field :is(input, select, textarea):focus-visible { border-color: var(--gold-deep); }
.co-field .is-invalid { border-color: var(--danger); }
.co-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
/* Both children are .co-field, so only the first zeroed its top margin and the
   second sat 16px lower than its partner. The row owns the spacing. */
.co-row .co-field { margin-top: 0; }
/* An input's width should hint at the length of what goes in it. At full column
   width these ran to ~600px, around 60 characters for a name. */
.co-field > :is(input, select, textarea) { max-width: 30rem; }
.co-row .co-field > :is(input, select) { max-width: none; }
.co-hint { font-size: 13px; color: var(--muted); }
.co-err { font-size: 13px; color: var(--danger); min-height: 0; }
.co-err:empty { display: none; }

.co-summary { background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r); padding: var(--s-3); position: sticky; top: calc(var(--nav-h) + 16px); }
.co-line { display: flex; justify-content: space-between; gap: var(--s-2);
  padding: 10px 0; border-bottom: 1px solid var(--line-2); }
/* The price column must never be the one that shrinks, or it steals width from
   the name and "100mg · ×2" breaks across two lines on a phone. */
.co-line > :first-child { flex: 1 1 auto; min-width: 0; }
.co-line > :last-child { flex: 0 0 auto; text-align: right; white-space: nowrap; }
.co-line-p { font-size: 15px; font-weight: 600; color: var(--ink); }
.co-line-p small { font-size: .74em; font-weight: 500; opacity: .55; }
/* The two agreements read as one decision point, so they sit together with a
   tighter gap than the sections around them. */
.co-agrees { display: grid; gap: 8px; }
.co-line-n { font-weight: 600; font-size: 15px; }
/* Marks which lines recur, in the summary where the buyer scans what they are
   about to agree to. Gold because it is the one thing on this line that
   changes what happens after today. */
.co-total { display: flex; justify-content: space-between; align-items: baseline;
  margin-top: var(--s-2); font-size: 18px; font-weight: 700; }
.co-secure { margin-top: 10px; color: var(--faint); font-size: 12.5px; }
.co-empty { display: grid; gap: var(--s-3); justify-items: start; }
.btn.is-disabled { opacity: .45; cursor: not-allowed; }

@media (max-width: 860px) {
  .co-grid { grid-template-columns: 1fr; }
  /* the summary leads on mobile: people want to see what they are paying for
     before they start typing */
  .co-summary { position: static; order: -1; }
}
@media (max-width: 480px) { .co-row { grid-template-columns: 1fr; } }

/* The cart image earns its place in empty states: nothing is competing with it
   there, and a bare "your order is empty" line is a dead end. Intrinsic
   width/height on the tags so neither reserves zero space while loading. */
/* Fixed width, not a percentage: these sit inside grids that size to their
   content, so a percentage resolves against a container that is itself waiting
   on this element and collapses. max-width keeps it honest on narrow screens. */
.co-empty-img {
  width: 320px; max-width: 100%; height: auto;
  border-radius: var(--r); margin-bottom: var(--s-2);
}

/* Order-summary banner. Height is capped and the image covers, because at its
   natural 1.9:1 it would run ~240px in this column and push the pay button
   down the page. object-position holds the vials and lets the empty backdrop
   and lower basket crop away. */
.co-banner {
  width: 100%; height: 132px; object-fit: cover; object-position: 50% 42%;
  border-radius: var(--r-sm); margin-bottom: var(--s-2); display: block;
}
@media (max-width: 860px) { .co-banner { height: 112px; } }

/* ---------- order status ----------
   One card, centred. The state colours the mark only: a full-page red on a
   declined payment reads as an error the buyer caused, when usually their bank
   simply said no and they can just try again. */
.or-card {
  max-width: 46rem; margin: 0 auto; text-align: center;
  background: var(--paper); border: 1px solid var(--line-2);
  border-radius: var(--r-lg); padding: clamp(28px, 5vw, 56px);
  box-shadow: var(--shadow-1);
}
.or-mark {
  width: 64px; height: 64px; margin: 0 auto var(--s-3);
  border-radius: var(--pill); display: grid; place-items: center;
}
.or-mark svg { width: 30px; height: 30px; }
.or-card[data-state="approved"] .or-mark { background: rgba(47,122,79,.12); color: var(--ok); }
.or-card[data-state="pending"]  .or-mark { background: var(--gold-tint);    color: var(--gold-deep); }
.or-card[data-state="declined"] .or-mark { background: rgba(179,69,47,.10); color: var(--danger); }
.or-card[data-state="error"]    .or-mark { background: var(--line-2);       color: var(--muted); }
.or-card h1 { margin-bottom: var(--s-2); }
.or-card .lead { margin-inline: auto; }
.or-meta {
  display: flex; justify-content: center; gap: var(--s-4);
  flex-wrap: wrap; margin-top: var(--s-3);
  padding-top: var(--s-3); border-top: 1px solid var(--line-2);
}
.or-meta[hidden] { display: none; }
.or-meta dt { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); }
.or-meta dd { font-weight: 600; margin-top: 2px; }
.or-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: var(--s-4); }
.or-help { margin-top: var(--s-3); color: var(--muted); }
.or-help a { color: var(--gold-deep); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 560px) { .or-actions .btn { width: 100%; } }

/* ---------- newsletter signup ----------
   Deliberately lighter chrome than the age gate. The gate is a wall the reader
   has to answer; this is an offer they can walk past, so it dims rather than
   blacks out, it does not lock scroll, and Escape closes it. */
.nl {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: var(--wrap-pad);
  opacity: 0; transition: opacity .22s var(--ease);
}
.nl.is-open { opacity: 1; }
.nl-scrim { position: absolute; inset: 0; background: rgba(26,24,21,.42); }
/* Same liquid glass as the age-gate card, so the two dialogs read as one
   family, but denser: .92/.84 rather than the gate's .86/.72. The gate carries
   large ink text and gets away with a thinner pane; this card has 12 to 14px
   supporting copy, and at the gate's opacity that copy measured 3.73:1 over a
   cream page and 2.55:1 over a dark one. Even at .84 the body had to move off
   --muted, which would have needed 99% opacity to clear 4.5:1 -- at which
   point it is not glass at all. See .nl-body .small below. */
.nl-card {
  position: relative; width: min(440px, 100%);
  border-radius: 28px;
  padding: var(--s-4) var(--s-3) var(--s-3);
  background: linear-gradient(165deg, rgba(252,250,245,.92), rgba(237,232,217,.84));
  backdrop-filter: blur(30px) saturate(180%);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  border: 1px solid rgba(255,255,255,.6);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(26,24,21,.05),
    0 30px 80px rgba(26,24,21,.22),
    0 4px 16px rgba(26,24,21,.08);
  transform: translateY(10px) scale(.985);
  transition: transform .26s var(--ease);
}
/* No backdrop-filter means no blur, and a translucent pane over unblurred
   catalogue rows is unreadable. Fall back to the opaque surface. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nl-card { background: var(--paper); box-shadow: var(--shadow-2); }
}

/* The offer, sized to be the first thing read. Measured on the glass at
   10.48:1 in the worst case. Deliberately --ink rather than gold: gold only
   reaches 2.39:1 here, and the darkest gold that clears the 3:1 large-text
   floor (#8B662E, 3.08:1) sits too close to the line and reads muddy. */
.nl-offer { display: flex; flex-direction: column; gap: 6px; }
.nl-pct {
  /* Measured: this line renders 3.575px wide per px of font-size, so at the
     78px ceiling it is 279px inside a 390px card, and at the floor it clears
     a 320px phone's 224px with room to spare. The floor is 52 rather than 58
     to leave headroom for the fallback face that renders before Inter swaps
     in, which is wider; without it a 320px screen could break "10% off"
     across two lines. */
  font-size: clamp(52px, 15vw, 78px); font-weight: 700;
  letter-spacing: -.045em; line-height: .9; color: var(--ink);
  /* the ceiling; fitBigLine() in site.js takes it down when the translated
     phrase is too wide, and only then */
  font-variant-numeric: tabular-nums;
}
.nl-rest {
  font-size: clamp(19px, 2.6vw, 22px); font-weight: 600;
  letter-spacing: -.02em; line-height: 1.15; color: var(--ink);
}
/* the success state has no figure to lead with, so it stays a plain heading */
.nl-offer--done { display: block; font-size: clamp(23px, 3.4vw, 28px); }
.nl.is-open .nl-card { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .nl, .nl-card { transition: none; }
}
.nl-body { display: flex; flex-direction: column; gap: 12px; }
.nl-card h2 { font-size: clamp(23px, 3.4vw, 28px); letter-spacing: -.02em; }
.nl-x {
  position: absolute; top: 10px; right: 10px;
  width: 40px; height: 40px; display: grid; place-items: center;
  border: 0; background: none; color: var(--muted); cursor: pointer;
  border-radius: 50%; transition: background .15s var(--ease), color .15s var(--ease);
}
.nl-x:hover { background: var(--line-2); color: var(--ink); }
.nl-x svg { width: 17px; height: 17px; }

.nl-form { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.nl-form input {
  flex: 1 1 190px; min-width: 0; min-height: 48px; padding: 12px 15px;
  border: 1px solid var(--line); border-radius: var(--pill);
  background: var(--cream); font: inherit; font-size: 16px; color: var(--ink);
  /* 16px, not smaller: iOS zooms the page on a focused input under 16px, and
     a popup that jerks the viewport on tap is a popup people close */
  transition: border-color .18s var(--ease);
}
.nl-form input:focus { outline: none; border-color: var(--gold); }
/* There is no visible <label>, so the placeholder is the field's label and
   has to be legible: --faint is 2.83:1 on --cream, --muted is 5.14:1. */
.nl-form input::placeholder { color: var(--muted); }
.nl-go { flex: 0 0 auto; min-height: 48px; }
.nl-go:disabled { opacity: .6; cursor: default; }
/* --danger is 3.26:1 on the glass. This is the same hue carried darker. */
.nl-err { font-size: 13.5px; color: #992B15; }
/* --ink-2, re-measured once the card became glass. --faint was 2.98:1 and
   --muted 3.93:1 on the translucent surface; --ink-2 is 6.04:1 in the worst
   case. This is where the consent and unsubscribe terms live. */
.nl-fine { font-size: 12px; line-height: 1.5; color: var(--ink-2); }
/* .small is --muted globally, which does not survive the glass */
.nl-body .small, .nl-card .eyebrow { color: var(--ink-2); }
/* 3.54:1 on its own tint, which clears the 3:1 floor because 21px at weight
   700 is "large text" under WCAG (14pt bold). Do not shrink it below 18.66px
   without darkening the ink. */
.nl-code {
  font-size: 21px; font-weight: 700; letter-spacing: .08em;
  /* Opaque, not a tint: a translucent fill would inherit whatever the glass
     is sitting over, and the code's contrast would depend on the page behind
     it. Fixed at 3.33:1, which the 21px/700 type clears as large text. */
  color: var(--gold-deep); background: #F3E8D2;
  border: 1px dashed rgba(157,120,64,.55); border-radius: var(--r-sm);
  padding: 14px; text-align: center;
  /* selectable on purpose: someone will want to paste this somewhere */
  user-select: all;
}

/* the code, named next to the saving it is responsible for */
.promo-chip {
  display: inline-block; margin-right: 7px;
  font-size: 11px; font-weight: 700; letter-spacing: .07em;
  /* A darker gold than --gold-deep: at 11px this is normal-size text and
     needs 4.5:1, which --gold-deep misses on its own tint (3.48:1). This
     lands at 4.60:1 and stays in the same hue family. */
  color: #89642C; background: rgba(201,160,85,.14);
  border-radius: var(--pill); padding: 3px 9px;
  font-variant-numeric: tabular-nums;
}

/* ---------- market picker ----------
   Same pill grammar as the language toggle beside it, one step quieter: the
   market matters, but it is not a thing most visitors will touch. */
.mkt { display: flex; gap: 2px; background: rgba(26,24,21,.05); border-radius: var(--pill); padding: 3px; }
.mkt button {
  min-height: 30px; padding: 4px 9px; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  color: var(--muted); border-radius: var(--pill);
  transition: background .18s var(--ease), color .18s var(--ease);
}
.mkt button:hover { color: var(--ink); }
.mkt button.is-on { background: var(--paper); color: var(--ink); box-shadow: 0 1px 2px rgba(26,24,21,.06); }

/* ---------- out of stock ----------
   Dimmed, not hidden: the line is carried and coming back, and someone who
   came looking for it should learn that rather than conclude it was dropped.
   Opacity alone would be ambiguous, so the label says it in words. */
.p--out { opacity: .62; }
.p--out:hover { transform: none; box-shadow: none; border-color: var(--line-2); }
.btn-add.is-out {
  background: transparent; color: var(--muted);
  border: 1px solid var(--line); cursor: not-allowed;
}
.btn-add.is-out:hover { transform: none; background: transparent; }
.p-size-chip.is-out {
  opacity: .45; text-decoration: line-through;
  text-decoration-thickness: 1px; cursor: not-allowed;
}
.p-size-chip.is-out:hover { border-color: var(--line); }
