/*
Theme Name: KAIRO
Description: KAIRO Peptides Storefront child theme. Full template port: header,
             footer, front page, shop archive, single product (incl. gated
             purchase state), and verify page, matching the static reference
             site at /Users/ct/dev/ovae-site-v2/kairo/site/ (parity is the
             spec). Process pattern reused from the luxe-peptides staging
             build (/Users/ct/dev/luxe-peptides/staging/); ZERO design/copy/
             theme convergence with Luxe.
Author: Ovae
Template: storefront
Version: 0.2.0
Text Domain: kairo
*/

/* =========================================================================
   Below: KAIRO design system, ported verbatim (selectors unchanged) from
   /Users/ct/dev/ovae-site-v2/kairo/site/css/site.css. Color/type tokens live
   in tokens.css (enqueued before this file). Only path fixed: the single
   url() reference (hero-mobile background) now points at this theme's own
   assets/ dir instead of the static site's relative ../assets/.
   ========================================================================= */

/* =========================================================================
   TYPE SCALE FLOOR (2026-07-26, CT typography + alignment sweep): CT's
   verbatim complaint was "too many places with too-small text", measured
   against the mini-cart drawer's own product-name size (.kc-item-name,
   13px) as the reference floor. Fixed here, once, as reusable custom
   properties instead of one-off px bumps scattered across every rule below,
   so the next size correction is a one-line edit, not a re-sweep:
     --fs-micro   13px  absolute floor, ONLY for wide-letterspaced mono
                        micro-labels (eyebrows, badges, step numerals,
                        table-header captions) -- text that reads as a small
                        caps LABEL, never as a sentence to read continuously.
     --fs-meta    15px  everything else informational or interactive: dose
                        lines, footer links/legal, table data, form inputs,
                        buttons, notes -- the hard floor CT set for this
                        whole class of text.
     --fs-body    16px  default paragraph/body copy.
     --fs-emphasis 18px "You're saving $X" cart/checkout line (CT-approved
                        2026-07-26 correction: deliberately larger than
                        --fs-meta so it reads as a stand-out conversion
                        signal, not shrunk to fit the meta tier). Never
                        resize this token to match --fs-meta; it is its own
                        deliberate step.
   Nav links (.site-header .nav-links, the hero-mobile nav row) and hero/
   display sizes (.h1/.h2/.h3, hero eyebrows excluded -- see below) are
   OUT of scope per CT's explicit instruction: nav was already enlarged in
   an earlier pass, and display sizing is a discretionary call, not a defect.
   ========================================================================= */
:root{
  --fs-micro:13px;
  --fs-meta:15px;
  --fs-body:16px;
  --fs-emphasis:18px;
}

*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
  background:var(--slate);
  color:var(--mist);
  font-family:var(--font-body);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img,video{max-width:100%;display:block;}
a{color:inherit;}
::selection{background:rgba(233,235,238,.24);}

/* =========================================================================
   GLOBAL FOCUS-VISIBLE SYSTEM (2026-07-24)
   Root cause: Storefront's own parent style.css ships one blanket rule,
   `a:focus,input:focus,textarea:focus,button:focus{outline:2px solid
   #7f54b3}` (WordPress-brand purple, WooCommerce's own admin color, not
   KAIRO's), firing on every focus event (not just keyboard) for exactly
   those four tags. Anything NOT one of those four tags (summary, custom
   controls) fell through to the browser's own default UA ring instead.
   Per-component overrides existed already (.btn:focus-visible, the two
   primary Woo Blocks CTAs, checkboxes, radios, the coupon button) but each
   one only covered its own selector, so any new/overlooked interactive
   element (nav links, sku-card, FAQ <summary>, the cart icon) kept showing
   whichever default was underneath.
   Fixed here ONCE, at the root, instead of per element:
   1) neutralize Storefront's blanket rule at equal specificity so nothing
      upstream can reassert purple;
   2) one global `:focus-visible` rule (keyboard-only, matches platform
      convention: no ring on mouse/touch clicks) applies copper to every
      focusable element sitewide, including tags/components with no
      bespoke rule at all.
   The per-component overrides above/below (.btn, Woo Blocks buttons,
   checkboxes, radios, coupon button, text inputs' border-color) still fire
   and still layer their own extra treatment (e.g. an input's border also
   goes copper) on top of this ring; this system is the sitewide floor, not
   a replacement for them. */
/* `:not(:focus-visible)`, not a blanket `:focus` reset: an earlier version
   of this rule used `a:focus,button:focus,input:focus,textarea:focus{
   outline:none !important}`, which has HIGHER specificity ((0,1,1): element
   + pseudo-class) than the bare `:focus-visible{...}` rule below it
   ((0,1,0): pseudo-class only) -- with both sides carrying `!important`,
   specificity is the tie-breaker, so that blanket reset was silently
   winning over the copper ring on exactly the 4 tags it targeted (a,
   button, input, textarea), the same 4 tags Storefront's purple rule
   targets. Confirmed on the PDP qty input via a real Tab keypress before
   fixing (outline-style computed as `none`, not `solid`). Scoping the
   reset to `:not(:focus-visible)` makes the two rules mutually exclusive
   by definition (one never matches when the other does) instead of
   competing on specificity. */
a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
input:focus:not(:focus-visible),
textarea:focus:not(:focus-visible){ outline:none !important; }

:focus-visible{
  outline:2px solid var(--copper) !important;
  outline-offset:2px;
}
/* WooCommerce Blocks' generic button base (quantity steppers, remove-item,
   any block button with no bespoke focus rule of its own) ships a
   WordPress-blue box-shadow ring independent of `outline`
   (`.wc-block-components-button:not(.is-link):focus`, packages-style.css);
   an `outline` override alone can't remove a `box-shadow`, so it rendered
   blue+copper stacked. Zeroed here, at the top of the cascade, so the
   copper `:focus-visible` outline above is the only ring on these; the
   cart/checkout submit buttons and the coupon-apply button further down
   this file redeclare their own `box-shadow` after this and still win
   (later same-specificity + !important beats !important here). */
.wc-block-components-button:focus{ box-shadow:none !important; }

/* ---------- container / section rhythm ---------- */
.container{ max-width:var(--container); margin:0 auto; padding:0 var(--gutter); }
.section{ padding:var(--section-y) 0; }
.section--tight{ padding:64px 0; }
.section--porcelain{ background:var(--porcelain); color:var(--ink); }
.section--slate{ background:var(--slate); color:var(--mist); }
.section-head{ max-width:640px; margin-bottom:48px; }
.section-head--center{ margin-left:auto; margin-right:auto; text-align:center; }

@media (max-width:767px){
  :root{ --section-y:72px; --gutter:6.5vw; }
  .section-head{ margin-bottom:32px; }
}

/* ---------- type scale ---------- */
/* 10.5px -> var(--fs-micro) (2026-07-26 sweep): the shared eyebrow label,
   the canonical "wide-letterspaced mono micro-label" the type-scale floor's
   13px exception exists for. */
.eyebrow{
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.22em; text-transform:uppercase; color:rgba(233,235,238,.62);
  display:block; margin-bottom:14px;
}
/* Contrast (2026-07-26 fix wave B): the porcelain variant of the eyebrow
   measured 3.99:1 against the porcelain ground (rgba(23,21,15,.56) over
   #F1EDE4) -- below the 4.5:1 AA floor, and 13px is nowhere near the
   large-text exemption. Alpha raised to .64, which measures 5.15:1; the
   minimum that clears 4.5:1 here is .5981, so .64 keeps headroom without
   flattening the label into the .h2 above it (the slate-side eyebrow reads
   at a comparable 6.4:1, so the two grounds now sit in the same band). */
.section--porcelain .eyebrow{ color:rgba(23,21,15,.64); }

.h1{ font-family:var(--font-display); font-weight:400; letter-spacing:.005em; line-height:1.12; color:var(--porcelain); }
.h2{ font-family:var(--font-display); font-weight:400; letter-spacing:.005em; line-height:1.16;
     font-size:clamp(28px,3.4vw,42px); color:var(--porcelain); }
.section--porcelain .h2{ color:var(--ink); }
.h3{ font-family:var(--font-display); font-weight:400; font-size:clamp(19px,1.8vw,23px); line-height:1.3; color:inherit; }

.body-lg{ font-size:16.5px; line-height:1.75; letter-spacing:.008em; color:rgba(233,235,238,.74); }
.section--porcelain .body-lg{ color:rgba(23,21,15,.68); }
/* 14.5px -> var(--fs-body) (2026-07-26 sweep): the sitewide default paragraph
   class (home description copy, PDP RUO text, policy pages, empty-state
   copy) was below the informational floor CT set. */
.body{ font-size:var(--fs-body); line-height:1.7; letter-spacing:.006em; color:rgba(233,235,238,.68); }
.section--porcelain .body{ color:rgba(23,21,15,.62); }

/* 11px -> var(--fs-micro) (2026-07-26 sweep): shared small-caps label used
   for footer nav-column headings ("Shop", "Company") -- a label, not a
   sentence, so it takes the micro floor rather than the meta floor. */
.mono-label{ font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em; text-transform:uppercase; }
.mono-data{ font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.01em; }

/* ---------- buttons ----------
   CTA style: bold-700 Switzer, uppercase, 8px radius (--btn-radius, shared
   token with the static site; never a full pill, per 2026-07-24 ruling).
   Accessibility (measured, STUDY-NOTES lock addendum):
   ember passes 4.5:1 ONLY with #000 text; deep-pressed requires #fff text.
   text-decoration:none is !important (2026-07-26 UI-state sweep): Storefront's
   own `.hentry .entry-content a{text-decoration:underline}` (two classes)
   outranks this rule's one class whenever a .btn lands inside the normal
   page-template's `the_content()` wrapper, the case every standalone
   template (404.php, search.php, front-page.php) never hits since none of
   them route through that wrapper, but the Cart/Checkout Blocks content and
   the order-received CTA do. Caught on the new empty-cart CTA underlining
   itself; fixed in the shared class so no future .btn inside page content
   can silently regress the same way. */
/* 12.5px -> var(--fs-meta) (2026-07-26 sweep): every primary/secondary CTA
   label sitewide was below the interactive floor CT set. min-height/padding
   are unaffected (buttons aren't fixed-width), so this only widens buttons
   slightly, it doesn't wrap or clip them (re-verified at 390/360). */
/* text-align:center (2026-07-26 fix wave B): `justify-content:center`
   centers the flex ITEM, and a bare text run inside a flex container becomes
   one anonymous block-level flex item that fills the available width, so the
   moment a label wraps to two lines the individual LINES align to the start
   of that item instead of to the pill. Caught on the affiliates CTA, whose
   "Become a KAIRO affiliate" label wraps at 390 and rendered ragged-left
   inside a centered ember pill. Fixed on the shared class, not the one
   caller, so every wrapping .btn label sitewide (404, empty cart,
   order-received, any future long CTA) is covered. */
.btn{
  appearance:none; border:none; cursor:pointer; text-decoration:none !important;
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  text-align:center;
  font-family:var(--font-body); font-weight:700; font-size:var(--fs-meta);
  letter-spacing:.14em; text-transform:uppercase;
  border-radius:var(--btn-radius); min-height:52px; padding:0 30px;
  transition:background-color .15s ease, color .15s ease, border-color .15s ease;
}
.btn-primary{ background:var(--ember); color:var(--on-ember); border:1px solid var(--ember); }
.btn-primary:hover{ background:#cf6527; border-color:#cf6527; color:var(--on-ember); }
.btn-primary:active{ background:var(--deep); border-color:var(--deep); color:var(--on-deep); }
.btn-primary--block{ width:100%; min-height:64px; font-size:var(--fs-meta); letter-spacing:.16em; }
.btn-secondary{ background:transparent; color:var(--pale); border:1.5px solid var(--copper); }
.btn-secondary:hover{ color:var(--porcelain); border-color:var(--pale); }
/* Contrast (2026-07-26 round-2 QA): copper on porcelain measures 3.48:1, under
   the 4.5:1 AA floor for the label AND for the hover fill (porcelain on
   copper is the same pair, 3.48:1). --deep on porcelain measures 7.79:1 both
   ways, so the label and the hover fill both move to it. The 1.5px copper
   border stays: it is non-text UI, and 3.48:1 clears the 3:1 floor. */
.btn-outline-ink{ background:transparent; color:var(--deep); border:1.5px solid var(--copper); }
.btn-outline-ink:hover{ background:var(--deep); border-color:var(--deep); color:var(--porcelain); }
/* Brand focus ring: the browser default (blue on Chrome, violet on this
   stack's WP-admin-color-adjacent UA styles) never appears on this theme's
   interactive controls, buttons included. */
.btn:focus-visible{ outline:2px solid var(--copper); outline-offset:2px; }

/* 11.5px -> var(--fs-meta) (2026-07-26 sweep): this is a real interactive
   link ("View the verification", order-received "Continue shopping"), not
   a micro-label -- takes the meta floor. */
.quiet-link{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta); letter-spacing:.1em; text-transform:uppercase;
  color:rgba(233,235,238,.68); text-decoration:underline; text-decoration-color:rgba(233,235,238,.34);
  text-underline-offset:3px;
}
.quiet-link:hover{ color:var(--porcelain); }
.section--porcelain .quiet-link{ color:rgba(23,21,15,.6); text-decoration-color:rgba(23,21,15,.3); }

/* ---------- trust-check list ---------- */
/* Root cause (2026-07-26 fix wave B): Storefront's parent style.css ships
   `ul,ol{margin:0 0 1.41575em 3em}`. That is a TYPE selector (0,0,1), which
   outranks this file's own `*{margin:0;padding:0}` reset (0,0,0) at the top,
   so every un-reset list in the theme silently inherits a 3em (48px) left
   margin plus a 22.65px bottom margin from the parent theme. On the desktop
   hero that pushed the trust rows 48px out of the hero column while their
   own `border-top` hairline (.trust-row-wrap, which is NOT a list) stayed at
   the column edge, so the hairline visibly overhung its own text (measured
   at 1440: wrap left 79.19, list left 127.19; identical 48px at 1920).
   Explicit reset here rather than a blanket `ul,ol` override: a sweep of
   every rendered list on home/shop/PDP/verify/cart/checkout/my-account/
   affiliates/policy (2026-07-26) found `.trust-list` was the ONLY leaker,
   every other list in this theme (.policy-list, .oc-overview, .oc-steps,
   .woocommerce-MyAccount-navigation ul, both pagination wrappers) already
   carries its own margin/padding reset, and the header/hero nav rows are
   <nav> elements holding bare <li>s, so the ul/ol rule never reaches them.
   A regression check for the
   whole class lives in import/scripts/verify-css-waveb.mjs. */
.trust-list{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:14px 28px; }
.trust-list--stacked{ flex-direction:column; align-items:flex-start; gap:12px; }
/* 12px -> var(--fs-meta) (2026-07-26 sweep): informational trust-row copy
   (home/PDP "PURITY EXCEEDS 99%" etc). */
.trust-row{ display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta); letter-spacing:.06em; color:rgba(233,235,238,.7); }
.trust-list--stacked .trust-row{ font-size:16px; letter-spacing:.03em; color:var(--mist); }
.trust-check{ flex:0 0 18px; width:18px; height:18px; fill:none; stroke:var(--copper); stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
.trust-list--stacked .trust-check{ flex-basis:20px; width:20px; height:20px; }

/* =========================================================================
   HEADER
   ========================================================================= */
.site-header{
  position:absolute; top:0; left:0; right:0; z-index:30;
  display:flex; align-items:center; justify-content:space-between;
  padding:32px var(--gutter) 0;
}
/* Storefront's own clearfix rule (`.site-header::before,.site-header::after
   {content:"";display:table}`) still fires on our flex header: a display
   value gets blockified for a flex item, but the generated box still counts
   as a flex participant, so justify-content:space-between was distributing
   space across 5 slots (2 invisible + wordmark + nav + cart) instead of 3,
   shoving the wordmark ~270px off the gutter at 1440. Kill the boxes
   outright; !important guards the same double-stylesheet load-order quirk
   documented below (kairo/style.css is enqueued twice, once before and once
   after Storefront's, so specificity ties are the only real defense). */
.site-header::before,
.site-header::after{ content:none !important; }
.site-header .wordmark{ display:block; line-height:0; }
.site-header .wordmark img{
  height:clamp(19px, calc(3.8px + 1.3333vw), 28px); width:auto; display:block;
}
.site-header .nav-links{
  display:flex; gap:40px; font-family:var(--font-mono);
  font-size:clamp(12.5px, calc(8.2px + 0.4028vw), 16px); font-weight:500;
  letter-spacing:.16em; text-transform:uppercase; color:rgba(233,235,238,.85);
}
.site-header .nav-links li{ list-style:none; }
.site-header .nav-links a{ text-decoration:none; }
.site-header .nav-links a:hover{ color:#fff; }
.site-header .nav-right{ display:flex; align-items:center; gap:20px; }
.nav-cart{
  position:relative; appearance:none; background:transparent; border:none; padding:8px; margin:-8px;
  display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--mist);
  text-decoration:none;
}
.nav-cart svg{ display:block; width:27px; height:27px; }
/* 10.5px -> var(--fs-micro) (2026-07-26 sweep): the cart-count badge is a
   "badge" per the micro floor's own naming; box grown 18px -> 20px so a
   two-digit count still has clear padding at the larger size. */
.nav-cart-count{
  position:absolute; top:-1px; right:-1px; min-width:20px; height:20px; padding:0 4px;
  border-radius:999px; background:var(--ember); color:var(--on-ember);
  font-family:var(--font-mono); font-size:var(--fs-micro); line-height:20px; text-align:center;
  display:none;
}
.nav-cart-count.is-visible{ display:block; }

/* pages without a hero image need a solid header ground (the static site's
   header is position:absolute over a hero image; interior pages compensate
   with padding-top on their first section, see below-fold rules). Storefront
   core also adds a body.home / body.woocommerce class we don't rely on. */
body:not(.home) .site-header{ background:var(--slate); border-bottom:1px solid var(--hairline); padding-bottom:20px; }
/* Root cause (found during the 2026-07-23 regression sweep): Storefront
   injects an inline customizer <style> block (no theme-mod ever set, so
   its own shipped default) that sets `.site-header{background-color:#fff}`
   unconditionally, specificity-tied with our own un-scoped `.site-header`
   rules and printed after them in the head, so it wins on the home page
   specifically (the one case body:not(.home) above deliberately excludes,
   since the hero is meant to show through a transparent header there).
   Result: a solid white bar across the full header width above the hero,
   every visit, home page only. Force it transparent for that one case.
   The same customizer block also sets a scoped `.site-header{border-bottom-
   color:#f0f0f0}` at >=768px (a Customizer "border color" default), which
   ties on specificity with our own bare `.site-header` border rules and
   reads as a bright full-bleed hairline sitting on top of the hero; the
   home page never had a kairo rule zeroing border-bottom at all (only
   `body:not(.home)` set one), so force it off here too. */
body.home .site-header{ background:transparent !important; border-bottom:0 !important; }

@media (max-width:767px){
  .site-header{ padding:18px var(--gutter) 0; }
  body:not(.home) .site-header{ padding-bottom:14px; }
  .site-header .wordmark img{ height:15px; }
  /* Mobile nav, interior pages (2026-07-24 fix; conductor's ruled design:
     no hamburger, one header row at every width). Below 768px there was
     previously no way to reach Shop/Verify at all: `display:none` on
     `.nav-links` with nothing rendered in its place. Shown here instead as
     a compact mono row, sized down from the desktop clamp() so wordmark +
     links + cart icon all fit inside a 320px viewport without crowding
     (measured; see import/scripts/verify-header-parity.mjs and the
     390/320 screenshots in the fix-round evidence). `justify-content:
     space-between` on `.site-header` (unchanged) still places wordmark
     left / nav-links / cart right, the same 3-slot layout as desktop, just
     re-sized. Home is unaffected: `body.home .site-header{display:none}`
     below still hides this entire row there; home's mobile nav lives in its
     own hero-nav-row instead (front-page.php), fixed separately below with
     the matching Shop/Verify treatment so no page is ever left without one,
     and the two rows never coexist so there's no double-nav.

     9.5px -> var(--fs-micro) (2026-07-26 fix wave B): the 2026-07-26 token
     sweep deliberately excluded nav sizing, which left this row rendering
     9.5px, 3.5px under the 13px absolute floor declared at the top of this
     file, on the store's only mobile navigation. It is a wide-letterspaced
     mono micro-label row, so --fs-micro (not --fs-meta) is the right token.
     Gap 11->8px, tracking .09->.05em and link padding 6->3px per side are
     the reflow budget that buys the 3.5px back: the 4-item menu (Shop,
     Verify, Affiliates, About) measured 196.9px at 9.5px and 257.3px at
     13px with the old tracking/gap, which does not fit beside the wordmark
     and cart at any mobile width. Measured after (interior /shop/):
     nav 227.0px wide, wordmark<->nav / nav<->cart gaps 17.7px at 430,
     8.9px at 390, 0px at 360 and 320. Zero horizontal overflow at every
     width. KNOWN RESIDUAL: at <=360 the three header slots now touch with
     no breathing room (they already touched at 320 before this change);
     a 4-item 13px mono row genuinely cannot fit beside a 61px wordmark and
     a 27px cart inside 360 minus the 2x23.4px site gutter. Closing that
     is closed by two measured tightening steps below (389/344), each of
     which keeps the full 13px and only spends tracking and gap.
     LOGO INTEGRITY GUARD, and it is not optional: all three header slots are
     flex items with the default `flex-shrink:1`, so the moment the row is
     over-constrained the browser compresses the WORDMARK IMAGE rather than
     the text beside it. Measured at 13px with no guard: the lockup rendered
     51.0px wide at 360 and 16.1px at 320 against its true 60.8px aspect-
     correct width, i.e. the KAIRO mark itself was being horizontally
     crushed. (This is pre-existing, not new: the shipped 9.5px build was
     already crushing it to 54.5px at 320. It simply never showed up before
     because nothing measured the mark's own width.) flex:0 0 auto pins the
     wordmark and the cart at their intrinsic sizes so the nav is the only
     thing that can ever give. */
  .site-header .wordmark,
  .site-header .nav-right{ flex:0 0 auto; }
  .site-header .nav-links{
    display:flex; flex:0 1 auto; gap:8px; font-size:var(--fs-micro); letter-spacing:.05em;
  }
  /* EBI self-round (2026-07-24): the links above measured a 12px-tall
     clickable box (bare text, no padding) -- well under the 24px WCAG
     2.5.5/2.5.8 minimum tap target, on a row that exists specifically to be
     tapped on a touch device. Padding grows the hit area to ~40px without
     touching the visible text size or the gap/positioning the 320/390
     measurements above were taken against: the matching negative margin
     cancels the padding's effect on layout, so SHOP/VERIFY still sit at the
     exact same visual coordinates already confirmed clear of the wordmark
     and cart icon. Side padding trimmed 6->3px with the 2026-07-26 type-floor
     bump above (the negative margin still cancels it, so the tap target keeps
     its full ~40px height and only the horizontal hit area narrows by 3px per
     side, still well over the 24px minimum in both axes once the 13px text
     itself is counted). */
  .site-header .nav-links a{ padding:14px 3px; margin:-14px -3px; }
  body.home .site-header{ display:none; }
}

/* (Narrow-phone tightening steps for the 13px nav live AFTER the hero-mobile
   block further down this file, so they win the equal-specificity tie
   against `.hero-mobile .hero-nav-row .nav-links` on source order.) */

/* =========================================================================
   FOOTER
   ========================================================================= */
.site-footer{ background:#0F1013; border-top:1px solid var(--hairline); padding:72px 0 32px; color:var(--mist); }
.footer-top{ display:flex; flex-wrap:wrap; gap:48px; justify-content:space-between; padding-bottom:44px; }
.footer-brand .wordmark img{ height:18px; width:auto; margin-bottom:14px; }
.footer-brand p{ max-width:320px; }
.footer-nav{ display:flex; gap:56px; flex-wrap:wrap; }
.footer-nav-col{ display:flex; flex-direction:column; gap:11px; }
/* footer smalls (2026-07-26 sweep): these are real navigable links, so the
   meta floor applies, not the micro one. */
.footer-nav-col a{ font-size:var(--fs-meta); color:rgba(233,235,238,.68); text-decoration:none; }
.footer-nav-col a:hover{ color:#fff; }
/* =========================================================================
   FOOTER CONTRAST (2026-07-26 fix wave B). Three of the footer's quiet tiers
   measured below the 4.5:1 AA floor against the footer's own #0F1013 ground.
   Measured before -> after (WCAG 2.x, alpha composited over #0F1013):
     .footer-legal a       .42 = 3.62:1  ->  .55 = 5.39:1
     .mono-label (column
       heading, footer.php
       inline style)       .40 = 3.39:1  ->  .55 = 5.39:1
     .footer-attributes
       (the copyright)     .32 = 2.59:1  ->  .50 = 4.65:1
   The minimum alpha that clears 4.5:1 on this ground is .4894, so each value
   above keeps headroom while preserving the existing hierarchy: nav links
   stay the loudest quiet tier (.68 = 7.70:1), legal links and column
   headings sit one step down, the copyright line stays the quietest.
   Already-passing tiers are left alone (.footer-meta .domain .55 = 5.39:1,
   .ruo-block .50 = 4.65:1, .footer-nav-col a .68 = 7.70:1).
   ========================================================================= */
.footer-legal{ display:flex; flex-wrap:wrap; gap:8px 22px; padding:20px 0 24px; border-bottom:1px solid var(--hairline); }
.footer-legal a{
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.05em; text-transform:uppercase;
  color:rgba(233,235,238,.55); text-decoration:none;
}
/* !important is load-bearing here, not laziness: footer.php prints the two
   column headings with an inline `style="color:rgba(233,235,238,.4)"`
   attribute, which outranks any author selector at normal importance. An
   author `!important` declaration is the one thing that beats a normal
   inline style in the cascade, so this is the only CSS-side fix available.
   ROOT FIX (owner: whoever holds footer.php): delete the two inline color
   declarations on `.mono-label` in footer.php and this rule can drop the
   !important and become a plain color declaration. */
.site-footer .footer-nav-col .mono-label{ color:rgba(233,235,238,.55) !important; }
.footer-legal a:hover{ color:rgba(233,235,238,.75); }
.footer-bottom{ padding-top:28px; display:flex; flex-direction:column; gap:18px; }
/* RUO/compliance paragraph: real informational copy CT wants legible, not
   the fine print it visually reads as today. The exact wording stays the
   same string (contract's RUO-string rule protects wording, not size). */
.ruo-block{ font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.85; letter-spacing:.01em; color:rgba(233,235,238,.5); max-width:920px; }
.footer-meta{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:12px; }
/* domain credit line + the tagline below it: small-caps mono labels, take
   the micro floor like .eyebrow/.mono-label. */
.footer-meta .domain{ font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em; color:rgba(233,235,238,.55); }
.footer-attributes{ font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.14em; text-transform:uppercase; color:rgba(233,235,238,.50); }

@media (max-width:767px){
  .footer-top{ flex-direction:column; gap:32px; }
  .footer-meta{ flex-direction:column; align-items:flex-start; }
}

/* =========================================================================
   HERO -- desktop (comp-1: full-viewport video, text left, vial right in video)
   ========================================================================= */
.hero-desktop{ position:relative; min-height:100vh; overflow:hidden; display:flex; align-items:center; background:var(--slate); }
.hero-video{ position:absolute; inset:0; z-index:0; width:100%; height:100%; object-fit:cover; object-position:68% 50%; }
.hero-poster{ position:absolute; inset:0; z-index:0; width:100%; height:100%; object-fit:cover; object-position:68% 50%; display:none; }
@media (prefers-reduced-motion: reduce){
  .hero-video{ display:none; }
  .hero-poster{ display:block; }
}
.hero-scrim{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(90deg, rgba(21,23,27,.92) 0%, rgba(21,23,27,.72) 34%, rgba(21,23,27,.32) 60%, rgba(21,23,27,.06) 100%);
}
.hero-desktop .hero-inner{ position:relative; z-index:3; width:100%; padding-top:64px; }
.hero-content{ padding:0 var(--gutter); display:flex; flex-direction:column; align-items:flex-start; gap:22px; max-width:640px; }
.hero-desktop .hero-eyebrow{ font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.22em; text-transform:uppercase; color:rgba(233,235,238,.62); }
.hero-desktop .h1{ font-size:clamp(36px,4.6vw,58px); }
.hero-desktop .hero-sub{ font-size:15.5px; line-height:1.75; letter-spacing:.01em; color:rgba(233,235,238,.72); max-width:460px; }
.hero-desktop .hero-cta{ display:flex; align-items:center; gap:22px; flex-wrap:wrap; margin-top:2px; }
.hero-desktop .trust-row-wrap{ margin-top:8px; padding-top:18px; border-top:1px solid var(--hairline); }

@media (max-width:767px){ .hero-desktop{ display:none; } }

/* ---------- HERO -- mobile (MOBILE HERO LAW: product-centered orbit, own composition) ---------- */
.hero-mobile{ display:none; }
@media (max-width:767px){
  /* overflow:hidden (2026-07-26 fix wave B) is the containment this hero was
     missing. Root cause of the mobile horizontal pan: .vial-glow below is
     `left:50%; width:88%; transform:translate(-27%,-50%)`, i.e. an ellipse
     88% as wide as the stage, deliberately offset 23% of its OWN width to
     the right of centre (asymmetric light source). That offset always pushes
     its right edge ~14% of the stage width past the stage's right edge, at
     every viewport: measured right edge 380.42 at 360, 414.70 at 390, 460.39
     at 430, i.e. 20/25/30px of real sideways document scroll (the exact
     deltas document.documentElement reported), which panned the page and
     slid the KAIRO lockup out of frame.
     `body{overflow-x:hidden}` did not stop it: with html's overflow left
     visible, body's value propagates to the viewport and body itself
     computes back to `visible`, so nothing in the chain actually clipped the
     glow. Clipping is applied HERE, on the hero wrap, rather than on
     .vial-stage: the stage's overflow:visible is load-bearing (see the
     2026-07-26 note below, it exists so the vial's glass foot and its 26px/
     34px drop-shadow are not cut off), and the hero wrap sits 24px further
     out on each side and is ~866px tall against a ~330px stage, so it
     contains the glow's overhang while leaving the vial's own bleed
     completely untouched (verified by measurement and by 360/390/430
     screenshots). Moving or resizing .vial-glow instead would have shifted
     the hero's light source, a design change, not a bug fix. */
  .hero-mobile{
    display:flex; flex-direction:column; align-items:center;
    min-height:100svh; padding:0 24px 24px; overflow:hidden;
    background-image:url('assets/img/bg-plaster.jpg'); background-size:cover; background-position:center; background-repeat:no-repeat;
  }
  .hero-mobile .hero-nav-row{ width:100%; display:flex; align-items:center; justify-content:space-between; padding:18px 0 0; }
  /* Same logo-integrity guard as the interior header (see the flex:0 0 auto
     note there): this row's lockup is a bare <img> in front-page.php with no
     .wordmark wrapper, so it is targeted as the row's direct img child. */
  .hero-mobile .hero-nav-row > img,
  .hero-mobile .hero-nav-row .nav-cart{ flex:0 0 auto; }
  .hero-mobile .hero-nav-row .nav-links{ flex:0 1 auto; }
  /* 2026-07-24 fix: home's mobile hero row only ever rendered wordmark +
     cart, so Shop/Verify were unreachable from mobile home (interior pages
     already carry the compact mono nav above). Same markup/values as the
     interior fix, mirrored on the static site's index.html, so all three
     treatments read identical.

     9.5px -> var(--fs-micro), gap 11->8px, tracking .09->.05em, link padding
     6->3px per side (2026-07-26 fix wave B): the mirror of the interior-page
     fix above, applied here for the same reason (the store's mobile-home nav
     was 3.5px under the 13px absolute floor) with the identical reflow
     budget, so the two rows still read as one treatment. Colour is unchanged
     (.72 mist): this row sits over a plaster photograph, not a flat token
     ground, so its contrast is not something a static ratio can be measured
     against and re-toning it was out of this pass's scope. */
  .hero-mobile .hero-nav-row .nav-links{
    display:flex; gap:8px; font-family:var(--font-mono); font-size:var(--fs-micro);
    letter-spacing:.05em; text-transform:uppercase; color:rgba(233,235,238,.72);
  }
  .hero-mobile .hero-nav-row .nav-links li{ list-style:none; }
  .hero-mobile .hero-nav-row .nav-links a{ text-decoration:none; padding:14px 3px; margin:-14px -3px; }
  .hero-mobile .hero-nav-row .nav-links a:hover{ color:#fff; }
  .hero-mobile .h1{ margin-top:30px; max-width:360px; text-align:center; color:var(--porcelain); font-size:clamp(40px,11.8vw,48px); }

  /* 2026-07-26 fix: overflow:hidden here clipped the vial's glass foot AND
     the CSS drop-shadow (26px offset + 34px blur below/around .vial-img,
     see .vial-img below) since the stage only left ~5px of clearance above
     and below the image box. overflow:visible lets the full foot + natural
     shadow falloff render; the image itself never exceeds the stage box
     (height:97% of vial-wrap, which is 100% of stage height), so nothing
     else here relied on the clip - body{overflow-x:hidden} still stops any
     horizontal page scrollbar. */
  .vial-stage{ position:relative; width:100%; margin-top:20px; overflow:visible; height:clamp(300px,40svh,380px); }
  .vial-glow{
    position:absolute; z-index:1; pointer-events:none; left:50%; top:46%; width:88%; height:62%;
    transform:translate(-27%,-50%);
    background:radial-gradient(ellipse at center, rgba(216,155,106,1) 0%, rgba(194,87,27,.9) 48%, transparent 78%);
    opacity:.09; filter:blur(4px); mix-blend-mode:screen; animation:glow-breathe 12s ease-in-out infinite;
  }
  @keyframes glow-breathe{ 0%{opacity:.07;} 50%{opacity:.13;} 100%{opacity:.07;} }
  .vial-wrap{ position:relative; z-index:2; height:100%; width:100%; display:flex; align-items:center; justify-content:center; }
  .vial-img{ height:97%; width:auto; filter:drop-shadow(0 26px 34px rgba(0,0,0,.55)); animation:vial-float 12s ease-in-out infinite; }
  @keyframes vial-float{ 0%{transform:translateY(0);} 50%{transform:translateY(-6px);} 100%{transform:translateY(0);} }
  @media (prefers-reduced-motion: reduce){ .vial-img{ animation:none; } .vial-glow{ animation:none; opacity:.1; } }

  .hero-mobile .trust-list--stacked{ margin-top:20px; padding-top:16px; border-top:1px solid var(--hairline); width:100%; }
  .hero-mobile .hero-cta{ margin-top:20px; width:100%; display:flex; flex-direction:column; align-items:center; gap:12px; }
}

/* =========================================================================
   NARROW-PHONE TIGHTENING STEPS for the 13px mobile nav (2026-07-26 fix
   wave B). Placed here, after BOTH nav rows are defined, deliberately: these
   selectors tie on specificity with `.hero-mobile .hero-nav-row .nav-links`
   above, so source order is the only tie-breaker and an earlier position
   silently lost on the home hero (measured: hero row stayed at .05em/8px and
   the nav ran flush into the cart icon at 360).
   Both rows carry the same 4 items (Shop, Verify, Affiliates, About) beside
   the same fixed neighbours (a 60.8px lockup, a 27px cart icon), so they get
   the same budget and the same steps. Each step spends ONLY tracking, gap
   and link padding: the 13px floor is never traded away, which is the whole
   point of the fix. Measured nav width and per-side clearance, /shop/:
     >=390  gap 8, .05em          -> nav 235.3, clearance 8.1 (390), 25.5 (430)
     <=389  gap 5, .01em          -> nav 213.3, clearance 12.6 (375), 6.0 (360)
     <=344  gap 4, 0em, 1px pad   -> nav 207.0, clearance 2.2 (344)
   KNOWN RESIDUAL at 320 and below, stated rather than hidden: four 13px
   labels cannot fit beside the lockup and cart inside 320 minus the 2x20.8px
   gutter (budget 190.8px, tightest possible nav 207.0px), so /shop/ at 320
   pans 4px horizontally and the home hero row runs flush into the cart.
   Before this pass 320 did not pan, but only because the browser was
   crushing the KAIRO lockup to 16.1px of its 60.8px width to make room,
   which is a hard brand-rule violation and far worse than 4px of pan; the
   flex:0 0 auto guard above deliberately trades one for the other. The real
   fix is a design call (drop one nav item, or give the header a second row
   below 344px, which needs every interior page's header-clearance value
   re-tuned) and is escalated, not silently invented here. 360/390/430, the
   widths this pass was scoped to, are all clean.
   ========================================================================= */
@media (max-width:389px){
  .site-header .nav-links,
  .hero-mobile .hero-nav-row .nav-links{ gap:5px; letter-spacing:.01em; }
}
@media (max-width:344px){
  .site-header .nav-links,
  .hero-mobile .hero-nav-row .nav-links{ gap:4px; letter-spacing:0; }
  .site-header .nav-links a,
  .hero-mobile .hero-nav-row .nav-links a{ padding:14px 1px; margin:-14px -1px; }
}

/* =========================================================================
   SHOP GRID
   ========================================================================= */
/* interior lines live on each card's own border, never on the grid container:
   a container-background gap trick leaves a phantom filled cell when the SKU
   count doesn't divide evenly into the column count (15 SKUs / 4 or 2 cols).
   Fixed 2026-07-24: the container's own perimeter border used to wrap the
   WHOLE grid box (right+bottom included), so an incomplete last row's unused
   trailing cell still got boxed in by that border on its right/bottom, the
   3rd card's own border-right on its left, and the row-above card's own
   border-bottom on its top, reading as an empty framed card. Right/bottom
   were always redundant with each card's own border-right/border-bottom
   anyway (identical position via the border-collapse margin trick); moving
   the two sides that WEREN'T redundant (top row's top edge, first column's
   left edge) onto the edge cards themselves via nth-child, and dropping the
   container border entirely, removes the phantom box for any incomplete
   row at any column count without hardcoding the current 15-SKU total. */
/* Grid anatomy reworked 2026-07-27 (CT direction, from the PDP related row +
   the SEVA card reference). The old grid butted square-cropped thumbnails
   edge to edge behind a shared hairline lattice: the 1/1 crop clipped the
   vial's cap and foot, and gap:0 read as one compacted block. This version
   matches the related row CT preferred: full uncropped 4:5 vials
   (woocommerce_single, uncropped, same size the PDP/related use), open gaps
   between borderless cards, name + fill weight on one line, a one-line RUO
   research description, then price + Add. Home #shop and /shop/ share the
   kairo_render_sku_card partial, so both surfaces render identically. */
.sku-grid{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:36px 24px; }
/* Category pills (2026-07-27, CT; renamed from "filter pills" 2026-07-28, CT:
   "we should call them categories... like what they are related to"):
   interactive siblings of the on-image .sku-pill, sitting above the grid on
   each shop surface. Active = filled copper with --ink text (5.2:1, AA);
   inactive = hairline outline with --pale text (7.6:1, AA), so both states
   clear AA on the slate section ground. Native <button>s: keyboard operable
   for free; focus-visible draws a --pale ring. */
.sku-category-bar{ display:flex; flex-wrap:wrap; gap:10px; margin-bottom:28px; }
.sku-category-pill{
  appearance:none; cursor:pointer;
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; line-height:1;
  color:var(--pale); background:transparent;
  border:1px solid var(--hairline); border-radius:999px; padding:9px 15px;
  transition:background-color .15s ease, border-color .15s ease, color .15s ease;
}
.sku-category-pill:hover{ border-color:var(--copper); }
.sku-category-pill.is-active{ background:var(--copper); border-color:var(--copper); color:var(--ink); }
.sku-category-pill:focus-visible{ outline:2px solid var(--pale); outline-offset:2px; }
/* Filtered-out cards drop from flow (no reload); shown cards fade in gently.
   The keyframe animates OPACITY ONLY on purpose: putting transform in it would
   overwrite the hover lift's translateY on any filtered-in card. */
.sku-card.is-filtered-out{ display:none; }
@keyframes skuCardIn{ from{ opacity:0; } to{ opacity:1; } }
.sku-card--in{ animation:skuCardIn .18s ease both; }
/* Hover/focus tile (2026-07-27, CT, SEVA reference): at rest the card floats
   borderless on the section; on hover (pointer devices) and :focus-within (all
   devices, keyboard) the whole card reads as a lifted tile. Only paint/transform
   properties change (background, box-shadow, translateY), so NO layout shift and
   siblings never move: the reserved box is identical at rest and lifted. The
   card's 8px radius rounds the tile background's bottom corners and the media's
   top-only radius rounds the full-bleed image's top corners, so no overflow clip
   is needed (which keeps the ADD button's focus ring unclipped).
   align-self:start REMOVED 2026-07-28 (store owner, PDP "Frequently paired
   with"/"Also recommended" review, "price and Add to Cart button wrap onto
   different heights"): cards now stretch to their row's tallest sibling
   (the grid's own default align-items:stretch) instead of hugging their
   own content, so .sku-card-body's flex:1 and .sku-card-foot's
   margin-top:auto below can pin every row's feet to one shared baseline.
   Measured live before shipping this: /shop/ had the identical latent bug
   in 3 of its 4 rows (any row mixing a 1-line and a 2-line product name),
   so this is fixed at the component, not per-section, per instruction. */
.sku-card{
  display:flex; flex-direction:column; text-decoration:none; color:inherit;
  border-radius:8px; background-color:transparent;
  transition:transform .18s ease, background-color .18s ease, box-shadow .18s ease;
}
.sku-card-media{ position:relative; aspect-ratio:4/5; overflow:hidden; background:#0F1013; border-radius:8px 8px 0 0; }
/* 4:5 box matches the source photo's own 4:5 aspect (486x607 master, 416x520
   and 240x300 intermediates), so object-fit:cover discards nothing: the cap
   and foot of the vial both stay in frame, unlike the old 1/1 crop. */
.sku-card-media img{ width:100%; height:100%; object-fit:cover; transition:transform .5s ease; }
.sku-card:hover .sku-card-media img{ transform:scale(1.04); }
/* Lifted-tile state. #1C1F25 is a hair above --slate (#15171B): an elevated
   dark ground, never white, per the dark system. The lift is a paint/transform
   change only (no size change), so the card box is byte-identical at rest and
   lifted and nothing reflows. :hover is gated behind (hover:hover) so touch
   devices get no sticky hover; :focus-within gives keyboard users the same tile
   on all devices when the card link or its ADD button is focused. */
.sku-card:focus-within{
  background-color:#1C1F25;
  box-shadow:0 10px 28px -12px rgba(0,0,0,.55), 0 2px 8px -5px rgba(0,0,0,.45);
  transform:translateY(-3px);
}
@media (hover:hover){
  .sku-card:hover{
    background-color:#1C1F25;
    box-shadow:0 10px 28px -12px rgba(0,0,0,.55), 0 2px 8px -5px rgba(0,0,0,.45);
    transform:translateY(-3px);
  }
}
/* Reduced motion (2026-07-27 self-EBI): the rest of the store honors this (hero
   video, vial float); the new grid interactions must too. Keep the tile's
   background + shadow (a non-motion hover/focus affordance) but drop the
   translateY lift, the image zoom, and the filter fade for vestibular safety.
   Placed after the hover rules so it wins on source order at equal specificity. */
@media (prefers-reduced-motion: reduce){
  .sku-card{ transition:background-color .15s ease, box-shadow .15s ease; }
  .sku-card:hover, .sku-card:focus-within{ transform:none; }
  .sku-card:hover .sku-card-media img{ transform:none; }
  .sku-card--in{ animation:none; }
}
/* Category pill (2026-07-27): a plain-language research-DOMAIN label on the
   image, top-left per the SEVA reference. KAIRO dark system: slate ground,
   hairline, DM Mono micro-caps, --pale text (7.5:1 on the slate pill ground,
   clears AA; the fs-micro floor is the store's standard for wide-letterspaced
   mono labels, same as .sku-badge). */
.sku-pill{
  position:absolute; top:12px; left:12px; z-index:2;
  max-width:calc(100% - 24px); text-align:center;
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--pale);
  background:rgba(21,23,27,.82); border:1px solid var(--hairline);
  border-radius:999px; padding:5px 11px; line-height:1.25;
}
/* Multi-category cards (2026-07-28, CT: "on the glow blend, that's both skin
   and recovery, so should we have two pills that show both of those tags").
   A product in more than one category renders one .sku-pill per category
   (kairo_render_sku_card() in functions.php), wrapped in .sku-pills so they
   lay out in a row instead of stacking on top of each other: the bare
   .sku-pill rule above is independently position:absolute at the same
   top-left spot, correct for the single-pill case (also what the PDP hero
   still renders, unwrapped), wrong the moment a second pill joins it. The
   wrapper takes over the absolute positioning at the same top-left
   coordinates the lone pill already used, and resets its own .sku-pill
   children to position:static so flex handles their layout. flex-wrap lets
   a narrow card drop a pill to a second row instead of overflowing. */
.sku-pills{
  position:absolute; top:12px; left:12px; z-index:2;
  display:flex; flex-wrap:wrap; gap:6px;
  max-width:calc(100% - 24px);
}
.sku-pills .sku-pill{ position:static; max-width:none; }
/* Inner padding (2026-07-27, CT, SEVA reference): the text block is inset from
   the card edges (18px sides, 18px bottom) so when the hover tile appears nothing
   sits at the tile's edge. The media above stays full-bleed to the tile top.
   flex:1 RESTORED 2026-07-28 (see .sku-card above for the review that
   brought it back): stretches the body to fill the now-equal-height card,
   so .sku-card-foot's margin-top:auto below can pin the foot to the
   bottom. HISTORY, because this is the second time this exact property
   flipped: it was removed 2026-07-27 because a flex-grown body plus
   margin-top:auto was "opening the loose gap CT flagged" ("the price and
   Add button need to move up, there's always a little too much space, on
   every compound"). That complaint's actual bulk cause was traced to a
   DIFFERENT rule, Storefront's inherited `p{margin-bottom:1.41575em}`
   (~21px), fixed separately and permanently by .sku-card-desc's own
   margin:0 below; that fix is untouched by this change and still holds.
   What THIS restores is only the row-to-row stretch, whose own cost is
   smaller and different in kind: a shorter card's foot now sits lower by
   however much its row's tallest sibling exceeds it (measured up to
   ~55-90px in a mixed 1-line/2-line-name row), not a uniform ~21px tax on
   every card regardless of its neighbours. The two complaints are not the
   same complaint; if a future review flags gap again, re-measure against
   THIS description before assuming the 2026-07-27 fix regressed. */
.sku-card-body{ padding:16px 18px 18px; display:flex; flex-direction:column; gap:8px; flex:1; }
/* Name + fill weight on one baseline-aligned row (2026-07-27, CT: "make it
   clear with the milligrams what's inside"). The separate dose subline is
   gone; the fill chip (DM Mono, --pale, 15px so it clears the informational
   floor and stays instantly visible) wraps beneath the name only when a long
   multi-part fill (the GLOW/blend vials) needs the room. */
.sku-card-head{ display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; }
.sku-card-name{ font-family:var(--font-display); font-size:19px; font-weight:400; color:var(--porcelain); line-height:1.12; }
/* No white-space:nowrap: a long multi-part fill (the GLOW/blend vials, "50 MG
   / 10 MG / 10 MG") wraps within the card at narrow mobile columns instead of
   bleeding past the card edge into the gutter. Short fills ("10 MG") never
   wrap anyway. */
.sku-card-mg{ font-family:var(--font-mono); font-size:var(--fs-meta); font-weight:500; letter-spacing:.03em; color:var(--pale); }
/* One-line research description (2026-07-27): compressed, RUO-vetted, from
   each SKU's PDP research section. --mist-dim (.55 alpha) reads as supporting
   copy under the name; 15px clears the informational floor. */
/* margin:0 kills Storefront's inherited `p{margin-bottom:1.41575em}` (~21px at
   15px): that inherited paragraph margin, uniform on every card, was the bulk of
   the "always a little too much space, on every compound" gap above the price
   row (2026-07-27, CT). With it gone the foot sits ~14px under the description
   (8px body gap + 6px foot padding-top), the intended tight foot. */
.sku-card-desc{ margin:0; font-size:var(--fs-meta); line-height:1.5; color:var(--mist-dim); }
/* Foot. margin-top:auto RESTORED 2026-07-28 (store owner, "price and Add
   to Cart button wrap onto different heights"): pins the foot to the
   bottom of the now-equal-height card (.sku-card / .sku-card-body above),
   so every card in a row shares one foot baseline regardless of whether
   its own name wrapped to 1 line or 2. This is a reversal of the
   2026-07-27 note that used to sit here preferring tight, per-card
   spacing over row alignment; see .sku-card-body's own comment for why
   both preferences can be true without one being a regression of the
   other. On a card whose content already reaches its row's tallest
   sibling, the foot still sits ~14px under the description exactly as
   before (8px body gap + 6px padding-top); the auto margin only adds
   space on a SHORTER card in a mixed row. */
.sku-card-foot{ padding-top:6px; display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:auto; }
.sku-card-price{ font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--pale); }
/* "Add" button (2026-07-26 sweep): interactive floor; height grown 36->40px
   (32->36px mobile below) so the larger label keeps clean vertical padding
   instead of feeling cramped against the button's own edges. */
/* Contrast (2026-07-26 round-2 QA): the label was --copper on --slate =
   4.41:1, just under AA, across all 26 product-card ADD buttons on home and
   shop. --pale on slate measures 7.53:1. The 1.5px copper border is unchanged
   (non-text UI, 4.41:1 on slate, clears the 3:1 floor), so the button's shape
   and weight are identical. */
.sku-add{
  appearance:none; background:transparent; border:1.5px solid var(--copper); color:var(--pale);
  font-family:var(--font-body); font-weight:700; font-size:var(--fs-meta); letter-spacing:.1em; text-transform:uppercase;
  border-radius:var(--btn-radius); padding:0 16px; height:40px; cursor:pointer; white-space:nowrap;
}
.sku-add:hover{ background:var(--ember); border-color:var(--ember); color:var(--on-ember); }
/* !important needed: Storefront's own `button:disabled{opacity:.5 !important}`
   (parent theme style.css) otherwise wins regardless of this rule's higher
   selector specificity, since importance outranks specificity. */
.sku-add[disabled]{ opacity:.55 !important; cursor:not-allowed; }
.sku-add[disabled]:hover{ background:transparent; border-color:var(--copper); color:var(--pale); }
/* Stock/gated badge: top-RIGHT (2026-07-27) so it never collides with the
   category pill now sitting top-left. Same micro-caps mono treatment. */
.sku-badge{
  position:absolute; top:12px; right:12px; z-index:2; font-family:var(--font-mono); font-size:var(--fs-micro);
  letter-spacing:.1em; text-transform:uppercase; color:rgba(233,235,238,.85);
  background:rgba(21,23,27,.72); border:1px solid var(--hairline); border-radius:999px; padding:5px 12px;
}

/* Density (2026-07-27): 4 columns at desktop honors "being able to see more on
   the screen" while the taller 4:5 vials + description keep each card's
   presence; 3 at tablet, 2 at phone. Borderless cards + grid gap, so no
   nth-child hairline lattice to re-derive per column count. */
@media (max-width:1024px){
  .sku-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:32px 22px; }
}
@media (max-width:767px){
  .sku-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:26px 16px; }
  .sku-card-name{ font-size:17px; }
  .sku-add{ font-size:var(--fs-meta); height:36px; padding:0 12px; }
  /* long disabled-state labels ("Available soon") don't fit beside the price
     in a 2-col mobile card; wrap the button under the price instead of
     letting it overflow the card/viewport. */
  .sku-card-foot{ flex-wrap:wrap; row-gap:8px; }
}

/* =========================================================================
   VERIFICATION BAND
   ========================================================================= */
.verify-band{ background:#0F1013; }
.verify-grid{ display:grid; grid-template-columns:1.05fr 1fr; gap:64px; align-items:center; }
.verify-steps{ display:flex; flex-direction:column; gap:28px; margin-top:32px; }
.verify-step{ display:flex; gap:18px; }
.verify-step-num{ font-family:var(--font-mono); font-size:var(--fs-micro); color:var(--pale); flex:0 0 auto; padding-top:2px; }
.verify-step-copy .h3{ margin-bottom:6px; color:var(--porcelain); }
/* The meter row is a SIBLING of the framed photo, not a child of it, and is
   positioned over the photo from the shared wrapper (2026-07-26 round-2 QA).
   It used to live inside .verify-visual, pinned by align-items:flex-end +
   28px padding; that made it structurally impossible to lift off the image
   at narrow widths, where the 16/9 squash below drove the row up to fracTop
   .463-.585 and printed copper type straight across the vial's own printed
   label ("BPC-157" read as "BPC-15P"). As a sibling it can simply become a
   static caption under the photo below 767px. */
.verify-visual-wrap{ position:relative; }
.verify-visual{
  position:relative; aspect-ratio:4/5; border-radius:4px; overflow:hidden;
  background:#0a0a0c center/cover no-repeat;
  border:1px solid var(--hairline);
}
.verify-visual::after{ content:''; position:absolute; inset:0; background:linear-gradient(0deg, rgba(8,8,10,.82) 0%, rgba(8,8,10,.1) 55%); }
.verify-visual-meter{ position:absolute; z-index:1; left:28px; right:28px; bottom:28px; display:flex; flex-direction:column; gap:10px; }
.verify-visual-meter .meter-label{ display:flex; justify-content:space-between; font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.1em; text-transform:uppercase; color:rgba(233,235,238,.6); }
.meter-track{ height:3px; width:100%; background:rgba(233,235,238,.14); border-radius:2px; overflow:hidden; }
.meter-fill{ height:100%; width:0%; background:var(--copper); border-radius:2px; }
.meter-fill.is-pending{ background:repeating-linear-gradient(90deg, rgba(216,155,106,.55) 0 6px, transparent 6px 12px); width:100%; }
.verify-status{ font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em; text-transform:uppercase; color:var(--pale); }

@media (max-width:900px){
  .verify-grid{ grid-template-columns:1fr; gap:36px; }
  .verify-visual-wrap{ order:-1; }
  .verify-visual{ aspect-ratio:16/9; }
}
/* Overlay-vs-printed-label collision (2026-07-26 round-2 QA). The 16/9 squash
   above is safe from 420px up (the row sits at fracTop .69-.84, below the
   vial's printed label), but under that the box is only 157-203px tall AND
   the two meter labels wrap to two lines, so the row climbed to fracTop
   .463-.585 -- across the printed label. Measured on the clean plate (meter
   hidden, scrim off, DSF3): near-white printed-glyph density inside the
   "Pending, per lot" text rect was 0.66% @320, 0.52% @360, 0.58% @375, 0.72%
   @390, 0.28% @414, versus 0.00% at the 430 and 1440 controls; "BPC-157" read
   as "BPC-15P".
   Fix below 767px: the row leaves the photo entirely and becomes a caption
   under it, so there is no overlap to tune at any narrow width (a taller
   mobile ratio was tried first and still put the row over the vial's white
   powder at 320). 768-900px keeps the overlay, which measures clean. */
@media (max-width:767px){
  .verify-visual-meter{ position:static; margin-top:16px; }
}

/* =========================================================================
   FULL-BLEED TEXTURE DIVIDER
   ========================================================================= */
.divider{
  position:relative; height:46vh; min-height:340px; max-height:520px;
  background:#0a0a0c center/cover no-repeat; display:flex; align-items:center; justify-content:center;
}
.divider::after{ content:''; position:absolute; inset:0; background:linear-gradient(180deg, rgba(8,8,10,.35) 0%, rgba(8,8,10,.55) 100%); }
/* The gutter is not optional. .divider is a full-bleed flex band with no
   .container inside it, so its eyebrow is the full viewport wide and its text
   runs to the bezel on any line long enough to wrap. Measured at 390: the
   About band's line rendered 3px from each edge and the affiliates band's
   ("30% is the number we would want...") is longer still. 24px matches the
   container's own mobile gutter, so a wrapped divider line sits on the same
   left edge as every other page. (2026-07-27) */
.divider .eyebrow{ position:relative; z-index:1; margin-bottom:0; color:rgba(233,235,238,.85); text-align:center; padding-left:24px; padding-right:24px; }

/* =========================================================================
   QUALITY / BRAND SECTION
   ========================================================================= */
.quality-grid{ display:grid; grid-template-columns:1fr 1.1fr; gap:64px; align-items:center; }
.quality-copy p + p{ margin-top:14px; }
.quality-media{ aspect-ratio:4/3; overflow:hidden; border-radius:4px; }
.quality-media img{ width:100%; height:100%; object-fit:cover; }

@media (max-width:900px){
  .quality-grid{ grid-template-columns:1fr; gap:28px; }
  .quality-media{ order:-1; }
}

/* =========================================================================
   FAQ (native details/summary, zero JS)
   ========================================================================= */
.faq-list{ max-width:760px; border-top:1px solid var(--hairline); }
.faq-item{ border-bottom:1px solid var(--hairline); }
.faq-item summary{
  cursor:pointer; list-style:none; padding:22px 0; display:flex; align-items:center; justify-content:space-between; gap:20px;
  font-family:var(--font-display); font-size:17px; color:var(--porcelain);
}
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary::after{
  content:'+'; font-family:var(--font-mono); font-size:18px; color:var(--copper); flex:0 0 auto; transition:transform .15s ease;
}
.faq-item[open] summary::after{ transform:rotate(45deg); }
.faq-item .faq-a{ padding:0 0 22px; max-width:620px; }

.section--porcelain .faq-list,
.section--porcelain .faq-item{ border-color:var(--hairline-light); }
.section--porcelain .faq-item summary{ color:var(--ink); }

/* =========================================================================
   SPEC TABLE (dataviz standard: mono type, hairline rules, copper accents)
   Shared design-system component for spec/data surfaces sitewide.
   ========================================================================= */
/* PDP spec table (2026-07-26 sweep): data cells take the meta floor; column
   header captions stay at the micro floor (small-caps labels, not data). */
.spec-table{ width:100%; border-collapse:collapse; font-family:var(--font-mono); font-size:var(--fs-meta); }
/* Storefront paints table/th/td backgrounds (base + customizer inline CSS); force the
   static-site transparency so the slate page ground shows through, matching site.css */
.spec-table, .spec-table th, .spec-table td{ background:transparent !important; }
.spec-table th{
  text-align:left; padding:10px 14px; font-weight:500; letter-spacing:.08em; text-transform:uppercase;
  font-size:var(--fs-micro); color:rgba(233,235,238,.5); border-bottom:1px solid var(--copper);
}
.spec-table td{ padding:12px 14px; border-bottom:1px solid var(--hairline); color:rgba(233,235,238,.82); }
.spec-table tr:last-child td{ border-bottom:none; }
.spec-table td.is-pending{ color:var(--pale); }

/* =========================================================================
   PAGINATION: WooCommerce core woocommerce_pagination() (/shop/ archive) and
   WordPress core the_posts_pagination() (search.php) render the same
   `page-numbers` class on their links/current-span, just under a different
   wrapper (a <ul class="page-numbers"> vs a plain <div class="nav-links">
   inside <nav class="pagination">). One rule set for both wrappers so a
   second pagination caller doesn't need its own copy.
   ========================================================================= */
.woocommerce-pagination,
nav.pagination{ display:flex; justify-content:center; }
.woocommerce-pagination .page-numbers,
nav.pagination .nav-links{
  list-style:none; display:flex; gap:8px; align-items:center; margin:0; padding:0;
}
.woocommerce-pagination .page-numbers li{ margin:0; }
/* pagination numerals (2026-07-26 sweep): interactive floor; box grown
   36->38px so the larger figure keeps clean padding inside the hit target. */
.woocommerce-pagination .page-numbers li .page-numbers,
nav.pagination .nav-links .page-numbers{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:38px; height:38px; padding:0 10px; border-radius:2px;
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.02em;
  color:var(--mist-dim) !important; text-decoration:none; border:1px solid transparent;
  transition:border-color .15s ease, color .15s ease;
}
.woocommerce-pagination .page-numbers li a.page-numbers:hover,
nav.pagination .nav-links a.page-numbers:hover{
  color:var(--mist) !important; border-color:var(--hairline);
}
.woocommerce-pagination .page-numbers li .page-numbers.current,
nav.pagination .nav-links .page-numbers.current{
  color:var(--on-ember) !important; background:var(--ember) !important; font-weight:500;
}
.woocommerce-pagination a.page-numbers.prev,
.woocommerce-pagination a.page-numbers.next,
nav.pagination .nav-links a.page-numbers.prev,
nav.pagination .nav-links a.page-numbers.next{
  color:var(--copper); font-size:15px;
}

/* =========================================================================
   Utility
   ========================================================================= */
.visually-hidden{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }

/* =========================================================================
   PRODUCT PAGE (single product), ported verbatim from the inline <style>
   block shared by every /products/*.html page in the static reference.
   ========================================================================= */
.breadcrumb-wrap{padding-bottom:0; padding-top:104px;}
@media (max-width:767px){.breadcrumb-wrap{padding-top:78px;}}
.kairo-breadcrumb{font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.04em; color:var(--mist-dim); padding:24px 0 0;}
.kairo-breadcrumb a{color:var(--mist-dim); text-decoration:none;}
.kairo-breadcrumb .sep{ margin:0 8px; }
.hero-grid{display:grid; grid-template-columns:1fr 1fr; gap:56px; align-items:start; padding-top:32px;}
@media (max-width:860px){.hero-grid{grid-template-columns:1fr; gap:32px;}}
/* position:relative added 2026-07-28 so the category pill (below, the
   shop card's own .sku-pill, top-left) has a positioning context to anchor
   to; overflow:hidden was already here for the rounded corners and needs
   no change, the pill sits inside this box the same way it already sits
   inside .sku-card-media on the shop grid. */
.hero-media{background:var(--slate); border-radius:3px; overflow:hidden; position:relative;}
.hero-media img{display:block; width:100%; height:auto; aspect-ratio:4/5; object-fit:contain; border-radius:3px;}
/* Root cause (2026-07-24): unscoped `.product-name` matched every element
   with that class, not just this PDP h1. WooCommerce's own order tables
   (order-received, view-order, order-pay) render
   `<td class="woocommerce-table__product-name product-name">`, so the same
   30-44px Marcellus display rule was blowing up "TB-500" to hero size
   inside a line-item row. Scoped to the PDP's own h1 so it can't leak into
   any other `.product-name` element WooCommerce ships.
   SIZE, +13% (2026-07-28, store owner correction): was clamp(30px,4vw,44px).
   The "KAIRO Peptides" eyebrow above this heading was deleted the same
   pass; the freed vertical space is absorbed here (the name grows) rather
   than by pushing price/trust-row/Add-to-cart down, so the buy box's total
   height is close to unchanged (measured in the hand-back). Stays clearly
   under .benefits-h (clamp(40px,6.9vw,100px) desktop, clamp(42px,11vw,68px)
   mobile) at every width: the product name is not allowed to become the
   page's largest moment, that is still the benefits headline below it. */
/* .hero-name-row / .inline-dose (2026-07-28, sitewide pattern change):
   ".product-mg" the separate "10 MG, LYOPHILIZED" line under the H1 is
   gone ("Let's kill that second line... we want to make this change
   throughout all the pages"). The dose now sits on the SAME line as the
   name, in .inline-dose: the identical treatment as .sku-card-mg (font,
   size, weight, tracking, --pale) so the shop card, the PDP hero, the
   sticky bar and the cart/checkout line item never carry two different
   copper doses. white-space:nowrap here (unlike .sku-card-mg, which
   deliberately omits it to let a long blend dose wrap inside a narrow
   card column) keeps a multi-part dose ("50 MG / 10 MG / 10 MG") from
   ever breaking mid-string on these wider surfaces: flex-wrap on the row
   sends the whole dose to its own line if it does not fit, never splits
   it. */
.hero-name-row{ display:flex; flex-wrap:wrap; align-items:baseline; gap:12px; margin:0 0 20px; }
h1.product-name{font-family:var(--font-display); font-size:clamp(34px,4.5vw,50px); margin:0; color:var(--mist); font-weight:400;}
.inline-dose{
  font-family:var(--font-mono); font-size:var(--fs-meta); font-weight:500;
  letter-spacing:.03em; color:var(--pale); white-space:nowrap;
}
/* HERO ONLY, doubled (2026-07-28, store owner: "it needs to be double the
   size... it needs to hold its own beside the product name"). .inline-dose
   is shared by the hero, the sticky bar, the cart line items, the
   checkout summary and the order confirmation; doubling the shared rule
   would have blown the dose up on all five. Scoped here to .hero-name-row
   only, so the bar (deliberately smaller than its own H1-equivalent),
   the cart, checkout and confirmation, and the sku card tiles (the
   owner's own reference for the base size) all keep the original
   var(--fs-meta) 15px untouched.
   PROPORTIONAL, not a fixed px value: same 4.5vw growth factor as
   h1.product-name's own clamp directly above, so the dose keeps a
   consistent relationship to the name at every width instead of holding
   one size while the H1 scales around it. Bounds land at 30px (390,
   exactly double the shared 15px) to 36px (1440, comfortably under the
   H1's own 50px ceiling so it holds its own without outweighing the
   name). align-items:baseline on .hero-name-row (above, unchanged)
   already keeps the two on a shared text baseline regardless of the size
   difference; white-space:nowrap above still keeps a multi-part blend
   dose from breaking mid-string at the larger size, only more likely to
   need the row's own flex-wrap to send it to its own line. */
.hero-name-row .inline-dose{ font-size:clamp(30px,4.5vw,36px); }
/* FIX, caught in this pass's own overflow check (390w, GLOW Blend, 32px
   real horizontal overflow): "never break mid-dose" assumed the dose
   would always fit ON ITS OWN LINE once the row wrapped it away from the
   name; doubled to 30px, a 3-part fill ("50 MG / 10 MG / 10 MG", 22
   characters) no longer does, at the narrowest widths this store
   supports. white-space:normal here lets a dose too wide for the
   viewport wrap at its own natural word/slash boundaries instead of
   overflowing, the same fallback .sku-card-mg already relies on for the
   identical string. Ordinary short doses ("10 MG") never reach this
   width and are unaffected. */
@media (max-width:480px){
  .hero-name-row .inline-dose{ white-space:normal; }
}
/* The plain-language line and the legal line were BOTH unstyled: each fell
   through to the 16px full-brightness body default, so the compliance
   paragraph rendered at exactly the same weight as the sentence that says what
   the product is, in a 533px column, 78px each. That is the buy box arguing
   with itself, which is the thing CT flagged. The summary keeps the weight it
   should always have had; the legal statement drops to the 15px informational
   floor under a hairline, which is what this template's own comment at the
   .buy-ruo markup always said it was for: unmissable, never shouted. Its TEXT
   is the dossier's ruo_statement and is untouched. */
.product-summary{
  margin:0 0 18px; font-size:var(--fs-body); line-height:1.56;
  color:var(--mist); max-width:48ch;
}
.buy-ruo{
  margin:18px 0 0; padding-top:16px; border-top:1px solid var(--hairline);
  font-size:var(--fs-meta); line-height:1.6; color:var(--dsr-ink-3);
}
.product-price{font-family:var(--font-mono); font-size:24px; margin:0 0 24px;}
.product-price .placeholder{font-size:15px; color:var(--mist-dim);}
/* Live tier price (2026-07-28, store owner review: set quantity to 10 and
   the headline price stayed at $89.00 while the tier table correctly showed
   $71.20). assets/pdp-motion.js swaps this element's contents (and, same
   day, .buybar-price's, sharing this exact rule rather than a second one)
   when the resolved quantity crosses a pack-tier threshold; the two spans
   below are its output shape, both at each element's own existing font
   size rather than demoted to a footnote. No motion: a plain text swap
   needs no transition and is reduced-motion safe by construction.
   COLOUR, corrected twice, this is the settled convention: the STRUCK
   (superseded) price is grey, the LIVE (actual, payable) price carries
   the accent. It briefly inverted to struck=copper on a misreading of
   "the slashed-out price should stand out" as "colour the struck number"
   rather than "make the number the customer will pay stand out"; the
   owner's correction, verbatim: "The slashed-out price should not be
   orange. It should be grey, and the copper should be the actual price,
   just like you did for the slide-out card." The mini-cart drawer
   (.kc-price-orig / .kc-price-now) is the reference this now matches
   exactly: struck = --mist-dim, live = --pale. Cart, checkout and the
   drawer were never wrong and stay untouched; a separate cart-page pass
   owns any future decision about bringing every surface to one literal
   colour. */
.product-price-orig{ color:var(--mist-dim); text-decoration:line-through; margin-right:10px; }
.product-price-now{ color:var(--pale); }
.hero-info .trust-list{margin-bottom:28px;}
/* 2026-07-24: 3 trust rows in a plain flex-wrap list break into a 2+1 with
   an empty quadrant at PDP widths once the checkmark icon's width is
   counted; a grid guarantees exactly 3-up on desktop (each item free to
   wrap its own text to 2 lines if narrow) and drops to 1-up at the same
   mobile breakpoint the hero-mobile trust list already stacks at. */
.hero-info .trust-list{ display:grid; grid-template-columns:repeat(3,1fr); gap:14px 16px; }
@media (max-width:767px){ .hero-info .trust-list{ grid-template-columns:1fr; } }
.buy-row{display:flex; gap:14px; align-items:center; margin-bottom:16px; flex-wrap:wrap;}
.qty-stepper{display:flex; align-items:center; border:1px solid var(--hairline); border-radius:2px;}
/* self-EBI catch (2026-07-26, sibling of the .kc-qty button fix above):
   Storefront's own em-scaled `button{padding:.618047em 1.41575em}` was
   winning over this unguarded width/height (same double-enqueue load-order
   hazard documented elsewhere in this file), rendering ~45px wide instead
   of the intended 38px. Pre-existing, not introduced by this sweep; fixed
   here since the root cause is identical and now confirmed. */
.qty-stepper button{background:none; border:0; color:var(--mist); width:38px !important; height:44px !important; padding:0 !important; font-size:16px; cursor:pointer;}
.qty-stepper button:hover{background:rgba(233,235,238,.06);}
.qty-stepper input{width:42px; text-align:center; background:none; border:0; color:var(--mist); font-family:var(--font-mono); font-size:var(--fs-meta);}
/* !important: Storefront's own button:disabled{opacity:.5 !important} otherwise overrides the intended value */
.btn-secondary[disabled]{opacity:.45 !important; cursor:not-allowed;}
.btn-secondary[disabled]:hover{color:var(--pale); border-color:var(--copper);}
.gated-note{font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--mist-dim); margin:0 0 16px;}

/* =========================================================================
   TIER LADDER (pack-quantity discounts, rendered by Discount Rules for
   WooCommerce via the woocommerce_before_add_to_cart_form hook). Restyled
   from the plugin's default table into a compact, branded offer card:
   DM Mono figures, copper label, ember discount price, one row per real
   tier. The 1-2/0% baseline row is hidden, it is not an offer.
   ========================================================================= */
/* 2026-07-27: the ladder was a correct table that did not read as an offer.
   The copper edge is the same device the certificate band uses for "this is
   the good part", and the discounted figure now outweighs the tier label it
   sits beside instead of matching it at 15px. Colour is unchanged (--pale,
   7.06:1 on this ground); only size, weight and the left rule change. */
.tier-ladder{
  margin:4px 0 20px; border:1px solid var(--hairline); border-radius:3px;
  border-left:2px solid var(--copper);
  background:rgba(233,235,238,.03); overflow:hidden;
}
.tier-ladder tbody tr{ transition:background .3s var(--dsr-ease); }
.tier-ladder tbody tr:hover{ background:rgba(233,235,238,.03); }
/* Contrast (2026-07-26 fix wave B): this card's ground is
   rgba(233,235,238,.03) composited over --slate, i.e. rgb(27.4,29.4,33.3).
   --copper measured 4.13:1 there, below the 4.5:1 AA floor at 13px, the same
   copper-on-dark-panel failure already recorded and fixed in
   assets/confirmation.css (.oc-status-chip, copper 4.41:1 -> --pale 7.53:1).
   Same swap, same reason: --pale measures 7.06:1 on this ground and is the
   token this store already uses for readable warm-accent text. The label
   still reads as its own tier against the price beside it via weight,
   tracking (.14em vs .02em) and small caps, not via hue alone. */
.tier-ladder-label{
  display:block; font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.14em; text-transform:uppercase; color:var(--pale);
  padding:14px 18px 10px;
}
/* margin:0 (2026-07-26 fix wave B): the SAME Storefront parent-theme leak as
   the .trust-list one documented at the top of this file, just on a different
   element selector. Storefront ships `table{margin:0 0 1.41575em}` (0,0,1),
   which beats this file's `*{margin:0}` (0,0,0), so the plugin's table
   carried a 22.652px bottom margin INSIDE a bordered, overflow:hidden card:
   measured 23.6px of unexplained dead space between the last tier row's
   bottom border and the card's own bottom edge, against 0px above the first
   row. Found by the item-1 leak sweep this pass ran across every rendered
   block element, which is exactly what that sweep was for. */
.tier-ladder table.wdr_bulk_table_msg{ width:100%; border-collapse:collapse; margin:0; }
.tier-ladder thead{ display:none; }
.tier-ladder tbody tr:first-child{ display:none; } /* the 1-2/0% baseline
  row created by import/scripts/create-tier-rules.php is always first;
  if that script's range order or count ever changes, re-check this rule */
.tier-ladder tbody tr{
  display:flex; align-items:center; justify-content:space-between;
  padding:12px 18px; border-top:1px solid var(--hairline);
}
.tier-ladder tbody tr:nth-child(2){ border-top:1px solid var(--hairline); }
.tier-ladder td{ padding:0; border:0; background:transparent !important; }
/* tier-price display text bumped for legibility (2026-07-26 sweep); the
   discount STRING/math itself is untouched, only the type size. */
.tier-ladder .wdr_bulk_title{
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.02em;
  color:var(--mist) !important; background:transparent !important;
}
.tier-ladder .wdr_bulk_range{ display:none; }
.tier-ladder .wdr_bulk_table_discount{ text-align:right; }
.tier-ladder .wdr_table_discounted_value{ display:none !important; }
/* Contrast (2026-07-26 fix wave B): --ember measured 3.74:1 against this
   card's composited ground (rgb(27.4,29.4,33.3)), below the 4.5:1 AA floor.
   tokens.css marks ember background-only for exactly this reason ("ember
   passes 4.5:1 ONLY with black text"), so it was never a legal text colour
   on a dark panel; --pale measures 7.06:1 here and is the store's readable
   warm-accent text token. The drawer/cart/checkout discounted prices
   (.kc-price-now, .wc-block-components-product-price__value.is-discounted)
   are the same "this is a discount" colour convention and are NOT changed
   here: they were outside this pass's contract, and they sit on a different
   ground (--slate, where ember measures 3.99:1). Flagged for a follow-up so
   the convention stays one colour across PDP/drawer/cart/checkout. */
.tier-ladder .wdr_table_discounted_price{
  font-family:var(--font-mono); font-size:19px; font-weight:500; color:var(--pale);
  letter-spacing:-.01em; font-variant-numeric:tabular-nums; white-space:nowrap;
}
.tier-ladder .wdr_table_discounted_price .woocommerce-Price-currencySymbol{ color:var(--pale); }
@media (max-width:520px){
  .tier-ladder tbody tr{ padding:10px 14px; }
  .tier-ladder-label{ padding:12px 14px 8px; }
  .tier-ladder .wdr_bulk_title{ font-size:var(--fs-meta); }
}
/* .coa-link-row DELETED 2026-07-28 with the "Verify a lot" buy-box link it
   wrapped (store owner review); nothing else used the class. */
.below-fold{padding-top:48px; padding-bottom:56px;}
.spec-block{margin-bottom:56px;}
.ruo-band{background:rgba(233,235,238,.03); border-radius:3px; padding:24px 28px; margin-bottom:40px;}
/* RUO disclaimer (2026-07-26 sweep): real compliance copy CT wants legible;
   exact string content is unchanged, only the type size. */
.ruo-band p{font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.9; color:var(--mist-dim); margin:0 0 8px;}
.ruo-band p:last-child{margin-bottom:0;}
/* .related-grid / .related-card / .related-name / .related-mg / .related-price
   DELETED 2026-07-28 (store owner review, flagged twice): this was bespoke
   card markup that did not match the shop page and cropped the vial photo
   (a 4:3 or `medium`-size box over a source shot at 4:5). "Also recommended"
   (woocommerce/single-product.php) now renders through kairo_render_sku_card(),
   the exact partial the shop page uses, so its cards are always the shop's own
   size (never a compact variant); see ".freqbuy .sku-grid, .related-block
   .sku-grid" further down for the one thing that IS scoped to this section,
   the grid's own column count (3, so a fixed 3-item row fills instead of
   leaving the shop's 4th column empty). */

/* =========================================================================
   VERIFY PAGE, ported verbatim from the inline <style> block in verify.html.
   ========================================================================= */
.verify-hero{padding-top:140px; padding-bottom:8px;}
@media (max-width:767px){.verify-hero{padding-top:104px;}}
.verify-hero h1{font-family:var(--font-display); font-weight:400; font-size:clamp(32px,4.4vw,50px); color:var(--mist); margin:14px 0 16px; max-width:14ch;}
.verify-hero p{font-size:15px; line-height:1.7; color:var(--mist-dim); max-width:52ch; margin:0;}

.lookup-panel{background:rgba(233,235,238,.03); border:1px solid var(--hairline); border-radius:4px; padding:32px; margin-top:40px;}
.lookup-caveat{font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--mist-dim); margin-top:14px; line-height:1.7;}
.lookup-row{display:flex; gap:12px; flex-wrap:wrap;}
.lookup-row input{
  flex:1 1 240px; background:var(--slate); border:1px solid var(--hairline); border-radius:2px;
  color:var(--mist); font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.04em;
  padding:0 16px; height:52px;
}
.lookup-row input::placeholder{color:var(--mist-dim);}
.lookup-row input:focus{outline:none; border-color:var(--copper);}
.lookup-status{margin-top:16px; font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--mist-dim); line-height:1.7;}
.lookup-status.is-hidden{display:none;}

.coa-result{margin-top:28px; display:none;}
.coa-result.is-visible{display:block;}
.coa{background:var(--porcelain); color:var(--ink); border-radius:3px; overflow:hidden;}
.coa-head{padding:20px 24px; border-bottom:1px solid var(--hairline-on-porcelain);}
.coa-head .name{font-family:var(--font-display); font-size:19px;}
/* =========================================================================
   COA CARD CONTRAST (2026-07-26 fix wave B, self-EBI round). Same
   ink-alpha-on-porcelain failure class as `.section--porcelain .eyebrow`
   above, on the surface where it matters most: the lot-lookup Certificate of
   Analysis is this brand's whole trust proposition, and every data value on
   it was below AA. It escaped the first sweep because `.coa-result` is
   display:none until a lookup succeeds, so nothing rendered it; found by
   forcing `.is-visible` and re-measuring.
   Measured before -> after (ink composited over #F1EDE4; min alpha for
   4.5:1 on this ground is .5981):
     .coa-head .lot            .55 = 3.87:1  ->  .64 = 5.15:1
     td.k  (row key)           .55 = 3.87:1  ->  .64 = 5.15:1
     td.v  (row VALUE)         .50 = 3.33:1  ->  .78 = 8.26:1
     .bar-label                .50 = 3.33:1  ->  .64 = 5.15:1
   The value column deliberately lands well above the key column rather than
   below it: these cells carry the actual purity/mass/endotoxin results, and
   the old scale had the data quieter (.50) than its own label (.55), which
   was backwards for a table whose entire job is presenting the numbers.
   ========================================================================= */
.coa-head .lot{font-family:var(--font-mono); font-size:var(--fs-meta); color:rgba(23,21,15,.64); margin-top:4px; letter-spacing:.02em;}
table.coa-table{width:100%; border-collapse:collapse; font-family:var(--font-mono); font-size:var(--fs-meta);}
table.coa-table tr{border-bottom:1px solid var(--hairline-on-porcelain);}
table.coa-table tr:last-child{border-bottom:none;}
table.coa-table td{padding:12px 24px;}
table.coa-table td.k{color:rgba(23,21,15,.64); width:42%;}
table.coa-table td.v{text-align:right; letter-spacing:.06em; color:rgba(23,21,15,.78);}
/* padding-bottom 24 -> 32px (2026-07-26 fix wave B, self-EBI round): the
   absolutely-positioned "99%" threshold label below the purity bar hangs
   `top:14px` off an 8px-tall track and is ~17px tall, so it reached 23px
   below the track against 24px of padding on paper, but measured 3px PAST
   the card's own bottom edge at both 1440 and 390 once real line-height was
   counted (labelBottom 1110.3 vs cardBottom 1107.2). Pre-existing, and
   invisible to the eye on a light card, which is exactly why design-qa
   measures containment instead of looking at it. 32px puts the label 5px
   inside the card at both widths. */
.coa-purity{padding:20px 24px 32px; border-top:1px solid var(--hairline-on-porcelain);}
.coa-purity .bar-label{display:flex; justify-content:space-between; font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em; text-transform:uppercase; color:rgba(23,21,15,.64); margin-bottom:10px;}
.coa-purity .bar-track{position:relative; height:8px; background:rgba(23,21,15,.10); border-radius:1px; overflow:visible;}
.coa-purity .bar-fill{height:100%; border-radius:1px; width:100%; background:repeating-linear-gradient(90deg, rgba(184,106,60,.55) 0 6px, transparent 6px 12px);}
.coa-purity .bar-threshold{position:absolute; top:-5px; bottom:-5px; left:99%; width:2px; background:var(--copper); opacity:.7;}
/* .68, not .64 like its siblings: this label overlaps the bar TRACK
   (rgba(23,21,15,.10) over porcelain = rgb(219,215,207)), a darker ground
   where the min alpha for 4.5:1 rises to .6226. Measured .45 = 2.76:1 ->
   .68 = 5.35:1 against that composited track, not against bare porcelain. */
.coa-purity .bar-threshold-label{position:absolute; top:14px; left:99%; transform:translateX(-100%); font-family:var(--font-mono); font-size:var(--fs-micro); color:rgba(23,21,15,.68); letter-spacing:.04em;}

.steps-section .verify-steps{margin-top:0;}

/* =========================================================================
   POLICY PAGES (Terms of Sale, Privacy, Shipping, Returns and Refunds):
   generic content pages, no hero, so the same top-offset pattern as the
   verify page applies. Quiet "draft, pending counsel" notice band sits
   above the body copy rather than being buried in the first paragraph.
   ========================================================================= */
/* 2026-07-26 (lane C): the measure moved from .container itself to its
   children. .container carries margin:0 auto, so capping the container at
   760px centred the whole policy page in the viewport while every other
   KAIRO surface (hero, section-head, quality-copy, the About page) starts at
   the left gutter. Measured before the fix at 1440: policy copy began at
   x=419, everything else at x=159. Children now hold the reading measure and
   the container keeps the site's standard 1280 + gutter geometry.

   Root cause of the horizontal half: the markup is `container section
   policy-page`, and `.section{ padding:var(--section-y) 0 }` is a SHORTHAND
   that lands after `.container{ padding:0 var(--gutter) }` at equal
   specificity, so it silently zeroed the gutter on every policy page. The
   old max-width hid that (a 760px box centred by margin:0 auto never
   reaches the viewport edge), so the CONTENT GUTTER comment further down
   this file describing policy pages as "already self-gutter" was wrong.
   Both axes are now spelled out here. */
.policy-page{
  padding-top:140px; padding-bottom:96px;
  padding-left:var(--gutter); padding-right:var(--gutter);
}
.policy-page > *{ max-width:640px; }
@media (max-width:767px){ .policy-page{ padding-top:104px; } }
/* Page title. Same Marcellus clamp as .verify-hero h1 so a policy page and
   the verify/About heroes read as one system; the eyebrow-less policy pages
   just start with it. Added 2026-07-26 with the h1 itself (see
   configure-content.php: the markdown H1 used to be dropped, leaving these
   pages with no h1 at all). */
.policy-title{
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(32px,4.4vw,50px); line-height:1.12; letter-spacing:.005em;
  color:var(--mist); margin:0 0 28px;
}
.policy-page h2.h3{ margin:40px 0 12px; }
.policy-page h2.h3:first-of-type{ margin-top:32px; }
.policy-page .policy-title + .policy-notice{ margin-top:4px; }
.policy-page p.body{ margin-bottom:14px; }
.policy-notice{
  display:flex; align-items:baseline; gap:14px; flex-wrap:wrap;
  background:rgba(233,235,238,.03); border:1px solid var(--hairline); border-radius:3px;
  padding:16px 20px; margin-bottom:32px;
}
.policy-notice .mono-label{ color:var(--copper); flex:0 0 auto; }
.policy-notice p{ margin:0; color:var(--mist-dim); }
.policy-notice{ flex-wrap:wrap; }
.policy-notice p{ flex:1 1 340px; min-width:0; }

/* Bullet list inside a policy page (markdown "- " blocks, see
   scripts/configure-content.php). Body copy stays at --fs-body 16px so the
   type floor holds; the marker is a copper hairline rather than a browser
   disc, so lists belong to the same rule system as the rest of the page. */
.policy-page ul.policy-list{ list-style:none; margin:0 0 18px; padding:0; }
.policy-page ul.policy-list li{
  position:relative; padding-left:20px; margin-bottom:10px;
  font-size:var(--fs-body); line-height:1.7; letter-spacing:.006em;
  color:rgba(233,235,238,.68);
}
.policy-page ul.policy-list li:last-child{ margin-bottom:0; }
.policy-page ul.policy-list li::before{
  content:""; position:absolute; left:0; top:.8em;
  width:8px; height:1px; background:var(--copper); opacity:.8;
}
.policy-page ul.policy-list li strong,
.policy-page p.body strong{ color:var(--mist); font-weight:600; }

/* CT-CONFIRM marker: an unresolved decision the page cannot launch carrying.
   Deliberately loud (copper wash + mono tag) so it can never be skimmed past
   or mistaken for finished copy. Wraps across lines via box-decoration-break
   so a long question keeps its background on every line. These come off with
   the .policy-notice draft band once CT answers them. */
/* Zero horizontal padding is deliberate. With padding + box-decoration-break
   clone, Chromium lays out the cloned side padding without reserving room for
   it, so a marker that wraps pushes 4 to 6px past .policy-page and the whole
   document gains horizontal scroll at 390 and 360 (measured 2026-07-26). The
   wash alone carries the emphasis. A per-line border-bottom was tried and
   removed: on a marker wrapping to 12 lines at 360 it renders as a stack of
   ragged copper stripes rather than one highlighted passage. */
.ct-confirm{
  box-decoration-break:clone; -webkit-box-decoration-break:clone;
  background:rgba(184,106,60,.13);
  color:var(--pale); padding:5px 0; overflow-wrap:anywhere;
}
.ct-confirm-tag{
  font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em;
  text-transform:uppercase; color:var(--copper); margin-right:8px; white-space:nowrap;
}
.section--porcelain .ct-confirm{ background:rgba(184,106,60,.16); color:var(--deep); }

/* =========================================================================
   SEARCH FORM (WordPress core get_search_form(), used on the search-empty
   state and the 404 page): unstyled by default (white input, gray submit),
   the exact "weird CSS bug" class this sweep targets. One rule set so every
   caller of get_search_form() picks it up, not a per-template fix.
   ========================================================================= */
.search-form{ display:flex; gap:10px; max-width:420px; }
.search-form .search-field{
  flex:1 1 auto; background-color:var(--slate) !important; border:1px solid var(--hairline);
  border-radius:2px; color:var(--mist); font-family:var(--font-body); font-size:var(--fs-meta);
  padding:0 16px; height:52px; color-scheme:dark;
}
.search-form .search-field:focus{
  outline:none; background-color:var(--slate) !important; border-color:var(--copper) !important; color:var(--mist) !important;
}
.search-form .search-field::placeholder{ color:var(--mist-dim); opacity:1; }
.search-form .search-submit{
  appearance:none; border:1px solid var(--ember); cursor:pointer; text-decoration:none;
  background:var(--ember); color:var(--on-ember); font-family:var(--font-body); font-weight:700;
  font-size:var(--fs-meta); letter-spacing:.14em; text-transform:uppercase; border-radius:var(--btn-radius);
  min-height:52px; padding:0 30px; flex:0 0 auto;
}
.search-form .search-submit:hover{ background:#cf6527; border-color:#cf6527; }
/* self-EBI pass (2026-07-24): every other CTA on the store gets the brand
   copper focus-visible ring via the shared `.btn` rule; this one has its
   own hand-written declarations rather than the `.btn` class, so it needs
   the same ring spelled out here or a keyboard user tabbing to it sees the
   browser default (blue on Chrome) instead. */
.search-form .search-submit:focus-visible{ outline:2px solid var(--copper); outline-offset:2px; }

/* =========================================================================
   SEARCH RESULTS (search.php) and 404 (404.php): empty-state copy uses the
   same eyebrow/h2/body rhythm as the policy pages so these edge pages read
   as part of the same system, not a bolt-on.
   ========================================================================= */
.search-empty-state{ max-width:480px; padding:24px 0 64px; }
.search-empty-state .search-form{ margin-top:20px; }
.policy-page .btn{ display:inline-flex; }

/* =========================================================================
   FREE SHIPPING PROGRESS BAR (cart page)
   ========================================================================= */
/* Header clearance (2026-07-26 round-2 QA). This wrap is the FIRST thing on
   a filled cart page, and .site-header is position:absolute, opaque and
   z-index:30, so 28px of padding put the whole module underneath it:
   measured at 1440 the wrap ran top 0 -> bottom 65.3 against a header
   bottom of 80 (label hit-test returned .nav-links, i.e. the header, not
   the label), and at 390 only ~5px of the track cleared a header bottom of
   60. Same escape hatch already used for checkout and the account pages
   (style.css ~:1274) and for the empty-cart block (~:2149): 140px desktop,
   104px under 767px. */
.free-ship-bar-wrap{ padding-top:140px; }
@media (max-width:767px){ .free-ship-bar-wrap{ padding-top:104px; } }
.free-ship-bar{ margin:0 0 24px; }
/* Lead text now WHITE (--mist), not grey (2026-07-28, CT: "the lead text
   goes white, not grey, so it carries"). The dollar figure inside it
   (assets/cart-checkout-enhance.js wraps it in .free-ship-figure) keeps the
   site's own established emphasis for "a number that matters": --pale, the
   same accessible copper the unit price, the line total and the savings
   line all already use. Unlocked state ("Free shipping applied", no
   separate figure to split out) is unchanged: the whole line already read
   as the pale "you got it" payoff before this pass and still does.
   Type ROUGHLY DOUBLED (CT: "that font size should be maybe twice as
   large, so that should be a big feature"), DESKTOP ONLY (CT: "I'm just
   focusing on desktop right now"): "Add $94.40 for free shipping" at 2x
   --fs-meta does not fit one line in a ~350px mobile column and this line
   has no wrap fallback the way the dose does, so mobile keeps its original
   size below rather than risk a mid-sentence wrap CT hasn't asked for
   here. */
.free-ship-label{
  font-family:var(--font-mono); font-size:calc(var(--fs-meta) * 2); letter-spacing:.03em;
  color:var(--mist); margin:0 0 10px;
}
@media (max-width:767px){
  .free-ship-label{ font-size:var(--fs-meta); }
}
.free-ship-bar.is-unlocked .free-ship-label{ color:var(--pale); }
.free-ship-figure{ color:var(--pale); }
/* Bar thickness doubled (CT: it "should be a big feature", was a
   hairline), and shortened to end around the cart items column instead of
   the full page width (CT: "end it around the right edge of the product-
   total column"). Width is MEASURED LIVE by assets/cart-checkout-
   enhance.js (renderFreeShipBar()) against the actual items table, not a
   static guess: the table is table-layout:auto and this same file's own
   thumbnail/total-column changes (below) shift its width, so a hardcoded
   px value would drift the first time either column's content changes.
   .free-ship-bar carries no horizontal auto-margin (unlike its
   .container-classed parent .free-ship-bar-wrap), so an explicit width
   here shrinks it from the right and keeps its LEFT edge aligned with the
   items table's own left edge -- exactly "end around the total column".
   Desktop only, matching the type-size scoping above; the custom property
   is simply unread below 768px, so mobile keeps its existing full-width
   bar. */
.free-ship-track{ height:6px; width:100%; background:var(--hairline); border-radius:2px; overflow:hidden; }
.free-ship-fill{ height:100%; width:0%; background:var(--copper); border-radius:2px; transition:width .2s ease; }
.free-ship-bar.is-unlocked .free-ship-fill{ background:var(--pale); width:100% !important; }
@media (min-width:768px){
  .free-ship-bar{ width:var(--free-ship-bar-w,100%); }
}

/* =========================================================================
   Storefront-core resets: this theme replaces Storefront's header/footer/
   breadcrumb/notices markup wholesale (header.php, footer.php below), so
   parent-theme chrome that would otherwise show through is hidden here
   rather than fought selector-by-selector.
   ========================================================================= */
.woocommerce-breadcrumb{ display:none; }
.storefront-sorting,
.woocommerce-result-count{ display:none; }
#page{ overflow:hidden; }

/* =========================================================================
   FULL-WIDTH CONTENT (root cause, 2026-07-23): header.php/footer.php below
   replace Storefront's own outright and never open the #content.site-content
   wrapper Storefront's page.php (the template WordPress falls back to for
   cart, checkout, my-account and the policy pages) expects to sit inside.
   Storefront's own clearfix, `.site-content::after{clear:both}`, never fires
   because that element doesn't exist in this theme's DOM, so parent-theme
   rules like `.content-area{float:left;width:73.9%}` / `.right-sidebar
   .content-area{...}` are left floating uncleared: the un-floated
   site-footer that follows in the markup has no room beside the float and
   its content wraps into the vacated right-hand column starting at the very
   top of the page, instead of appearing full-width below it.
   `#primary.content-area` (id+class) outranks every *-sidebar /
   .storefront-full-width-content two-class combinator in Storefront's own
   stylesheet, so this one rule forces full width everywhere Storefront
   templates render #primary, with no per-page spot fix. */
#primary.content-area{ float:none; width:100%; margin:0; }

/* =========================================================================
   CONTENT GUTTER (2026-07-23 follow-up): #primary being full-width doesn't
   by itself give every child the site's .container gutter. On the cart page
   the free-ship-bar-wrap and wc-block-store-notices siblings already carry
   their own .container-equivalent framing (so they were already correctly
   inset), and the policy pages bake a `.container.policy-page` div straight
   into page content (staging/scripts/configure-content.php) that's likewise
   already self-gutter. The one thing genuinely missing a gutter is the raw,
   ungutted WooCommerce Blocks/shortcode markup itself: the Cart block's
   product-table/totals grid, the Checkout block's form/order-summary grid,
   and the my-account shortcode output. Scoped to exactly those three (not
   #primary itself, which would double the inset on the elements above that
   already self-gutter) so cart/checkout/my-account/policy content all lands
   on the same gutter line without any element getting it twice. */
.entry-content > .wp-block-woocommerce-cart,
.entry-content > .wp-block-woocommerce-checkout,
body.woocommerce-account .entry-content > .woocommerce{
  max-width:var(--container); margin:0 auto; padding:0 var(--gutter);
}
/* Root cause (2026-07-24): the my-account shortcode's own tab nav
   (Dashboard/Orders/Downloads/...) is the first thing WooCommerce prints
   inside `.woocommerce`, with no top margin of its own; now that the
   phantom entry-title above it is gone (see functions.php,
   kairo_remove_storefront_hooks), this element sits at y:0, directly
   under the position:absolute .site-header, and its first three tabs
   render invisibly behind it (confirmed via getBoundingClientRect: nav
   top 0, bottom ~155, header bottom ~72). Same padding-top values as the
   other interior-page containers (breadcrumb-wrap, policy-page,
   verify-hero) so my-account reads as part of the same system.

   2026-07-24 correction: an earlier version of this comment claimed
   "the cart/checkout Blocks next to this rule don't need the same fix:
   their own top-of-page element already has enough margin to clear the
   header on its own." That was true for the Cart block by coincidence
   (the free-ship-bar-wrap injected above it, kairo_inject_free_shipping_bar
   in functions.php, happens to supply enough clearance) but false for
   Checkout: measured with no fix, the "Contact information" step title sat
   at top:0 and the mobile order-summary toggle's title text at y37-69,
   BOTH fully under the fixed header (bottom 52 at 390px, 72 at 1440px), at
   every viewport tested, not just mobile. `.wp-block-woocommerce-checkout`
   gets the exact same padding-top escape hatch here instead of relying on
   a Cart-only coincidence. */
body.woocommerce-account .entry-content > .woocommerce,
.entry-content > .wp-block-woocommerce-checkout{ padding-top:140px; }
@media (max-width:767px){
  body.woocommerce-account .entry-content > .woocommerce,
  .entry-content > .wp-block-woocommerce-checkout{ padding-top:104px; }
}
/* Login/Register headings (myaccount/form-login.php's own bare <h2>Login
   </h2> / <h2>Register</h2>, no class to hook): the last place on the
   account surfaces still off the h1/h2 Marcellus scale used everywhere
   else on the store (dashboard, order-received, policy pages), rendering
   as a small default browser heading (2026-07-26 UI-state sweep). Scoped
   to account pages' direct .woocommerce child so it can't reach some
   future core h2 the account area gains for a different job. */
body.woocommerce-account .entry-content > .woocommerce > h2{
  font-family:var(--font-display); font-weight:400; font-size:clamp(24px,2.8vw,32px);
  color:var(--porcelain); margin:0 0 20px;
}

/* Account tab nav (Dashboard/Orders/Downloads/Addresses/Account details/Log
   out): stock WP bullet-list markers on an otherwise chromeless UI, the
   same bullet-leak class the header/footer nav already guard against (see
   verify-header-parity.mjs). Flattened to a row of quiet links matching the
   site's other nav language, current tab picked out in copper instead of
   WooCommerce's default bold-plus-nothing-else. */
.woocommerce-MyAccount-navigation ul{
  list-style:none; margin:0 0 40px; padding:0; display:flex; flex-wrap:wrap; gap:12px 28px;
}
.woocommerce-MyAccount-navigation li{ list-style:none; margin:0; }
.woocommerce-MyAccount-navigation a{
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.1em; text-transform:uppercase;
  color:rgba(233,235,238,.68); text-decoration:none;
}
.woocommerce-MyAccount-navigation a:hover{ color:var(--mist); }
.woocommerce-MyAccount-navigation li.is-active a{ color:var(--copper); }

/* =========================================================================
   NOTICES: this theme calls wc_print_notices() itself on the PDP/archive
   (single-product.php, archive-product.php) and, for the gated-add-attempt
   notice (2026-07-23), via a `woocommerce/store-notices` block on the cart
   page, so notices are deliberate, not stray parent-theme chrome. Root-cause
   fix: an earlier blanket `.woocommerce-notices-wrapper{display:none}` here
   was silencing every notice this theme itself renders, including this one.
   Styled to the brand instead of hidden.
   ---------------------------------------------------------------------- */
.woocommerce-notices-wrapper{ margin-bottom:20px; }
.woocommerce-notices-wrapper:empty{ margin-bottom:0; } /* core always prints this wrapper div even with zero notices */
.woocommerce-notices-wrapper .woocommerce-info,
.woocommerce-notices-wrapper .woocommerce-message,
.woocommerce-notices-wrapper .woocommerce-error,
/* my-account's "No order has been made yet." (myaccount/orders.php calling
   wc_print_notice() directly): same .woocommerce-info markup as every other
   notice above, but printed as a bare sibling of .woocommerce-notices-wrapper
   rather than inside it, so the wrapper-scoped selectors above never
   reached it (2026-07-26 UI-state sweep). Same card treatment, not a
   separate look, since it's the same component in a different spot. */
.woocommerce-MyAccount-content > .woocommerce-info{
  list-style:none; font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.7; letter-spacing:.01em;
  color:var(--mist); background:rgba(233,235,238,.04); border:1px solid var(--hairline); border-left:2px solid var(--copper);
  border-radius:2px; padding:14px 18px; margin:0 0 16px;
}
.woocommerce-notices-wrapper .woocommerce-error{ border-left-color:var(--ember); }
.woocommerce-notices-wrapper .woocommerce-error li{ list-style:none; }
/* Notice action composition (2026-07-24, CT: "this button still looks
   terrible"): the printed "TB-500 has been added to your cart. View cart"
   notice was rendering its <a class="button wc-forward"> through the
   sitewide .woocommerce a.button rule, the same full-billboard CTA treatment
   (52px min-height, 30px side padding, 8px radius sized for a hero/PDP
   action) used for real primary actions elsewhere. Inside a one-line notice
   row that reads as an oversized, disproportionate button, not a supporting
   action. Grid instead of relying on the bare text node's implicit flex
   sizing: message text is column 1 (1fr, no wrapping element in core's
   markup to target directly), the action anchor is column 2 (auto width),
   vertically centered against the message. A dedicated, higher-specificity
   rule (message/info + a.button.wc-forward) beats the generic CTA rule
   without needing !important or source-order tricks. */
.woocommerce-notices-wrapper .woocommerce-message,
.woocommerce-notices-wrapper .woocommerce-info,
.woocommerce-MyAccount-content > .woocommerce-info{
  display:grid; grid-template-columns:1fr auto; align-items:center; gap:10px 16px;
}
.woocommerce-notices-wrapper .woocommerce-message a.button.wc-forward,
.woocommerce-notices-wrapper .woocommerce-info a.button.wc-forward,
.woocommerce-MyAccount-content > .woocommerce-info a.button.wc-forward{
  display:inline-flex; align-items:center; justify-content:center; white-space:nowrap;
  min-height:40px; padding:10px 18px; margin:0;
  font-family:var(--font-mono); font-weight:700; font-size:var(--fs-meta); letter-spacing:.1em; text-transform:uppercase;
  border-radius:var(--btn-radius);
}
@media (max-width:767px){
  .woocommerce-notices-wrapper .woocommerce-message,
  .woocommerce-notices-wrapper .woocommerce-info,
  .woocommerce-MyAccount-content > .woocommerce-info{ grid-template-columns:1fr; }
  .woocommerce-notices-wrapper .woocommerce-message a.button.wc-forward,
  .woocommerce-notices-wrapper .woocommerce-info a.button.wc-forward,
  .woocommerce-MyAccount-content > .woocommerce-info a.button.wc-forward{ justify-self:end; }
}
/* Same compact, subordinate treatment for WooCommerce Blocks' own notice
   component (see .wc-block-components-notice-banner below): defensive,
   scoped to the action-button class Blocks renders when a notice carries
   one. No notice on this store currently ships an action (the one
   wc_add_notice() call, functions.php, is plain text), so this has nothing
   to visually regress; it exists so the same terrible-billboard defect
   can't recur the moment a block notice does grow an action. */
.wc-block-components-notice-banner__action{
  display:inline-flex !important; align-items:center; justify-content:center; white-space:nowrap;
  min-height:40px !important; padding:10px 18px !important; margin:0 !important;
  font-family:var(--font-mono) !important; font-weight:700 !important; font-size:var(--fs-meta) !important;
  letter-spacing:.1em !important; text-transform:uppercase !important;
  border-radius:var(--btn-radius) !important;
}
/* core WC ships an icon-font ::before on .woocommerce-info/-error/-message,
   positioned for core's own padding-left; this theme doesn't load that icon
   font, so suppress it rather than let it visually overlap the message text. */
.woocommerce-notices-wrapper .woocommerce-info:before,
.woocommerce-notices-wrapper .woocommerce-message:before,
.woocommerce-notices-wrapper .woocommerce-error:before,
.woocommerce-MyAccount-content > .woocommerce-info:before{ display:none; }
.wc-block-store-notices{ max-width:var(--container); margin:0 auto; padding:24px var(--gutter) 0; }
/* WooCommerce's own a11y JS gives the printed notice tabindex="-1" and
   focuses it so screen readers announce it; that paints the browser's
   default blue focus ring over the brand border above. tabindex="-1" is
   never Tab-reachable, so this is not a real keyboard focus stop and
   suppressing the ring is safe. */
.woocommerce-notices-wrapper .woocommerce-info:focus,
.woocommerce-notices-wrapper .woocommerce-message:focus,
.woocommerce-notices-wrapper .woocommerce-error:focus,
.woocommerce-MyAccount-content > .woocommerce-info:focus{ outline:none; }

/* =========================================================================
   WOOCOMMERCE BLOCKS (Cart/Checkout): the Proceed to Checkout / Place Order
   buttons are JS-hydrated after page load (no PHP template to edit), so
   they're styled by the classes WooCommerce Blocks renders, matched to the
   .btn/.btn-primary ember pill. Scoped to the two primary CTAs only, not
   every `.wc-block-components-button` (coupon apply, qty steppers keep
   core's own quieter affordance). !important matches the specificity of the
   layered WP core / WooCommerce Blocks "contained" button defaults these
   override.
   ========================================================================= */
/* Proceed to Checkout / Place Order (2026-07-26 sweep): the two highest-
   stakes CTAs on the store, bumped to the interactive floor like every
   other button. */
.wc-block-cart__submit-button,
.wc-block-components-checkout-place-order-button{
  background:var(--ember) !important; color:var(--on-ember) !important;
  font-family:var(--font-body) !important; font-weight:700 !important; font-size:var(--fs-meta) !important;
  letter-spacing:.14em !important; text-transform:uppercase !important;
  border-radius:var(--btn-radius) !important; border:1px solid var(--ember) !important;
  min-height:52px !important; text-decoration:none !important;
}
/* Root cause (2026-07-23): Proceed to Checkout renders as an <a>, not a
   <button> (WooCommerce Blocks markup), so the browser's default anchor
   underline painted straight through the ember pill; text-decoration:none
   above kills it at the source instead of hiding it with color tricks.
   Separately, both CTAs' immediate wrapper divs carry an opaque white
   background from upstream WC Blocks/Storefront defaults, sized exactly to
   the button; since the button itself is a rounded (--btn-radius) shape, that
   square white box shows through at the corners as a white sliver. Neither
   wrapper renders anything but the button, so making both transparent
   removes the sliver with no visual loss. */
.wc-block-cart__submit-container,
.wc-block-checkout__actions_row{
  background:transparent !important;
}
.wc-block-cart__submit-button:hover,
.wc-block-components-checkout-place-order-button:hover{
  background:#cf6527 !important; border-color:#cf6527 !important;
}
/* Pressed state, matching .btn-primary:active's accessibility-tested pair
   (STUDY-NOTES lock addendum: deep-pressed requires #fff text). */
.wc-block-cart__submit-button:active,
.wc-block-components-checkout-place-order-button:active{
  background:var(--deep) !important; border-color:var(--deep) !important; color:var(--on-deep) !important;
}
/* WooCommerce Blocks' own button base ships a #0675c4 (WordPress-blue)
   focus ring (`.wc-block-components-button:not(.is-link):focus`); these two
   CTAs are the highest-visibility buttons on the site, so they get the same
   copper focus treatment as `.btn:focus-visible` above instead of that
   default blue. */
.wc-block-cart__submit-button:focus,
.wc-block-components-checkout-place-order-button:focus{
  box-shadow:0 0 0 2px var(--copper) !important; outline:none !important;
}
/* WooCommerce Blocks disables these while shipping/totals recalculate or a
   Place Order submission is in flight; both `disabled` and `aria-disabled`
   are covered since the block toggles either depending on version. Matches
   the .sku-add[disabled] / .btn-secondary[disabled] dimming already used
   elsewhere in this theme, so a mid-recalculation button doesn't still read
   as a fully-live ember CTA. */
.wc-block-cart__submit-button:disabled,
.wc-block-cart__submit-button[aria-disabled="true"],
.wc-block-components-checkout-place-order-button:disabled,
.wc-block-components-checkout-place-order-button[aria-disabled="true"]{
  opacity:.45 !important; cursor:not-allowed !important;
}

/* 2026-07-24 dead-first-click fix (see assets/site.js): a single overlay
   div, appended to <body> (outside the checkout block's own React tree),
   positioned over the Place Order button on click. Living outside React's
   tree means its visibility survives every re-render the button goes
   through while a submission is pending/retrying, which an in-place text
   swap on the button itself would not (React owns that node and wipes any
   DOM we write into it on its next render). pointer-events:none so it's
   purely visual and never blocks the retry click site.js dispatches on the
   real button underneath. */
.kairo-checkout-overlay{
  position:absolute; z-index:60; display:none; align-items:center; justify-content:center; gap:10px;
  background:var(--ember); color:var(--on-ember); border:1px solid var(--ember); border-radius:var(--btn-radius);
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta); letter-spacing:.14em; text-transform:uppercase;
  pointer-events:none;
}
.kairo-checkout-overlay.is-visible{ display:flex; }
.kairo-checkout-overlay__spinner{
  width:14px; height:14px; border-radius:50%;
  border:2px solid rgba(0,0,0,.25); border-top-color:var(--on-ember);
  animation:kairo-checkout-spin .7s linear infinite;
}
@keyframes kairo-checkout-spin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .kairo-checkout-overlay__spinner{ animation:none; }
}

/* =========================================================================
   AGE GATE: theme-native 18+ attestation modal (2026-07-23), replacing the
   "Fastest Age Verification" plugin's stock white modal (blue Yes / red No,
   grey admin-style bar). Rendered by header.php only when the `ageVerified`
   cookie is absent (see kairo_age_gate_confirmed() in functions.php), so a
   verified returning visitor's HTML never contains this markup at all: no
   client-side flash to suppress, and no white flash on first visit either,
   since the modal is part of the initial HTML painted by the render-
   blocking stylesheet already in <head>, not injected after JS runs.
   Structure/pattern reference: the age-gate modal at
   /Users/ct/dev/ovae-site-v2/obsidian/index.html (slate panel, hairline
   border, centered card, quiet secondary action); colors and copy are
   KAIRO's own tokens, not Obsidian's palette.
   ========================================================================= */
.age-gate{
  position:fixed; inset:0; z-index:500; display:flex; align-items:center; justify-content:center;
  padding:24px; background:rgba(9,10,12,.86); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
}
body.has-age-gate{ overflow:hidden; }
.age-gate-panel{
  width:min(440px,100%); background:var(--slate); border:1px solid var(--hairline); border-radius:4px;
  padding:40px 36px 34px; display:flex; flex-direction:column; align-items:center; text-align:center;
}
/* tabindex="-1" takes it out of the Tab order entirely, so any focus it
   ever receives is the page-load autofocus, never real keyboard use; never
   show the sitewide :focus-visible ring on it. */
.age-gate-panel:focus-visible{ outline:none !important; }
.age-gate-logo{ height:16px; width:auto; margin-bottom:28px; }
.age-gate-eyebrow{ margin-bottom:14px; }
.age-gate-title{ font-family:var(--font-display); font-weight:400; font-size:clamp(21px,2.6vw,25px); line-height:1.32; color:var(--porcelain); }
.age-gate-desc{ margin-top:12px; font-size:var(--fs-meta); line-height:1.7; color:var(--mist-dim); max-width:34ch; }
.age-gate-actions{ margin-top:28px; width:100%; display:flex; flex-direction:column; gap:12px; }
.age-gate-confirm{ width:100%; }
.age-gate-decline{
  appearance:none; background:none; border:none; cursor:pointer; padding:4px;
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.08em; text-transform:uppercase;
  color:var(--mist-dim); text-decoration:underline; text-decoration-color:rgba(233,235,238,.3); text-underline-offset:3px;
}
.age-gate-decline:hover{ color:var(--mist); }

/* Decline state: the SAME overlay swaps from the card to a full-bleed quiet
   notice (no red, no dismiss) instead of navigating off-site the way the
   old plugin's "No" button did (it sent visitors to google.com). */
.age-gate-notice{ display:none; width:100%; height:100%; align-items:center; justify-content:center; }
.age-gate.is-declined .age-gate-panel{ display:none; }
.age-gate.is-declined .age-gate-notice{ display:flex; }
.age-gate-notice-inner{ max-width:420px; text-align:center; }
.age-gate-notice-logo{ height:16px; width:auto; margin-bottom:28px; }
.age-gate-notice-title{ font-family:var(--font-display); font-weight:400; font-size:clamp(22px,2.8vw,27px); color:var(--porcelain); line-height:1.32; }
.age-gate-notice-body{ margin-top:14px; font-size:var(--fs-meta); line-height:1.75; color:var(--mist-dim); }

@media (max-width:560px){
  .age-gate-panel{ padding:32px 24px 28px; }
}

/* =========================================================================
   GLOBAL DARK FORM-CONTROL BASELINE (2026-07-24)
   CT-caught regression: the PDP qty-stepper's <input type="text" data-qty-input>
   (single-product.php) flipped full white with a purple ring on focus, on an
   otherwise all-dark page. Root cause: Storefront's parent style.css ships
   `input[type=text]:focus, input[type=number]:focus, ..., textarea:focus,
   .input-text:focus{background-color:#ededed}` (specificity (0,2,1), no
   !important) plus the blanket purple `input:focus{outline:#7f54b3}`
   neutralized in the GLOBAL FOCUS-VISIBLE SYSTEM above. Every rule in the
   FORM CONTROLS section below this one only matched inputs wrapped in a
   specific selector (.form-row input.input-text, .wc-block-components-text-
   input input, .lookup-row input, ...); the qty-stepper's input had none of
   those wrappers (bare <input type="text"> inside a plain <div
   class="qty-stepper">), so it fell straight through to Storefront's
   unconditional default. Enumerate-and-override-per-component was the actual
   bug: every new control had to remember to redeclare dark, and the ones
   that didn't stayed a ticking time bomb.
   This section is the fix: every input/textarea/select/checkbox/radio/button
   on the site is dark BY DEFAULT, in every state (rest, hover, focus,
   active, disabled, autofill), with no class required, declared once at
   element scope, ahead of every other rule in this file. `!important` on
   the color-bearing properties is required to beat Storefront's higher-
   specificity `[type=text]:focus` family; every component-specific rule
   below (FORM CONTROLS, WooCommerce Blocks, native selects) still layers
   its own sizing/padding/label color/copper-border treatment on top and
   still wins ties (equal specificity, later source order, or plain higher
   specificity against this bare-element baseline): this floor only needs
   to be re-asserted here if a brand-new UNCLASSED control appears with no
   layered rule at all, which is exactly the class of bug this closes. */
input,
textarea,
select{
  background-color:var(--slate) !important;
  border:1px solid var(--hairline);
  color:var(--mist) !important;
  color-scheme:dark;
  caret-color:var(--mist);
  font-family:inherit;
}
input::placeholder,
textarea::placeholder{ color:var(--mist-dim); opacity:1; }
input:hover,
textarea:hover,
select:hover{ background-color:var(--slate) !important; border-color:var(--hairline); }
input:focus,
textarea:focus,
select:focus{
  background-color:var(--slate) !important; border-color:var(--copper); color:var(--mist) !important; outline:none;
}
input:active,
textarea:active,
select:active{ background-color:var(--slate) !important; color:var(--mist) !important; }
input:disabled,
textarea:disabled,
select:disabled{ background-color:var(--slate) !important; color:var(--mist-dim) !important; opacity:.5; cursor:not-allowed; }
input[type=checkbox],
input[type=radio]{ accent-color:var(--ember); }
/* Native <button>/reset elements with no component class of their own (a
   bare <button> not covered by .btn/.qty-stepper button/.sku-add/etc.) get
   the same dark floor; any classed button still wins on specificity. */
button{ background-color:var(--slate); border-color:var(--hairline); color:var(--mist); font-family:inherit; }

/* Password-manager / browser autofill: kills the white-or-yellow autofill
   flash on ANY input sitewide, one rule instead of one per field. The 9999s
   transition is the standard trick (Chrome/Safari apply the autofill color
   via an internal transition; a near-infinite delay on that same property
   means it never gets far enough to render before this overrides it). */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
select:-webkit-autofill{
  -webkit-text-fill-color:var(--mist) !important;
  -webkit-box-shadow:0 0 0 1000px var(--slate) inset !important;
  box-shadow:0 0 0 1000px var(--slate) inset !important;
  caret-color:var(--mist);
  transition:background-color 9999s ease-in-out 0s, color 9999s ease-in-out 0s;
}

/* =========================================================================
   FORM CONTROLS -- INPUT STATE MATRIX (2026-07-23): every input/select/
   textarea/checkbox/radio across cart, checkout, my-account, and the verify
   page's lot lookup runs one brand system: slate ground (never white),
   hairline border, mist text, copper focus, DM Mono for data-like figures.
   Layers on top of the GLOBAL DARK FORM-CONTROL BASELINE above (sizing,
   padding, per-surface label color); the baseline is what keeps a control
   dark if it ISN'T covered by one of the rules below.

   Two root causes fixed here, not just symptoms:
   1) Storefront's own parent stylesheet ships
      `input[type=text]:focus, ..., .input-text:focus{background-color:#ededed}`
      (storefront/style.css). This theme's own :focus rules previously set
      border-color/outline only; CSS cascades per PROPERTY, not per rule, so
      Storefront's background-color:#ededed kept winning regardless of
      selector order. This is what flipped the verify-page "Look up lot"
      field white on focus. Every :focus rule below redeclares
      background-color explicitly so nothing upstream can reassert it.
   2) WooCommerce Blocks' text-input/select/checkbox/radio CSS ships a light
      theme by default, with a `.has-dark-controls` ancestor variant for
      dark themes (packages-style.css). `has-dark-controls` is added to
      <body> unconditionally (functions.php, kairo_body_class_flags) so Woo
      Blocks' own dark variant applies everywhere these controls render; the
      rules below layer KAIRO's exact brand colors (hairline vs translucent-
      white borders, copper vs default focus/checked color) on top of it.
   3) The native <select> (#shipping-country / #shipping-state) still
      painted a solid white box on this stack even though its own CSS
      computed background-color as transparent; an explicit opaque
      background-color (not the `none`/`transparent` keyword) is what
      actually suppresses that native fill, confirmed with a forced-style
      test before writing the rule below.
   ========================================================================= */

/* ---------- native inputs sitewide (verify lookup, my-account) ---------- */
/* 14px -> var(--fs-meta) (2026-07-26 sweep); also fixes a same-specificity
   cascade order bug this rule created against the earlier .lookup-row input
   rule above (LOOKUP-PANEL section): this later declaration was winning and
   silently reverting that one back to 14px. Both now share the one token. */
/* width:100% (2026-07-26 fix wave B): this rule set every other property of
   the classic account inputs but never their width, so they fell back to the
   browser's default `size=20` intrinsic width, 200px measured, inside a
   1121px form row. Combined with the inline `<label>` that WooCommerce's own
   markup puts before each one, the label and the 52px-tall input shared a
   single baseline-aligned line box, dropping the short label ~17.5px below
   the input's top edge and staircasing the whole Login / Lost password form
   (measured before, /my-account/: input 200px wide, label top 216.34 against
   input top 198.84). Matches the sibling `.form-row select` rule directly
   below, which already carried width:100%; both now behave identically. */
.lookup-row input,
.woocommerce form .form-row input.input-text,
.woocommerce-page form .form-row input.input-text{
  background-color:var(--slate) !important; border:1px solid var(--hairline);
  border-radius:2px; color:var(--mist); font-family:var(--font-body); font-size:var(--fs-meta);
  padding:0 16px; height:52px; color-scheme:dark; width:100%;
}
/* The label half of the same defect: block, above its field, everywhere in
   the classic account forms. Scoped off `.woocommerce-form__label-for-
   checkbox` because those labels WRAP their input ("Remember me", the
   marketing opt-ins) and already run as a flex row; forcing them to block
   would stack the tick above its own text. */
.woocommerce form .form-row > label:not(.woocommerce-form__label-for-checkbox),
.woocommerce-page form .form-row > label:not(.woocommerce-form__label-for-checkbox){
  display:block; margin-bottom:8px;
  font-size:var(--fs-meta); letter-spacing:.02em;
  color:var(--mist-dim);
}
/* self-EBI catch (2026-07-26): the first version of the rule above also set
   `font-family:var(--font-mono)`, which rendered the classic account form's
   labels in DM Mono while the Woo Blocks checkout's labels
   (.wc-block-components-text-input label, which sets colour only) stayed in
   the body font. That is precisely the split this file's FORM CONTROLS
   section exists to prevent: "every input/select/textarea/checkbox/radio
   across cart, checkout, my-account, and the verify page runs one brand
   system". Dropped, so both form systems inherit --font-body and a customer
   moving from checkout to my-account sees one component, not two.
   Mono stays reserved for data and micro-labels, per the type-scale header. */
/* The "Remember me" row packs a checkbox label AND the submit button into one
   form-row (WooCommerce's own markup), and the checkbox label is excluded
   from the block rule above, so it had no bottom margin at all and the LOG IN
   button sat ~10px under the tick. Same 8px rhythm as every other label. */
.woocommerce form .form-row > label.woocommerce-form__label-for-checkbox{ margin-bottom:14px; }
/* Reading measure for the single-column account forms. width:100% above
   resolves against #primary's full 1121px otherwise, which is not a form,
   it is a horizon. 520px is the same order as the measure the rest of the
   store already reads at (.policy-page > * caps at 640px, .kairo-empty-state
   at 480px). Deliberately NOT applied to .woocommerce-address-fields: the
   edit-address form is a two-column form-row-first/-last layout with its own
   widths. */
body.woocommerce-account .woocommerce form.woocommerce-form-login,
body.woocommerce-account .woocommerce form.woocommerce-form-register,
body.woocommerce-account .woocommerce form.woocommerce-ResetPassword,
body.woocommerce-account .woocommerce form.woocommerce-EditAccountForm{
  max-width:520px;
}
.lookup-row input:focus,
.woocommerce form .form-row input.input-text:focus,
.woocommerce-page form .form-row input.input-text:focus{
  outline:none; background-color:var(--slate) !important; border-color:var(--copper) !important; color:var(--mist) !important;
}
.lookup-row input::placeholder,
.woocommerce form .form-row input.input-text::placeholder{ color:var(--mist-dim); opacity:1; }

/* Classic country/state <select> (My Account address forms): native now
   that kairo_dequeue_select2_on_account_pages() (functions.php) stops
   selectWoo from wrapping it, matching the same dark-native treatment the
   WooCommerce Blocks checkout's own select already gets below
   (`.wc-blocks-components-select__select`), so the two never look like two
   different components. */
.woocommerce form .form-row select,
.woocommerce-page form .form-row select{
  background-color:var(--slate) !important; border:1px solid var(--hairline);
  border-radius:2px; color:var(--mist); font-family:var(--font-body); font-size:var(--fs-meta);
  padding:0 16px; height:52px; color-scheme:dark; width:100%;
}
.woocommerce form .form-row select:focus,
.woocommerce-page form .form-row select:focus{
  outline:none; background-color:var(--slate) !important; border-color:var(--copper) !important; color:var(--mist) !important;
}

/* autofill: now defined once, sitewide, in the GLOBAL DARK FORM-CONTROL
   BASELINE above; no per-section redeclaration needed. */

/* ---------- WooCommerce Blocks text inputs (coupon field, checkout) ---------- */
.wc-block-components-text-input input[type=email],
.wc-block-components-text-input input[type=text],
.wc-block-components-text-input input[type=tel],
.wc-block-components-text-input input[type=number],
.wc-block-components-text-input input[type=password]{
  background-color:var(--slate) !important; border-color:var(--hairline) !important;
  color:var(--mist) !important; color-scheme:dark;
}
.wc-block-components-text-input input:focus{
  background-color:var(--slate) !important; border-color:var(--copper) !important; color:var(--mist) !important;
  outline:none !important; /* WP/WooCommerce core's default focus-visible outline is #7f54b3, WooCommerce's own brand purple, not KAIRO's; the copper border above is the single brand focus signal */
}
.wc-block-components-text-input label{ color:var(--mist-dim) !important; }
.wc-block-components-text-input.has-error input{ border-color:var(--ember) !important; }

/* ---------- WooCommerce Blocks select (Country/Region, State) ---------- */
.wc-blocks-components-select__select{
  background-color:var(--slate) !important; border-color:var(--hairline) !important;
  color:var(--mist) !important; color-scheme:dark;
}
.wc-blocks-components-select__select:focus{ border-color:var(--copper) !important; outline:none !important; }
.wc-blocks-components-select__label{ color:var(--mist-dim) !important; }
.wc-blocks-components-select svg{ fill:var(--mist-dim); }
/* Root cause (2026-07-24): WooCommerce Blocks hardcodes the same stock red
   (#cc1818) for every `.has-error` state across its components, not just
   text inputs (line 920 above already covers those). The State/Country
   select's own error variant repaints both its container border AND its
   chevron svg's `fill` with that same red, confirmed by sampling the
   rendered pixel after an empty-checkout submit; brought in line with the
   ember used everywhere else on this page for an invalid field. */
.wc-block-components-state-input.has-error .wc-blocks-components-select__container,
.wc-block-components-country-input.has-error .wc-blocks-components-select__container{
  border-color:var(--ember) !important;
}
.wc-block-components-state-input.has-error svg,
.wc-block-components-country-input.has-error svg{ fill:var(--ember) !important; }

/* ---------- checkboxes (billing-same-as-shipping, order note, T&C) ---------- */
.wc-block-components-checkbox__input[type=checkbox]{
  background-color:var(--slate) !important; border-color:var(--hairline) !important;
}
.wc-block-components-checkbox__input[type=checkbox]:checked{
  background-color:var(--ember) !important; border-color:var(--ember) !important;
}
.wc-block-components-checkbox__input[type=checkbox]:focus{
  outline:1.5px solid var(--copper) !important; outline-offset:1.5px;
}
.wc-block-components-checkbox__mark{ fill:var(--on-ember) !important; }
.wc-block-components-checkbox__label{ color:var(--mist); }
.wc-block-components-checkbox__label a{ color:var(--pale); text-decoration-color:rgba(216,155,106,.4); }
/* Root cause (2026-07-24): same stock-red-on-.has-error pattern as the
   select above, here on the required Terms/Privacy checkbox: WooCommerce
   Blocks' own `.wc-block-components-checkbox.has-error` rule repaints the
   label (and its Terms/Privacy links) #cc1818, with enough specificity to
   beat the brand-color label/link rules directly above. Confirmed by
   submitting checkout with the box unchecked. */
.wc-block-components-checkbox.has-error .wc-block-components-checkbox__label{ color:var(--ember) !important; }
.wc-block-components-checkbox.has-error .wc-block-components-checkbox__label a{ color:var(--ember) !important; text-decoration-color:rgba(194,87,27,.5) !important; }

/* ---------- radio cards (shipping method, payment method) ---------- */
.wc-block-components-radio-control__option{ background:rgba(233,235,238,.03); border-radius:3px; }
.wc-block-components-radio-control--highlight-checked:after{ border-color:var(--hairline) !important; }
.wc-block-components-radio-control--highlight-checked .wc-block-components-radio-control__option:after,
.wc-block-components-radio-control--highlight-checked div.wc-block-components-radio-control-accordion-option:after{
  background:var(--hairline) !important;
}
.wc-block-components-radio-control--highlight-checked .wc-block-components-radio-control__option--checked-option-highlighted,
.wc-block-components-radio-control--highlight-checked label.wc-block-components-radio-control__option--checked-option-highlighted,
.wc-block-components-radio-control-accordion-option--checked-option-highlighted{
  box-shadow:inset 0 0 0 1.5px var(--copper) !important;
}
.wc-block-components-radio-control__input[type=radio]{
  background-color:var(--slate) !important; border-color:var(--hairline) !important;
}
.wc-block-components-radio-control__input[type=radio]:checked{ border-color:var(--ember) !important; }
.wc-block-components-radio-control__input[type=radio]:checked:before{ background:var(--ember) !important; }
.wc-block-components-radio-control__input[type=radio]:focus{
  outline:1.5px solid var(--copper) !important; outline-offset:2px;
}
.wc-block-components-radio-control__label{ color:var(--mist); font-family:var(--font-body); }
.wc-block-components-radio-control__secondary-label{ font-family:var(--font-mono); color:var(--mist-dim); font-size:var(--fs-meta); }

/* =========================================================================
   CART TABLE + TOTALS + CHECKOUT SECTIONS: brand craft pass (2026-07-23),
   bringing WooCommerce Blocks' default light-theme chrome up to the static
   site's card/hairline/DM-Mono system instead of just recoloring it.
   ========================================================================= */
.wc-block-cart-items__header{ border-bottom:1px solid var(--hairline); }
/* table column captions ("Product", "Total"): micro-label pattern. */
.wc-block-cart-items__header th span{
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500; letter-spacing:.14em;
  text-transform:uppercase; color:var(--mist-dim);
}
.wc-block-cart-items__row{ border-bottom:1px solid var(--hairline); }
.wc-block-components-product-name{
  font-family:var(--font-display); font-size:17px; color:var(--mist) !important; text-decoration:none !important;
}
/* cart-line item meta (dose subline): 2026-07-26 sweep, contract-named
   cart-line-row target. */
/* Woo Blocks' own checkout.css ships `.wc-block-components-product-metadata
   .wc-block-components-product-metadata__description > p{font-size:...}`,
   two classes (0,2,0); matched here (not just beaten) so this rule wins on
   both cart AND checkout regardless of source order. */
.wc-block-components-product-metadata .wc-block-components-product-metadata__description p{ font-size:var(--fs-meta); color:var(--mist-dim); line-height:1.6; text-transform:uppercase; }
.wc-block-components-product-price,
.wc-block-components-totals-item__value,
.wc-block-components-order-summary-item__total-price,
.wc-block-components-order-summary-item__individual-price,
.wc-block-components-checkout-order-summary__title-price{
  font-family:var(--font-mono) !important; letter-spacing:.02em;
}

/* =========================================================================
   CART ROW LAYOUT REWORK (2026-07-28, CT 8-point cart feedback pass)
   Row order: title+dose share a line, then price, then quantity+delete.
   Larger thumbnail (uncropped) and title on desktop; discount proof moves
   from the unit price to the line total. Structural/colour rules only:
   money math is untouched (assets/cart-checkout-enhance.js only re-derives
   already-authoritative numbers for display -- regular unit price times
   the row's own quantity, both already on screen -- never a new formula).
   ========================================================================= */

/* ---- 1. Title + dose share the top line ----
   .kairo-inline-dose rules REMOVED (2026-07-28): dose-beside-title is now
   handled by assets/cart-dose-filter.js (a WooCommerce Blocks `itemName`
   filter reading a genuine Store API extension channel), which renders the
   dose as part of the product name itself rather than a separate cloned
   span. Two independent fixes for the same requirement were shipping the
   dose twice; this side lost.
   .wc-block-components-product-metadata stays hidden here regardless: it is
   React-owned, stays exactly where Blocks renders it, and this rule is now
   a standalone defensive backstop (not paired with any clone-and-show
   mechanism) against the OLD stacked-second-row dose layout ever
   resurfacing if short_description (which the surviving mechanism keeps
   deliberately empty on cart/checkout) is ever non-empty again. */
.wc-block-cart-item__wrap > .wc-block-components-product-metadata,
.wc-block-components-order-summary-item__description > .wc-block-components-product-metadata{
  display:none;
}
/* The row: title+dose on one flex line (wrapping onto two if they don't
   fit), price and quantity each forced onto their own full-width line
   below via flex-basis:100%. Delivers the vertical order CT asked for
   (identification block first, commerce block second) without needing to
   touch DOM order Blocks itself controls: title and dose are true DOM
   siblings that simply place onto line 1 together; price/quantity, later
   in source order, claim the rest of the row's height instead of stacking
   in strict source order (the old title/price/dose/quantity reading).
   !important on checkout's own row (2026-07-28, caught in verification):
   Woo Blocks' own checkout.css ships `.wc-block-components-order-summary
   .wc-block-components-order-summary-item__description{display:flex;
   flex-direction:column;...}`, two classes (0,2,0), beating this file's
   original one-class rule regardless of source order -- the exact
   contract-named cascade fight this file already documents fighting
   elsewhere (search "two classes (0,2,0)"). Silent failure mode, not a
   visible break: flex-direction:column still validated as display:flex,
   just wrong axis, so flex-basis:100% on the price row below was resolving
   against the column's HEIGHT instead of its width and the price row
   never actually wrapped -- it kept sharing line 1 with the title,
   overflowing into the total column beside it. Confirmed via getComputedStyle
   before this fix (flexDirection:'column') and by walking document.
   styleSheets for every rule matching the element, not assumed. */
.wc-block-cart-item__wrap,
.wc-block-components-order-summary-item__description{
  display:flex !important; flex-direction:row !important; flex-wrap:wrap !important; align-items:baseline !important;
  column-gap:10px; row-gap:6px; min-width:0;
}
.wc-block-cart-item__wrap > .wc-block-cart-item__prices,
.wc-block-cart-item__wrap > .wc-block-cart-item__quantity,
.wc-block-components-order-summary-item__description > .wc-block-cart-item__prices{
  flex-basis:100%;
}

/* ---- 2. Sizes: larger title + thumbnail, text column shifts right ----
   Desktop-scoped (CT: "I'm just focusing on desktop right now"); mobile
   keeps the sizes below unless separately noted. */

/* Title roughly DOUBLES (CT: "make the first line where it shows the
   product title larger... maybe twice the size"), CART ONLY: the base
   .wc-block-components-product-name rule above stays shared with
   checkout's own (already compact, already fixed for overflow further
   below) order-summary title, which was not part of this ask. clamp(), not
   a hard breakpoint swap, so a long blend name ("BPC-157 / TB-500 Blend")
   scales down smoothly at narrower widths instead of jumping straight to
   28px and fighting the row for space. Bounds: 22px at 390 (still a real
   increase over the 14px this was rendering at) to 28px at 1440 (exactly
   double 14px, the size CT is reacting to). !important matches this
   file's own existing product-name rule, which already needs it to beat
   Woo Blocks' bundled default -- confirmed by computed style before this
   fix: this file's own unguarded font-size:17px was losing, rendering
   14px. */
.wc-block-cart-item__wrap > .wc-block-components-product-name{
  font-size:clamp(22px,4.5vw,28px) !important;
}

/* Thumbnail: larger AND uncropped. CT repeated this a second time using
   the word "cropped" specifically -- cropped vials are a standing defect
   class on this project, not a preference, so this is treated as a bug,
   not a style pass. object-fit:contain is universal (mobile included): it
   costs no layout width and simply stops cropping wherever this image
   renders. aspect-ratio 4:5 matches the uncropped source file's own native
   ratio exactly (bpc-157-pdp-2-416x520.jpg measured 416x520 on disk via
   the container filesystem), the same ratio .hero-media already uses for
   this photography. The BOX only grows on desktop; mobile gets the same
   uncropped source in roughly its previous footprint so it never regresses
   or crowds a 390px row. */
.wc-block-cart-item__image img,
.wc-block-components-order-summary-item__image img{
  object-fit:contain; object-position:center;
}
.wc-block-cart-item__image{ width:132px; }
.wc-block-cart-item__image img{
  width:110px !important; height:137px !important; aspect-ratio:4/5;
}
@media (max-width:767px){
  /* Root cause, found after two wrong-mechanism attempts (2026-07-28,
     caught in this pass's own screenshot review, not asked for but a real
     defect -- documented in full since it cost real time to find): the
     cart row is NOT a table row at this width. WooCommerce Blocks' own
     cart.css switches .wc-block-cart-items__row to `display:grid;
     grid-template-columns:80px 132px` below the tablet breakpoint (image
     in column 1, product SPANNING column 2 through an implicit auto-sized
     column 3, total auto-placed into whatever's left). table-layout:fixed
     on the outer <table> -- the first two attempts here -- has no effect
     on a row that has been switched to CSS Grid; that is why explicit
     column/cell widths kept computing correctly in isolation while the
     rendered row still overflowed by the SAME amount regardless of what
     was tried, confirmed by reading Woo's own bundled CSS directly for the
     exact selector after computed-style checks stopped explaining the
     numbers.
     At 390 with a 4-digit line total, the percent tag ("20% OFF APPLIED"
     at the 13px floor, itself already unshrinkable) measured 129px wide --
     the actual widest thing in that column, wider than either price --
     and Woo's implicit third grid column, being auto-sized, grew to fit
     it without regard for whether the row's total then fit the viewport.
     FIX: declare the grid explicitly instead of fighting the implicit
     column. Three named tracks -- image, product, total -- with total
     pinned to 140px (a 11px margin over the 129px tag floor -- self-EBI
     widened from an initial 135px/6px margin, since this catalog's tiers
     already reach "20% OFF APPLIED" and a same-length-or-longer future
     string, e.g. a 3-digit coupon percentage, shouldn't need a second
     round to fit) and product taking the flexible remainder (1fr), so
     product is what yields under pressure (its own title/dose/unit price
     already wrap or have a wrap fallback) while total and image never do.
     Verified against the SAME 21x GLOW Blend cart this was caught from:
     0px overflow at 360 and 390. */
  .wc-block-cart-items__row{ grid-template-columns:70px 1fr 140px !important; }
  .wc-block-cart-item__image{ width:auto; grid-column-start:1; }
  .wc-block-cart-item__image img{ width:64px !important; height:80px !important; }
  .wc-block-cart-item__product{ grid-column-start:2 !important; grid-column-end:3 !important; min-width:0; }
  .wc-block-cart-item__total{ grid-column-start:3 !important; width:auto; }
}
/* Checkout thumbnail: crop fix only, box left at its previous WIDTH (48px,
   not part of this ask to enlarge) -- but width alone plus contain would
   visibly SHRINK the vial (a 4:5 image fit into a 48-wide SQUARE box under
   contain letterboxes down to ~38px wide), a regression the crop fix
   itself would cause. Grown in HEIGHT instead so the vial's apparent width
   stays close to its previous cropped-to-fill size while showing all of
   it, cap to foot. */
.wc-block-components-order-summary-item__image img{
  width:48px !important; height:60px !important; aspect-ratio:4/5;
}

/* ---- 3. Discount visibility: total is the hero pair, unit price is quiet ----
   CT: "I think it should be slashed out so they can see the total being
   slashed out." Colour is untouched here: struck mist-dim, live pale, per
   the DISCOUNT PARITY rules further below and the standing store-wide
   colour rule (--pale is the ONLY accessible discounted-price copper;
   --copper measures 4.41:1 and fails AA for text). This section only
   resizes which pair carries the weight; assets/cart-checkout-enhance.js
   (injectTotalDiscount()) is what gives the total a struck/live pair to
   size in the first place. */

/* Unit price: quiet supporting detail, not competing with the title.
   CHECKOUT stays at --fs-micro, the 13px floor (this ask was cart-only,
   and checkout's own sizing/overlap is a separate fix above). CART bumped
   to --fs-meta (2026-07-28, owner: "the price and the struck-out price
   look a little small since everything else has been increased... slightly
   larger... not a doubling, roughly 15 to 20 percent"): 13px -> 15px is
   +15.4%, inside the asked range, and reuses the existing --fs-meta token
   (the SAME size as the dose beside the title) rather than a one-off value.
   Struck and live were already the same size as each other in this file's
   own rule (both listed together below); kept that way rather than
   introduced. Title:price ratio moves from 28:13 (2.15) to 28:15 (1.87):
   still a clear hierarchy, less extreme than right after the title/
   thumbnail pass, which is what read as undersized. */
.wc-block-components-order-summary-item__individual-prices .wc-block-components-product-price__regular,
.wc-block-components-order-summary-item__individual-prices .wc-block-components-product-price__value{
  font-size:var(--fs-micro) !important;
}
.wc-block-cart-item__prices .wc-block-components-product-price__regular,
.wc-block-cart-item__prices .wc-block-components-product-price__value{
  font-size:var(--fs-meta) !important;
}
.wc-block-cart-item__prices .wc-block-components-product-price__value.is-discounted,
.wc-block-components-order-summary-item__individual-prices .wc-block-components-product-price__value.is-discounted{
  font-weight:400 !important;
}

/* Line total: the hero pair. Bigger than the unit price's --fs-micro and a
   clear step above this row's --fs-meta baseline, so it reads as the
   number the row is actually for. The stacked, right-aligned layout
   (struck total, then live total, then the percent tag) is WooCommerce
   Blocks' OWN default on .wc-block-cart-item__total-price-and-sale-badge-
   wrapper (align-items:flex-end; flex-direction:column, unchanged), so it
   needs no new layout rule here, only sizing. Shared by cart AND checkout:
   both wrap their total in this exact class. */
.wc-block-cart-item__total-price-and-sale-badge-wrapper .wc-block-components-product-price__regular,
.wc-block-cart-item__total-price-and-sale-badge-wrapper .wc-block-components-product-price__value{
  font-size:18px !important;
}
.wc-block-cart-item__total-price-and-sale-badge-wrapper .wc-block-components-product-price__value.is-discounted{
  font-weight:600 !important;
}

/* ---- Checkout order-summary column: overflow, then the overlap it caused,
   then the sprawl THAT fix caused (2026-07-28, three owner reports on the
   same panel, all the same root fight: this row is __image/__description/
   __total-price, and the first two passes tried to referee it with flex
   alone -- "share one line" or "stack fully," nothing in between).

   ROUND 1, owner: "the prices on the far right there are overlapping the
   border of the container." Reproduced with 21x GLOW Blend ($1,495.20, the
   owner's own repro figure): the line total's right edge measured 27px
   past the panel's padding-box edge. ROOT CAUSE, confirmed by computed
   style AND by reading WooCommerce's own bundled checkout.css directly
   (not assumed): .wc-block-components-order-summary-item is display:flex
   with no flex-wrap; its __description child has flex-grow:1 but no
   min-width override, so the flexbox default min-width:auto refuses to
   let it shrink below its own content's intrinsic width; __total-price had
   no explicit width or flex-shrink at all. min-width:0 on __description
   was the textbook release (kept below, a Grid track needs the identical
   escape hatch from the same default).

   ROUND 2, owner: "the checkout page has some weird alignment and sizing…
   text is overlapping." Fixing round 1 stopped the total from pushing past
   the CONTAINER, but the total pair had also just gotten wider (section 3
   above: struck original + 18px, where it used to be one plain value) --
   and __description and __total-price are FLEX SIBLINGS sharing the SAME
   horizontal line by Woo's own default, so a wider __total-price meant a
   narrower __description, measured down to 121px total (85px of actual
   content) on this 333px sidebar: not enough room for a title, an inline
   dose AND a full unit-price row, so .wc-block-cart-item__prices
   overflowed ITS OWN allocation by 77px, painting over the total column.
   The fix shipped then (flex-wrap on the row, flex-basis:100% on
   __total-price) stopped the two sharing a line by forcing the total onto
   its own full-WIDTH line below the entire description block.

   ROUND 3, owner: "each line item breaking into 3 stacked blocks with
   large vertical gaps, and the line total floating far below the product
   it belongs to... TB-500's row is the clearest failure: a $54.00 unit
   price, then a gap, then $54.00 again on its own line." Round 2 traded
   overlap for sprawl: flex-basis:100% doesn't just move the total to a new
   line, it stretches that line to the row's FULL width, so even a short,
   undiscounted total (TB-500: one price, no struck original, no tag)
   claims an entire empty row below a description block that is already
   two lines tall on its own -- the "large empty gap," worst exactly where
   there is nothing else on the total's line to explain it.

   FIX: stop refereeing this with flex (a single axis choosing between "one
   line" and "stacked") and give image/description/total three real GRID
   columns instead. The total gets its OWN column -- never sharing a line
   with description (no more Round-1/2 overlap) -- sitting BESIDE it,
   top-aligned to the product-name band (align-items:start), rather than
   below it (no more Round-3 sprawl). description keeps wrapping internally
   across its own multiple lines (name+dose, then unit price) exactly as
   before, unaffected, since grid tracks size independently of each other's
   height. This is the checkout-sidebar equivalent of what CT is comparing
   it against: /cart/ never fought this at all because its total lives in a
   real TABLE COLUMN, not a flex sibling competing for a line -- a grid
   column here carries the same row hierarchy into the narrower panel.
   min-width:0 on __description still does real work: a 1fr grid track has
   the identical content-based min-width:auto floor a flex basis does, so
   it needs the same release to shrink below its own intrinsic width.

   !important throughout (self-EBI catch, first pass shipped without it):
   confirmed via getComputedStyle AND by walking document.styleSheets for
   every rule matching the row, not assumed -- Woo Blocks' own checkout.css
   ships `.wc-block-components-order-summary
   .wc-block-components-order-summary-item{display:flex;padding-bottom:1px;
   width:100%}`, two classes (0,2,0), the exact fight this file already
   documents fighting elsewhere (search "two classes (0,2,0)"). It beat
   this rule's original one-class `display:grid` outright regardless of
   source order, so the row silently stayed flex, grid-column on every
   child was inert (a grid-column declaration on a child of a non-grid
   parent does nothing), and the row's own intrinsic content width -- not
   the panel's -- won: measured 449px wide against a 333px panel once a
   4-digit discounted total (GLOW Blend, 21 units) made __total-price wide
   enough to expose it. The child placements need the same !important:
   Woo's checkout.css scopes __image/__description/__total-price with the
   identical two-class prefix (grep'd directly, not assumed either). */
.wc-block-components-order-summary-item{
  display:grid !important;
  grid-template-columns:auto 1fr auto !important;
  align-items:start !important;
  column-gap:12px;
}
.wc-block-components-order-summary-item__image{
  grid-column:1 !important; grid-row:1 !important;
}
.wc-block-components-order-summary-item__description{
  grid-column:2 !important; grid-row:1 !important;
  min-width:0;
  /* Woo core ships padding:4px 12px 12px 24px here (checkout.css), sized
     for its OWN single-line-flex layout where __description sits directly
     against the image with no other gap -- this grid already separates
     image/description/total with its own column-gap:12px, so that 24px
     LEFT padding was pure double-spacing, not visual breathing room.
     Caught on mobile (self-EBI catch, fourth pass): at 390px the row's
     already-narrow 93px description column had 36px of Woo's own padding
     (24 left + 12 right) eaten out of it before any text, leaving 57px --
     not even enough for one unbroken unit price string ("$44.00" needs
     ~55-60px), so it broke MID-DIGIT ("$44.0" / "0" on separate lines,
     confirmed live) via the same inherited word-break:break-word this file
     already relies on elsewhere for a LAST-resort break, never a routine
     one. Zeroed rather than reduced: the grid's own column-gap already
     does this job on both sides now. !important for the same reason
     display:grid needed it above: Woo's own rule is the identical
     two-class (0,2,0) .wc-block-components-order-summary
     .wc-block-components-order-summary-item__description selector. */
  padding:0 !important;
}
/* The SAME min-width:auto trap one level deeper (self-EBI catch, third
   pass): __description shrinking to 121px did nothing for the product
   NAME inside it -- .wc-block-components-product-name is itself a flex
   item (of the __description row) carrying a nowrap dose span (the OTHER
   agent's cart-dose-filter.js, an inline `itemName` filter, not touched
   here), and an unbreakable nowrap token sets that flex item's own
   automatic min-width to its full one-line content width. Confirmed live:
   GLOW Blend's name box rendered 132px wide inside a 121px column and ran
   14-35px into the total column's own text ("GLOW Blend" overlapping
   "$1,691.00" character-for-character in this pass's own screenshot
   review). Scoped to the checkout order-summary only, not the sitewide
   .wc-block-components-product-name rule, so cart's own larger clamp()
   title (a different, already-working sizing rule above) is untouched. */
.wc-block-components-order-summary-item__description .wc-block-components-product-name{
  min-width:0;
}
/* max-width, not white-space:nowrap (self-EBI catch, second pass): a wide
   4-digit struck+live pair (GLOW Blend x19, "$1,691.00 $1,352.80") with
   nowrap forced its column to its own full unbroken width, which on a
   20% OFF row starved __description down to 53px and broke its OWN price
   pair mid-digit (Woo's own word-break:break-word on __description,
   inherited, doing exactly what it's for once nothing fit at all --
   confirmed live, caught in this pass's own screenshot review). Capped
   instead, at the SAME 140px this file's mobile cart-row grid already
   proved on the identical content (search "129px" -- the percent tag's own
   measured floor). */
.wc-block-components-order-summary-item__total-price{
  grid-column:3 !important; grid-row:1 !important;
  max-width:140px;
  margin-top:0;
}
/* Third pass, same row (self-EBI catch): max-width above constrains the
   OUTER __total-price box correctly (confirmed via computed style: renders
   at exactly 140px) but NOT the price pair inside it. Root cause, found by
   comparing computed style against /cart/'s own already-working mobile
   version of this identical pairing side by side (both go through the same
   injectTotalDiscount() in cart-checkout-enhance.js, so the DOM shape --
   no whitespace node between </del> and the value span -- is identical on
   both surfaces): .wc-block-cart-item__total-price-and-sale-badge-wrapper
   is align-items:flex-end (Woo core, cross-axis for its column
   flex-direction), which sizes a flex child to its OWN content instead of
   stretching it to the wrapper's width -- so .price.wc-block-components-
   product-price rendered its natural 208px, unclipped, and (since
   flex-end right-aligns it) overflowed 68px past the wrapper's LEFT edge,
   under the product name next to it. /cart/'s mobile row does not hit this
   because .wc-block-cart-item__total is a literal <td>: table layout
   constrains a cell's content to its resolved column width in a way a
   plain flex/grid box does not, so the identical price pair renders
   pinned to 140px there with computed overflow-wrap:break-word (also
   confirmed live) breaking it cleanly between struck and live with no
   natural space needed. Checkout has no table cell to inherit that from,
   so both halves are set explicitly here instead of relied upon: a hard
   width matching the 140px cap (stopping the flex-end overflow) and
   overflow-wrap so the break-with-no-whitespace case cart gets for free
   from table layout still lands here. */
.wc-block-components-order-summary-item__total-price .wc-block-cart-item__total-price-and-sale-badge-wrapper,
.wc-block-components-order-summary-item__total-price .wc-block-components-product-price{
  max-width:140px;
  overflow-wrap:break-word;
}

.wc-block-components-quantity-selector{ border:1px solid var(--hairline); border-radius:2px; background:transparent; }
.wc-block-components-quantity-selector__button{ color:var(--mist); background:none; }
.wc-block-components-quantity-selector__button:hover{ background:rgba(233,235,238,.06); }
.wc-block-components-quantity-selector__input{ background:none; color:var(--mist); font-family:var(--font-mono); }
.wc-block-cart-item__remove-link{ color:var(--mist-dim); }
.wc-block-cart-item__remove-link:hover{ color:var(--ember); }

.wc-block-components-panel__button{ color:var(--mist); }
.wc-block-components-totals-coupon__button{
  background:transparent !important; color:var(--pale) !important; border:1.5px solid var(--copper) !important;
  text-decoration:none !important; font-family:var(--font-body) !important; font-weight:700 !important;
  font-size:var(--fs-meta) !important; letter-spacing:.1em !important; text-transform:uppercase !important; border-radius:var(--btn-radius) !important;
}
.wc-block-components-totals-coupon__button[aria-disabled="true"]{ opacity:.4 !important; }
.wc-block-components-totals-coupon__button:focus{ box-shadow:0 0 0 2px var(--copper) !important; outline:none !important; }

/* ---------- APPLIED-COUPON CHIP (2026-07-26 fix wave B) ----------
   WooCommerce Blocks renders an applied coupon as a `.wc-block-components-
   chip` pill under the Discount totals row (cart AND checkout order
   summary). It was the last stock-Woo light-grey element left on the store:
   a pure-white 1px border, Switzer sentence-case text at 10.72px (Blocks'
   own `.875em` of the chip's 12.25px base, both well under the 13px floor),
   and a #E0E0E0 light-grey circular remove button with a near-invisible
   glyph on it. Measured before: chip text 10.72px, remove button
   background rgb(224,224,224) at 1.11:1 against its own icon.
   Brought onto the same badge language as .sku-badge / .kc-tag /
   .kairo-off-tag (DM Mono, small caps, hairline border, pill radius) and
   both type sizes lifted to --fs-micro. Kept beside the coupon BUTTON rules
   above rather than with the quantity-badge override at the end of this
   file, so everything the coupon component renders lives in one place.
   !important throughout: Woo Blocks ships these as element-scoped
   `.wc-block-components-chip` rules in packages-style.css that tie or beat a
   bare single-class selector depending on which copy of this stylesheet the
   double-enqueue puts last (documented elsewhere in this file). */
.wc-block-components-chip{
  font-family:var(--font-mono) !important; font-size:var(--fs-micro) !important;
  letter-spacing:.06em !important; text-transform:uppercase !important;
  color:var(--mist) !important; background:transparent !important;
  border:1px solid var(--hairline) !important; border-radius:999px !important;
  padding:5px 10px !important; min-height:0 !important;
}
.wc-block-components-chip__text{
  font-family:var(--font-mono) !important; font-size:var(--fs-micro) !important;
  letter-spacing:.06em !important; text-transform:uppercase !important;
  color:var(--mist) !important;
}
/* the remove control: stock Woo paints a filled light-grey disc here. Made a
   quiet mist glyph on the chip's own ground, going ember on hover/focus to
   match .wc-block-cart-item__remove-link's destructive-action colour. */
.wc-block-components-chip__remove{
  background:transparent !important; background-color:transparent !important;
  border:0 !important; box-shadow:none !important;
  color:var(--mist-dim) !important; width:14px !important; height:14px !important;
  margin-left:6px !important; padding:0 !important; cursor:pointer;
}
.wc-block-components-chip__remove svg,
.wc-block-components-chip__remove-icon{ fill:currentColor !important; background:transparent !important; }
.wc-block-components-chip__remove:hover{ color:var(--ember) !important; }
.wc-block-components-chip__remove:focus-visible{ outline:2px solid var(--copper) !important; outline-offset:2px; }

.wc-block-components-totals-item{ border-color:var(--hairline) !important; }
/* .wc-block-components-totals-footer-item's own border-top REMOVED
   (2026-07-28, owner-flagged: "there's a weird extra line above the
   estimated total... that's really close to the actual words"). Diagnosed
   against the live DOM before removing, per instruction: TWO separate
   rules were painting two stacked lines in the gap above "Estimated
   total" -- this one (authored in this file, zero padding before the
   text, sitting tight against it) and a SEPARATE one on the wrapping
   .wc-block-components-totals-wrapper, which is WooCommerce Blocks' OWN
   core default (confirmed by grepping the plugin's bundled packages-
   style.css directly, not assumed: `.wc-block-components-totals-wrapper{
   border-top:1px solid color-mix(in srgb,currentColor 20%,transparent);
   padding:16px 0}`). That core border is the SAME mechanism already
   separating every other totals section in this exact block (e.g. "Add
   coupons" from "Shipping"), sits with a full 16px of padding on both
   sides, and is left untouched here: it is the one deliberate separator
   this block already uses everywhere else, not a second line to suppress.
   Removing only the rule below leaves exactly that one core line between
   the savings line and "Estimated total", with 16px of breathing room on
   both sides. Does not touch anything after "Estimated total": there was
   never a border there (the gap before the checkout button is plain
   margin on .wc-block-cart__submit-container), so nothing to preserve or
   lose. Shares its class with checkout's own "Total" footer row, so this
   applies there too; confirmed still correct on /checkout/ after removal,
   not just on /cart/. */
.wc-block-cart__totals-title{
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500; letter-spacing:.14em;
  text-transform:uppercase; color:var(--mist-dim) !important;
}
/* 2026-07-24: the checkout's collapsible "Order summary" heading shared the
   Cart page's small-caps DM-Mono totals-label treatment, one tier below its
   sibling step headings ("Contact information", "Shipping address") which
   render Marcellus sentence-case at 20px. Same heading tier as those, not
   the Cart's totals label. */
.wc-block-components-checkout-order-summary__title-text{
  font-family:var(--font-display); font-weight:400; font-size:20px;
  letter-spacing:normal; text-transform:none; color:var(--porcelain) !important;
}

/* Checkout step headings ("Contact information", "Shipping address",
   "Shipping options", "Payment options"): Storefront's default body-copy
   color is rgb(51,51,51), near-black, unreadable on this theme's dark
   ground; this is the exact defect, not just a stylistic mismatch. */
.wc-block-components-checkout-step__title{
  font-family:var(--font-display); font-weight:400; font-size:20px; color:var(--porcelain) !important;
}
.wc-block-checkout__guest-checkout-notice{ color:var(--mist-dim); font-size:var(--fs-meta); }

/* =========================================================================
   BLOCK VALIDATION ERRORS (2026-07-24): WooCommerce Blocks' own inline
   field-level error, used by the invalid-coupon message, checkout field
   validation (empty submit), and the terms-unchecked error alike; ships in
   stock pink-red (rgb(241,140,140)), the same off-brand-color class this
   sweep targets everywhere else. The icon is an inline SVG using
   `fill:currentColor`, so setting color here recolors it too, no separate
   fill rule needed. One rule, every caller of this WooCommerce Blocks
   component picks it up.
   ========================================================================= */
.wc-block-components-validation-error{ color:var(--ember) !important; }
.wc-block-components-validation-error p{ font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.01em; }

/* =========================================================================
   BLOCK NOTICE BANNER / SNACKBAR (2026-07-24): the other WooCommerce Blocks
   notice component, `.wc-block-components-notice-banner` (persistent, e.g.
   inline block notices) and its `.wc-block-components-notice-snackbar`
   variant (the transient toast that pops bottom-left on "coupon applied",
   "coupon removed", cart quantity updates). Both ship a stock light card
   (white/pale-blue background, near-black text) regardless of severity,
   the same off-brand-color class as the classic notices this sweep started
   from. Brought to the same slate-panel-plus-accent-border language as
   `.woocommerce-notices-wrapper` above so every notice system on the store
   reads as one system: copper for success/info, ember for error/warning.
   The dismiss/icon SVGs have no explicit fill, so they inherit `currentColor`
   already -- setting the text color here recolors them too.
   ========================================================================= */
.wc-block-components-notice-banner{
  background:rgba(233,235,238,.04) !important; border:1px solid var(--hairline) !important;
  border-left:2px solid var(--copper) !important; border-radius:2px !important;
  color:var(--mist) !important; box-shadow:none !important;
  font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.01em; line-height:1.7;
}
.wc-block-components-notice-banner.is-error,
.wc-block-components-notice-banner.is-warning{ border-left-color:var(--ember) !important; }
.wc-block-components-notice-banner .wc-block-components-notice-banner__content{ color:var(--mist) !important; }
/* the icon glyph's own `fill` was already currentColor and read brand-correct
   in computed styles; the surviving stock-blue badge is a separate
   `background-color` core paints behind the svg as a circular chip (not
   `fill`), confirmed by sampling the rendered pixel, not just computed
   `fill`, after the first pass still showed blue. Dropping that background
   leaves a plain mist glyph on the slate panel, matching how the classic
   notices above suppress their icon entirely rather than keeping a stock
   accent-colored badge. */
.wc-block-components-notice-banner svg{ fill:currentColor; background-color:transparent !important; }
.wc-block-components-notice-snackbar{ background:var(--slate) !important; }
/* Snackbar/footer collision (2026-07-24): core's toast list is
   `position:fixed; bottom:24px` (packages-style.css), viewport-relative
   with no content awareness, so it renders directly over the footer's
   legal-links row whenever the footer is close enough to the viewport
   bottom (a coupon-apply on a short cart, confirmed via screenshot).
   `--kairo-footer-overlap` is kept live by assets/site.js (0 when the
   footer isn't in view, otherwise exactly how much of it is showing);
   adding it to the toast's own bottom offset anchors the toast just
   above the footer whenever it's in view instead of sitting on top of it. */
.wc-block-components-notice-snackbar-list{
  bottom:calc(24px + var(--kairo-footer-overlap, 0px)) !important;
}

/* =========================================================================
   MY ACCOUNT (native WooCommerce shortcode login/register form, not Woo
   Blocks): same defect class as above, Storefront's default dark-gray body
   copy and a plain grey submit button on this theme's dark ground.
   ========================================================================= */
body.woocommerce-account .entry-content > .woocommerce{ color:var(--mist); }
/* .entry-title on this page used to need a visibility override here; the
   root cause (storefront_page_header firing on every WP Page, see
   functions.php kairo_remove_storefront_hooks 2026-07-24) is fixed at the
   source now, so the heading never renders and this rule has nothing left
   to target. */
/* Root cause (2026-07-24): the direct-child selector this rule shipped with
   only ever matched the Login/Register heading. "Billing address" /
   "Shipping address" on /my-account/edit-address/ sit one level deeper
   (inside .u-columns.woocommerce-Addresses), so they fell through to
   Storefront's default heading color (near-black, rgb(19,19,21), invisible
   on the slate ground). Widened to a descendant selector so every h2 in the
   account area is covered, not just the ones at the top level. */
body.woocommerce-account .woocommerce h2{
  font-family:var(--font-display); font-weight:400; color:var(--porcelain); font-size:clamp(24px,3vw,30px); margin-bottom:8px;
}
/* Root cause (2026-07-24): "Order details" / "Billing address" / "Shipping
   address" on the order-received (thank-you) page and /my-account/view-order/
   render via WooCommerce's own `.woocommerce-order-details__title` /
   `.woocommerce-column__title` classes, on a body that never carries
   `woocommerce-account` (it's `woocommerce-order-received` /
   `woocommerce-view-order` instead), so the rule above never reached them
   and they fell back to Storefront's default near-black heading color on
   this money-surface page. Targeted directly by class rather than by body
   context so it survives on every template that uses these classes. */
.woocommerce-order-details__title,
.woocommerce-column__title{
  font-family:var(--font-display); font-weight:400; color:var(--porcelain) !important;
  font-size:clamp(22px,2.6vw,28px); margin-bottom:16px;
}

/* =========================================================================
   ORDER SUMMARY TABLE (order-received thank-you page, /my-account/view-order/):
   a customer-facing money surface, per the brand bar DM Mono is for data/
   labels; this table previously inherited plain body-font numbers with no
   row separation at all. Hairline row dividers plus mono figures bring it
   in line with the cart/checkout totals rows elsewhere on the store.
   ========================================================================= */
.woocommerce-table--order-details{ width:100%; border-collapse:collapse; margin:24px 0 40px; }
.woocommerce-table--order-details th,
.woocommerce-table--order-details td{
  padding:16px 0; border-bottom:1px solid var(--hairline); text-align:left; color:var(--mist);
}
.woocommerce-table--order-details thead th{
  font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.08em; text-transform:uppercase; color:var(--mist-dim);
  border-bottom:1px solid var(--hairline); padding-bottom:12px;
}
.woocommerce-table--order-details tfoot th{ font-weight:500; color:var(--pale); }
/* Root cause (2026-07-26 fix wave B): the Total-row emphasis used to ride on
   `tfoot tr:last-child`, which assumes Total is the last row in the foot. It
   is not. WooCommerce's `WC_Order::get_order_item_totals()` appends the
   Payment method row AFTER order_total whenever the `email_improvements`
   feature flag is on (it is, on this install), and order-details.php then
   appends further non-total rows of its own after the whole loop: a "Note:"
   row when the order carries a customer note, and an "Actions:" row when the
   order has actions. Reproduced on a staging order carrying a shipping
   method, a payment method and a customer note: the rendered tfoot was
   Subtotal / Shipping / Total / Payment method / Note, so the porcelain
   700-weight emphasis landed on "Note:" and Total read identically to
   Subtotal on a money surface.
   Split into the two things that rule was conflating:
   1) `tr:last-child` keeps ONLY the structural job it was always right
      about, zeroing the final row's bottom border.
   2) The Total row is picked out explicitly. With no class on any of these
      rows (core's template prints a bare <tr>), the one stable structural
      fact is that Total is the LAST foot row containing a price: refund rows
      are inserted BEFORE order_total in every WooCommerce code path, and
      every row that can follow Total (payment method, note, actions) carries
      plain text or links, never a `.woocommerce-Price-amount`. So "has a
      price and no later sibling row has one" identifies Total under both
      states of the email_improvements flag, with or without refunds, notes
      or actions. `:has()` is already an established idiom in this file (see
      `.wp-block-woocommerce-cart:has(.kairo-empty-state)`).
   Regression check: import/scripts/verify-css-waveb.mjs asserts the emphasis
   lands on the row labelled Total on an order with all of these rows. */
.woocommerce-table--order-details tfoot tr:last-child th,
.woocommerce-table--order-details tfoot tr:last-child td{ border-bottom:0; }
.woocommerce-table--order-details tfoot tr:has(.woocommerce-Price-amount):not(:has(~ tr .woocommerce-Price-amount)) th,
.woocommerce-table--order-details tfoot tr:has(.woocommerce-Price-amount):not(:has(~ tr .woocommerce-Price-amount)) td{
  font-weight:700; color:var(--porcelain);
}
.woocommerce-table--order-details .woocommerce-Price-amount,
.woocommerce-table--order-details .product-quantity,
.woocommerce-table--order-details .shipped_via{
  font-family:var(--font-mono); letter-spacing:.01em;
}
/* `<small class="shipped_via">` (2026-07-26 sweep, missed in the first
   pass: this size comes from the browser's own `small{font-size:80%}` UA
   default, not a declaration anywhere in this file, so it needed an
   explicit override rather than a token swap on an existing rule). */
.woocommerce-table--order-details .shipped_via{ font-size:var(--fs-meta); }
.woocommerce-table--order-details .product-name a{ color:var(--mist); }
/* /my-account/view-order/'s "Order #_ was placed on _ and is currently _."
   intro line wraps the order number, date, and status in `<mark>`, the
   browser's default yellow-highlight element (UA stylesheet: black text,
   yellow background); some earlier rule already stripped the yellow
   background here but never gave the text its own color, so it rendered
   black-on-slate, functionally invisible. Confirmed via computed style
   (color rgb(0,0,0)) before writing this. */
mark{ background:transparent; color:var(--pale); }

/* order-received page visited with an invalid/expired key: WooCommerce
   core deliberately shows this same generic "order received" line rather
   than confirming or denying whether the order exists (order_received() in
   class-wc-shortcode-checkout.php sets $order = false silently; no
   distinct "invalid order" notice ships for this endpoint, unlike the
   pay-for-order page). Bare <p>, previously plain default-UA text; brought
   to the same .body treatment as every other message page on this store
   (2026-07-26 UI-state sweep). */
.woocommerce-thankyou-order-received{
  font-size:var(--fs-body); line-height:1.7; letter-spacing:.006em; color:rgba(233,235,238,.68); margin:0 0 24px;
}

/* =========================================================================
   QUIET EMPTY-STATE MESSAGE (2026-07-26 UI-state sweep): shared "nothing
   here" block reused across the empty cart and empty shop-search/filter
   results (kairo_brand_empty_cart_state() in functions.php; the else
   branch of woocommerce/archive-product.php). Same eyebrow-less h2/body/
   btn-primary rhythm the 404 and policy pages already use, so every
   "nothing to show" surface on the store reads as one system instead of
   three one-off treatments. Layout-only: page-specific header clearance
   lives with each page's own selector below, not baked in here, since this
   block sits inside different parents (the Cart block vs. the shop
   archive's own section) with different existing clearance sources.
   ========================================================================= */
.kairo-empty-state{ max-width:480px; padding:48px 0; }
.kairo-empty-state .h2{ margin:0 0 12px; font-size:clamp(24px,2.8vw,32px); }
.kairo-empty-state .body{ margin:0 0 28px; max-width:44ch; }

/* Empty cart (/cart/, zero items): root cause of the CT-flagged
   header-clipped icon was that this page's ONLY clearance from the fixed
   header came from the free-ship-bar-wrap injected above the Cart block
   (kairo_inject_free_shipping_bar() in functions.php), which returns ''
   the instant the cart is empty. Applied to both the branded replacement
   (.kairo-empty-state, the normal case) and WooCommerce's own default
   markup (.wc-block-cart__empty-cart__title, a defensive fallback in case
   kairo_brand_empty_cart_state()'s regex ever fails to match) so the
   clearance holds either way. */
.wp-block-woocommerce-cart:has(.kairo-empty-state),
.wp-block-woocommerce-cart:has(.wc-block-cart__empty-cart__title){ padding-top:140px; padding-bottom:96px; }
@media (max-width:767px){
  .wp-block-woocommerce-cart:has(.kairo-empty-state),
  .wp-block-woocommerce-cart:has(.wc-block-cart__empty-cart__title){ padding-top:104px; padding-bottom:64px; }
}
/* Fallback-only styling for WooCommerce's own default icon/heading (see
   above): same Storefront-default near-black text color as every other
   unstyled heading this sweep found; its icon is a
   `background-color:currentColor` CSS-mask shape on the same element's
   `::before`, so fixing `color` here fixes the barely-visible bag icon
   too, not just the text. */
.wc-block-cart__empty-cart__title{ color:var(--mist) !important; }
.wc-block-cart__empty-cart__title::before{ background-color:rgba(233,235,238,.18) !important; }
.woocommerce-form-row label{ color:var(--mist-dim); font-size:var(--fs-meta); letter-spacing:.02em; }
/* Contrast (2026-07-26 round-2 QA): --ember on --slate measures 3.99:1, under
   AA, and the asterisk is the only VISUAL required-state indicator (the
   sibling .screen-reader-text covers AT, not low vision). --pale = 7.53:1,
   the same token the savings line and discount tags were unified to. */
.woocommerce-form-row .required{ color:var(--pale); }
.woocommerce-form__input-checkbox{ accent-color:var(--ember); width:16px; height:16px; }
.woocommerce-form-login__rememberme{ display:flex; align-items:center; gap:8px; color:var(--mist-dim); font-size:var(--fs-meta); }
.woocommerce-button.button,
.woocommerce-Button.button,
.woocommerce a.button,
button.button[name="save_address"]{
  appearance:none; background:var(--ember) !important; color:var(--on-ember) !important; border:1px solid var(--ember) !important;
  border-radius:var(--btn-radius); font-family:var(--font-body); font-weight:700; font-size:var(--fs-meta); letter-spacing:.14em;
  text-transform:uppercase; padding:0 30px; min-height:52px; cursor:pointer; text-decoration:none !important;
}
.woocommerce-button.button:hover,
.woocommerce-Button.button:hover,
.woocommerce a.button:hover,
button.button[name="save_address"]:hover{ background:#cf6527 !important; border-color:#cf6527 !important; }
/* Root cause (2026-07-24): WC core's myaccount/form-edit-address.php is the
   one classic-template button on this store that renders plain
   `class="button"` with no `woocommerce-Button`/`woocommerce-button` class
   (every sibling template: login, register, lost-password, edit-account,
   reset-password, all add one); the selector above targets it by its stable
   `name="save_address"` attribute (shared by billing and shipping, same
   template) instead of patching each page separately. */
.woocommerce-LostPassword a{
  color:var(--pale); font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.04em;
  text-decoration:underline; text-decoration-color:rgba(216,155,106,.4); text-underline-offset:3px;
}
/* The show/hide-password toggle (WooCommerce's JS wraps the password input
   in `<span class="password-input">` and injects an EMPTY
   `<button class="show-password-input">` after it; the glyph is meant to
   come from `.show-password-input::after{font-family:WooCommerce;
   content:"\e010"}` in core's woocommerce-general.css).
   Root cause (2026-07-26 fix wave B): that stylesheet is not loaded on this
   store, so the button rendered with NO content and NO position at all,
   measured at 45.28x19.75 (pure Storefront `button{padding:.618047em
   1.41575em}` on an empty box), sitting inline AFTER the field as an
   invisible but fully focusable and clickable control. An earlier pass here
   only neutralised its background/border, which made a broken control quiet
   rather than fixed.
   RESOLUTION: supply the glyph rather than hide the control. Hiding it would
   remove a real affordance from a password field on a store that has no
   other way to reveal a typo'd password, and it would leave the button in
   the tab order anyway unless it were display:none'd (a functional
   regression, not a fix). The glyph is an inline SVG data-URI used as a CSS
   mask, so it takes its colour from a brand token (no icon font to load, no
   second network request, no dependency on core's stylesheet ever coming
   back). The button is also positioned properly inside the field for the
   first time, and the field gains right padding so typed text cannot run
   underneath it. */
.password-input{ display:block; position:relative; }
.woocommerce form .form-row .password-input input.input-text{ padding-right:46px; }
.show-password-input{
  /* self-EBI (2026-07-26): the glyph URL used to sit in a second, separate
     `.show-password-input{}` block below the layout one, which read as an
     accidental duplicate selector in review. Merged: the custom property is
     declared here with the rest of the element's own state, and only the
     `.display-password` variant redeclares it. */
  --kairo-eye:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.7 12S5.6 5 12 5s10.3 7 10.3 7-3.9 7-10.3 7S1.7 12 1.7 12Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E");
  position:absolute; top:50%; right:12px; transform:translateY(-50%);
  width:22px; height:22px; padding:0 !important; margin:0;
  display:flex; align-items:center; justify-content:center;
  background:none !important; border:none !important; appearance:none;
  color:var(--mist-dim); cursor:pointer;
}
.show-password-input::after{
  content:""; display:block; width:20px; height:20px;
  background-color:currentColor;
  -webkit-mask:var(--kairo-eye) center / 20px 20px no-repeat;
  mask:var(--kairo-eye) center / 20px 20px no-repeat;
}
.show-password-input:hover{ color:var(--mist); }
/* WooCommerce's JS adds .display-password while the password is revealed;
   swap in a struck-through eye so the toggle's two states are distinguishable
   by SHAPE, not by colour alone (WCAG 1.4.1), and take the copper accent. */
.show-password-input.display-password{
  color:var(--copper);
  --kairo-eye:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.7 12S5.6 5 12 5s10.3 7 10.3 7-3.9 7-10.3 7S1.7 12 1.7 12Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M3 3l18 18'/%3E%3C/svg%3E");
}
.show-password-input:focus-visible{ outline:2px solid var(--copper); outline-offset:2px; }

/* =========================================================================
   MINI-CART DRAWER (2026-07-26): CT feedback -- replace the default Woo
   add-to-cart banner/redirect flow with the same slide-in drawer the static
   reference site shipped (/Users/ct/dev/ovae-site-v2/kairo/site/js/cart.js,
   the kc- prefixed classes). Markup/behavior lives in
   assets/cart-drawer.js; classes and visual language are ported verbatim
   from that reference (slate surface, DM Mono figures, ember accent), only
   the data source changes (this drawer reads the live WooCommerce Store
   API cart, not a static localStorage/JSON catalog). Discounted-price color
   matches the existing PDP tier-ladder convention already on this theme
   (.wdr_table_discounted_price, above) -- ember, not copper -- for one
   consistent "this is a discount" color across PDP/drawer/cart/checkout.
   ========================================================================= */
/* OVERLAY NEUTRALISED ON DESKTOP (2026-07-31, store owner: "let's
   definitely remove the overlay so it's not as distracting, so they
   can still keep shopping"). Origin: the first-customer feedback
   session, where the tester said adding to cart "takes you out of the
   flow of wanting to add more stuff".

   The element is kept rather than deleted, because assets/cart-drawer.js
   creates it, animates it and binds close-on-click to it; removing it from the
   DOM would mean editing the JS lifecycle for no gain. Instead it is made
   invisible AND click-through in both states, ON DESKTOP.

   pointer-events stays `none` even when open, and that is the whole point.
   With `auto` the overlay covers the page, so the first click anywhere lands
   on the overlay and closes the drawer instead of doing what the shopper
   meant. That is precisely the "I have to click back" complaint. The cost is
   that click-outside-to-close is gone; the close button and the Escape key
   still close it, and cart-drawer.js binds Escape independently of this
   element.

   MOBILE EXCEPTION (2026-08-04, store owner: "if you click to the left,
   in the space where it still shows the screen, it'll just automatically
   close the cart"). Below the mobile breakpoint the drawer OVERLAYS
   instead of pushing (see style.css's DESKTOP: PUSH. MOBILE: OVERLAY.
   block), which reopens the reason click-outside-to-close was removed
   in the first place -- there is no "keep shopping in place" affordance
   to protect on mobile, since body scroll is locked behind the drawer
   there regardless (see the MOBILE OVERLAY block further down this
   file). This same full-viewport element becomes visible and clickable
   again, ONLY on mobile, ONLY while open: no separate "left strip"
   element is sized for it, because the drawer itself (z-index:9999,
   above this element's 9998) already occupies the covered 80vw, so
   this only ever receives a click in the exposed ~20vw strip -- a real
   scrim across the uncovered area, not a document-wide handler that
   would swallow taps meant for the drawer. */
.kc-overlay{
  position:fixed; inset:0; background:rgba(8,9,11,.55); opacity:0; pointer-events:none;
  transition:opacity .28s ease; z-index:9998;
}
.kc-overlay.kc-open{ opacity:0; pointer-events:none; }
@media (max-width:600px){
  .kc-overlay.kc-open{ opacity:1; pointer-events:auto; }
}
/* The overlay's opacity transition was harmless to skip on desktop
   (opacity never actually changes there: 0 in both states), but it is
   now a real, visible scrim fade on mobile -- gets the same
   reduced-motion treatment as the rest of the drawer's motion. */
@media (prefers-reduced-motion:reduce){
  .kc-overlay{ transition:none; }
}
/* Two closed-state defects fixed together here (2026-07-26 fix wave B),
   because they share one cause: the drawer was only ever moved out of the
   viewport by `transform`, and a transformed element is still painted, still
   casts its shadow, and is still part of the tab order.
   1) SHADOW SMUDGE. `box-shadow:-24px 0 48px` was declared unconditionally,
      so a 48px-blurred black shadow was painted leftward from x=viewport
      width on EVERY page, all the time: a dark vertical smear down the right
      edge of the document, most visible against the porcelain sections.
      Moved onto `.kc-open` so it exists only while the drawer is on screen,
      which is the only time a drawer shadow means anything.
   2) OFF-SCREEN TAB STOPS. The closed drawer carries aria-hidden="true"
      (set by assets/cart-drawer.js) but its controls stayed keyboard
      focusable, so Tab walked into an aria-hidden subtree and focus vanished
      off the right edge of the screen. Measured on the home page with an
      empty cart: 2 of 51 tabbable elements sat inside the closed drawer
      (the close button and the checkout action; the count rises with every
      cart line, each of which adds its own name link, two qty buttons and a
      remove button). `visibility:hidden` removes them from the tab order and
      from hit-testing without touching the JS, which only ever toggles the
      .kc-open class. The `visibility 0s linear .32s` delay is what keeps the
      slide-OUT animation intact: visibility flips to hidden only after the
      .32s transform transition has finished, and flips back with no delay on
      open. Purely the CSS half; the JS-side focus trap and focus return are
      Builder A's. */
.kc-drawer{
  /* WIDTH 420 -> 315 (2026-07-31, store owner: "cut the slider down by maybe
     25% in width"). Exactly 25% narrower. It stays a comfortable reading
     column for a cart line (product name, qty stepper, price) while leaving
     more of the catalog visible behind it, which is the point now that the
     dim and the scroll lock are gone: the drawer sits beside the shop rather
     than on top of it. The 92vw cap is unchanged and still governs small
     screens, where the separate max-width:520px rule below takes it full
     width anyway.

     This width is superseded by the var()-based `.kc-drawer{width:min(
     var(--kc-w),92vw)}` rule further down the file (--kc-w:300px as of
     2026-08-04) and by the ~480px-and-under copy of this same literal a
     little further down still -- kept in lockstep with both rather than
     deleted, since it is the one that wins if an engine ever drops a
     custom property nested inside min() (see the PLAIN-VALUE FALLBACK
     note lower in this file for the fuller version of this same trap). */
  position:fixed; top:0; right:0; bottom:0; width:min(300px,92vw); background:var(--slate);
  color:var(--mist); z-index:9999; transform:translateX(100%); visibility:hidden;
  transition:transform .32s cubic-bezier(.4,0,.2,1), visibility 0s linear .32s;
  display:flex; flex-direction:column; font-family:var(--font-body);
}
.kc-drawer.kc-open{
  transform:translateX(0); visibility:visible;
  transition:transform .32s cubic-bezier(.4,0,.2,1), visibility 0s;
  box-shadow:-24px 0 48px rgba(0,0,0,.35);
}
.kc-head{
  display:flex; align-items:center; justify-content:space-between; padding:22px 24px;
  border-bottom:1px solid var(--hairline);
}
.kc-title{ font-family:var(--font-display); font-size:18px; letter-spacing:.02em; color:var(--porcelain); }
.kc-close{
  background:none; border:0; color:var(--mist); font-size:22px; line-height:1; cursor:pointer;
  opacity:.7; padding:4px 8px;
}
.kc-close:hover{ opacity:1; }
.kc-close:focus-visible{ outline:2px solid var(--copper); outline-offset:2px; opacity:1; }
/* padding trimmed 24->22px (2026-07-26 alignment sweep): reclaims 4px per
   side for the row grid below, so the item-name/dose column has more room
   before wrapping now that its type sizes are larger. */
.kc-body{ flex:1; overflow-y:auto; padding:8px 22px; }
/* 2026-07-26 sweep: .kc-item-name is CT's own reference floor for this whole
   sweep, and it was itself below the meta floor once the scale was defined
   (see the TYPE SCALE FLOOR block at the top of this file) -- an actual
   clickable product-name link, not a micro-label, so it takes --fs-meta. */
.kc-empty{ padding:48px 0; text-align:center; color:var(--mist-dim); font-size:var(--fs-meta); }
/* Row alignment fix (2026-07-26, CT-flagged: "product name row and its
   price row should share a baseline"): align-items:center was the root
   cause -- it vertically centers all 3 grid tracks against whichever one
   is tallest, so the name/price starting position silently shifted any
   time the middle column's content grew (a 2-line dose subline, e.g. a
   blend product's longer name). align-items:start pins all 3 tracks
   (thumb, name/mg/qty/remove, price stack) to the same top edge every
   time, so the product-name row and the price stack's first line always
   share the same baseline regardless of how tall anything else in the row
   grows. Gap trimmed 14->12px to reclaim a little more width for the
   middle column at the same time. */
.kc-item{
  display:grid; grid-template-columns:56px 1fr auto; gap:12px; padding:16px 0;
  border-bottom:1px solid var(--hairline); align-items:start;
}
.kc-item-thumb{ display:block; }
.kc-item-thumb:hover img{ opacity:.85; }
.kc-item img{ width:56px; height:56px; object-fit:cover; border-radius:2px; background:var(--slate); }
.kc-item-name{ font-size:var(--fs-meta); letter-spacing:.01em; color:inherit; text-decoration:none; display:block; cursor:pointer; }
.kc-item-name:hover{ text-decoration:underline; }
/* FIX handed over from the drawer agent (2026-07-28, their file is fenced
   out of mine): the dose now renders as a child <span> inside this same
   anchor (assets/cart-drawer.js, not edited here), so it inherited the
   hover underline meant for the product name only. text-decoration:none
   on the span opens its own decoration context, which stops the parent's
   underline from drawing across it, while the anchor's own text (the
   name) still underlines on hover as before. */
.kc-item-name span{ text-decoration:none; }
.kc-item-mg{
  font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--mist-dim);
  margin-top:3px; text-transform:uppercase;
}
.kc-item-right{ text-align:right; display:flex; flex-direction:column; align-items:flex-end; gap:6px; }
.kc-item-price{ font-family:var(--font-mono); font-size:var(--fs-meta); color:var(--mist); }
.kc-price-row{ font-family:var(--font-mono); font-size:var(--fs-meta); display:flex; gap:6px; }
.kc-price-orig{ color:var(--mist-dim); text-decoration:line-through; }
.kc-price-now{ color:var(--pale); }
/* "% OFF APPLIED" tag: micro-label pattern (small-caps mono badge). */
.kc-tag{
  font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.06em;
  text-transform:uppercase; color:var(--pale);
}
/* qty stepper: button box grown 20->22px so the larger glyph keeps clean
   padding inside its own hit target. self-EBI catch (2026-07-26): Storefront's
   own parent style.css ships `button{padding:.618047em 1.41575em}`
   (golden-ratio, EM-scaled -- grows with THIS button's own font-size); this
   file's own double-enqueue quirk (kairo/style.css loads once before and
   once after Storefront's, documented elsewhere in this file) means source
   order can't be trusted as the tiebreaker, so an unguarded width:22px was
   silently losing and rendering ~44px wide once the qty font grew to 15px.
   padding:0 + !important neutralizes it at the actual property that was
   fighting back, not just re-asserting width. */
.kc-qty{ display:flex; align-items:center; gap:8px; font-family:var(--font-mono); font-size:var(--fs-meta); }
.kc-qty button{
  width:22px !important; height:22px !important; padding:0 !important;
  background:none; border:1px solid var(--hairline);
  color:var(--mist); cursor:pointer; line-height:1; border-radius:2px;
}
.kc-qty button:hover{ background:rgba(233,235,238,.06); }
.kc-qty button:disabled{ opacity:.35; cursor:not-allowed; }
/* In-flight write on this line (2026-07-26 round-2 QA): the shopper's own
   number is already painted, so this is the "still saving" signal only. The
   buttons stay live on purpose -- see cart-drawer.js stepQty(): a taken-away
   button drops the tap, and a dropped tap is a dropped pack tier. */
.kc-qty[aria-busy="true"]{ opacity:.6; }
.kc-remove{
  font-size:var(--fs-meta); letter-spacing:.06em; text-transform:uppercase; color:var(--mist-dim);
  background:none; border:0; cursor:pointer; text-decoration:underline; padding:0;
}
.kc-remove:hover{ color:var(--mist); }
.kc-foot{ padding:20px 24px 24px; border-top:1px solid var(--hairline); }
.kc-subtotal{ display:flex; justify-content:space-between; font-size:var(--fs-body); margin-bottom:6px; color:var(--porcelain); }
.kc-subtotal .val{ font-family:var(--font-mono); }

/* ---------- drawer discount row (2026-07-26 fix wave B) ----------
   `.kc-discount` (assets/cart-drawer.js) piggybacks `.kc-subtotal` for
   layout and shipped with no treatment of its own, so an applied coupon read
   as a second, identical Subtotal line. Given the same type scale and rhythm
   as the rows around it (it IS a totals row) and separated by colour only:
   the value takes the store's warm discount accent, the label steps back one
   tier from Subtotal's porcelain so the row reads as a modifier rather than
   a peer.
   COLOUR NOTE, deliberate substitution: the brief asked for a copper/ember
   value to match the cart page. Measured on this drawer's --slate ground,
   --ember is 3.99:1 and --copper 4.21:1 at 16px, both under the 4.5:1 AA
   floor, i.e. exactly the failure the contrast half of this same pass exists
   to remove (and tokens.css already marks ember background-only). --pale
   measures 7.53:1 and is the token this pass gave the PDP tier ladder's
   discounted price for the identical reason, so PDP and drawer now carry one
   readable discount colour instead of two illegal ones. Flagged rather than
   silently swapped.
   The cart page's own Discount row is brought to the same colour below, since
   it was rendering plain mist and was the third variant of this one idea. */
.kc-discount .kc-discount-label{ color:var(--mist); }
.kc-discount .kc-discount-val{ color:var(--pale); }
/* [hidden] guard, and it is the root cause not a nicety: `.kc-subtotal` sets
   `display:flex`, an author rule, which beats the UA stylesheet's
   `[hidden]{display:none}`, so the coupon row rendered as a phantom empty
   line on every coupon-less cart. cart-drawer.js currently works around that
   by also writing an inline `style="display:none"` from JS. This restores the
   guarantee on the CSS side for the whole drawer subtree, so the workaround
   stops being load-bearing and no future `.kc-*` rule can resurrect a hidden
   element the same way. Scoped to the drawer rather than declared globally:
   a sitewide `[hidden]{display:none !important}` would also override
   WooCommerce Blocks components that legitimately re-show hidden nodes. */
.kc-drawer [hidden]{ display:none !important; }
/* drawer footer smalls (2026-07-26 sweep, CT screenshot: "footer area too
   small"): the two notes under Subtotal/Checkout. */
.kc-note{ font-size:var(--fs-meta); color:var(--mist-dim); margin-bottom:14px; }
.kc-checkout{
  display:block; width:100%; text-align:center; background:var(--ember); color:var(--on-ember);
  font-family:var(--font-body); font-weight:700; font-size:var(--fs-meta); letter-spacing:.14em; text-transform:uppercase;
  padding:14px 0; border-radius:var(--btn-radius); text-decoration:none; border:0; cursor:pointer;
}
.kc-checkout:hover{ background:#cf6527; }
.kc-checkout:focus-visible{ box-shadow:0 0 0 2px var(--copper); outline:none; }
.kc-checkout-note{ font-size:var(--fs-meta); color:var(--mist-dim); text-align:center; margin-top:10px; }
/* SCROLL LOCK REMOVED (2026-07-31). `overflow:hidden` on the body was the
   mechanical cause of the first customer's complaint that the drawer "takes
   you out of the flow of wanting to add more stuff": the page physically
   could not be scrolled while the drawer was open, so the only way back to
   the catalog was to close the drawer. A side drawer is not a modal and must
   not behave like one.

   The class is still applied by cart-drawer.js (line ~806) and is left in
   place as a hook, so the JS needs no edit and any future need for a lock has
   somewhere to live. It simply no longer locks anything. */
body.kc-scroll-lock{ /* intentionally empty: see note above */ }
@media (max-width:480px){
  /* MOBILE WIDTH (2026-07-31): was 100vw, now inherits the same narrow
     rail as desktop per the store owner's "150 wide for both". Value
     updated 2026-08-04 to the new narrow figure (264px, --kc-w's own
     <=600px breakpoint) so this pre-var() fallback cannot drift from
     the number it is standing in for -- see the lockstep note above. */
  .kc-drawer{ width:min(264px,92vw); }
}

/* Notice suppression: when the drawer successfully opens on a fresh
   classic-form add-to-cart page load (PDP), the default WooCommerce
   success notice is hidden so the two don't stack. Only applied via JS
   after a successful drawer render -- if the Store API fetch fails, the
   notice is left visible as the fallback path. */
.woocommerce-notices-wrapper.kairo-notice-suppressed{ display:none; }

/* =========================================================================
   DISCOUNT PARITY (2026-07-26, CT contract addition): WooCommerce Blocks'
   default discounted-price treatment (white/porcelain "ins" price + a grey
   "Save $X" chip) reads as a generic Woo cart, not this brand. Cart page,
   checkout order summary, and the drawer above all now present a tier
   discount identically: struck grey original, ember discounted price, ember
   "N% OFF APPLIED" tag. The stock "Save $X" chip is replaced by the percent
   tag (assets/cart-checkout-enhance.js computes and injects
   .kairo-off-tag; the stock chip is hidden here rather than deleted from
   the DOM, since Blocks re-renders it on every totals recalculation).
   ========================================================================= */
.wc-block-components-product-price__regular{
  color:var(--mist-dim) !important; text-decoration:line-through !important; font-weight:400 !important;
}
.wc-block-components-product-price__value.is-discounted{
  color:var(--pale) !important; font-weight:500 !important; text-decoration:none !important;
}
.wc-block-components-sale-badge{ display:none !important; }
/* badge pattern (2026-07-26 sweep): micro floor. */
.kairo-off-tag{
  display:block; font-family:var(--font-mono); font-size:var(--fs-micro); letter-spacing:.06em;
  text-transform:uppercase; color:var(--pale); margin-top:4px; white-space:nowrap;
}

/* "You're saving $X" -- total-savings line inserted directly above the
   Total row on the cart page and the checkout order summary (moved off
   Subtotal 2026-07-26: a total-savings figure belongs next to the Total it
   explains). Computed in assets/cart-checkout-enhance.js from the live
   WooCommerce Store API cart (tier + coupon + free-shipping-waiver
   savings), never a DOM-text guess -- see that file's header comment for
   the full derivation. --fs-emphasis is its OWN token (18px), CT-approved
   2026-07-26 to read as a stand-out conversion signal: never resize it to
   match --fs-meta. */
.kairo-savings-line{
  display:block; text-align:center;
  font-family:var(--font-mono); font-size:var(--fs-emphasis); font-weight:500; letter-spacing:.05em;
  color:var(--pale); padding:12px 16px 16px; margin:0;
}

/* Cart / checkout Discount row value (2026-07-26 fix wave B): was rendering
   plain mist, identical to Subtotal beside it, while the drawer and the PDP
   tier ladder both call a discount out in a warm accent. Same --pale token as
   `.kc-discount .kc-discount-val` above (7.53:1 on this ground; see the
   colour note there for why not ember/copper), so a discount now reads the
   same on the PDP, in the drawer, on the cart and in the checkout summary. */
.wp-block-woocommerce-cart-order-summary-discount-block .wc-block-components-totals-item__value,
.wc-block-components-totals-discount .wc-block-components-totals-item__value{
  color:var(--pale) !important;
}

/* FREE shipping/delivery value: a conversion signal, called out in copper
   (CT: "utilizing Copper where it makes sense"). Scoped to the shipping/
   delivery totals row's <strong>Free</strong> value only. */
.wc-block-components-totals-shipping .wc-block-components-totals-item__value strong{
  color:var(--copper) !important;
}
/* shipping-method subtext ("Insured shipping" under the Shipping totals
   row): 2026-07-26 sweep, missed in the first pass. WooCommerce Blocks'
   own cart.css ships `.wc-block-components-totals-shipping
   .wc-block-components-totals-shipping__via{font-size:.875em}`, two
   classes (0,2,0); a bare single-class override here loses that
   specificity fight regardless of source order, so the parent class is
   repeated here too. */
/* Inverted hierarchy (2026-07-26 round-2 QA): the __via sub-caption measured
   15px against its OWN row's label and value at Woo Blocks' default 14px, so
   "FedEx Ground" outsized the "Shipping" it belongs to, and both rows sat
   under this stylesheet's declared 15px meta floor. The rows take the meta
   floor and the sub-caption drops to the micro floor, so the summary reads
   16px Total > 15px row > 13px method. */
.wc-block-components-totals-item__label,
.wc-block-components-totals-item__value{ font-size:var(--fs-meta); }
.wc-block-components-totals-shipping .wc-block-components-totals-shipping__via{ font-size:var(--fs-micro); }

/* Totals hierarchy: the Total row reads strongest (larger, porcelain);
   Subtotal/Shipping/Discount rows stay at the existing quieter weight. */
.wc-block-components-totals-footer-item .wc-block-components-totals-item__label,
.wc-block-components-totals-footer-item .wc-block-components-totals-item__value{
  font-size:16px !important; color:var(--porcelain) !important; font-weight:500 !important;
}

/* Checkout order-summary thumbnail quantity badge: was a generic white
   circle (Woo Blocks default); brand treatment (slate fill, copper ring,
   DM Mono figure) matches every other quantity readout on the store. */
.wc-block-components-order-summary-item__quantity{
  background:var(--slate) !important; border:1px solid var(--copper) !important;
  color:var(--pale) !important; font-family:var(--font-mono) !important; font-weight:500 !important;
  box-shadow:none !important;
  /* 12.25px -> var(--fs-micro) (2026-07-26 sweep, missed in the first pass:
     this badge's size came from Woo Blocks' own em-scaled CSS, not a
     declaration in this file). Badge pattern, so it takes the micro floor. */
  font-size:var(--fs-micro) !important;
}

/* =========================================================================
   EDUCATION (lane A, 2026-07-26): PDP "Research context" section. Markup
   comes from woocommerce/single-product.php's `.research-block` (content:
   kairo_get_research_content() in functions.php); this is its only styling,
   it shipped with none. Visual language reuses existing PDP tokens
   (hairline border + rgba(233,235,238,.03) panel fill, the same treatment
   .tier-ladder/.ruo-band already use above) rather than introducing a new
   surface, so the section reads as part of one system.
   ========================================================================= */
.research-block{
  margin-bottom:32px; padding:26px 32px;
  border:1px solid var(--hairline); border-radius:3px;
  background:rgba(233,235,238,.03);
}
.research-title{
  font-family:var(--font-display); font-weight:400; font-size:clamp(20px,2vw,25px);
  color:var(--porcelain); line-height:1.3; margin:0 0 18px;
}
/* .body ships no margin of its own (it's reused sitewide as a single,
   standalone paragraph); scoped here, not on .body itself, so consecutive
   research paragraphs get breathing room without adding margin to every
   other .body caller sitewide. */
.research-block > p.body{ max-width:68ch; margin:0 0 14px; }
.research-block > p.body:last-of-type{ margin-bottom:0; }
.research-facts{
  margin:24px 0 0; padding-top:20px; border-top:1px solid var(--hairline);
  display:grid; grid-template-columns:repeat(2,1fr); gap:14px 32px;
}
/* Label ABOVE value, left-aligned, at EVERY width. The first pass set these
   as a baseline flex row with `justify-content:space-between` and a
   right-aligned <dd>. That measured clean but read badly at 1440: the value
   floats away from its own label across up to ~350px of dead gap (rendered
   check, BPC-157 at 1440: CLASS label at x=37 with its value at x=193,
   MOLAR MASS label at x=576 with its value ending at x=1086), so the eye
   has to re-pair label to value on every row, and any value long enough to
   wrap ("Preclinical tissue-repair and gastrointestinal models") wraps
   ragged-left against a hard right edge. Stacking pairs each label to its
   value by proximity instead of by row position. It is also exactly what
   the mobile breakpoint below already did, so this is now ONE component
   that reads the same at both widths rather than two divergent treatments. */
.research-fact{ display:flex; flex-direction:column; align-items:flex-start; gap:3px; }
.research-fact dt{
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.06em; text-transform:uppercase; color:rgba(233,235,238,.5);
  margin:0;
}
.research-fact dd{
  font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.55;
  color:rgba(233,235,238,.82); margin:0; overflow-wrap:break-word;
}
/* The scope note is real compliance copy in full sentences, not a
   wide-letterspaced micro-label, so it takes --fs-meta (15px), not
   --fs-micro: the same call, for the same reason, that .ruo-band p directly
   below this section already makes ("real compliance copy CT wants
   legible"). Per the type-scale header at the top of this file, --fs-micro's
   13px floor is reserved for mono micro-labels (.eyebrow, .mono-label,
   .research-fact dt), and this is neither. */
.research-note{
  margin:20px 0 0; font-family:var(--font-mono); font-size:var(--fs-meta);
  letter-spacing:.02em; line-height:1.8; color:var(--mist-dim);
}
@media (max-width:767px){
  .research-block{ padding:24px 20px; }
  /* .research-fact itself needs no override now: the stacked treatment above
     is the default at every width, so mobile only drops the grid to 1-up. */
  .research-facts{ grid-template-columns:1fr; gap:14px; }
}

/* =========================================================================
   ABOUT PAGE: statement line (2026-07-27)
   The About page is a long prose read, and the KAIRO system had no device
   between "paragraph" and "section heading" to break one. This is that
   device: one Marcellus line at display weight, hung off a short copper
   rule, used exactly once on the page so it still lands.
   Selector is `.quality-copy p.about-pull` (0,2,1), not a bare `.about-pull`
   (0,1,0): `.quality-copy p + p{ margin-top:14px }` is (0,1,1) and would
   otherwise win the margin and collapse this line back into the paragraph
   flow it exists to interrupt.
   Rule, not a border-top: a full-measure hairline across 640px reads as a
   section divider (the job .divider and .faq-list already do); a 44px rule
   reads as an accent on the line beneath it, which is the copper token's
   documented role in the locked system ("rules, highlights, price
   underlines"). No max-width: at the 640px measure this line sets on one
   line above 767px and two below, and a ch-based cap made it ragged.
   ========================================================================= */
.quality-copy p.about-pull{
  position:relative; margin:34px 0 30px; padding-top:30px;
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(22px,2.3vw,28px); line-height:1.32; letter-spacing:.005em;
  color:var(--mist);
}
.quality-copy p.about-pull::before{
  content:""; position:absolute; left:0; top:0; width:44px; height:1px;
  background:var(--copper);
}

/* =========================================================================
   CONTACT FORM (page-templates/page-contact.php, 2026-07-27)
   Sits inside the shared .lookup-panel, so the panel's ground, hairline and
   padding are inherited from the verify page's component rather than
   restated here. Only the field system is new.
   Type: labels take the --fs-micro mono micro-label register (the same call
   .mono-label and .research-fact dt make: a wide-letterspaced small-caps
   LABEL, not a sentence); every input, error and control takes the --fs-meta
   15px interactive floor.
   ========================================================================= */
.contact-form{ display:flex; flex-direction:column; gap:22px; }
.contact-field{ display:flex; flex-direction:column; gap:8px; }
.contact-label{
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.08em; text-transform:uppercase; color:rgba(233,235,238,.72);
}
.contact-optional{ color:var(--mist-dim); letter-spacing:.06em; }

/* Geometry and colors copied from `.lookup-row input` deliberately: the lot
   input and these fields are the same control in the same panel on two
   pages, and they have to be indistinguishable. textarea adds its own
   padding/height because it is multi-line.
   Every rule here is scoped to .contact-form (0,2,0) rather than written on
   the bare class (0,1,0), and that is load-bearing: Storefront's parent
   style.css styles fields by ATTRIBUTE, `input[type=text], input[type=email]
   {border:0; padding:.618em; box-shadow:inset 0 1px 1px rgba(0,0,0,.125)}`,
   which is (0,1,1) and quietly beats a single-class component rule. Measured
   on the first build of this form: the <textarea> took the intended 1px
   hairline border and 14px/16px padding while the three <input>s next to it
   computed `border:0px none` and `padding:9.27px`, i.e. the same form
   rendered two different controls. The verify page's `.lookup-row input`
   never hit this because it is (0,1,1) itself and wins on source order.
   ONE control (the textarea) looking right is exactly what makes this class
   of bug survive a screenshot: the crop has to be compared control to
   control. */
.contact-form .contact-input,
.contact-form .contact-textarea{
  width:100%; background:var(--slate); border:1px solid var(--hairline); border-radius:2px;
  color:var(--mist); font-family:var(--font-mono); font-size:var(--fs-meta); letter-spacing:.04em;
  color-scheme:dark;
}
.contact-form .contact-input{ height:52px; padding:0 16px; }
.contact-form .contact-textarea{ padding:14px 16px; line-height:1.7; min-height:150px; resize:vertical; }
.contact-form .contact-input::placeholder,
.contact-form .contact-textarea::placeholder{ color:var(--mist-dim); opacity:1; }
.contact-form .contact-input:focus,
.contact-form .contact-textarea:focus{ border-color:var(--copper); }
/* Errored fields carry three signals, not just color (WCAG 1.4.1): this
   border, the message below the field, and aria-invalid on the control. */
.contact-form .contact-input.has-error,
.contact-form .contact-textarea.has-error{ border-color:var(--pale); }

/* The three rules below are scoped under .lookup-panel on purpose, and it is
   not specificity golf. `.verify-hero p` (0,1,1) sets color, font-size,
   max-width:52ch AND margin:0 on EVERY paragraph inside the hero block, not
   just the hero's own sub-line, because it is a descendant selector where a
   child combinator was meant. Any single-class component rule on a <p> in
   there loses to it: measured on this form before scoping, .contact-error
   rendered mist-dim (rgba(233,235,238,.55)) instead of --pale and
   .contact-alert lost its 24px bottom margin, while the same block's
   non-<p> rules (labels, inputs, the alert's own border and wash) applied
   normally, which is what makes the failure look like a color bug rather
   than a specificity one. Scoping to .lookup-panel (0,2,0) beats (0,1,1) and
   is semantically true: these belong to the panel.
   Narrowing `.verify-hero p` to `.verify-hero > p` is the root fix and is
   deliberately NOT done here: the verify page's own .lookup-status and
   .lookup-caveat are nested in that same hero and would lose the 52ch
   measure they currently inherit from it, which is another page's design.
   Flagged rather than changed.
   --pale on the panel ground measures 5.14:1 composited, clear of AA, and
   stays inside the locked copper family rather than importing a red this
   brand does not own. */
.lookup-panel .contact-error{
  font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.6; color:var(--pale);
}
.lookup-panel .contact-alert{
  margin:0 0 24px; padding:14px 16px; border-radius:2px; max-width:none;
  border:1px solid rgba(184,106,60,.5); background:rgba(184,106,60,.10);
  font-family:var(--font-mono); font-size:var(--fs-meta); line-height:1.6; color:var(--pale);
}
.lookup-panel .contact-sent p.body{ font-size:var(--fs-body); color:rgba(233,235,238,.68); max-width:52ch; }
.contact-actions{ margin-top:2px; }

/* .contact-note: the quiet standing notes on this form (the research-use
   boundary under the message field, the one-line data note under the button).
   Scoped to .lookup-panel for the same specificity reason .contact-error is:
   `.verify-hero p` (0,1,1) sits above the bare class and would otherwise take
   the color and the measure. Deliberately quieter than .contact-error, which
   is a response to something the sender did; these are always on screen. */
.lookup-panel .contact-note{
  margin:0; max-width:60ch;
  font-family:var(--font-mono); font-size:var(--fs-micro); line-height:1.7;
  letter-spacing:.02em; color:var(--mist-dim);
}
.lookup-panel .contact-actions .contact-note{ margin-top:16px; }

/* Honeypot: off-screen rather than display:none or visibility:hidden, both
   of which a form-filling bot can detect and skip. tabindex="-1" in the
   markup keeps it out of the keyboard path and aria-hidden keeps it out of
   the accessibility tree, so the only visitor who can reach it is a script. */
.contact-hp{
  position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;
}

.contact-sent .h3{ margin:10px 0 12px; color:var(--porcelain); }

@media (max-width:767px){
  .contact-form{ gap:18px; }
}

/* =========================================================================
   RESEARCH DOSSIER (PDP) -- 2026-07-27
   Styles the verified-research block on the peptide product pages. Markup is
   emitted by woocommerce/single-product.php from research/<slug>-page.json;
   this file owns every pixel of it plus assets/pdp-motion.js.

   Three rules the whole block is built on:
   1. Plain language leads, the technical record follows. Each study card puts
      a one-sentence human answer first and files the verbatim journal finding
      underneath it as secondary evidence, never the other way around.
   2. Nothing is hidden, plenty is folded. Every claim and citation stays in
      the DOM; disclosure only controls what competes for attention first.
   3. Confidence is a sequential scale, not a traffic light. Three steps of one
      copper hue (pale -> copper -> deep) plus a pip count, so the tier is
      never carried by color alone and never implies "bad".
   ========================================================================= */

:root{
  /* Dossier-local surfaces. Composited over --slate #15171B, kept as flat
     hex (not rgba) so nested surfaces do not stack into mud. */
  --dsr-surface:#1B1E23;        /* card plate */
  --dsr-surface-hi:#22262C;     /* hover / open plate */
  --dsr-sunken:#131519;         /* inset wells: technical record, refs */
  --dsr-rule:rgba(233,235,238,.13);
  --dsr-rule-soft:rgba(233,235,238,.08);
  --dsr-ink:#E9EBEE;            /* primary */
  --dsr-ink-2:rgba(233,235,238,.80);  /* body prose */
  --dsr-ink-3:rgba(233,235,238,.62);  /* secondary / technical */
  --dsr-ease:cubic-bezier(.16,1,.3,1);
}

/* -------------------------------------------------------------------------
   STUDY CARDS
   Two columns from 1000px up. Not three: the plain-language sentence needs a
   ~46ch measure to stay readable, and three columns inside a 1280 container
   pushes it under 30ch. Below 1000 it is a single column.
   ------------------------------------------------------------------------- */
.study-cards{
  display:grid; gap:16px; margin:0; padding:0; list-style:none;
  grid-template-columns:minmax(0,1fr);
}
@media (min-width:1000px){
  
}


/* Edge refraction, not a glow: a 1px top highlight reading as a bevel on the
   plate, plus a copper spine on the leading edge that lengthens on hover.
   Both are pseudo-elements so they never enter the layout. */
.study-card::before{
  content:""; position:absolute; inset:0 0 auto; height:1px; border-radius:3px 3px 0 0;
  background:linear-gradient(90deg,rgba(233,235,238,.10),rgba(233,235,238,.02) 60%,transparent);
  pointer-events:none;
}


/* The research-area name is the filing label, not the headline: it tells you
   which drawer you are in, then gets out of the way. */
.study-area{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  line-height:1.5; letter-spacing:.16em; text-transform:uppercase;
  color:var(--pale); max-width:34ch;
}

/* The plain-language headline. This is what a reader actually came for, so it
   gets the display face and the largest type in the card. */
/* No margin of its own: it is the first thing in the card, so a top margin is
   dead space stacked on the summary's own padding, and the plain sentence
   below already carries the gap between the two (x8 cards, ~208px). */
.study-plain{
  margin:0;
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(20px,1.55vw,24px); line-height:1.28; letter-spacing:.004em;
  color:var(--porcelain); max-width:26ch; text-wrap:balance;
}

/* One warm sentence, in plain English, at full body weight. Deliberately the
   most legible thing in the card. */
/* Measure widened from 46ch to 54ch: at 46ch this sentence wrapped to five
   lines inside a card column ~600px wide, so a third of the card's height was
   a measure narrower than the column that holds it. 54ch is still inside the
   comfortable 45-75ch band and costs a line, not legibility. */
.study-plain-line{
  margin-top:12px;
  font-family:var(--font-body); font-size:17px; line-height:1.6; letter-spacing:.004em;
  color:var(--dsr-ink-2); max-width:54ch;
}

/* -------------------------------------------------------------------------
   BADGES: model + confidence
   Styled on the direct children of .study-badges rather than on a badge class,
   so the pill system holds whatever element the template emits. Confidence
   tiers are matched through every plausible hook (data attribute, BEM
   modifier, is- class) because this block and the template are authored in
   parallel; the base pill is correct even if none of them match.
   ------------------------------------------------------------------------- */
.study-badges{
  display:flex; flex-wrap:wrap; gap:8px; margin-top:18px; padding:0; list-style:none;
}


/* Confidence pips: three slots, filled to the tier. Ordinal magnitude gets a
   sequential ramp of ONE hue, never a red/amber/green status set, which would
   read as a safety verdict on the compound rather than a strength-of-evidence
   note about the literature. */
.study-badges [data-confidence]::before,
.study-badges [class*="confidence"]::before{
  content:""; flex:none; width:19px; height:5px; border-radius:1px;
  background:
    linear-gradient(90deg,
      currentColor 0 5px, transparent 5px 7px,
      currentColor 7px 12px, transparent 12px 14px,
      currentColor 14px 19px);
  opacity:.9;
}


/* -------------------------------------------------------------------------
   CONFIDENCE LEGEND
   A hard requirement of the research file: the tiers are defined terms, so the
   definitions ship next to the badges that use them. Injected by
   pdp-motion.js only if the template has not already rendered it.
   ------------------------------------------------------------------------- */
.study-legend{
  margin:0 0 24px; padding:20px 22px;
  background:var(--dsr-sunken); border:1px solid var(--dsr-rule-soft); border-radius:3px;
}


@media (min-width:860px){  }


/* -------------------------------------------------------------------------
   THE EVIDENCE MOMENT
   The credibility centerpiece: the paragraph that says out loud how thin the
   human evidence is. It is the most valuable thing on the page, so it is set
   like a pull quote rather than filed as another paragraph.

   The panel look is carried by the two named children rather than by a
   wrapper. .dossier-evidence-lede paints the top of the plate (rule, radius,
   top padding) and .dossier-evidence-body paints the bottom, so the pair
   reads as one object whether or not the template wraps them. If a
   .dossier-evidence wrapper does exist it repaints the identical surface,
   which is a no-op, not a second plate.
   ------------------------------------------------------------------------- */
.dossier-evidence{
  background:var(--dsr-surface); border-radius:3px; overflow:hidden;
}


/* The warm wash. Very low alpha copper, top-right, no blur and no spread, so
   it reads as light falling across the plate rather than as a glow. */
.dossier-evidence-lede::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(120% 160% at 92% 0%, rgba(184,106,60,.10), transparent 62%);
}

/* Lifting the opening line to full porcelain gives the paragraph an entry
   point with no extra markup and no drop cap, which at this measure would
   read as a magazine affectation rather than a lab document. */
.dossier-evidence-body::first-line{
  color:var(--porcelain); letter-spacing:.012em;
}
/* Any figure the template marks up inside the statement stays in the data
   face, so counts read as counts. */
.dossier-evidence-body strong,
.dossier-evidence-body b,
.dossier-evidence-body .mono-data{
  font-family:var(--font-mono); font-weight:500; font-size:.94em;
  letter-spacing:.01em; color:var(--pale);
}

/* -------------------------------------------------------------------------
   SECTION FURNITURE: display heading, deck, quiet note
   .dossier-sub is written to work as a deck under the display heading, and is
   element-qualified so it still reads as a heading if the template emits it
   as one. Both are safe; neither depends on which.
   ------------------------------------------------------------------------- */
.dossier-display{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(27px,3.1vw,42px); line-height:1.14; letter-spacing:.004em;
  color:var(--porcelain); max-width:22ch; text-wrap:balance;
}
.dossier-sub{
  margin:14px 0 0; font-family:var(--font-body);
  font-size:17px; line-height:1.68; letter-spacing:.004em;
  color:var(--dsr-ink-2); max-width:58ch;
}
h2.dossier-sub, h3.dossier-sub, h4.dossier-sub{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(20px,1.9vw,26px); line-height:1.24; color:var(--porcelain); max-width:28ch;
}
/* The standing fine print: research-use boundary, re-verification date,
   coverage caveats. Quiet, but never below the 13px mono floor and never
   below AA, because on this page the fine print is part of the argument. */
.dossier-note{
  margin:16px 0 0; font-family:var(--font-mono); font-weight:400;
  font-size:var(--fs-micro); line-height:1.7; letter-spacing:.02em;
  color:var(--mist-dim); max-width:64ch;
}
.dossier-note a{ color:var(--pale); text-underline-offset:3px; }

/* -------------------------------------------------------------------------
   ORIENTATION BLOCK
   The warm plain-English answer to "what am I looking at", placed before any
   technical claim. Split into an asymmetric two-up at desktop so it reads as
   an opening statement with a margin note beside it, not as another stacked
   paragraph, and so it costs one row of height instead of three.
   ------------------------------------------------------------------------- */
.dossier-orient{
  position:relative; display:grid; gap:18px;
  margin:0 0 clamp(24px,2.2vw,32px);
  padding:clamp(24px,2.5vw,34px) clamp(22px,3vw,40px);
  background:
    linear-gradient(180deg, rgba(241,237,228,.045), rgba(241,237,228,.018)),
    var(--slate);
  border:1px solid var(--dsr-rule-soft);
  border-left:2px solid var(--copper);
  border-radius:3px;
}
@media (min-width:1000px){
  
  
  
  
}
/* The lede is the one sentence a reader should leave with even if they read
   nothing else, so it carries the display face at reading size rather than at
   headline size: warm and plain, not declamatory. */
.dossier-orient-lede{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(19px,1.75vw,25px); line-height:1.44; letter-spacing:.004em;
  color:var(--porcelain); max-width:34ch;
}


/* -------------------------------------------------------------------------
   DISCLOSURE
   Native details/summary, styled so it reads as an instrument panel rather
   than a stock accordion: no triangle, no full-width chevron bar, no rounded
   FAQ pill. The marker is a bordered square holding a plus that becomes a
   minus, the summary label sits left with the count right, and the open state
   is a real state change (copper marker, lifted plate, a rule that runs the
   width of the panel) rather than just "content is now visible".

   Height animation is driven by pdp-motion.js with the Web Animations API,
   not by CSS on the details element, because a closed details has no layout
   box to transition from. CSS here owns appearance and the reduced-motion
   contract only.
   ------------------------------------------------------------------------- */
.refs-disclosure,
.study-tech-fold{
  margin:0; background:var(--dsr-sunken);
  border:1px solid var(--dsr-rule-soft); border-radius:3px;
  transition:border-color .45s var(--dsr-ease), background .45s var(--dsr-ease);
}
.refs-disclosure[open],
.study-tech-fold[open]{ border-color:var(--dsr-rule); }
.refs-disclosure:hover,
.study-tech-fold:hover{ border-color:var(--dsr-rule); }

.refs-summary,
.study-tech-fold > summary{
  display:flex; align-items:center; gap:16px;
  margin:0; padding:18px 20px; cursor:pointer;
  list-style:none; -webkit-tap-highlight-color:transparent;
  transition:background .3s var(--dsr-ease);
}
.refs-summary::-webkit-details-marker,
.study-tech-fold > summary::-webkit-details-marker{ display:none; }
.refs-summary:hover,
.study-tech-fold > summary:hover{ background:rgba(233,235,238,.028); }
.refs-summary:focus-visible,
.study-tech-fold > summary:focus-visible{
  outline:2px solid var(--pale); outline-offset:-2px;
}

/* The marker. Two bars in a bordered square; the vertical bar collapses on
   open, so the gesture is plus-to-minus rather than a spinning arrow. */
.refs-summary::before,
.study-tech-fold > summary::before{
  content:""; flex:none; order:-1;
  width:24px; height:24px; border-radius:2px;
  border:1px solid var(--dsr-rule);
  background:
    linear-gradient(currentColor,currentColor) center/9px 1px no-repeat,
    linear-gradient(currentColor,currentColor) center/1px 9px no-repeat;
  color:rgba(233,235,238,.62);
  transition:color .4s var(--dsr-ease), border-color .4s var(--dsr-ease),
             background-size .4s var(--dsr-ease), transform .4s var(--dsr-ease);
}
.refs-summary:hover::before,
.study-tech-fold > summary:hover::before{ color:var(--pale); border-color:rgba(216,155,106,.4); }
.refs-disclosure[open] > .refs-summary::before,
.study-tech-fold[open] > summary::before{
  color:var(--copper); border-color:rgba(184,106,60,.55);
  background-size:9px 1px, 1px 0px;   /* vertical bar collapses: plus -> minus */
}

.refs-summary-label,
.study-tech-fold > summary > span:first-of-type{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta);
  letter-spacing:.13em; text-transform:uppercase; color:var(--dsr-ink);
}
/* The count is data, so it is set in the data face and pushed to the far edge
   where a running total belongs. */
.refs-count{
  margin-left:auto; padding:3px 9px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.08em; color:var(--pale);
  background:rgba(216,155,106,.10); border:1px solid rgba(216,155,106,.24); border-radius:2px;
}

/* The open-state rule: a hairline that runs the full width under the summary,
   present only when open, so the panel visibly becomes two parts. */
.refs-disclosure[open] > .refs-summary,
.study-tech-fold[open] > summary{
  border-bottom:1px solid var(--dsr-rule-soft);
}

/* The content wrapper pdp-motion.js animates. Overflow is clipped only while
   an animation is running (.is-folding), so an open panel never traps a
   focus ring or a long citation URL. */
.fold-body{ padding:20px; }
.is-folding{ overflow:hidden; }

/* PACING (CT review, 2026-07-27): the two technical folds. Everything inside
   them used to render inline and cost ~2,000px of scroll from every reader,
   including readers who came for the plain-English answer directly above.
   Collapsed, not deleted, so the material is one click away and still in the
   DOM for search, find-in-page and citation anchors.

   `.research-block > p.body` is a DIRECT-child rule, so the prose would lose
   its measure and its inter-paragraph rhythm to the fold wrapper. Restated
   here at the same values rather than loosened to a descendant selector,
   which would also catch the co-marketed paragraphs three sections down. */
.research-fold .fold-body > p.body{ max-width:68ch; margin:0 0 14px; }
.research-fold .fold-body > p.body:last-of-type{ margin-bottom:0; }
.research-fold .fold-body > .research-facts{ margin-top:20px; }
/* The fold body already supplies the gutter these two carried themselves. */
.identity-fold .fold-body > .seq-rail{ margin-top:0; }
.identity-fold .fold-body > .dossier-note{ margin-top:14px; }

/* The partner name and its two status chips are one label, so they sit on one
   line instead of stacking into 82px of card head. Wraps back to two lines on
   its own when the name and chips cannot share a row. */
.comarket-head{
  display:flex; flex-wrap:wrap; align-items:baseline;
  column-gap:16px; row-gap:8px;
}

@media (prefers-reduced-motion: reduce){
  .refs-disclosure, .study-tech-fold,
  .refs-summary, .study-tech-fold > summary,
  .refs-summary::before, .study-tech-fold > summary::before{ transition:none; }
}

/* -------------------------------------------------------------------------
   THE TECHNICAL RECORD (secondary to the plain-language answer)
   Set in a sunken well so it reads as the source document quoted underneath
   the human sentence, not as a competing headline. Kept at 15px, the
   informational floor, because "secondary" is a hierarchy decision, not a
   licence to make the evidence hard to read.
   ------------------------------------------------------------------------- */
.study-tech{
  margin-top:16px; width:100%;
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.68;
  letter-spacing:.004em; color:var(--dsr-ink-3);
}
.study-tech > *{ margin:0; }
.study-tech > * + *{ margin-top:10px; }
/* The verbatim journal title, whatever element carries it. */
.study-tech strong, .study-tech b, .study-tech dt, .study-tech h4, .study-tech h5,
.study-tech .study-tech-title{
  display:block; font-family:var(--font-mono); font-weight:500;
  font-size:var(--fs-micro); line-height:1.6; letter-spacing:.11em; text-transform:uppercase;
  color:rgba(233,235,238,.72);
}

/* Per-study citation: the receipt on the card. */
.study-cite{
  margin-top:16px; padding-top:14px; width:100%;
  border-top:1px solid var(--dsr-rule-soft);
}
.study-cite-text{
  margin:0; font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.6;
  color:rgba(233,235,238,.58);
}
.study-cite-links,
.ref-links{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:8px; margin-top:10px;
}
.study-cite-links a,
.ref-links a,
.ref-anchor{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.09em; text-transform:uppercase; text-decoration:none;
  color:var(--pale);
  border-bottom:1px solid rgba(216,155,106,.32); padding-bottom:2px;
  transition:color .3s var(--dsr-ease), border-color .3s var(--dsr-ease);
}
.study-cite-links a:hover,
.ref-links a:hover,
.ref-anchor:hover{ color:var(--porcelain); border-bottom-color:var(--porcelain); }
.study-cite-links a:focus-visible,
.ref-links a:focus-visible,
.ref-anchor:focus-visible{ outline:2px solid var(--pale); outline-offset:3px; border-radius:1px; }
/* The separator is punctuation, not a link: no border, no interaction. */
.ref-anchor-sep{
  font-family:var(--font-mono); font-size:var(--fs-micro);
  color:rgba(233,235,238,.30); border:0; user-select:none;
}

/* -------------------------------------------------------------------------
   REFERENCE LIST
   A numbered ledger, not a bulleted list: the index sits in its own rail so
   the citations align on a single left edge and the numbers can be scanned
   down the column when following a superscript from the cards.
   ------------------------------------------------------------------------- */
.ref-list{ margin:0; padding:0; list-style:none; counter-reset:none; }
.ref-item{
  display:grid; grid-template-columns:34px minmax(0,1fr); column-gap:16px;
  margin:0; padding:16px 0;
  border-top:1px solid var(--dsr-rule-soft);
  scroll-margin-top:96px;
  transition:background .5s var(--dsr-ease);
}
.ref-item:first-child{ border-top:0; padding-top:4px; }
.ref-n{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  line-height:1.75; letter-spacing:.04em; color:var(--pale);
  font-variant-numeric:tabular-nums; text-align:right;
}
.ref-body{ min-width:0; }
.ref-cite{
  margin:0; font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.66;
  letter-spacing:.004em; color:var(--dsr-ink-3);
  overflow-wrap:anywhere;   /* DOIs and long journal strings must not overflow */
}
/* Arriving from a citation link: the target row lights briefly rather than
   leaving the reader to hunt for which of fifteen rows they landed on. */
.ref-item:target{
  background:rgba(216,155,106,.08);
  box-shadow:inset 2px 0 0 var(--copper);
}
.ref-item:target .ref-n{ color:var(--porcelain); }

/* -------------------------------------------------------------------------
   MOTION
   Entrance reveals are opt-in from script: the initial hidden state is scoped
   to .js-reveal, which pdp-motion.js sets on <html> before first paint. With
   JS off or the script failed, nothing is ever hidden.

   Only opacity and transform are animated, so a reveal cannot move a
   neighbour: every element occupies its final box from the first frame.
   ------------------------------------------------------------------------- */
.js-reveal [data-reveal]{
  opacity:0; transform:translate3d(0,16px,0);
  transition:opacity .7s var(--dsr-ease), transform .7s var(--dsr-ease);
  transition-delay:var(--reveal-delay,0ms);
}
/* No will-change here. It was measured promoting all 37 reveal targets to
   compositor layers at first paint on a page that runs to ~15,000px at 390,
   and a hint is only worth its memory when the animation is imminent, which
   for a target eight screens down it is not. The browser composites an
   opacity/transform transition without being told to. */
.js-reveal [data-reveal].is-in{ opacity:1; transform:none; }
/* Print, PDF export and full-page screenshot capture never scroll the way a
   reader does, so an un-revealed target would come out blank. Caught on a
   full-page capture of this page: everything below the fold printed empty.
   The reveal is a scroll gesture; on paper there is no scroll. */
@media print{
  .js-reveal [data-reveal]{ opacity:1; transform:none; transition:none; }
}
/* Same problem, screen media (2026-07-28): pdp-motion.js's own already-past
   and safety-net paths show a target with no scroll-triggered fade to play,
   either because it was already scrolled past when observed or because the
   observer never fired at all (a full-page screenshot capture never moves
   window.scrollY, so it never will). Three selectors beats the two-selector
   base rule above on specificity, so no !important is needed. */
.js-reveal [data-reveal].reveal-instant{ transition:none; }

/* The card spine grows on hover. Rewritten from the height transition above
   to a scaleY on a full-length bar: same gesture, composited, and it cannot
   trigger layout on a hover that fires eight times as the pointer crosses the
   grid. Later in the cascade at equal specificity, so this is the one that
   applies. */
.study-card::after{
  top:26px; height:calc(100% - 52px);
  transform:scaleY(.16); transform-origin:top center;
  transition:transform .5s var(--dsr-ease), opacity .5s var(--dsr-ease);
}


@media (prefers-reduced-motion: reduce){
  .js-reveal [data-reveal],
  .js-reveal [data-reveal].is-in{
    opacity:1; transform:none; transition:none; will-change:auto;
  }
  
  
}

/* -------------------------------------------------------------------------
   NARROW VIEWPORTS
   Nothing reflows into a second pattern here; the desktop layout is already
   single-column below 1000px. This pass is padding, measure and wrapping so
   that at 360-430 the block still reads like a document.
   ------------------------------------------------------------------------- */
@media (max-width:600px){
  .study-card{ padding:20px 18px 18px; }
  .study-card::after{ top:20px; height:calc(100% - 40px); }
  .study-plain{ font-size:20px; max-width:none; }
  .study-plain-line{ font-size:16px; max-width:none; }
  .study-area{ max-width:none; }
  .study-badges{ gap:6px; }
  .study-badges > *{ padding:5px 9px; letter-spacing:.07em; }
  .dossier-orient{ padding:22px 18px; }
  .dossier-evidence-lede{ max-width:none; }
  .dossier-evidence-body{ max-width:none; }
  .refs-summary, .study-tech-fold > summary{ padding:15px 16px; gap:12px; }
  .fold-body{ padding:16px; }
  .ref-item{ grid-template-columns:26px minmax(0,1fr); column-gap:12px; }
}
/* Long DOIs, PubMed URLs and journal strings are the one real overflow risk
   in this block: they are unbreakable tokens inside a fixed grid column. */
.study-cite-text, .study-cite-links a, .ref-links a, .ref-anchor{
  overflow-wrap:anywhere;
}
.study-cite-links, .ref-links{ max-width:100%; }

/* -------------------------------------------------------------------------
   AS-BUILT MARKUP
   single-product.php ships the card as a <details> whose <summary> carries an
   index, the area name, the badge row and a preview of the finding, with the
   model line, full finding and citation in the body; the confidence legend is
   a <dl class="conf-legend"> and the pips are real .conf-step elements rather
   than generated content. The block above is written against the class names
   in the dossier contract; this block maps the as-built names onto the same
   system so both resolve to one design. Nothing here restates a rule already
   set above, it only re-points it.
   ------------------------------------------------------------------------- */

/* --- section rhythm: this is where the 8,700px comes down ---------------- */
/* PACING (CT review, 2026-07-27): the section rhythm was set before the page
   grew to nine dossier sections. At 1440 the old clamp put 132px of padding
   between every pair of them, ~1,060px of the page spent on gaps alone. The
   rule still has to read as a document with air, so this is a tightening of
   the interval, not its removal: 95px between sections instead of 132. */
.dossier{
  padding:clamp(32px,2.8vw,42px) 0;
  border-top:1px solid var(--dsr-rule-soft);
}
.dossier:first-of-type{ border-top:0; }
.dossier > .eyebrow{ display:block; margin:0 0 12px; color:var(--pale); }
/* Two sections are now a heading over a single closed disclosure. A full
   section interval above and below one 60px control is dead space, so they
   take a shorter one; every other section keeps the standard rhythm. */
.dossier-identity{ padding-top:clamp(24px,2.1vw,32px); padding-bottom:clamp(24px,2.1vw,32px); }
.dossier-h2, .dossier-h{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(25px,2.9vw,38px); line-height:1.16; letter-spacing:.004em;
  color:var(--porcelain); max-width:24ch; text-wrap:balance;
}
.dossier-h2 + .dossier-sub{ margin-top:12px; }

h2.dossier-eyebrow-h{ margin:0 0 14px; }


@media (min-width:860px){  }


/* One copper hue at three intensities. Legend order is fixed by the research
   file: strong, moderate, preliminary. */
.conf-legend-row:nth-child(1) dt{ color:var(--pale); }


/* --- confidence meter: filled pips, never color alone -------------------- */
.conf-meter{ display:inline-flex; align-items:center; gap:2px; flex:none; }
.conf-step{
  display:block; width:6px; height:6px; border-radius:1px;
  background:rgba(233,235,238,.20);
}
.conf-step.is-on{ background:currentColor; }


/* The index is a filing number, so it is set as one: data face, tabular, and
   quiet enough that it never competes with the area name beside it. */
.study-index{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  line-height:1.9; letter-spacing:.08em; color:rgba(216,155,106,.72);
  font-variant-numeric:tabular-nums; text-align:right;
}


/* The preview is clamped to three lines. The full finding is two elements
   away in the card body, so clamping hides nothing: it just stops eight
   900-character abstracts from being the first thing the page shows. */
.study-preview{
  margin-top:14px;
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.66;
  letter-spacing:.004em; color:var(--dsr-ink-3); max-width:52ch;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}


/* The toggle: same plus-to-minus instrument as the references panel. */
.study-toggle{
  width:24px; height:24px; border-radius:2px; margin-top:2px;
  border:1px solid var(--dsr-rule); color:rgba(233,235,238,.62);
  background:
    linear-gradient(currentColor,currentColor) center/9px 1px no-repeat,
    linear-gradient(currentColor,currentColor) center/1px 9px no-repeat;
  transition:color .4s var(--dsr-ease), border-color .4s var(--dsr-ease), background-size .4s var(--dsr-ease);
}


.study-model{
  margin:0 0 14px; font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.66;
  color:var(--dsr-ink-3);
}
.study-model-k, .study-cite-label{
  display:block; margin-bottom:5px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.14em; text-transform:uppercase; color:rgba(233,235,238,.46);
}
/* The verbatim finding: the primary record, in a well of its own so it reads
   as quoted source rather than as site copy. */
.study-finding{
  margin:0; padding:18px 20px;
  background:var(--dsr-sunken); border-left:2px solid rgba(184,106,60,.5); border-radius:2px;
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.72;
  letter-spacing:.004em; color:var(--dsr-ink-2); max-width:70ch;
}
@media (max-width:600px){
  
  
  .study-finding{ padding:15px 16px; }
  
  .dossier-h2, .dossier-h{ max-width:none; }
}
@media (prefers-reduced-motion: reduce){
  .study-badges .badge, .study-toggle{ transition:none; }
}

/* =========================================================================
   PDP REBUILD 2026-07-27: STAT BAND, RESEARCH FOCUS GRID, REFUSALS,
   EVIDENCE TIMELINE, MATERIAL SECTION, CERTIFICATE, JOURNAL PLATES,
   STICKY BUY BAR.

   Built to research/pdp-design-direction.md section 6. Palette and type are
   fixed by that document and unchanged here: slate, --dsr-surface, --dsr-
   sunken, the three ink steps, hairlines at .13 and .08, copper as the one
   accent, ember reserved for the research-use register so the two never
   compete, Marcellus display, Switzer body, DM Mono data.

   ONE DELIBERATE DEVIATION from that document, recorded rather than
   silently applied: it specifies DM Mono at 11px for the chips. This theme
   has a 13px micro floor (--fs-micro), set after CT's "too many places with
   too-small text" sweep on 2026-07-26 and asserted by
   verify-research-sections.mjs. The floor wins; chips are 13px.
   ========================================================================= */

/* -------------------------------------------------------------------------
   2. STAT BAND
   Three numerals that count the literature instead of counting customers.
   The strip is sunken and full-bleed so it reads as a plate set into the
   page, and the numerals are the only place on this page where type is
   allowed to be this large without being a heading.

   REBALANCED 2026-07-28 (store owner review, "irrelevant"): the fourth stat
   (0 randomized controlled trials) and its is-zero styling path are both
   gone; grid-template-columns dropped from repeat(4,...) to repeat(3,...)
   here, and the ONE responsive override that changed column count for this
   selector (was: 2 columns at <=900px) is removed below rather than kept
   and re-tuned, since 3 columns is already less crowded than the 4 it
   replaces and carries cleanly through that breakpoint. See the PDP VISUAL
   AMBITION PASS block further down for this band's full-bleed desktop
   treatment, which layers on top of these base rules unchanged.
   ------------------------------------------------------------------------- */
.statband{
  margin:clamp(30px,3.4vw,48px) 0 0;
  padding:clamp(30px,3.4vw,46px) 0;
  background:var(--dsr-sunken);
  border-top:1px solid var(--dsr-rule-soft);
  border-bottom:1px solid var(--dsr-rule-soft);
}
.statband-inner{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(16px,2vw,30px); align-items:start;
}
.statband-item{
  display:flex; flex-direction:column; gap:10px;
  padding-left:clamp(14px,1.5vw,22px);
  border-left:1px solid var(--dsr-rule-soft);
}
.statband-item:first-child{ border-left:0; padding-left:0; }
.statband-fig{
  font-family:var(--font-mono); font-weight:500;
  font-size:clamp(46px,6.2vw,92px); line-height:.92;
  letter-spacing:-.02em; color:var(--porcelain);
  font-variant-numeric:tabular-nums;
}
.statband-label{
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.5;
  letter-spacing:.004em; color:var(--dsr-ink-3); max-width:22ch;
}

/* 1. HERO ANCHOR CHIP: DELETED 2026-07-28 (store owner, buy-box review).
   .hero-anchor / .anchor-chip / .anchor-chip-n / .anchor-chip-arrow removed
   with the markup in woocommerce/single-product.php; nothing else
   referenced any of the four. The .anchor-chip mention in the
   prefers-reduced-motion transition reset further down this file is
   trimmed from that selector list in the same pass. */

/* -------------------------------------------------------------------------
   3. RESEARCH ROWS
   Eight full-width rows, stacked, one research area each.

   REBUILT 2026-07-28 from a 4-across tile grid, on CT's note: "I don't like
   the vertical tiles ... maybe we need to have 8 rows instead of 2 rows with
   4 columns", and "when you click on it, it's hard to really read". Both
   complaints had the same cause, measured: a tile column was 297px wide at
   1440, so the headline ran at a 12-character measure and the OPEN panel's
   verbatim finding ran at 25 characters per line. A row hands the same eight
   findings a real reading measure and lets the eye run left to right.

   Hairlines, not cards: the rule is drawn by the ROW, so the block reads as
   one ruled ledger rather than as eight objects.

   THE TRACK IS ONE VARIABLE. .focus-cols (the header), .focus-summary (the
   row) and .focus-panel (its indent) all size off --focus-cols and its three
   parts, so the header can never drift out of register with the rows it
   labels, and the panel can never stop aligning with the headline above it.
   ------------------------------------------------------------------------- */
.focusgrid{
  --focus-pad:clamp(14px,1.5vw,20px);
  --focus-icon:clamp(30px,2.6vw,36px);
  --focus-gap:clamp(16px,1.8vw,26px);
  /* FIXED px, not a vw clamp, and sized from the WIDEST thing that must sit
     on one line in the rail: the preliminary confidence chip, natural width
     256px ("Preclinical / meter / preliminary"), and the longest model
     string, 240px. Measured, not guessed: at 212px the chip broke onto two
     lines inside its own border on seven of eight rows, and a clamp(232px,
     18vw,262px) still broke it at every viewport under 1423, which is most
     laptops. A rail whose content does not scale must not be sized in vw. */
  --focus-rail:262px;
  --focus-cols:var(--focus-icon) minmax(0,.78fr) minmax(0,1fr) var(--focus-rail) 13px;
  margin:clamp(40px,4.4vw,64px) 0 0;
  padding:clamp(30px,3.4vw,48px) clamp(20px,2.6vw,38px) clamp(26px,2.8vw,38px);
  background:var(--dsr-sunken);
  border:1px solid var(--dsr-rule-soft); border-radius:3px;
  scroll-margin-top:96px;
}
.focusgrid > .eyebrow{ display:block; margin:0 0 14px; color:var(--pale); }
.focusgrid-h{
  margin:0 0 clamp(26px,2.8vw,38px);
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(25px,2.9vw,38px); line-height:1.18; letter-spacing:.004em;
  color:var(--porcelain); max-width:34ch; text-wrap:balance;
}

/* The column header: "Studied for" said once instead of eight times. */
.focus-cols{
  display:grid; grid-template-columns:var(--focus-cols);
  column-gap:var(--focus-gap); align-items:end;
  padding:0 var(--focus-pad) 11px;
}
.focus-cols span{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.14em; text-transform:uppercase; color:rgba(233,235,238,.32);
}
.focus-col-head{ grid-column:2; }
.focus-col-line{ grid-column:3; }
.focus-col-chips{ grid-column:4; text-align:right; }

.focus-tiles{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:minmax(0,1fr);
  border-top:1px solid var(--dsr-rule);
}
/* One hairline per row, drawn by the ROW, so the block reads as a ruled
   ledger rather than as eight objects. */
.focus-item{
  border-top:1px solid var(--dsr-rule-soft);
  min-width:0;
}
.focus-item:first-child{ border-top:0; }
/* One accent, one place: the copper rule marks the single STRONG row. */
.focus-item.is-strong{ box-shadow:inset 2px 0 0 var(--copper); }

.focus-tile{ height:auto; }
/* Five cells, left to right: assay icon, headline, plain finding, chips,
   toggle. The two hidden spans in the summary (.focus-kicker and the
   screen-reader hint) are position:absolute, so they take no cell and the
   children auto-place into the track above with no explicit areas. */
.focus-summary{
  display:grid; grid-template-columns:var(--focus-cols);
  column-gap:var(--focus-gap); row-gap:0; align-items:center;
  cursor:pointer; list-style:none; position:relative;
  padding:clamp(15px,1.55vw,19px) var(--focus-pad);
  transition:background .4s var(--dsr-ease);
}
.focus-summary::-webkit-details-marker{ display:none; }
/* Hover was .022 alpha, set when the target was a 297px tile. Spread across
   a 1045px band it was invisible, and eight clickable rows in a list need to
   answer "am I on one" at a glance. The icon lifting to full opacity does
   most of the work; the wash only has to say WHICH row. */
.focus-tile:hover .focus-summary{ background:rgba(233,235,238,.05); }
.focus-tile:hover .focus-icon{ opacity:1; }
.focus-summary:focus-visible{ outline:2px solid var(--copper); outline-offset:-3px; }

/* The icon anchors the left edge of the row. At row scale it is the only
   thing that tells eight otherwise-identical rows apart at a glance, and it
   is the one element that still earns its place from the tile build. */
.focus-icon{ display:block; margin:0; color:var(--mist); opacity:.72; line-height:0; }
.focus-icon .assay{ width:var(--focus-icon); height:var(--focus-icon); }
/* Exactly one element inside each icon is copper: the thing being measured. */
.focus-icon .assay .assay-hot{ stroke:var(--copper); opacity:1; }

/* A display serif on a research label is the single biggest premium cue
   available here and it costs nothing. Every competitor sets this in a sans.
   Two steps smaller than at tile scale: a row is wide and short, so the
   headline has to sit level with a body line rather than tower over it. */
.focus-head{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(18px,1.45vw,20px); line-height:1.28; letter-spacing:.004em;
  color:var(--porcelain); text-wrap:balance;
}
.focus-line{
  display:block; margin:0;
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.62;
  letter-spacing:.004em; color:var(--dsr-ink-3);
}
/* The right rail: model over confidence, both flush right, so eight rows'
   worth of apparatus reads as one column and never crowds the headline.
   A COLUMN, not a wrap: wrapping put row 1 at 129px of chips against row
   4's 63px, and ragged right edges down the eight rows. */
.focus-chips{
  display:flex; flex-direction:column; align-items:flex-end;
  gap:5px; min-width:0; text-align:right;
}
/* The model is set as plain mono text rather than in a chip. Measured: the
   longest model string ("Cells / chick membrane / rat") needed 250px inside
   a border, which forced a 212px rail to wrap the chip onto two lines inside
   its own outline. Unboxed it wraps as text, which is what it is: a caption
   on the finding, not a rated value. The one chip left in the rail is the
   confidence badge, which has a meter and is the rated value. */
.focus-chips .chip-model{
  display:block; padding:0; border:0; border-radius:0;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.06em; text-transform:uppercase; line-height:1.42;
  color:rgba(233,235,238,.5); white-space:normal; text-wrap:balance;
}
.focus-chips .chip-model + .chip-model{ margin-top:-2px; color:rgba(233,235,238,.38); }
.focus-chips .chip-model:last-of-type{ margin-bottom:2px; }
/* Tightened to hold "Preclinical / meter / level" on ONE line inside the
   rail. At the tile scale's .1em tracking this chip measured 229px, which no
   sane row rail can carry beside a headline and a sentence. */
.focus-chips .chip-conf{ padding:5px 8px; gap:6px; letter-spacing:.06em; }
.focus-chips .chip-conf .chip-pre,
.focus-chips .chip-conf .chip-word{ letter-spacing:.06em; }

/* Three type registers, strictly separated. The mono chips are what make the
   grid read as instrumentation rather than as marketing. */
.chip{
  display:inline-flex; align-items:center; gap:7px;
  padding:5px 9px; border:1px solid var(--dsr-rule-soft); border-radius:2px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.1em; text-transform:uppercase; line-height:1.25;
  color:var(--dsr-ink-3); white-space:nowrap;
  transition:border-color .35s var(--dsr-ease);
}
.focus-tile:hover .chip{ border-color:var(--dsr-rule); }
/* Confidence is an ink-opacity step, never a colour: a green/amber/red ramp
   across these three levels would read as a quality verdict on the compound
   and would make STRONG look clinical. */
.chip-conf{ position:relative; color:rgba(233,235,238,.52); }
.chip-conf[data-conf="moderate"]{ color:rgba(233,235,238,.72); }
.chip-conf[data-conf="strong"]{ color:var(--dsr-ink); border-color:var(--dsr-rule); }
.chip-pre{ opacity:.62; letter-spacing:.1em; }
.chip-word{ letter-spacing:.1em; }

/* The legend travels with every chip. Pointer path: hover. Assistive path:
   aria-describedby, which points at this same node. Touch path: the open
   panel prints it as a full row, because there is no hover on a phone. */
.conf-pop{
  position:absolute; left:0; bottom:calc(100% + 9px); z-index:6;
  width:max-content; max-width:min(300px,72vw);
  padding:11px 13px; background:var(--dsr-surface-hi);
  border:1px solid var(--dsr-rule); border-radius:2px;
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.5;
  letter-spacing:.004em; text-transform:none; color:var(--dsr-ink-2);
  white-space:normal; opacity:0; visibility:hidden;
  transform:translateY(3px);
  transition:opacity .22s var(--dsr-ease), transform .22s var(--dsr-ease), visibility .22s;
}
.chip-conf:hover .conf-pop{ opacity:1; visibility:visible; transform:none; }
/* Alignment only, NOT an overflow fix. The suspicion was that a left-anchored
   300px popover on a flush-right chip would run off the section; measuring it
   refuted that at every width the row layout applies (1181: 1093 against a
   1116 section edge; 1440: 1244 against 1281, so 23 to 37px inside). What it
   DID do was hang 61px past the chip that owns it, in a rail whose whole
   point is a flush right edge. Anchored to the chip's right edge instead, the
   legend lines up with the rail. Re-anchored left at the stacked breakpoint
   below, where the chip is flush left. verify-focus-rows.mjs hovers the last
   row's chip at both ends of the ladder so a future re-anchor cannot push it
   off screen unseen. */
.focus-chips .conf-pop{ left:auto; right:0; }

/* The open/closed affordance is now the row's last cell rather than an
   absolutely-positioned mark, which is what let the tile's bottom padding
   drop from 54px (a reserve for it) to nothing. */
.focus-toggle{
  position:relative; right:auto; bottom:auto;
  width:13px; height:13px; justify-self:end;
}
.focus-toggle::before, .focus-toggle::after{
  content:""; position:absolute; background:var(--dsr-ink-3);
  transition:transform .3s var(--dsr-ease), background .3s var(--dsr-ease);
}
.focus-toggle::before{ left:0; top:6px; width:13px; height:1px; }
.focus-toggle::after{ left:6px; top:0; width:1px; height:13px; }
.focus-tile[open] .focus-toggle::after{ transform:scaleY(0); }
.focus-tile:hover .focus-toggle::before,
.focus-tile:hover .focus-toggle::after{ background:var(--copper); }

/* An open row keeps its own single column (the grid has only one), so the
   row simply grows and everything under it moves down: "expands in place",
   with no re-placement and nothing positioned. */
.focus-item:has(> .focus-tile[open]){ background:rgba(233,235,238,.045); }

/* THE READING PANEL.
   Set as prose, not as a data dump. Two measured columns rather than one
   full-bleed block: the record on the left at a real measure, the apparatus
   (confidence, source, how it was checked) in a narrow rail on the right.
   The left indent puts the panel under the headline, not under the icon, so
   the open state reads as the row continuing rather than as a new object. */
.focus-panel{
  display:grid;
  grid-template-columns:minmax(0,60ch) minmax(0,32ch);
  justify-content:start;
  column-gap:clamp(26px,3.2vw,54px); row-gap:clamp(20px,2.2vw,28px);
  margin:0; border-top:1px solid var(--dsr-rule-soft);
  padding:clamp(18px,1.9vw,24px) var(--focus-pad) clamp(26px,2.6vw,34px)
          calc(var(--focus-pad) + var(--focus-icon) + var(--focus-gap));
}
.focus-panel .mono-label{
  display:block; margin:0 0 8px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.14em; text-transform:uppercase; color:rgba(233,235,238,.46);
}
.focus-panel .study-area{
  margin:0 0 16px; font-family:var(--font-body); font-weight:500;
  font-size:var(--fs-body); line-height:1.45; color:var(--dsr-ink);
  max-width:52ch;
}
/* The verbatim finding is the main event and now runs first, at the column's
   own measure rather than at a 70ch cap that was set for a different column.
   The extra leading is deliberate: this is the one block on the page a reader
   is expected to read word by word rather than scan. */
.focus-panel .study-finding{
  max-width:none; margin:0; padding:clamp(16px,1.7vw,20px) clamp(18px,1.9vw,24px);
  font-size:var(--fs-meta); line-height:1.8;
}
/* Separated from the finding by a rule, not just by space, because CT's note
   was that the model, the citation and the check all ran together. */
.focus-panel .study-model{
  margin:18px 0 0; padding-top:16px;
  border-top:1px solid var(--dsr-rule-soft);
  font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.66; color:var(--dsr-ink-3);
}
.focus-panel .study-model-k{
  display:block; font-family:var(--font-mono); font-size:var(--fs-micro);
  letter-spacing:.14em; text-transform:uppercase;
  color:rgba(233,235,238,.46); margin-bottom:5px;
}
/* The apparatus rail. Three blocks that CT read as one run-on: the
   confidence definition, the citation, and how the citation was checked.
   Each keeps its own mono label and its own rule, and the rail sits at a
   32ch measure so none of them can stretch into a data dump. */
.focus-panel .study-cite{ margin-top:20px; padding-top:18px; }
.focus-panel .study-cite-text{ line-height:1.66; max-width:44ch; }
.focus-panel .vnote-fold{ margin-top:20px; }
.focus-panel .vnote-text{ line-height:1.72; max-width:44ch; }
.conf-inline{
  padding:14px 16px; background:rgba(233,235,238,.03);
  border-left:1px solid var(--dsr-rule); border-radius:2px; margin-bottom:18px;
}
.conf-inline-def{
  margin:0; font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.6; color:var(--dsr-ink-2);
}
.vnote-fold{ margin-top:16px; border-top:1px solid var(--dsr-rule-soft); }
.vnote-fold > summary{
  cursor:pointer; list-style:none; padding:12px 0 0;
  font-family:var(--font-mono); font-size:var(--fs-micro);
  letter-spacing:.1em; text-transform:uppercase; color:rgba(233,235,238,.52);
  transition:color .3s var(--dsr-ease);
}
.vnote-fold > summary::-webkit-details-marker{ display:none; }
.vnote-fold > summary:hover{ color:var(--pale); }
.vnote-fold .fold-body{ padding:10px 0 4px; }
.vnote-text{
  margin:0; font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.68; color:var(--dsr-ink-3); overflow-wrap:anywhere;
}

/* The mobile-only "show all eight" control, and the evidence-strength split
   line the design direction puts directly under the grid rather than in the
   band, where a fifth numeral would break the descent to zero. */
/* .focus-all-btn and .focus-all-toggle deleted 2026-07-28 with the "Show all
   8" control they styled; nothing is hidden at any breakpoint now. */
.focus-split{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 14px;
  margin:clamp(20px,2.1vw,28px) 0 0; padding-top:clamp(16px,1.8vw,22px);
  border-top:1px solid var(--dsr-rule-soft);
}
.focus-split-fig{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta);
  letter-spacing:.1em; text-transform:uppercase; color:var(--pale);
}
.focus-split-note{
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.55;
  color:var(--dsr-ink-3);
}

/* -------------------------------------------------------------------------
   4. REMOVED: "WHAT THIS PAGE WILL NOT TELL YOU"
   Section deleted from the template on CT's instruction, 2026-07-27, and its
   .nottell* rules deleted with it rather than left orphaned. Nothing replaced
   it. The dossier's evidence_summary, which this section used to carry, now
   renders once as .evtl-ev at the foot of the timeline below.
   ------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
   5. EVIDENCE TIMELINE
   Mars's Day 7 / 30 / 90 device with the axis moved off the body and onto
   the literature. Ordinal nodes on one rail, real years printed, ending on
   the year the record stops.
   ------------------------------------------------------------------------- */
.evtl{ margin:clamp(40px,4.4vw,64px) 0 0; padding:clamp(28px,3vw,40px) 0 0; border-top:1px solid var(--dsr-rule-soft); }
.evtl > .eyebrow{ display:block; margin:0 0 12px; color:var(--pale); }
.evtl-h{
  margin:0 0 clamp(28px,3vw,40px);
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(25px,2.9vw,38px); line-height:1.16;
  color:var(--porcelain); max-width:26ch;
}
.evtl-nodes{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(7,minmax(0,1fr));
  gap:clamp(10px,1.2vw,18px);
}
.evtl-node{ position:relative; padding-top:28px; min-width:0; }
/* The rail itself, drawn by the nodes so it can never be a different length
   from the row of nodes sitting on it. */
.evtl-node::before{
  content:""; position:absolute; left:0; right:calc(-1 * clamp(10px,1.2vw,18px));
  top:21px; height:1px; background:var(--dsr-rule-soft);
}
.evtl-node:last-child::before{ right:0; }
.evtl-year{
  display:block; margin:0 0 14px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-meta);
  letter-spacing:.06em; color:var(--pale); font-variant-numeric:tabular-nums;
}
.evtl-dot{
  position:absolute; left:0; top:17px; width:9px; height:9px;
  border-radius:50%; background:var(--dsr-sunken);
  box-shadow:inset 0 0 0 1px rgba(233,235,238,.42);
}
.evtl-line{
  margin:0; font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.58; color:var(--dsr-ink-3);
}
/* The terminal node. Copper, because "the record stops here" is the single
   most valuable sentence on this rail, and hollow-dashed so the end reads as
   an end rather than as one more entry. */
.evtl-stop .evtl-year{ color:var(--copper); }
.evtl-stop .evtl-dot{ box-shadow:inset 0 0 0 1px var(--copper); background:var(--copper); }
.evtl-stop .evtl-line{ color:var(--dsr-ink-2); }
.evtl-cap{
  margin:clamp(20px,2.2vw,28px) 0 0; padding-top:16px;
  border-top:1px solid var(--dsr-rule-soft);
  font-family:var(--font-mono); font-size:var(--fs-micro); line-height:1.7;
  letter-spacing:.02em; color:var(--mist-dim);
}
/* The page's one evidence note, at the informational floor. It inherited
   .nottell-ev's type when section 4 was deleted; what it deliberately does not
   inherit is that section's copper rule, display heading and eyebrow. */
.evtl-ev{
  margin:10px 0 0; font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.72; color:var(--dsr-ink-3);
}

/* -------------------------------------------------------------------------
   7. WHAT IS IN THE VIAL
   The wrapper that gathers the material facts. Its children (.spec-block,
   .research-block, .dossier-identity, .dossier-storage) keep their own
   styling; this only supplies the section head and the interval.
   ------------------------------------------------------------------------- */
.vialsec{ margin:clamp(40px,4.4vw,64px) 0 0; padding:clamp(28px,3vw,40px) 0 0; border-top:1px solid var(--dsr-rule-soft); }
.vialsec > .eyebrow{ display:block; margin:0 0 12px; color:var(--pale); }
.vialsec-h{
  margin:0 0 clamp(22px,2.4vw,32px);
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(25px,2.9vw,38px); line-height:1.16; color:var(--porcelain);
}
.vialsec .spec-block{ margin-top:0; }

/* -------------------------------------------------------------------------
   8. HOW WE PROVE A LOT
   The one trust artifact no consumer-wellness reference page can match, and
   it was a link in a row. Photograph left, document story right.
   ------------------------------------------------------------------------- */
.certsec{
  margin:clamp(40px,4.4vw,64px) 0 0; padding:clamp(28px,3vw,40px) 0 0;
  border-top:1px solid var(--dsr-rule-soft);
  display:grid; grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(24px,3vw,52px); align-items:start;
}


/* -------------------------------------------------------------------------
   10. CO-MARKETED COMPOUNDS
   ROOT-CAUSE FIX (2026-07-27): .comarket-name and .badge-flag had NO rules
   anywhere in this stylesheet. The h3 fell through to the UA default (bold,
   inherited body colour) and the two status chips rendered as unstyled inline
   spans, so the whole section read as near-invisible dark-on-dark text. Both
   are now on the same system as every other name and chip on the page, and
   the honest half of the relationship (the "Not co-studied" state) is the
   one given the copper rule, because that is the half a shopper is least
   likely to expect a seller to print.
   ------------------------------------------------------------------------- */
.comarket{ padding:clamp(18px,2vw,26px) 0 0; }
.comarket + .comarket{ margin-top:clamp(16px,1.8vw,22px); border-top:1px solid var(--dsr-rule-soft); }
.comarket-head{
  display:flex; flex-wrap:wrap; align-items:baseline; gap:10px 16px;
  margin-bottom:12px;
}
.comarket-name{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(19px,1.7vw,24px); line-height:1.24; letter-spacing:.004em;
  color:var(--porcelain);
}
.comarket-name a{
  text-decoration:none; color:inherit;
  border-bottom:1px solid rgba(233,235,238,.24);
  transition:border-color .3s var(--dsr-ease), color .3s var(--dsr-ease);
}
.comarket-name a:hover{ color:var(--pale); border-bottom-color:var(--copper); }
.comarket-flags{ display:flex; flex-wrap:wrap; gap:6px; }
.badge-flag{
  display:inline-flex; align-items:center;
  padding:5px 9px; border:1px dashed rgba(233,235,238,.26); border-radius:2px;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.1em; text-transform:uppercase; line-height:1.25;
  color:rgba(233,235,238,.52); white-space:nowrap;
}
.badge-flag.is-on{
  border-style:solid; border-color:var(--dsr-rule);
  color:var(--dsr-ink-2);
}
.comarket-body{
  margin:0 0 10px; font-family:var(--font-body); font-size:var(--fs-meta);
  line-height:1.72; color:var(--dsr-ink-3); max-width:78ch;
}
.comarket-body:last-child{ margin-bottom:0; }

/* -------------------------------------------------------------------------
   11. JOURNAL PLATES
   The authority-by-row-of-portraits rhythm with documents instead of faces.
   Typographic plates, never scraped logos: better looking, no trademark
   exposure, and every plate links to the paper it stands for.
   ------------------------------------------------------------------------- */
.plates{
  list-style:none; margin:0 0 clamp(22px,2.4vw,30px); padding:0;
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(8px,1vw,12px);
}
.plate a{
  display:flex; align-items:center; justify-content:center; height:100%;
  min-height:72px; padding:14px 12px; text-align:center; text-decoration:none;
  border:1px solid var(--dsr-rule-soft); border-radius:2px;
  font-family:var(--font-display); font-weight:400; font-size:var(--fs-meta);
  line-height:1.35; letter-spacing:.01em; color:var(--dsr-ink-2);
  transition:border-color .35s var(--dsr-ease), color .35s var(--dsr-ease);
}
.plate a:hover{ border-color:var(--copper); color:var(--porcelain); }
.refs-split{
  margin:clamp(16px,1.8vw,22px) 0 6px;
  font-family:var(--font-mono); font-size:var(--fs-micro); line-height:1.7;
  letter-spacing:.02em; color:var(--mist-dim);
}

/* -------------------------------------------------------------------------
   12. STICKY BUY BAR, REBUILT 2026-07-28 (store owner: "this doesn't
   fundamentally function"). Never taller than 72px.

   1. TOP, NOT BOTTOM. "I don't like this being at the bottom... Let's put
      it at the top." position:fixed;top:0, sliding down into view
      (translateY(-101% -> none)) instead of up from the bottom. The site
      header (.site-header) is position:absolute, not fixed or sticky, so
      it scrolls away with the page well before this bar's own trigger
      (the hero leaving the viewport) ever fires; the two are never on
      screen at the same time, so there is no real double-border or
      z-index fight to resolve, only the hidden-by-default transform to
      get right so nothing flashes over the header on first paint. z-index
      kept above the header's own 30 in case that ever changes.
   2. LAYOUT grouped in two: .buybar-info (name + spec line, stacked
      tightly so they read as one unit) on the left, .buybar-shop (price +
      quantity + Add) on the right, justify-content:space-between between
      them. The single flex row this replaced let .buybar-ruo grow to fill
      all the leftover space (flex:1 1 auto), which is what actually made
      the bar "read as too spread out": a huge gap between the name and
      the purchase controls with no group boundary. The outer
      .buybar-inner is still the page's own .container, so the name starts
      on the same left text edge as the H1 above it, same gutter, no
      separate offset.
   ------------------------------------------------------------------------- */
.buybar{
  position:fixed; left:0; right:0; top:0; z-index:40;
  max-height:72px; background:rgba(19,21,25,.94);
  backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--dsr-rule);
  transform:translateY(-101%); transition:transform .42s var(--dsr-ease);
}
.buybar.is-up{ transform:none; }
.buybar-inner{
  display:flex; align-items:center; justify-content:space-between;
  gap:clamp(12px,1.6vw,26px);
  height:72px; padding-top:0; padding-bottom:0;
}
/* .buybar-info: ROW, not column (2026-07-28, superseding the same day's
   earlier stacked-spec-line draft). Sitewide correction: no second line
   anywhere, name and dose inline instead. Same .inline-dose class the
   hero, the sku card and the cart/checkout line item all share. */
.buybar-info{
  display:flex; align-items:baseline; gap:10px;
  min-width:0;
}
/* SIZE, +21% (2026-07-28, store owner: "reads as small grey furniture
   next to a large price"). Was 19px; now clearly the loudest text on the
   left side of the bar, ahead of the inline dose and (at 18px) the price,
   matching the name-leads-price hierarchy the main buy box already has
   (its own H1 dwarfs its 24px price). */
.buybar-name{
  font-family:var(--font-display); font-weight:400; font-size:23px;
  line-height:1.2; color:var(--porcelain);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.buybar-shop{
  display:flex; align-items:center; gap:clamp(12px,1.6vw,26px);
  flex:0 0 auto;
}
.buybar-price{
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-emphasis);
  color:var(--dsr-ink); white-space:nowrap; font-variant-numeric:tabular-nums;
}
/* del{display:none} REMOVED 2026-07-28: it predates the live tier price and
   was hiding the ORIGINAL price entirely whenever the bar's starting HTML
   already carried a native WooCommerce <del>/<ins> pair (the cart-aware
   case: a shopper who already has units of this SKU in their cart sees
   the discounted price on load, before this bar's own JS ever touches
   quantity 1). That is exactly the moment "show the slashed original"
   needs to hold, not the one moment it was being suppressed.
   COLOUR, corrected: struck = --mist-dim (grey), matching
   .product-price-orig above and the drawer's own .kc-price-orig, not
   --copper as an earlier pass here had it. ins{color:var(--pale)} is NEW,
   not a colour swap: this native <ins> (the live, payable price in the
   SAME cart-aware case) had NO colour rule at all before this, so it was
   inheriting .product-price's own base text colour and rendering the
   live price uncoloured while the JS-driven .product-price-now class sat
   right next to it correctly pale. That gap, not a wrong colour, is what
   made the live price read as plain text/white in that one code path;
   this closes it so a struck/live pair reads the same whether the server
   rendered it (page load, cart-aware) or this page's own script did
   (typing or clicking the stepper). */
.product-price del,
.buybar-price del{ color:var(--mist-dim); text-decoration:line-through; }
.product-price ins,
.buybar-price ins{ color:var(--pale); text-decoration:none; }
.buybar-form{ display:flex; align-items:center; gap:10px; margin:0; }
.buybar-form .btn{ white-space:nowrap; }

/* -------------------------------------------------------------------------
   MOTION for the rebuilt sections. Everything here is opt-in from script
   (.js-reveal) and everything is disabled under prefers-reduced-motion.
   ------------------------------------------------------------------------- */
/* Icon stroke-draw on first reveal: the highest premium-per-byte gesture on
   the page. pathLength is not set, so the dash values are deliberately
   generous and the draw is clipped by the 700ms window rather than measured
   per path, which keeps this to two declarations instead of a script. */
.js-reveal .focus-item .assay *{
  stroke-dasharray:160; stroke-dashoffset:160;
}
.js-reveal .focus-item.is-in .assay *{
  stroke-dashoffset:0;
  transition:stroke-dashoffset .7s var(--dsr-ease);
  transition-delay:calc(var(--reveal-delay,0ms) + 120ms);
}
/* The rail draws left to right as it enters. */
.js-reveal .evtl-node::before{ transform:scaleX(0); transform-origin:left center; }
.js-reveal .evtl-node.is-in::before{
  transform:scaleX(1); transition:transform .5s var(--dsr-ease);
  transition-delay:var(--reveal-delay,0ms);
}
@media (prefers-reduced-motion: reduce){
  .js-reveal .focus-item .assay *,
  .js-reveal .focus-item.is-in .assay *{ stroke-dasharray:none; stroke-dashoffset:0; transition:none; }
  .js-reveal .evtl-node::before,
  .js-reveal .evtl-node.is-in::before{ transform:none; transition:none; }
  .buybar{ transition:none; }
  .conf-pop{ transition:none; }
  .chip, .plate a, .focus-summary, .focus-toggle::before, .focus-toggle::after{ transition:none; }
}
@media print{
  .buybar{ display:none; }
  .js-reveal .focus-item .assay *{ stroke-dasharray:none; stroke-dashoffset:0; }
  .js-reveal .evtl-node::before{ transform:none; }
}

/* -------------------------------------------------------------------------
   PDP REBUILD: NARROW VIEWPORTS
   The research ROWS keep one row per area at every width; only the row's own
   five columns collapse. Below 860 the icon keeps the left edge, the headline
   sits beside it, and the finding and the chips each take a full-width line
   underneath, so the chips can never crowd the headline. Nothing is hidden on
   any breakpoint: the "Show all 8" control went with the tiles, because a row
   is about a third of a tile's height and all eight now cost roughly what the
   first four used to.
   ------------------------------------------------------------------------- */
/* 1180, and it is a MEASURED breakpoint, not a device one: the five-column
   row needs 262px of chip rail plus an icon, a toggle and four gaps before
   any prose gets a pixel. Below 1181 that leaves the finding under a
   300px column (a 40-character measure), which is the cramped-column
   problem this rebuild exists to remove, so the row stacks instead. One
   breakpoint governs the whole section, summary and panel together. */
@media (max-width:1180px){
  .focus-cols{ display:none; }
  .focus-summary{
    grid-template-columns:var(--focus-icon) minmax(0,1fr) 13px;
    grid-template-areas:
      "icon  head   toggle"
      "line  line   line"
      "chips chips  chips";
    align-items:start; row-gap:10px; column-gap:clamp(12px,3.4vw,16px);
    padding:16px var(--focus-pad);
  }
  .focus-icon{ grid-area:icon; margin-top:2px; }
  .focus-head{ grid-area:head; }
  /* Stacked, the finding has the row's whole width, which at 1180 is a
     120-character line. Capped to a real measure; the cap does not bind on
     a phone, where the viewport is already the constraint. */
  .focus-line{ grid-area:line; max-width:72ch; }
  /* The rail has the row's full width here, so it runs horizontally and
     left-aligned: a right-flush column of two items under a left-flush
     headline reads as two unrelated blocks on a phone. */
  .focus-chips{
    grid-area:chips; flex-direction:row; flex-wrap:wrap;
    align-items:center; justify-content:flex-start; gap:5px 8px; text-align:left;
  }
  /* A short model ("Rat") sits on the same line as the confidence chip. A
     SECOND model line (only row 1 has one) takes a line of its own: run
     inline it needs a separator, and a separator inside a string that
     already reads "cells / chick membrane / rat" is one slash too many. */
  .focus-chips .chip-model{ display:block; }
  .focus-chips .chip-model + .chip-model{ margin-top:0; flex-basis:100%; }
  /* Chip is flush left here, so the legend opens rightward again. */
  .focus-chips .conf-pop{ left:0; right:auto; }
  .focus-toggle{ grid-area:toggle; margin-top:6px; }
  /* The panel's apparatus rail cannot hold 32ch beside a 60ch record here,
     so it stacks under it; both keep the same indent and the same measure. */
  .focus-panel{
    grid-template-columns:minmax(0,60ch); row-gap:24px;
    padding-left:var(--focus-pad); padding-right:var(--focus-pad);
  }
}
@media (max-width:1100px){
  .plates{ grid-template-columns:repeat(3,minmax(0,1fr)); }
  .evtl-nodes{ grid-template-columns:repeat(4,minmax(0,1fr)); row-gap:30px; }
  .evtl-node:nth-child(4n)::before{ right:0; }
  .certsec{ grid-template-columns:minmax(0,1fr); gap:26px; }
  .certsec-media{ max-width:520px; }
}
@media (max-width:900px){
  /* .statband-inner 2-column override and its nth-child(odd) border
     compensation REMOVED 2026-07-28: with 3 stats instead of 4, the base
     3-column rule already carries cleanly through this breakpoint (see the
     STAT BAND block comment above), so this tier needs no override at all. */
  .nottell-list{ grid-template-columns:minmax(0,1fr); gap:16px; }
}
@media (max-width:700px){
  .focusgrid{ padding:26px 16px 24px; }
  .focusgrid-h{ max-width:none; }
  .nottell-h, .evtl-h, .vialsec-h, .certsec-h{ max-width:none; }
  /* The rail becomes a vertical spine: seven ordinal columns cannot carry a
     sentence each at 390 wide. Same device, rotated. */
  .evtl-nodes{ grid-template-columns:minmax(0,1fr); row-gap:0; }
  .evtl-node{ padding:0 0 22px 26px; }
  .evtl-node::before{
    left:4px; right:auto; top:9px; bottom:0; width:1px; height:auto;
    transform-origin:top center;
  }
  .evtl-node:last-child::before{ bottom:auto; height:12px; }
  .evtl-dot{ left:0; top:5px; }
  .evtl-year{ margin-bottom:7px; }
  .js-reveal .evtl-node::before{ transform:scaleY(0); }
  .js-reveal .evtl-node.is-in::before{ transform:scaleY(1); }
  .plates{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .comarket-head{ gap:8px 12px; }
}
@media (max-width:560px){
  .statband{ padding:26px 0; }
  .statband-inner{ gap:14px; row-gap:22px; }
  .statband-fig{ font-size:clamp(40px,13vw,58px); }
  .statband-label{ max-width:none; }
  
  /* The bar keeps the control and the price; the compound name and the
     spec line would each be one truncated word at this width, and a
     truncated line is worse than none. The full name and spec are in the
     buy box above; RUO is there too and in the all-caps band below. */
  .buybar-info{ display:none; }
  .buybar-inner{ gap:12px; }
  .buybar-form{ flex:0 1 auto; }
}
/* Long journal names, DOIs and PubMed URLs are the one real overflow risk in
   these new blocks: they are unbreakable tokens inside fixed grid columns. */
.plate a, .focus-panel .study-model, .certsec-why{ overflow-wrap:anywhere; }

/* =========================================================================
   PDP REBUILD: VERTICAL RHYTHM TIGHTENING (2026-07-27, round 2)

   The rebuild added four sections the previous page did not have (stat band
   231, refusals 515, timeline 596, certificate 800 = 2,142px at 1440), so
   section-interval parity alone would have put the page ~2,100px ABOVE the
   7,993px it replaced. This pass takes the interval back, caps the two
   blocks that were sized by their own content rather than by the grid, and
   is paired with two structural collapses in the template (storage rows and
   the open-questions answers, both behind disclosures, neither deleted).

   Everything here is spacing, measure and one aspect-ratio. No type size
   drops below the theme's 13px/15px floors and no content is removed.
   ========================================================================= */

/* Section interval: 104px between the new sections was set before the page
   had six of them. 68px still reads as a document with air. */
.focusgrid{ margin-top:clamp(32px,3.2vw,44px); }
.nottell, .evtl, .vialsec, .certsec{
  margin-top:clamp(30px,3vw,40px);
  padding-top:clamp(24px,2.4vw,30px);
}
.focusgrid{ padding:clamp(26px,2.8vw,36px) clamp(20px,2.6vw,38px) clamp(22px,2.4vw,30px); }
.focusgrid-h{ margin-bottom:clamp(22px,2.3vw,28px); }
.nottell-h, .evtl-h, .vialsec-h{ margin-bottom:clamp(18px,1.9vw,24px); }

/* The dossier sections inside and after the material block. */
.dossier{ padding:clamp(26px,2.3vw,34px) 0; }
.vialsec .spec-block{ margin-bottom:clamp(24px,2.4vw,32px); }
.vialsec .research-block{ margin-bottom:clamp(18px,1.9vw,24px); }

/* The tile-scale rhythm rules that used to live here (summary padding, icon
   size and every child's bottom margin) are gone with the tiles: a row sizes
   from its own five-column track and its children carry no bottom margin at
   all. See "3. RESEARCH ROWS" above, which is now the single home for that
   geometry rather than a base rule plus four later corrections. */
.focus-split{ margin-top:clamp(16px,1.8vw,22px); padding-top:clamp(14px,1.5vw,18px); }

/* The refusals. The evidence summary is the dossier's own paragraph and is
   not cut; it is given a full measure instead of a 78ch column, which is
   three fewer wrapped lines at 1440. */
.nottell-list{ margin-bottom:clamp(18px,2vw,24px); }
.nottell-why{ margin-bottom:11px; }
.nottell-ev{ max-width:none; }

/* The timeline. */
.evtl-h{ margin-bottom:clamp(22px,2.4vw,30px); }
.evtl-cap{ margin-top:clamp(16px,1.8vw,22px); padding-top:13px; }

/* The certificate. The photograph is capped by ratio so the section is sized
   by its argument and not by whatever crop the asset happens to be, and the
   rows lose the stacked value/reason pair for a single line each. */
.certsec{ align-items:center; }


/* Co-marketed: a full measure rather than a 78ch column, same three
   paragraphs. */
.comarket{ padding-top:clamp(14px,1.6vw,20px); }
.comarket-head{ margin-bottom:10px; }
.comarket-body{ margin-bottom:8px; max-width:none; }

/* Journal plates: eleven plates over four columns is three rows, two of them
   half empty. Six columns is two full rows and reads more like a plate row
   and less like a grid of cards. */
.plates{ grid-template-columns:repeat(6,minmax(0,1fr)); }
.plate a{ min-height:62px; padding:11px 9px; font-size:var(--fs-micro); }


/* The storage rows now sit inside a disclosure, so the dl loses the interval
   the section used to give it. */
.storage-fold .storage-rows{ margin-top:0; }

@media (max-width:700px){
  .plates{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  
}

/* ---- rhythm round 3: the last two blocks still sized by their own content */
/* The certificate rows were a stacked value-over-reason pair at 96px each.
   One line each, same words, same three rows. */
.certsec-row{ grid-template-columns:minmax(120px,.36fr) minmax(0,1fr); padding:9px 0; gap:4px 18px; }


/* The material section's two collapsed sub-blocks are a heading over a 60px
   control; a full dossier interval above and below each is dead space. */
.vialsec .dossier{ padding:clamp(18px,1.7vw,24px) 0; }
.vialsec .spec-block{ margin-bottom:clamp(20px,2vw,26px); }
.vialsec .research-block{ padding:clamp(20px,2vw,24px) 0; margin-bottom:clamp(14px,1.5vw,18px); }
.vialsec .spec-table td{ padding-top:9px; padding-bottom:9px; }

/* The timeline caption and rail. */
.evtl-h{ margin-bottom:clamp(18px,2vw,24px); }
.evtl-cap{ margin-top:clamp(14px,1.5vw,18px); padding-top:11px; }
.evtl-line{ line-height:1.52; }

/* ---- rhythm round 4: the section intervals themselves, and the two
   sub-headings inside the material block, which are set at full section
   scale for what is now a heading over one closed control. */
.focusgrid{ margin-top:clamp(26px,2.6vw,32px); }
.nottell, .evtl, .vialsec, .certsec{
  margin-top:clamp(24px,2.4vw,30px); padding-top:clamp(20px,2vw,26px);
}
.statband{ margin-top:clamp(24px,2.6vw,34px); padding:clamp(26px,3vw,40px) 0; }
.vialsec .dossier .dossier-h2{ font-size:clamp(20px,1.8vw,25px); max-width:34ch; }
.vialsec .dossier > .eyebrow{ margin-bottom:8px; }

/* -------------------------------------------------------------------------
   FIX (2026-07-27, measured): grid min-content blowout in the focus rows.

   The model chips carried white-space:nowrap, so the longest of them ("Cells
   / chick membrane / rat") set a min-content floor wider than its track, the
   column grew to meet it, and every child sharing that column was dragged
   past the row's right edge. Measured at the time: right edge 459 vs 496.

   Fixed at the cause, not at the two strings that happen to trip it today:
   every track in --focus-cols is a minmax(0,...) so it can never exceed
   itself, and a chip is allowed to wrap. STILL LOAD-BEARING after the 2026-
   07-28 row rebuild: the chip rail is 186 to 238px, which is narrower than
   the longest model string wants.
   ------------------------------------------------------------------------- */
.focus-chips{ min-width:0; }
.chip{ white-space:normal; }
.chip .conf-meter{ flex:0 0 auto; }

/* -------------------------------------------------------------------------
   FIX (2026-07-27, measured): the sticky bar's form bled past the viewport
   at 360 and 390 (body overflow-x:hidden was masking it, so the page did not
   scroll sideways but the Add to cart button was partly off-screen). The
   quantity stepper is the widest thing in the bar and the least necessary
   one there: quantity is settable in the buy box above and in the cart
   drawer after, so the bar keeps the price and the button.
   ------------------------------------------------------------------------- */
@media (max-width:560px){
  .buybar-form .qty-stepper{ display:none; }
  /* .buybar-shop is the only visible child of .buybar-inner at this width
     (.buybar-info is hidden just above); it needs to fill the row itself
     now, or justify-content:space-between on .buybar-inner has nothing to
     space a single child against and the price+button stack collapses to
     the left edge with dead space on the right. */
  .buybar-shop{ flex:1 1 auto; justify-content:space-between; }
  .buybar-form{ flex:0 1 auto; justify-content:flex-end; }
  .buybar-form .btn{ min-width:0; }
}
.comarket-h{ margin-bottom:0; max-width:30ch; }

/* The chip is inline-flex, so white-space:normal alone cannot break it: its
   three children (the "Preclinical" prefix, the meter, the level word) are
   flex items, and without flex-wrap their combined width is the chip's
   min-content floor. Measured at 1440: the preliminary chip needed 243px in
   a 213px tile. */
.chip{ flex-wrap:wrap; row-gap:3px; }

/* =========================================================================
   BLOCKER, 2026-07-27 CT review: an expanded tile painted OVER its
   neighbours instead of pushing the grid down. Kept here as the regression
   this section must never re-earn; verify-focus-rows.mjs asserts it on every
   run, at 1440 and 390, one row at a time AND with all eight forced open.

   ROOT CAUSE was three compounding mistakes, all structural to a COLUMN
   grid: a height:100% pin on the <details> (so an opening panel overflowed a
   box that could not grow), a negative margin-top compensator on the panel
   (overlap by construction), and grid-column:1/-1 on the open item (which
   re-placed a tile out of its own row).

   ALL THREE ARE GONE WITH THE TILES (2026-07-28). The rows are a
   single-column grid, so nothing is pinned to a neighbour's height, nothing
   is negative, and there is no second column to be re-placed into: an open
   row is auto-height and the rows below it simply move down.
   ========================================================================= */
/* The hover legend is redundant the moment the panel is open, because the
   panel prints the same definition as a row. Suppressing it there is also
   the fix for the tooltip CT saw layered over the panel prose. */
.focus-tile[open] .chip-conf:hover .conf-pop{ opacity:0; visibility:hidden; }

/* FIX (2026-07-27, CT review): content flush against the left edge under the
   RESEARCH CONTEXT heading. Self-inflicted: the round-3 rhythm pass set
   `.vialsec .research-block{ padding: Xpx 0 }` to reclaim vertical space and
   took the block's 32px horizontal padding with it, while the block still
   draws its own border and background. Vertical tightening only; the
   horizontal padding is restored and is now written explicitly so the next
   vertical pass cannot silently eat it again. */
.vialsec .research-block{ padding:clamp(20px,2vw,24px) 32px; }
@media (max-width:600px){ .vialsec .research-block{ padding:20px 18px; } }

/* =========================================================================
   CT REVIEW ROUND (2026-07-27): certificate band, compressed unknowns,
   frequently-purchased product row, obviously-clickable citations.
   ========================================================================= */

/* 8. PER-LOT PROOF: DELETED 2026-07-28 (store owner: "remove that whole
   thing"). .certband / .certband-line removed with the markup; nothing else
   referenced either selector. */

/* 10. FREQUENTLY PAIRED WITH (was FREQUENTLY PURCHASED). The shop's own
   .sku-grid and .sku-card, unmodified: this row and "Also recommended"
   below both call kairo_render_sku_card() so the card component can never
   drift from the shop's own. */
.freqbuy-h{ margin-bottom:10px; }
.freqbuy-note{
  margin:0 0 clamp(16px,1.8vw,22px);
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.62;
  color:var(--dsr-ink-3); max-width:80ch;
}
.freqbuy-lit{ margin-top:clamp(16px,1.8vw,22px); border-top:1px solid var(--dsr-rule-soft); }
.freqbuy-lit > summary{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  cursor:pointer; list-style:none; padding:14px 0;
  font-family:var(--font-mono); font-weight:500; font-size:var(--fs-micro);
  letter-spacing:.12em; text-transform:uppercase; color:rgba(233,235,238,.52);
  transition:color .3s var(--dsr-ease);
}
.freqbuy-lit > summary::-webkit-details-marker{ display:none; }
.freqbuy-lit > summary:hover{ color:var(--pale); }
.freqbuy-lit > summary:focus-visible{ outline:2px solid var(--copper); outline-offset:3px; }
.freqbuy-lit .fold-body{ padding:4px 0 0; }
/* BOTH "Frequently paired with" and "Also recommended", FOURTH pass
   (2026-07-28). History, because this property has now flipped twice:
     1. A 3-column override on both rows, to avoid a bare fourth track
        since each always holds exactly 3 cards. Reverted because it
        rendered cards at 358px at 1440, wider than the shop's own 262px
        card, contradicting "the shop-page card size" read literally.
     2. A compact variant on "Frequently paired with" ONLY. Reverted
        outright: "just copy the same format of what it looks like down
        here under the 'Also Recommended'... Same size, it all looks fine
        here."
     3. Reverted to the shop's own plain, unmodified 4-column .sku-grid on
        both sections (matching #2's instruction literally), which left a
        bare fourth column empty in each row at the desktop tier.
   THIS PASS: the empty-fourth-column reading of "same size" was itself
   corrected. The owner's actual intent, stated directly after seeing #3
   measured (both grids 159 to 1281 / 1122 wide, but only 3 of 4 columns
   holding content, 287px left empty): give these two PDP sections their
   OWN 3-column track so the row fills, landing cards near 355px, NOT the
   shop's 4-column track applied to a 3-item catalogue slice. "Same size"
   always meant "same size AS EACH OTHER" (the equality check below), not
   "identical CSS to the shop grid" - #2's own screenshot evidence (358px
   cards) is what #1 was chasing all along.
   SCOPE: .freqbuy and .related-block ONLY. /shop/ and the front page keep
   their native 4-column .sku-grid unmodified, since they hold a full
   14-SKU catalogue, not a fixed 3-item slice; do not widen this selector
   to affect them. */
.freqbuy .sku-grid, .related-block .sku-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); }
/* Mobile: 2 columns, not a 3-up fluid track. This selector's 2-class
   specificity beats the shop's own ".sku-grid" breakpoint rules at every
   width unless matched here, so without this override 3 columns would
   persist all the way to 360px: three fluid 1fr tracks at ~105-113px
   each, far narrower than this full-size (non-compact) card was ever
   designed for. 2 columns at this breakpoint matches the shop grid's OWN
   proven phone-width card size exactly (measured ~162px at 390, the
   site's native mobile card), rather than inventing a new narrow variant
   the way the reverted "40% smaller" pass did. */
@media (max-width:767px){
  .freqbuy .sku-grid, .related-block .sku-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}

/* 11. CITATIONS THAT LOOK LIKE CITATIONS. CT could not tell the superscript
   reference marks were links. They now carry a real underline and the copper
   hover every other link on this page uses, and the plate row above them
   says out loud what they do. */
.plates-prompt{
  margin:0 0 12px; font-family:var(--font-mono); font-size:var(--fs-micro);
  letter-spacing:.1em; text-transform:uppercase; color:var(--pale);
}
.ref-anchor a{
  text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:rgba(184,106,60,.7); text-decoration-thickness:1px;
  color:var(--pale);
  transition:color .3s var(--dsr-ease), text-decoration-color .3s var(--dsr-ease);
}
.ref-anchor a:hover{ color:var(--porcelain); text-decoration-color:var(--copper); }
.cite-link{
  text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:rgba(184,106,60,.55);
  transition:color .3s var(--dsr-ease), text-decoration-color .3s var(--dsr-ease);
}
.cite-link:hover{ color:var(--porcelain); text-decoration-color:var(--copper); }
/* The "one step smaller, cropped media, hidden description" treatment this
   comment used to describe is GONE (store owner review, 2026-07-28, flagged
   twice: the crop clipped the vial and the description never showed at
   all). A LATER same-day compact variant (fixed-width card, object-fit:
   contain media, "roughly 40% smaller") was also tried and fully reverted;
   see the "BOTH 'Frequently paired with' and 'Also recommended', FOURTH
   pass" comment above ("6. FREQUENTLY PURCHASED / FREQUENTLY PAIRED WITH")
   for the full same-day history. Both sections now call the shop card
   partial completely unmodified (the CARD component itself); the two
   sections' shared GRID gets its own 3-column scoping so a fixed 3-item
   row fills instead of leaving the shop's 4th column empty. */

/* =========================================================================
   PLAIN-VOICE PASS (2026-07-27). CT: "let the page speak plainly ... write
   like a knowledgeable person explaining this to a friend who has never heard
   of it." Type only, no new components; every rule here exists because a
   block's copy changed length or job.
   ========================================================================= */

/* The research section now opens with a short headline over one real
   paragraph, instead of one long headline doing both jobs. Placed after the
   rhythm rounds above so it wins the h2's own bottom margin. */
.focusgrid-h{ margin-bottom:12px; }
.focusgrid-sub{
  margin:0 0 clamp(22px,2.3vw,28px);
  font-family:var(--font-body); font-size:var(--fs-body); line-height:1.62;
  color:var(--dsr-ink-2); max-width:66ch;
}

/* The cross-sell intro is 6 words now, not a 2-sentence guard paragraph, so
   it reads as a caption on the row rather than as a wall above it. */
.freqbuy-note{
  font-family:var(--font-mono); font-size:var(--fs-micro);
  letter-spacing:.06em; text-transform:uppercase; color:var(--mist-dim);
  margin-bottom:clamp(14px,1.5vw,18px); max-width:none;
}

/* =========================================================================
   PDP VISUAL AMBITION PASS (2026-07-28)

   CT, on the page this replaces: "I gave you examples on design, and it just
   looks so boring compared to all the examples I gave you." Measured against
   Onnit / Mars Men / Gruns / IM8 at 1440, he was right, and the diagnosis was
   mechanical rather than a matter of taste:

     - ten sections, three background values between them, zero background
       images: 6,674px of one uninterrupted dark panel;
     - one photograph in the whole body (533x666), and six thumbnails;
     - a type ladder of 89 / 44 / 38 / 38 / 38 / 38 / 38 / 25 / 20: one big
       moment, then a five-times-repeated plateau, so no section ever gets to
       BE the thing you look at;
     - copper only ever as 13px eyebrow text, a hairline, and one button fill.

   The references do not beat this page on restraint, they beat it on RHYTHM:
   Onnit changes its ground colour eight times, IM8 alternates ivory / oxblood
   / photographic black and prints 95% at ~60px, Mars Men keeps a dark page but
   never lets two sections share a texture. So this pass adds no copy and takes
   nothing away. It gives four blocks genuinely different treatments and lets
   type get big twice:

     1. STAT BAND     full-bleed metal plate, numerals to 158px
     2. BREATH        full-bleed still life between the two longest reads
     3. THE RECORD    the one light section: ink on porcelain, bleeding
     4. THE MATERIAL  full-bleed labelled still life under a 96px heading
     5. RESEARCH      an editorial two-column spread, not another stacked card

   Contrast is measured, never assumed (values in each block). Type only ever
   goes up from the 15px informational / 13px micro floors. Every ground image
   is decoration on an aria-hidden node, so the accessibility tree is unchanged.
   ========================================================================= */

/* The bleed. `.below-fold` is a max-width:1280px container with a 5.5vw
   gutter, so a child escapes it with the classic centred-negative-margin
   pair, which resolves to exactly 0 at the viewport edge in both regimes
   (viewport wider than the container and narrower than it: worked through in
   the commit note). body already carries overflow-x:hidden, so 100vw can
   never introduce a horizontal scrollbar. */
.bleedfig,
.evtl-light,
.vialsec-band,
.backglow{
  margin-inline:calc(50% - 50vw);
  width:100vw; max-width:100vw;
}

/* -------------------------------------------------------------------------
   1. STAT BAND -> the page's first big moment
   Was: 89px numerals on #131519, which is 2 values away from the page
   background, inside 46px of padding. Now: a metal plate at viewport width,
   numerals at up to 158px, and the four labels lifted off the floor.
   Contrast on the plate, worst case (text landing on the copper light streak
   under the 0.74 slate scrim): porcelain 10.4:1. AA needs 4.5:1.
   ------------------------------------------------------------------------- */
.statband{
  position:relative; isolation:isolate; overflow:hidden;
  margin:clamp(34px,4vw,58px) 0 0;
  padding:clamp(52px,6vw,88px) 0 clamp(44px,4.8vw,68px);
  background:#101216;
  border-top:1px solid var(--dsr-rule);
  border-bottom:1px solid var(--dsr-rule);
}
/* Framed BELOW the plate's copper light streak rather than on it: the streak
   is the brightest thing in the plate, so the band is positioned on the
   stone body instead and the scrim can then be light enough for the grain
   to actually read. */
.statband::before{
  content:""; position:absolute; left:0; right:0; top:-7%; bottom:-7%; z-index:-2;
  background:url("assets/img/texture-dark-metal.jpg") center 76% / cover no-repeat;
  opacity:.9;
}
.statband::after{
  content:""; position:absolute; inset:0; z-index:-1;
  /* Gradient stops measured against a 4-numeral band whose lighter grain
     patch sat under the (now-removed) fourth column; copper at 150px came
     back 2.89:1 there against its brightest ground pixel, three hundredths
     under the 3.0 that display-size type owes, and .78 at the right stop
     graded it to 3.4:1. REBALANCED 2026-07-28 to 3 columns: re-measured
     against the new layout (see the hand-back's contrast evidence); the
     stops are unchanged because the same rightward gradient still covers
     the band's brightest patch regardless of which column it falls under. */
  background:linear-gradient(90deg,rgba(16,18,22,.86) 0%,rgba(16,18,22,.80) 55%,rgba(16,18,22,.78) 100%);
}
.statband-inner{ gap:clamp(18px,2.4vw,40px); align-items:stretch; }
.statband-item{
  gap:clamp(14px,1.4vw,20px);
  padding-left:clamp(18px,2vw,30px);
  border-left:1px solid var(--dsr-rule);
}
/* Body face, not mono. DM Mono draws a slashed zero as its DEFAULT glyph
   (not an OpenType feature, so there is nothing to switch off); that is
   moot now that the band's one zero-valued stat is gone (2026-07-28), but
   the type choice is left as-is rather than reverted, since it was never
   solely about the zero: every reference PDP sets its hero stats in a
   display or body face, and mono still owns the specs table, chips and
   eyebrows, so the lab-data voice is intact everywhere it is actually
   reading as data. */
.statband-fig{
  font-family:var(--font-body); font-weight:600;
  font-size:clamp(72px,10.4vw,158px); line-height:.86;
  letter-spacing:-.03em;
  text-shadow:0 2px 30px rgba(0,0,0,.55);
}
/* The label is informational copy, not a caption on a chart: it carries the
   whole meaning of the numeral above it and was the smallest, dimmest thing
   in the band. Body face, at the floor, at prose contrast (12.0:1). */
.statband-label{
  font-size:var(--fs-meta); line-height:1.5; color:var(--dsr-ink-2);
  max-width:20ch;
}

/* -------------------------------------------------------------------------
   2. THE BREATH
   Eight full-width research rows above, a dense timeline below. One image at
   viewport width, no copy, no claim: the reference pages all buy their pace
   this way (Onnit's black flow-state band, IM8's flavour band).
   ------------------------------------------------------------------------- */
/* The photograph lives on a ::before that is 114% tall and exactly 100% wide,
   inside a clipped box, so the scroll drift below has room to move vertically
   and no room at all to move horizontally. */
.bleedfig{
  position:relative; overflow:hidden;
  margin-top:clamp(34px,3.6vw,52px);
  height:clamp(230px,25vw,350px);
  background:#101216;
  border-top:1px solid var(--dsr-rule-soft);
  border-bottom:1px solid var(--dsr-rule-soft);
}
.bleedfig::before{
  content:""; position:absolute; left:0; right:0; top:-7%; bottom:-7%;
  background:url("assets/img/quality-stilllife.jpg") center 34% / cover no-repeat;
}

/* -------------------------------------------------------------------------
   3. THE RECORD -> the one light section
   The single temperature change on the page. Measured on this ground:
   ink #17150F 15.62:1, deep #6E3B1E 7.79:1, ink at 78% 8.21:1. Copper is
   3.48:1 on porcelain and so appears ONLY on the 72px heading accent and as
   a rule, never on prose; --pale (2.0:1 here) is overridden everywhere it
   would otherwise inherit, which is what the .ref-anchor rules below are for.
   ------------------------------------------------------------------------- */
.evtl-light{
  position:relative; isolation:isolate; overflow:hidden;
  margin-top:0; padding:clamp(56px,6.4vw,96px) 0 clamp(52px,6vw,88px);
  border-top:0;
  background:var(--porcelain);
  color:var(--ink);
}
.evtl-light::before{
  content:""; position:absolute; inset:0; z-index:-2;
  background:url("assets/img/texture-porcelain.jpg") center 46% / cover no-repeat;
}
.evtl-light::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(180deg,rgba(241,237,228,.86) 0%,rgba(241,237,228,.90) 55%,rgba(241,237,228,.94) 100%);
}
.evtl-inner{ max-width:var(--container); margin:0 auto; padding:0 var(--gutter); }
.evtl-light > .evtl-inner > .eyebrow{ display:block; margin:0 0 16px; color:var(--deep); }
.evtl-light .evtl-h{
  margin:0 0 clamp(34px,3.8vw,52px);
  font-size:clamp(34px,5vw,72px); line-height:1.04; letter-spacing:-.012em;
  color:var(--ink); max-width:18ch;
  /* WIDOW 3 fix (2026-07-28, coordinator flag): wraps to "...published" /
     "work." at 390 and 430 mobile (measured 81px orphan last line). Balance
     redistributes the two forced lines evenly instead of hand-tuning the ch
     cap, which would only re-break at the next copy edit. */
  text-wrap:balance;
}
/* The years are the data, so they are typeset as data. Was 15px mono in
   --pale, i.e. the smallest thing in a section whose whole job is a count. */
/* min-height, not a smaller size: one node on this rail is "2017 and 2020",
   which cannot fit a ~155px column on one line at any size the rest of the
   row deserves. Two lines of headroom reserved on EVERY year keeps all seven
   body paragraphs on one baseline instead of stepping down under that node. */
.evtl-light .evtl-year{
  margin:0 0 16px; min-height:2.3em;
  font-size:clamp(19px,1.95vw,28px); line-height:1.1; letter-spacing:.01em;
  color:var(--deep);
}
.evtl-light .evtl-node{ padding-top:38px; }
.evtl-light .evtl-node::before{ top:26px; background:rgba(23,21,15,.22); }
.evtl-light .evtl-dot{
  top:21px; width:11px; height:11px;
  background:var(--porcelain); box-shadow:inset 0 0 0 1px rgba(23,21,15,.45);
}
.evtl-light .evtl-line{ color:rgba(23,21,15,.78); }
.evtl-light .evtl-stop .evtl-year{ color:var(--ember); }
.evtl-light .evtl-stop .evtl-dot{ background:var(--ember); box-shadow:inset 0 0 0 1px var(--ember); }
.evtl-light .evtl-stop .evtl-line{ color:var(--ink); }
.evtl-light .evtl-ev{
  margin-top:clamp(30px,3vw,42px); padding-top:22px;
  border-top:1px solid rgba(23,21,15,.20);
  font-size:var(--fs-body); line-height:1.7; color:var(--ink); max-width:62ch;
}
/* Citation superscripts inherit --pale, which is 2.0:1 here. Re-tinted to
   --deep (7.79:1) with a matching underline; hover/focus keep a visible
   change of state on the light ground instead of aiming at --porcelain. */
.evtl-light .ref-anchor{ color:var(--deep); border-bottom-color:rgba(110,59,30,.4); }
.evtl-light .ref-anchor a{ color:var(--deep); text-decoration-color:rgba(110,59,30,.55); }
.evtl-light .ref-anchor:hover,
.evtl-light .ref-anchor a:hover{ color:var(--ink); border-bottom-color:var(--ink); text-decoration-color:var(--ink); }
.evtl-light .ref-anchor:focus-visible{ outline-color:var(--deep); }
.evtl-light .ref-anchor-sep{ color:rgba(23,21,15,.38); }
::selection{ background:rgba(233,235,238,.24); }
.evtl-light ::selection{ background:rgba(23,21,15,.18); }

/* -------------------------------------------------------------------------
   4. THE MATERIAL -> full-bleed still life, 96px heading
   The one place the product is allowed to be the size it is in the room. The
   photograph is 1600x1197 with the vial held left and roughly half the frame
   empty, so the copy sits in the negative space the shot already has rather
   than being dropped on top of the subject. Contrast: porcelain on the
   darkest region of the scrimmed plate measures 13.9:1.
   ------------------------------------------------------------------------- */
.vialsec{ border-top:0; padding-top:0; margin-top:clamp(34px,3.6vw,52px); }
.vialsec-band{
  position:relative; isolation:isolate; overflow:hidden;
  display:flex; align-items:flex-end;
  min-height:clamp(320px,40vw,576px);
  padding:clamp(30px,4vw,60px) var(--gutter) clamp(28px,3.4vw,52px);
  border-top:1px solid var(--dsr-rule-soft);
  border-bottom:1px solid var(--dsr-rule-soft);
  background:#0E1013;
}
/* `auto 118%`, left-anchored and masked, NOT `cover`.
   Cover on a 4:3 source in a 16:5 band cropped the cap off the vial and then
   ran the bottle straight through the headline: the first pass had "What is
   in the" sitting on the product's own label. Fitting to HEIGHT instead keeps
   the whole vial in frame at any band height, holds it in the left third, and
   guarantees the copy column can never collide with the subject. The right
   edge of the photograph is already near-black, and the mask dissolves it
   into the band's ground so the plate has no visible seam. */
.vialsec-band-media{
  position:absolute; left:0; right:0; top:-7%; bottom:-7%; z-index:-2;
  background:url("assets/img/vial-labeled-stilllife.jpg") left center / auto 118% no-repeat;
  -webkit-mask-image:linear-gradient(90deg,#000 0%,#000 34%,rgba(0,0,0,.4) 54%,transparent 66%);
  mask-image:linear-gradient(90deg,#000 0%,#000 34%,rgba(0,0,0,.4) 54%,transparent 66%);
}
.vialsec-band::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(0deg,rgba(14,16,19,.5) 0%,rgba(14,16,19,0) 55%);
}
.vialsec-band-copy{
  display:block; position:relative;
  width:min(50%,600px); margin-left:auto;
}
.vialsec-band-copy > .eyebrow{ display:block; margin:0 0 clamp(18px,2vw,26px); color:var(--pale); }
.vialsec-band .vialsec-h{
  margin:0;
  font-size:clamp(40px,6.4vw,96px); line-height:1.04; letter-spacing:-.016em;
  color:var(--porcelain); max-width:14ch;
  text-shadow:0 2px 40px rgba(0,0,0,.5);
  /* WIDOW 1 fix (2026-07-28, coordinator flag, MAJOR): "What is in the
     vial." wrapped to "What is in the" / "vial." at every width 1000 to
     1920 (measured 94 to 134px orphan last line), the page's single largest
     display moment landing on a one-word second line. Balance over widening
     the cap: it redistributes the forced 2-line wrap this heading already
     wants instead of guessing a new ch value that breaks on the next edit. */
  text-wrap:balance;
}
/* The spec table used to sit 40px under a 38px heading; under a full-bleed
   band it needs the interval back. */
.vialsec .spec-block{ margin-top:clamp(34px,3.6vw,52px); }

/* -------------------------------------------------------------------------
   5. RESEARCH CONTEXT -> editorial spread
   Was a fifth stacked card with a heading on top of a fold. Now the heading
   holds its own column and the technical fold runs beside it, which is the
   only two-column reading structure on the page and the reason the block
   stops looking like the four blocks around it. One column below 900px.
   ------------------------------------------------------------------------- */
@media (min-width:900px){
  .vialsec .research-block{
    display:grid;
    grid-template-columns:minmax(0,.72fr) minmax(0,1fr);
    column-gap:clamp(36px,4.6vw,84px);
    align-items:start;
    padding:clamp(32px,3.4vw,44px) clamp(30px,3.2vw,42px);
  }
  .vialsec .research-block > .eyebrow{ grid-column:1; grid-row:1; }
  .vialsec .research-block > .research-title{
    grid-column:1; grid-row:2; align-self:start;
    font-size:clamp(25px,3vw,42px); line-height:1.14; margin:0;
    max-width:14ch;
    /* WIDOW 2 fix (2026-07-28, coordinator flag, MEDIUM): "A chain of 15
       amino acids." wrapped to "...amino" / "acids." at 1440 (measured 93 to
       102px orphan last line, 1280 to 1920). Same balance treatment as the
       other two forced-wrap display headings on this page. */
    text-wrap:balance;
  }
  .vialsec .research-block > .research-fold{ grid-column:2; grid-row:1 / span 2; }
  .vialsec .research-block > .research-note{ grid-column:2; grid-row:3; margin-top:18px; }
}

/* -------------------------------------------------------------------------
   6. FREQUENTLY PURCHASED / FREQUENTLY PAIRED WITH
   REMOVED 2026-07-28: the sunken-plate background, border and (critically)
   HORIZONTAL PADDING this rule used to give the row. That padding
   (clamp(24px,2.6vw,38px) left/right, ~38px at 1440) was the exact cause
   of a live-measured alignment defect the store owner caught: the
   section's own .container wrapper correctly matched "Also recommended"
   at 159 to 1281, but this rule was insetting the heading and grid INSIDE
   it by another ~39px each side, so they measured 198 to 1242 instead.
   The section is now a bare wrapper with no background/border treatment
   of its own, matching .related-block exactly, which was the explicit
   ask ("just copy the same format of what it looks like down here under
   the 'Also Recommended'"). Do not re-add a background/padding rule here
   without re-checking this exact alignment. */

/* -------------------------------------------------------------------------
   BACK-THIRD BREATH (2026-07-28)
   An adversarial design critic measured two related defects against the
   reference set and this section answers both at once:

     - no section anywhere on the page fills a block with the brand colour:
       grep for background:var(--copper)/var(--ember) turns up a button, a
       category pill, progress fills, a marker, a tick and a chip tint, never
       a section. mengotomars.com is the existence proof: 34.6% colorful, 33.0
       of that in the SAME 0-40 degree hue family as --copper/--ember (both
       measure ~21-22 degrees), achieved with atmospheric glow over a dark
       ground, not a flat fill, and it still reads dark, premium and
       clinical.
     - from the spec table through storage, cross-sell and "also research" to
       the footer (roughly the back 30% of the page) the ground never
       changes at all, while every one of the page's existing visual events
       (the stat plate, the breath, the record, the material band) is packed
       into the 17% right above it: the page front-loads and then dies.

   One full-bleed glow, in the dark register, no photograph required: it is a
   punctuation mark, so it is not repeated anywhere else. Placed after the
   RUO band and before "Also research" (the critic's own named candidate for
   the ground change), which puts it closer to the middle of the dead zone
   than the front of it, and turns it into a lead-in to a browsing moment
   rather than an interruption of a reading one. Gated on $dossier in
   single-product.php: a product with no dossier never renders the
   spec/storage/references sprawl this section sits inside, so it gets none
   of this either.
   ------------------------------------------------------------------------- */
.backglow{
  margin-top:clamp(34px,3.6vw,52px);
  margin-bottom:clamp(34px,3.6vw,52px);
  height:clamp(260px,26vw,380px);
  /* EBI self-round, same day: the first pass (a 58%/90% ellipse at .34 peak)
     was measured against three live variants side by side and read as too
     contained, close to a corner smudge rather than a moment. This is the
     middle variant of three: a taller ellipse that bleeds from just above
     the top edge, a mid-stop so the falloff is gradual instead of a hard
     ring, and a slightly stronger secondary echo bottom-right so the band
     never reads as "mostly empty with one accent." The strongest of the
     three variants was rejected: at that size it started competing with the
     stat plate and material band for attention, which is wrong for a device
     that is explicitly a punctuation mark and not a second hero moment. */
  background:
    radial-gradient(ellipse 72% 130% at 20% -6%, rgba(194,87,27,.46) 0%, rgba(194,87,27,.08) 46%, rgba(194,87,27,0) 74%),
    radial-gradient(ellipse 60% 95% at 100% 112%, rgba(184,106,60,.30) 0%, rgba(184,106,60,0) 70%),
    #0E1013;
  border-top:1px solid var(--dsr-rule-soft);
  border-bottom:1px solid var(--dsr-rule-soft);
}

/* -------------------------------------------------------------------------
   7. MOTION THAT REWARDS THE SCROLL
   Scroll-driven, so it is tied to the reader's own movement rather than to a
   timer, and free of JavaScript: the three photographic grounds drift
   vertically across their pass through the viewport.

   VERTICAL ONLY, AND ON AN OVER-TALL INNER LAYER, WHICH IS THE WHOLE POINT.
   The first pass animated `scale(1.07) -> scale(1)` directly on the bleeding
   elements, and a scale on a 100vw box is 3.5% of the viewport of real
   horizontal overflow on each side: measured by verify-pdp-rebuild.mjs as
   exactly 13px at 360, 14px at 390, 15px at 430 and 50px at 1440, which is
   1440 * .07 / 2 to the pixel. A vertical translate on a layer that is 100%
   wide and 114% tall inside an overflow:hidden parent cannot widen anything:
   the horizontal extent is the parent's, at every frame.

   Gated three ways: the @supports test (no scroll-timeline support, no rule
   at all), the reduced-motion query, and `will-change` scoped to the three
   nodes that use it so nothing else is promoted to a layer.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    @keyframes kairo-drift{ from{ transform:translate3d(0,-5%,0); } to{ transform:translate3d(0,5%,0); } }
    .bleedfig::before, .vialsec-band-media, .statband::before{
      animation:kairo-drift linear both;
      animation-timeline:view();
      animation-range:entry 0% cover 100%;
      will-change:transform;
    }
  }
}

/* -------------------------------------------------------------------------
   8. THE NEW BLOCKS BELOW 900 / 700 / 560
   The two full-bleed photographs and the light band all survive the collapse;
   what changes is the vial band, whose whole desktop composition is "subject
   left, copy in the negative space right". There is no negative space at 390,
   so the band re-composes as copy over the photograph's own dark base rather
   than beside it, which is the only version of it that can hold 15px+ type at
   AA on a 390 viewport.
   ------------------------------------------------------------------------- */
@media (max-width:900px){
  .vialsec-band{
    min-height:clamp(300px,58vw,420px);
    align-items:flex-end;
  }
  .vialsec-band-media{
    background-position:center 34%; background-size:cover;
    -webkit-mask-image:none; mask-image:none;
  }
  /* Bottom-weighted scrim, measured on the photograph's darkest base region:
     porcelain over it reads 14.6:1 where the heading sits. */
  .vialsec-band::after{
    background:linear-gradient(0deg,rgba(14,16,19,.95) 0%,rgba(14,16,19,.86) 34%,rgba(14,16,19,.45) 68%,rgba(14,16,19,.12) 100%);
  }
  .vialsec-band-copy{ width:100%; margin-left:0; }
  .vialsec-band .vialsec-h{ max-width:16ch; }
}
@media (max-width:700px){
  /* The rail is a vertical spine here, so the reserved two lines of year
     headroom that keep seven columns on one baseline at desktop are just a
     gap between a year and its own sentence. */
  .evtl-light .evtl-year{ min-height:0; margin-bottom:8px; }
  .evtl-light{ padding:clamp(40px,9vw,60px) 0 clamp(38px,8vw,56px); }
  .evtl-light .evtl-h{ margin-bottom:clamp(26px,6vw,34px); }
  .vialsec .research-block{ padding:24px 20px; }
}
@media (max-width:560px){
  /* Stated here rather than inherited: the 560 breakpoint above this block
     caps the numerals at 58px, and this pass deliberately overrides that. 76
     is the largest "11" that clears a two-up cell at 360 (measured 86px of
     glyph in a 160px cell). */
  .statband-fig{ font-size:clamp(56px,15vw,76px); }
  .statband{ padding:clamp(38px,9vw,52px) 0 clamp(34px,8vw,46px); }
  .statband-item{ padding-left:clamp(12px,3vw,16px); }
  .bleedfig{ height:clamp(200px,52vw,260px); }
  .backglow{ height:clamp(190px,50vw,240px); }
}

/* =========================================================================
   WHAT IT IS STUDIED FOR (2026-07-28)

   The section the page went four drafts without: plain English on what the
   compound is studied for, first thing after the hero. Its build note is
   explicit that it must NOT look like the eight research rows underneath it,
   which are a reference table; this is the moment that has to land. So it
   takes the opposite construction from those rows at every level:

     rows                          this section
     ---------------------------   ---------------------------------------
     one shared grid track          asymmetric two-column spread per item
     20px headline                  the page's largest heading, 100px lede
     a boxed plate                  a full-bleed band on a warm plaster wash
     8 items, uniform               5 items, each with its own numeral

   The chip is deliberately NOT re-styled: it is the SAME component the rows
   carry, so its "Preclinical" prefix and its aria-describedby legend note
   behave identically here, which is what the dossier's standing render note
   requires. Only its popover anchor differs, because this chip is flush left.
   ========================================================================= */
.benefits{
  position:relative; isolation:isolate; overflow:hidden;
  margin:0; padding:clamp(56px,6.6vw,104px) 0 clamp(50px,5.8vw,88px);
  border-bottom:1px solid var(--dsr-rule-soft);
  background:#131519;
}
/* EBI round 1. The first pass set the plate at .5 under a .72-to-.95 scrim,
   and measuring the result showed the ground under the headline at
   rgb(22,24,28) against a page background of rgb(21,23,27): one value apart,
   i.e. the exact "ten sections, three background values" defect this whole
   pass exists to fix, reproduced inside its own new section. The plate now
   carries, and the warm light wash is positioned to fall to the RIGHT of the
   headline rather than under it, so the ground gets brighter as the eye
   travels into empty space instead of into type. */
.benefits::before{
  content:""; position:absolute; left:0; right:0; top:-7%; bottom:-7%; z-index:-2;
  background:url("assets/img/bg-plaster.jpg") 78% 28% / cover no-repeat;
  opacity:.8;
}
.benefits::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(165deg,rgba(19,21,25,.50) 0%,rgba(19,21,25,.72) 46%,rgba(19,21,25,.88) 78%,rgba(19,21,25,.94) 100%);
}

/* THE HEADLINE MOMENT. Largest heading on the page: 100px against the
   material band's 84 and the record's 72, so the payoff for the whole
   compound category is unambiguously the thing the eye lands on first after
   the buy box.
   max-width DROPPED from 16ch (2026-07-28): it used to be the mechanism
   forcing this heading to wrap, which is exactly the anti-pattern the
   store owner's typography correction retired ("Less inflammation. More"
   / "blood flow." stranded the parallel statements across the wrong break).
   The two statements now break structurally via .hl-line below; a stray
   width cap narrower than "Less inflammation." (19ch) would still force an
   unwanted SECOND wrap inside that one line at any width, so it is gone
   rather than just widened. */
.benefits-lede{ max-width:none; margin:0 0 clamp(44px,4.8vw,72px); }
.benefits-h{
  margin:0 0 clamp(20px,2.2vw,30px);
  font-family:var(--font-display); font-weight:400;
  font-size:clamp(40px,6.9vw,100px); line-height:1.02; letter-spacing:-.018em;
  color:var(--porcelain);
}
/* .hl-line: STANDING RULE (2026-07-28, store owner correction), reusable
   anywhere a display heading is two or more PARALLEL STATEMENTS. Each
   statement is wrapped in its own .hl-line and gets display:block, so the
   break between statements is structural and can never move to a
   different word at any viewport: never left to text-wrap:balance (which
   optimizes for even line lengths with no notion of a sentence boundary,
   and is exactly what produced the bad break here) or to the wrapping
   algorithm. text-wrap:balance stays ON here, but scoped to each
   .hl-line's OWN text only, so a single statement long enough to need a
   second line at a narrow width can still balance internally without ever
   touching the hard boundary between statements. A heading that is a
   single statement is not a candidate for this class; it keeps ordinary
   wrapping. Not used with <br>: a block span keeps the markup meaningful. */
.hl-line{ display:block; text-wrap:balance; }
.benefits-sub{
  margin:0; font-family:var(--font-body);
  font-size:clamp(17px,1.35vw,20px); line-height:1.6;
  color:var(--dsr-ink-2); max-width:56ch;
}

.benefit-list{ list-style:none; margin:0; padding:0; }
.benefit{
  display:grid; grid-template-columns:minmax(0,.78fr) minmax(0,1fr);
  column-gap:clamp(32px,4.4vw,80px);
  padding:clamp(26px,2.8vw,38px) 0;
  border-top:1px solid var(--dsr-rule-soft);
}
.benefit:first-child{ border-top:1px solid rgba(184,106,60,.5); }
.benefit-head{ display:flex; flex-direction:column; align-items:flex-start; gap:14px; }
/* The index numeral is the device the reference table below does not have.
   Mono, copper, tabular, and big enough to be a landmark on a fast scroll. */
.benefit-n{
  font-family:var(--font-mono); font-weight:500;
  font-size:clamp(28px,2.9vw,44px); line-height:1;
  letter-spacing:.02em; color:var(--copper);
  font-variant-numeric:tabular-nums;
}
.benefit-title{
  margin:0; font-family:var(--font-display); font-weight:400;
  font-size:clamp(22px,2.2vw,32px); line-height:1.18;
  color:var(--porcelain); max-width:19ch;
}
/* Flush left here, so the legend anchors left rather than to the chip's right
   edge the way it does in the right-aligned rail below. */
.benefit-chip .conf-pop{ left:0; right:auto; }
.benefit-copy{
  margin:0; font-family:var(--font-body);
  font-size:var(--fs-body); line-height:1.68;
  color:var(--dsr-ink-2); max-width:62ch;
}
.benefit-links{ margin:clamp(16px,1.7vw,22px) 0 0; display:flex; flex-direction:column; align-items:flex-start; gap:9px; }
/* The label is the research row's own name, so the link says exactly which
   row it lands on. Sized at the informational floor, never below it. */
/* EBI round 1: ONE chip family per block. These were bordered chips, which
   put two competing pill shapes in one row (the confidence badge and the
   link) and imported the reference table's vocabulary into the section whose
   whole job is plain English. As plain underlined links the badge is the only
   chip in the block, and the eye reads rating first, route second. */
.benefit-link{
  display:inline-flex; align-items:flex-start; gap:8px;
  font-family:var(--font-mono); font-size:var(--fs-micro); line-height:1.55;
  letter-spacing:.06em; color:var(--pale); text-decoration:underline;
  text-underline-offset:4px; text-decoration-thickness:1px;
  text-decoration-color:rgba(216,155,106,.34);
  transition:color .35s var(--dsr-ease), text-decoration-color .35s var(--dsr-ease);
}
/* flex-start, not center: two of these labels are long enough to wrap at
   1440 and a centred arrow floats into the gap between the two lines. */
.benefit-link::before{ content:"\2193"; opacity:.7; line-height:1.55; flex:none; }
.benefit-link:hover{ color:var(--porcelain); text-decoration-color:var(--porcelain); }
.benefit-link:focus-visible{ outline:2px solid var(--pale); outline-offset:3px; }
/* Quiet, no box, no icon: it carries the frame without a defensive heading. */
.benefits-close{
  margin:clamp(30px,3.2vw,44px) 0 0; padding-top:clamp(20px,2.2vw,28px);
  border-top:1px solid var(--dsr-rule-soft);
  font-family:var(--font-body); font-size:var(--fs-meta); line-height:1.7;
  color:var(--dsr-ink-3); max-width:70ch;
}
/* One step down so the new headline can be the page's largest without the
   material band tying with it. */
.vialsec-band .vialsec-h{ font-size:clamp(38px,5.9vw,84px); }

@media (max-width:900px){
  .benefit{ grid-template-columns:minmax(0,1fr); row-gap:16px; }
  .benefit-head{ flex-direction:row; align-items:center; flex-wrap:wrap; gap:12px; }
  .benefit-title{ max-width:none; flex:1 1 auto; }
  .benefits{ padding:clamp(44px,9vw,64px) 0 clamp(40px,8vw,58px); }
  /* The moment has to survive the collapse: at 390 the desktop clamp bottomed
     out at its 40px floor, four steps from the 25px section headings around
     it. 11vw keeps it a moment at every width below 900. */
  .benefits-h{ max-width:none; font-size:clamp(42px,11vw,68px); }
}
@media (max-width:560px){
  .benefit-n{ font-size:clamp(24px,7vw,30px); }
  .benefit-title{ font-size:clamp(20px,5.6vw,24px); flex:1 1 100%; }
}

/* The storage sub-line, one sentence under the heading and above the fold
   control, at the informational floor. */
.storage-sub{ margin:10px 0 0; max-width:60ch; }

/* ============================================================
   HERO CATEGORY CHIPS + HERO LINE (2026-07-31)
   Store owner, PDP review: "I like that the recovery is the green
   color, and it's above the product title" and "one line that's
   super short and sweet".

   Per-category hue exists because the first-customer session named
   the real problem: "the products look almost identical". They are
   identical, so the chip is the only thing distinguishing a
   recovery SKU from a skin SKU at a glance. One hue per category,
   held at the same saturation so no single category shouts over
   the others, and every one of them checked against the --slate
   ground rather than picked for looks.

   Slug classes come from sanitize_title() on the taxonomy label,
   so a renamed category (Metabolic -> Weight Loss is pending a
   decision) needs its selector renamed here in the same commit.
   Unknown categories fall through to the base .cat-chip copper,
   which is the current sitewide treatment, so nothing breaks.
   ============================================================ */
.hero-cats{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px; }
.cat-chip{
  display:inline-flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-size:var(--fs-micro); font-weight:500;
  letter-spacing:.12em; text-transform:uppercase;
  color:var(--pale); border:1px solid currentColor;
  border-radius:999px; padding:5px 12px; line-height:1.25;
}
.cat-chip::before{
  content:""; width:6px; height:6px; border-radius:50%;
  background:currentColor; flex:none;
}
.cat-recovery{ color:#3E8E7E; }
.cat-skin{ color:#B8628C; }
.cat-brain{ color:#5B7FB9; }
.cat-longevity{ color:#8A7BC8; }
.cat-weight-loss{ color:#C2571B; }
.cat-growth{ color:#C9A227; }
.cat-supplies{ color:#7C8284; }

/* The hook line. Larger and warmer than .product-summary directly
   beneath it, so the eye takes the short sentence first and the
   full research-material sentence second. Capped near 34ch: past
   that it stops reading as one breath. */
.hero-line{
  font-size:clamp(17px,1.45vw,20px); line-height:1.5;
  color:var(--pale); margin:0 0 10px; max-width:34ch;
}

/* ============================================================
   MARKET PRICE COMPARISON (2026-07-31)
   The market figure IS struck through, because without it the eye
   reads $68.50 as a price you might pay. What keeps that honest is
   the label immediately before it: "Typical market price" states
   whose price it is. A bare strikethrough with no label is the
   deceptive pattern (it implies a former price of OUR own); a
   labelled one is ordinary comparative advertising. Never render
   this element without its label. See kairo_market_compare().

   The footnote is deliberately not hidden behind a tooltip: the
   sample size IS the credibility, and this store's whole position
   is that it shows its evidence rather than asserting it.
   ============================================================ */
/* SUPERSEDED 2026-07-31 by the revised block further down this file.
   The `flex-direction:column` that used to live here outlived the layout
   it belonged to: the later rule restyled .market-compare without
   resetting direction, so the label and the figure kept stacking even
   though the new rule reads like a single row. Left as an empty
   selector rather than deleted so the comment above it still has
   something to attach to and the history stays readable.

   Lesson worth keeping: when a component is restyled in a second block,
   reset the layout properties the first block set, or delete the first
   block. Do not rely on the later rule "winning" a property it never
   mentions. */
.mc-line{
  display:flex; align-items:baseline; flex-wrap:wrap; gap:7px;
  font-family:var(--font-mono); font-size:13px; line-height:1.4;
}
.mc-label{ color:var(--mist-dim); letter-spacing:.02em; }
.mc-price{ color:var(--mist-dim); text-decoration:line-through; text-decoration-thickness:1px; }
.mc-price .woocommerce-Price-amount{ color:inherit; font-size:inherit; }
.mc-save{
  color:#3E8E7E; font-weight:500; letter-spacing:.06em;
  text-transform:uppercase; font-size:11px;
}
.mc-foot{
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.04em;
  color:var(--mist-dim); opacity:.75;
}
.market-compare.is-compact{ margin:2px 0 0; }
.market-compare.is-compact .mc-line{ font-size:11.5px; gap:5px; }
.market-compare.is-compact .mc-save{ font-size:10px; }

/* ============================================================
   DRAWER CHROME REBUILT (2026-08-04, store owner; width and row layout
   both revised further within the same day -- see the WIDTH 360 note
   and the ITEM ROW note below for the final numbers and shapes)
   Supersedes the 2026-07-31 squeeze-everything-into-a-132-150px-rail
   design that used to occupy this part of the file. That design's
   several incremental passes are gone from here; the decisions
   underneath them (non-modal-on-desktop, push-on-desktop, one ladder
   rung at a time) are not -- they are just no longer split across a
   dozen overlapping rules chasing a rail that no longer exists at this
   width.

   --kc-w ends this day at 360px desktop / 80vw mobile (was 132-150px).
   At 132-150px the drawer had to choose between showing the total and
   showing the items; at the current width it holds a proper head, an
   incentives block, the item list and a foot, in that order, and each
   gets to be close to its natural size instead of fighting for 118px
   of usable width.
   ============================================================ */

/* HEAD: .kc-head / .kc-title / .kc-close (style.css, ~line 3077) already
   carry the right treatment from before the 2026-07-31 removal and
   needed no edit beyond padding, to sit comfortably at the new width.
   Mobile-specific sizing (bigger title, a 44px close hit area) is in the
   MOBILE OVERLAY block further down this file. */
.kc-head{ padding:18px 20px; }

/* INCENTIVES: savings line, discount row, reward ladder, in that order
   -- see assets/cart-drawer.js injectMarkup() for why the discount row
   sits here rather than by the ladder. */
.kc-incentives{
  display:flex; flex-direction:column; gap:10px;
  padding:14px 20px 4px;
}
/* Drawer-scoped savings line, replacing the old .kc-top-scoped rules
   (that wrapper is gone). The shared .kairo-savings-line component
   (style.css:3271) carries padding and a centered 18px treatment sized
   for the full-width cart/checkout surfaces -- wrong for a drawer rail
   even at 360px, so it is overridden here exactly as it was before,
   just re-scoped to the drawer itself instead of to .kc-top. */
.kc-drawer .kairo-savings-line{
  padding:0; margin:0; text-align:left; white-space:normal;
  font-family:var(--font-mono); font-size:13px; line-height:1.35;
  font-weight:600; letter-spacing:0; color:var(--pale);
}
/* Discount row gets no drawer-specific override: .kc-subtotal (base,
   style.css:3168) already gives it the label-left/value-right row, and
   .kc-discount's own color rules (style.css:3190-3191) apply wherever
   the row sits in the tree. */

/* ============================================================
   ITEM ROW: A REAL 2-COLUMN GRID (2026-08-04, third pass, store owner
   correction: "the checkout row he asked you to copy is TWO COLUMNS,
   product info on the left and money right-aligned in its own column...
   no amount of spacing tweaks fixes that; it needs to be a 2-column
   row"). Two failed shapes preceded this one and are gone from here:
   the original thumb/info/price 3-column grid (photo too large, price
   column fighting the name column for width), then a 3-independent-
   flex-rows attempt (each row did its own left/right split, so nothing
   forced the right edges onto the SAME x -- the "shared edge" that is
   the actual thing that makes the checkout layout read cleanly).

   .kc-item-info is display:grid, grid-template-columns:1fr auto, and
   assets/cart-drawer.js's render() emits its 6 (or, undiscounted, 3)
   children as DIRECT children in a fixed order -- grid auto-placement
   turns every 2 children into one row, so the row structure lives in
   the markup order, not in wrapper divs:
     1 name+dose (left)            2 struck original total (right)
     3 unit price pair (left)      4 live total, bold (right)
     5 qty stepper + trash (left)  6 percent tag (right)
   justify-self:end on every right-column element (2/4/6 below) is what
   creates the shared edge: the `auto` column is exactly as wide as its
   widest item, and every item in it sits flush against that column's
   own right boundary regardless of the item's own width. Undiscounted
   items only emit 1+2 then 5 -- children 3/4/6 are omitted, not emitted
   empty, and CSS grid simply leaves those cells unfilled (confirmed:
   collapses cleanly to 2 rows, nothing orphaned).

   Thumbnail 48x60 (not square) matches /checkout/'s own
   .wc-block-components-order-summary-item__image measurement exactly,
   since the owner asked for that row structure copied, not
   approximated. */
.kc-item{
  display:grid; grid-template-columns:48px 1fr; column-gap:12px;
  align-items:start; padding:14px 0; border-bottom:1px solid var(--hairline);
}
.kc-item:last-child{ border-bottom:0; }
.kc-item-thumb{ display:block; }
.kc-item img{ width:48px; height:60px; object-fit:cover; border-radius:3px; background:var(--slate); }

.kc-item-info{
  display:grid; grid-template-columns:1fr auto; column-gap:10px; row-gap:4px;
  align-items:baseline; /* rows 1-2: text baselines line up across columns */
  min-width:0; /* see .kc-item-controls' note: this is the other half of it */
}
/* MEASURED BUG (2026-08-04, self-catch on the first live check of this
   grid): a CSS grid item's default automatic minimum size is its
   content's min-content, not 0 -- so .kc-item-info (a `1fr` item inside
   .kc-item's own 48px/1fr grid) was refusing to shrink below whatever
   its widest row's LEFT column needed, forcing .kc-item wider than its
   real 268px box at 390px/80vw and spilling ~35-46px into a silent
   horizontal scroll inside .kc-body (clipped there, never reaching the
   drawer or the page, which is why it did not show up as page-level
   overflow). The measurable symptom: two items in the same cart landed
   their right-column edges at two DIFFERENT x positions (413.6 vs
   402.8) instead of the same one, because each row's outer 1fr track
   was resolving to a different, content-driven width instead of the
   one true available width. min-width:0 here and on
   .kc-item-controls below (the actual wide content: two 44px mobile
   tap targets + a number span) fixes both the overflow and the
   cross-row edge mismatch in one change -- confirmed live, see the
   VERIFY section of this task's hand-back for the re-measured numbers. */
/* Row 1 left: title (14px) + dose (15px, set inline in cart-drawer.js) --
   both measured off /checkout/'s .wc-block-components-product-name /
   .inline-dose. */
.kc-item-name{ font-size:14px; font-weight:500; }
/* Row 1 right: struck ORIGINAL line total, discounted rows only. 18px
   mirrors checkout's .order-summary-item__total-price del measurement. */
.kc-item-total-was{
  font-family:var(--font-mono); font-size:18px; font-weight:400;
  color:var(--mist-dim); text-decoration:line-through; white-space:nowrap;
  justify-self:end;
}
/* Row 2 right (discounted) / row 1 right (not discounted): the LIVE /
   only line total. Base treatment (18px/400/mist) matches checkout's
   non-discounted total exactly; .kc-item-price-live layers the
   discounted (bold/pale) treatment on top, matching checkout's
   .is-discounted total. Verify-drawer-cart-parity.mjs reads
   .kc-item-price specifically, on either variant. */
.kc-item-price{
  font-family:var(--font-mono); font-size:18px; font-weight:400;
  color:var(--mist); white-space:nowrap; justify-self:end;
}
.kc-item-price-live{ color:var(--pale); font-weight:600; }
/* Row 2 left: unit price pair, struck then live, ONE line always (store
   owner: "never stacked") -- .kc-price-row's base rule (style.css:3131,
   display:flex, no wrap) already does this; 15px already matches
   checkout's individual-price measurement, so no size override needed. */
/* Row 3: qty stepper + trash left, percent tag right. Baseline alignment
   does not suit a button group, so both get their own align-self. */
.kc-item-controls{ display:flex; align-items:center; gap:10px; align-self:center; min-width:0; }
.kc-tag{ justify-self:end; align-self:center; }

.kc-qty{ gap:6px; }
/* Quantity number: fixed width so 1 -> 10 does not nudge the trash
   button sideways when a tier bump changes the digit count. */
.kc-qty span{ display:inline-block; min-width:14px; text-align:center; }
.kc-qty button{ width:24px !important; height:24px !important; font-size:13px; }

/* TRASH ICON, NOT THE WORD "REMOVE" (2026-08-04, store owner). The
   button is icon-only now (assets/cart-drawer.js renders WooCommerce
   Blocks' own remove-item SVG); this replaces the old text-link
   treatment (font-size/letter-spacing/underline) with an icon-button
   one. Mobile's 44px hit-area override is in the MOBILE OVERLAY block
   further down this file. */
.kc-remove{
  display:inline-flex; align-items:center; justify-content:center;
  color:var(--mist-dim); background:none; border:0; cursor:pointer; padding:0;
}
.kc-remove:hover{ color:var(--mist); }
.kc-remove svg{ display:block; }

/* FOOT: CENTRED (2026-08-04, store owner corrected an earlier
   right-aligned pass: "the subtotal and Go to Cart again... it should
   likely need to be center-aligned"). The button reverts to the
   ORIGINAL .kc-checkout base treatment (style.css:3206: display:block,
   width:100%) rather than the auto-width pill the right-aligned pass
   used -- "a proper primary action at 360px, not a small right-hand
   link" reads as full-width, not as a smaller centred button, so this
   is a full reversion to the base rule on the button specifically, no
   override left here for it. */
.kc-foot{
  display:flex; flex-direction:column; align-items:center; gap:10px;
  padding:16px 20px 20px;
}
.kc-foot .kc-subtotal{ justify-content:center; align-items:baseline; gap:10px; }
.kc-subtotal-label{ color:var(--mist-dim); }
.kc-subtotal-val{ font-size:19px; color:var(--porcelain); font-variant-numeric:tabular-nums; }

/* ============================================================
   MARKET PRICE, REVISED (2026-07-31, store owner review)
   "It's too small" / "market price should be slashed out, and then
   our price below that" / no save badge / no methodology footnote.

   Sizing: the market figure is deliberately close to our own price
   in size, not a footnote. It is the comparison that carries the
   value argument, so shrinking it to 11px threw away the point. It
   stays slightly smaller and dimmer than our price so the hierarchy
   still reads "this is what you pay" at a glance.

   The label is not optional. A struck-through number with no label
   implies a former price of OUR OWN, which is the deceptive
   pattern; "MARKET PRICE" states whose price it is.
   ============================================================ */
.market-compare{
  /* Explicit row: the superseded block earlier in this file once set
     flex-direction:column and this rule inherited it silently, stacking
     the label above the figure. Never omit direction on a flex component
     that another rule has already touched. */
  display:flex; flex-direction:row; align-items:baseline;
  flex-wrap:nowrap; gap:9px; white-space:nowrap;
  margin:0 0 2px;
}
.mc-label{
  font-family:var(--font-mono); font-size:11px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--mist-dim);
}
.mc-price{
  font-family:var(--font-mono); font-size:19px; color:var(--mist-dim);
  text-decoration:line-through; text-decoration-thickness:1.5px;
  text-decoration-color:var(--copper); font-variant-numeric:tabular-nums;
}
.mc-price .woocommerce-Price-amount{ color:inherit; font-size:inherit; }
/* Our price sits directly beneath and keeps its existing size, so the
   pair reads as a hierarchy rather than two equal figures. */
.market-compare + .product-price{ margin-top:0; }
.market-compare.is-compact{ gap:6px; margin-bottom:1px; }
.market-compare.is-compact .mc-label{ font-size:9px; letter-spacing:.1em; }
.market-compare.is-compact .mc-price{ font-size:13px; }

/* .kc-item img and .kc-item-thumb (2026-07-31 to 2026-08-04: grown to
   100%/full-width here, twice -- once on the wrong element, then again
   "actually targeted" on .kc-item img itself with !important) are back
   to the base rule's fixed 56x56 (style.css:3114) as of the 2026-08-04
   rebuild: "SMALLER product thumbnail." No override needed; deleting
   the two passes that grew it is the whole fix. See the DRAWER CHROME
   REBUILT block (style.css, ~line 5981) for the replacement item-row
   layout, and .kc-drawer .kairo-savings-line there for the savings-line
   treatment that replaces the .kc-top-scoped one this block carried. */

/* ------------------------------------------------------------
   SHOP CARD PILLS: PER-CATEGORY COLOUR (2026-07-31)
   The hero chip got its hue on the earlier pass and the shop grid
   did not, so the catalogue still showed 17 identical copper pills
   over 17 near-identical vial photographs. That is the exact
   complaint from the first-customer session ("the products look
   almost identical"), and the grid is where it actually bites,
   because that is the screen where a shopper compares.

   Same hues as .cat-chip, applied to the on-image pill. The pill
   keeps its dark plate behind the text so it stays legible over a
   photograph; only the text and border take the category colour.
   ------------------------------------------------------------ */
.sku-pill.cat-recovery{ color:#4FB79E; border-color:rgba(79,183,158,.45); }
.sku-pill.cat-skin{ color:#D584AC; border-color:rgba(213,132,172,.45); }
.sku-pill.cat-brain{ color:#7B9BD4; border-color:rgba(123,155,212,.45); }
.sku-pill.cat-longevity{ color:#A596DD; border-color:rgba(165,150,221,.45); }
.sku-pill.cat-weight-loss{ color:#E0763A; border-color:rgba(224,118,58,.45); }
.sku-pill.cat-growth{ color:#D9B43F; border-color:rgba(217,180,63,.45); }
.sku-pill.cat-supplies{ color:#9AA0A2; border-color:rgba(154,160,162,.45); }

/* PDP: the on-image pill is redundant now that the coloured .cat-chip
   sits above the product title. Hidden only on the single-product
   template; the shop grid still needs it, which is why the element is
   not removed from kairo_render_sku_card(). */
.single-product .hero-media .sku-pill,
.single-product .hero-media .sku-pills{ display:none; }

/* ------------------------------------------------------------
   REWARD LADDER, CONSOLIDATED (2026-08-04). One ruleset replacing four
   layered 2026-07-31 passes (base rail-fit sizing, an edge-to-edge
   bleed via negative margins, a colour/size revision, a vertical
   compression) that existed to make this component survive a
   132-150px rail. It no longer needs to: .kc-ladder sits in normal
   flow inside .kc-incentives now (that wrapper's own padding applies),
   so the negative-margin bleed trick is gone, and the message is ONE
   line (row, not column) since 264-300px comfortably fits the longest
   case ("$142 to free shipping") without the old two-line, reward-
   name-never-broken split -- see assets/cart-drawer.js's renderLadder()
   comment. Shows ONE goal, not all three tiers at once (unchanged).
   ------------------------------------------------------------ */
.kc-ladder{
  display:flex; flex-direction:column; gap:6px;
  padding:10px 12px;
  border:1px solid var(--hairline); border-radius:4px;
  background:rgba(194,87,27,.08);
}
.kc-ladder[hidden]{ display:none; }
.kc-ladder-msg{
  display:flex; flex-direction:row; flex-wrap:wrap; align-items:baseline; gap:4px;
  font-size:12.5px; line-height:1.3; color:var(--pale);
}
.kc-l-amt{ white-space:nowrap; font-weight:700; color:var(--ember); font-size:13.5px; }
.kc-l-reward{ white-space:nowrap; font-weight:600; color:var(--pale); font-size:12.5px; }
.kc-ladder-track{
  display:block; height:4px; border-radius:100px;
  background:rgba(233,235,238,.12); overflow:hidden;
}
.kc-ladder-fill{
  display:block; height:100%; border-radius:100px;
  background:linear-gradient(90deg,var(--deep),var(--ember));
  transition:width .45s cubic-bezier(.2,.7,.3,1);
}
/* Copper on completion, not green: green read as a system success
   message rather than as money the shopper is keeping. */
.kc-ladder.is-complete .kc-ladder-fill{ background:var(--ember); }
@media (prefers-reduced-motion:reduce){ .kc-ladder-fill{ transition:none; } }

/* ============================================================
   DRAWER RESERVES SPACE INSTEAD OF COVERING (2026-07-31)
   Store owner: "the cart covers up the Add to Cart... I can't add
   the card on some of the items."

   The drawer is position:fixed, so it sat ON TOP of the right edge
   of the shop grid and swallowed whatever was under it, including
   the ADD button of every product in the right-hand column. Making
   the background non-inert and scrollable (earlier today) let the
   shopper reach the catalogue conceptually, but they still could
   not click the part of it hidden behind the rail.

   Fix: while the drawer is open the document reserves its width
   rather than letting it overlap. The grid reflows into the
   narrower space and every control stays hittable. body.kc-scroll-lock
   is the hook, which is exactly why that class was kept alive when
   its overflow rule was removed.

   Anything position:fixed has to be inset separately, since padding
   on the body does not move it: .buybar is the one such element on
   the product template.
   ============================================================ */
/* WIDTH 360 (2026-08-04, store owner: first "double the width" to 300,
   then corrected to "bump the width up to 360 pixels" once the row
   redesign needed the extra room). --kc-w is the single source of truth;
   .kc-drawer's own width always tracks it, on every viewport. */
:root{ --kc-w:360px; }
.kc-drawer{ width:min(var(--kc-w),92vw); }

/* ============================================================
   DESKTOP: PUSH. MOBILE: OVERLAY. (2026-08-04, store owner: "this whole
   slide-out cart just doesn't really seem to work on mobile... the
   content squeezed that way just doesn't work, so it's just gonna have
   to cover up the screen.")

   Desktop (>600px) is UNCHANGED from the 2026-07-31 "drawer reserves
   space instead of covering" decision directly below: while the drawer
   is open the page reserves --kc-w of width (body padding-right,
   .site-header/.buybar right-inset) rather than letting the drawer
   overlap it. That whole mechanism is now scoped to
   @media(min-width:601px) explicitly, so it can never also fire on
   mobile and fight the overlay behaviour below it.

   Mobile (<=600px) gets NONE of that: --kc-w becomes 80vw ("it'll have
   to just become, let's call it, 80% of the screen" -- deliberately
   leaving ~20% of the page visible on the left as the affordance for
   the next item), and body/.site-header/.buybar are left completely
   unset, so they render at their normal, unpushed position while the
   drawer (position:fixed, unchanged) slides over them. A REAL scroll
   lock and a tappable scrim replace the desktop mechanism -- see the
   MOBILE OVERLAY block further down this file.
   ============================================================ */
@media (min-width:601px){
  body.kc-scroll-lock{
    padding-right:min(var(--kc-w),92vw);
    transition:padding-right .32s cubic-bezier(.4,0,.2,1);
  }
  body{ transition:padding-right .32s cubic-bezier(.4,0,.2,1); }
  body.kc-scroll-lock .buybar{
    right:min(var(--kc-w),92vw);
    transition:right .32s cubic-bezier(.4,0,.2,1);
  }
  .buybar{ transition:right .32s cubic-bezier(.4,0,.2,1); }
}
@media (max-width:600px){
  :root{ --kc-w:80vw; }
}
@media (prefers-reduced-motion:reduce){
  body, body.kc-scroll-lock, .buybar, body.kc-scroll-lock .buybar{ transition:none; }
}

/* ============================================================
   MOBILE OVERLAY: SCROLL LOCK + THUMB-SIZED TOUCH TARGETS (2026-08-04,
   store owner)
   ============================================================ */
/* Real scroll lock, mobile only. The desktop body.kc-scroll-lock rule
   (style.css, ~line 3224, deliberately empty) is untouched -- non-modal
   desktop still lets the page scroll behind the drawer, unchanged.
   Mobile overlays the page instead of pushing it, so the page
   underneath has to stop scrolling while the drawer covers it, the
   same as any modal dialog. */
@media (max-width:600px){
  body.kc-scroll-lock{ overflow:hidden; }
}

/* Thumb-sized touch targets (2026-08-04, store owner: "your cart needs
   to be a little bigger so it's just easier to click with your thumb
   at the top of the screen... check the quantity steppers and the new
   trash icon against the same 44px floor"). 44x44 CSS px is the WCAG
   target-size floor. The glyph inside each control keeps its existing
   visual size; only the tappable BOX grows, via min-width/min-height +
   flex centring rather than padding math, which stays correct
   regardless of the glyph's own metrics. */
@media (max-width:600px){
  .kc-title{ font-size:20px; }
  .kc-close{
    min-width:44px; min-height:44px; padding:0;
    display:inline-flex; align-items:center; justify-content:center;
  }
  .kc-qty button{ width:44px !important; height:44px !important; }
  .kc-remove{ min-width:44px; min-height:44px; }

  /* MEASURED HARD CONSTRAINT, mobile only: row 3 (qty stepper + trash,
     now 44px targets, + the percent tag) needs ~303px on one row
     (controls 168px measured live + 10px column-gap + ~125px for
     "10% OFF APPLIED" unwrapped); 80vw of a 390px phone leaves
     .kc-item-info only ~208px. The two explicit mobile requirements --
     44px touch targets and a percent tag that never wraps -- cannot
     both be satisfied on ONE shared row at this width; every spacing
     reduction tried (tighter gaps, min-width:0 cascades) closed at most
     ~40px of a ~95px gap. Touch-target size is the one treated as
     non-negotiable here (a target below 44px is a real usability
     failure, not a polish gap), so the tag moves to its OWN row instead
     of wrapping -- grid-row:4, explicitly placed rather than
     auto-flowed, which is what leaves row 3's second column empty
     instead of auto-placing the tag back into it. It keeps the SAME
     right-column edge, so "shares one edge down all rows" still holds
     visually; only the row count for a discounted item becomes 4 on
     mobile instead of 3. FLAGGED rather than silently shipped: desktop
     (360px, confirmed live) fits the literal 3-row spec with zero
     wrapping and no placement override, so this is a mobile-only,
     width-driven exception, not a reinterpretation of the row
     structure generally.

     SECOND CATCH on the first version of this fix: grid-column:2 alone
     still confined the tag to the SAME narrow auto column controls
     forces on row 3, because CSS grid column TRACKS are shared across
     every row they pass through -- moving to a new row does not widen
     the column an item sits in. grid-column:1/-1 (span both tracks,
     full row width) plus justify-self:end is what actually frees it:
     its own right edge still lands on the shared column-2 boundary
     (nothing to its right constrains that), but it can now use the
     FULL ~208px row instead of the ~84px sliver row 3's controls left
     for column 2, comfortably over the ~125px "10% OFF APPLIED" needs
     unwrapped. Confirmed live: see this hand-back's VERIFY section. */
  .kc-tag{ grid-column:1/-1; grid-row:4; justify-self:end; align-self:start; margin-top:2px; }
}

/* NARROW PHONES: buy back the width the 44px targets cost (2026-08-04,
   caught by verify-drawer-row-geometry.mjs on its very first run, which is
   the entire reason that suite now exists).

   MEASURED, at a 360px viewport (Galaxy S8/S9/S10, Pixel 3a, most budget
   Androids -- not an edge case):
     drawer            = 80vw           = 288px
     .kc-body          - 22px padding x2 = 244px for .kc-item
     .kc-item          - 48px thumb - 12px column-gap = 184px for .kc-item-info
     .kc-item-info NEEDS 114px (left track's floor, set by the two 44px
       stepper targets + trash) + 10px column-gap + 75.6px (the struck
       line total, nowrap by design) = 199.6px.
   Short by 15.6px, so the whole right column overflowed its own container
   and landed 6.4px off the drawer's edge instead of the intended 22px. The
   three right-column elements still shared one edge with EACH OTHER
   (variance 0.0), which is why this survived the earlier visual passes: it
   does not read as misalignment, it reads as slightly-too-tight, and only
   on a discounted line where the struck total is the widest thing present.

   Fixed by giving the row 20px back rather than by shrinking a touch
   target: padding-inline 22 -> 14 (16px) and .kc-item's thumb gap 12 -> 8
   (4px), for 204px against the 199.6px needed. The 44px floor set in the
   600px block above is deliberately left alone; it is the requirement, not
   the slack.

   Breakpoint is 380px, NOT 400px, on purpose: 390px already clears this
   with 8.4px to spare and is the width the store owner reviewed and
   approved, so it keeps its 22px padding untouched. 375px (iPhone SE, 13
   mini) was over by 3.6px and is inside this fix. */
@media (max-width:380px){
  .kc-body{ padding-inline:14px; }
  .kc-item{ column-gap:8px; }
}

/* ============================================================
   CATEGORY FILTER BAR: PER-CATEGORY COLOUR (2026-07-31)
   Store owner: "you changed the category pills on the product
   images, but you did not change it at the top where you can
   click on it to filter."

   Keyed off data-category, which holds the STABLE taxonomy key
   (recovery, skin, cognition, metabolic, growth, longevity,
   diluent), not the display label. That is deliberate: the labels
   were renamed today (COGNITION->BRAIN, METABOLIC->WEIGHT LOSS,
   GROWTH HORMONE->GROWTH) and the card pills, which key off
   sanitize_title(label), had to be renamed with them. These
   cannot break that way. Same hues as .sku-pill so a category
   reads as one colour in the filter and on the card.

   Only the resting state is tinted. .is-active keeps the existing
   selected treatment untouched, so "which filter is on" still
   reads as a single consistent signal rather than 7 different ones.
   ============================================================ */
.sku-category-pill[data-category="recovery"]:not(.is-active){ color:#4FB79E; border-color:rgba(79,183,158,.45); }
.sku-category-pill[data-category="skin"]:not(.is-active){ color:#D584AC; border-color:rgba(213,132,172,.45); }
.sku-category-pill[data-category="cognition"]:not(.is-active){ color:#7B9BD4; border-color:rgba(123,155,212,.45); }
.sku-category-pill[data-category="metabolic"]:not(.is-active){ color:#E0763A; border-color:rgba(224,118,58,.45); }
.sku-category-pill[data-category="growth"]:not(.is-active){ color:#D9B43F; border-color:rgba(217,180,63,.45); }
.sku-category-pill[data-category="longevity"]:not(.is-active){ color:#A596DD; border-color:rgba(165,150,221,.45); }
.sku-category-pill[data-category="diluent"]:not(.is-active){ color:#9AA0A2; border-color:rgba(154,160,162,.45); }

/* MOBILE GRID DROP RETIRED (2026-07-31 to 2026-08-04). This used to
   collapse the shop grid to one column while the drawer was open on a
   narrow screen, because the PUSH reserve was squeezing it down to
   ~126-258px 2-up columns. Mobile no longer pushes at all (the DESKTOP:
   PUSH. MOBILE: OVERLAY. block above) -- the grid behind an overlaying
   drawer is simply covered, not narrowed, so it never needs to reflow
   in the first place. Removed rather than left inert, since a reader
   finding an unconditional `@media(max-width:600px){ body.kc-scroll-
   lock .sku-grid{...} }` with no matching squeeze anywhere nearby would
   have no way to tell it was dead without re-deriving this same history. */

/* ------------------------------------------------------------
   ACTIVE FILTER PILL TAKES ITS OWN HUE (2026-07-31, store owner:
   "if I click brain it should then be solid blue").

   The base rule (.sku-category-pill.is-active, line ~675) fills
   every selected pill with copper. My earlier colour rules were
   scoped :not(.is-active) to avoid fighting it, which left the
   selected pill copper no matter which category it was. That is
   the bug: the colour disappeared at exactly the moment it carried
   the most meaning.

   Text is --ink on every fill, not white. All 7 hues are light
   enough that dark type clears contrast on them, and it matches
   the ALL pill's existing copper-with-ink treatment, so the bar
   reads as one system. This mirrors the --on-ember token's own
   note: these accents pass only with dark text.
   ------------------------------------------------------------ */
.sku-category-pill.is-active[data-category="recovery"]{ background:#4FB79E; border-color:#4FB79E; color:var(--ink); }
.sku-category-pill.is-active[data-category="skin"]{ background:#D584AC; border-color:#D584AC; color:var(--ink); }
.sku-category-pill.is-active[data-category="cognition"]{ background:#7B9BD4; border-color:#7B9BD4; color:var(--ink); }
.sku-category-pill.is-active[data-category="metabolic"]{ background:#E0763A; border-color:#E0763A; color:var(--ink); }
.sku-category-pill.is-active[data-category="growth"]{ background:#D9B43F; border-color:#D9B43F; color:var(--ink); }
.sku-category-pill.is-active[data-category="longevity"]{ background:#A596DD; border-color:#A596DD; color:var(--ink); }
.sku-category-pill.is-active[data-category="diluent"]{ background:#9AA0A2; border-color:#9AA0A2; color:var(--ink); }

/* ------------------------------------------------------------
   ABSOLUTE/FIXED ELEMENTS MUST BE INSET TOO (2026-07-31, DESKTOP ONLY
   as of 2026-08-04)
   body{padding-right} moves normal-flow content out from under the
   drawer, but it does nothing for elements taken out of flow.
   Measured on live at 390px with the drawer open: 8 elements still
   extended past the drawer's left edge, and every one of them was
   either .site-header (position:absolute, full 390px) or a child of
   it, plus .shop-hero-sweep (a decorative absolute layer). That is
   why the nav read "SHOP VERIFY AFFILIAT" with the word cut.

   Scoped to @media(min-width:601px) now: that measurement was taken
   when 390px was still a PUSH viewport. Mobile overlays as of this
   pass, so .site-header is never inset there any more (see DESKTOP:
   PUSH. MOBILE: OVERLAY. above) -- it stays full-width and simply gets
   covered by the drawer sliding over it, same as the rest of the page.
   ------------------------------------------------------------ */
@media (min-width:601px){
  body.kc-scroll-lock .site-header{
    right:min(var(--kc-w),92vw);
    transition:right .32s cubic-bezier(.4,0,.2,1);
  }
  .site-header{ transition:right .32s cubic-bezier(.4,0,.2,1); }
  body.kc-scroll-lock .shop-hero-sweep{ max-width:calc(100% - min(var(--kc-w),92vw)); }
}
@media (prefers-reduced-motion:reduce){
  .site-header, body.kc-scroll-lock .site-header{ transition:none; }
}

/* NAV-LINKS-HIDE RETIRED (2026-07-31 to 2026-08-04). Existed only
   because insetting .site-header on a narrow, PUSHED screen left too
   little width for the wordmark + nav + cart to all fit. .site-header
   is never inset on mobile any more (immediately above), so its full
   width is always available and the nav never needs to make room. */

/* ============================================================
   RESERVE WIDTH, PLAIN-VALUE FALLBACK (2026-08-01)
   Store owner reported the drawer still covering the catalogue on
   iOS Safari, in incognito, after the fix verified clean in
   Chromium at the same viewport. The difference is the engine, and
   the most likely failure is the declaration itself: the rules
   above express the reserve as min(var(--kc-w),92vw), and a
   custom property nested inside a math function is exactly the
   construct that gets dropped by an older engine. A dropped
   declaration produces silence, not an error, which is why it
   verified clean here and failed there.

   These restate the same intent with literal lengths and no
   custom properties, no min(), no calc(). They come last in the
   file, so where the modern form works this changes nothing, and
   where it was being dropped this is what applies.

   Also belt-and-braces: the grid gets an explicit max-width, so
   even if the body padding is ignored entirely, the catalogue
   still cannot render underneath the rail.

   KEEP THIS IN LOCKSTEP WITH --kc-w (found while doubling the width,
   2026-08-04): these three properties (padding-right / site-header
   right / buybar right) and --kc-w's own min(var(--kc-w),92vw) rules
   above are the SAME specificity (one class each) and these come
   later in the file, so on every engine, not just an old one, these
   plain values are what actually wins the cascade for those three
   properties -- the min(var(...),92vw) declarations above are only
   ever live for .kc-drawer's own width and .shop-hero-sweep's
   max-width, which nothing later overrides. A change to --kc-w that
   is not mirrored here does not "gracefully fall back" on an old
   engine; it silently renders the OLD reserve width in every browser
   while the drawer itself renders the NEW width, reopening exactly
   the "cart covers the Add to Cart button" bug fixed above. Confirmed
   by updating together across every pass on this value: 150 -> 300 ->
   360.

   DESKTOP ONLY as of 2026-08-04 (@media(min-width:601px), matching the
   DESKTOP: PUSH. MOBILE: OVERLAY. block above): mobile does not push at
   all any more, so a mobile-scoped copy of this fallback would be
   restoring the exact squeeze the overlay redesign exists to remove.
   Absence is the correct mobile behaviour here, not an oversight.
   ============================================================ */
@media (min-width:601px){
  body.kc-scroll-lock{ padding-right:360px; }
  body.kc-scroll-lock .site-header{ right:360px; }
  body.kc-scroll-lock .buybar{ right:360px; }
  body.kc-scroll-lock .sku-grid{ max-width:calc(100% - 0px); }
}

/* Market price: figure left, label right (2026-08-01, store owner).
   The figure now shares a left edge with .product-price directly beneath
   it, so the eye reads $200 struck / $89 as one vertical pair. The label
   is pushed down in visual weight since it is now trailing context
   rather than the lead-in. */
.market-compare{ align-items:baseline; }
.mc-price{ order:1; }
.mc-label{ order:2; font-size:10px; opacity:.85; }
