/* peptriva design tokens
   COLOR RULES → see styles/color-rules.md.
   TYPOGRAPHY RULES → see styles/component-rules.md §11 (Switzer for caps/pills).
   Dark UI surfaces are pure black (#000000). Blurple is the brand accent. */

/* Switzer — used for labels, eyebrows, pills, all-caps subtext.
   The "Switzer 650" reference weight from Figma renders via Semibold
   (600 — the nearest available pre-rendered weight in the .otf set). */
@font-face {
  font-family: 'Switzer';
  src: url('../assets/switzer/Switzer-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Switzer';
  src: url('../assets/switzer/Switzer-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Switzer';
  src: url('../assets/switzer/Switzer-Semibold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: optional;
}
@font-face {
  font-family: 'Switzer';
  src: url('../assets/switzer/Switzer-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: optional;
}

:root {
  /* Brand colors — sampled from hero video (electric blues + blurple) */
  --blurple: #5C4DFF;       /* Purple brand accent (hero glow, links, viz) */
  --royal: #0073FF;          /* Mid-tone royal blue */
  --electric: #0017BC;       /* Deep saturated electric blue */
  --halo: #A3B9FF;           /* Luminous pale blue */
  --cyan: #00D4FF;
  --magenta: #FF5DB1;
  --sunset: #FF8E00;
  --yellow: #FFD400;

  /* Neutrals — dark surfaces are PURE BLACK. Do not use navy off-blacks
     (e.g. the old #050814) for backgrounds, buttons, or footer/CTA bands. */
  --black: #000000;
  --midnight: #000000;       /* Pure black; was #050814 (had a navy cast) */
  --ink: #000000;            /* Pure black; was #050814 */
  --slate: #425466;
  --slate-2: #6B7C93;
  --hairline: #E6EBF1;
  --hairline-strong: #D5DCE5;
  --card-border: rgba(0, 0, 0, 0.14);  /* card/panel outer border — darker, neutral hairline */
  --surface: #F6F9FC;
  --white: #FFFFFF;

  /* Peptide Elements v2 — expanded palette for cards, accents, and dark panels */
  --comp: #F0FF4D;            /* Lime complement — featured tier accent */
  --contrast: #1500FE;        /* Deep indigo — featured pricing card bg */
  --analog: #4D97FF;          /* Analog blue — secondary variant */
  --deep-blue-2: #001478;     /* Pairs with --electric (#0017BC) for gradients */
  --glass-bg: rgba(255, 255, 255, 0.10);
  --glass-bg-strong: rgba(255, 255, 255, 0.18);
  --glass-border: rgba(255, 255, 255, 0.22);
  --glass-blur: blur(14px) saturate(140%);

  /* Type */
  --font-display: "Inter Tight", "Inter", system-ui, sans-serif;
  --font-body: "Inter", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, monospace;
  /* Switzer 650-style — used for labels, eyebrows, pills, small-caps
     subtext per component-rules.md §11. Always paired with weight 600
     (the nearest pre-rendered weight to Figma's "650" reference). */
  --font-meta: "Switzer", "Inter", system-ui, sans-serif;

  /* Spacing scale (4-base) */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;
  --s-32: 128px;

  /* Radii */
  --r-sm: 4px;
  --r-md: 4px;
  --r-lg: 4px;
  --r-pill: 999px;

  /* Container */
  --max: 1240px;
  --max-narrow: 880px;

  /* Motion */
  --dur-instant:  0ms;
  --dur-micro:    150ms;
  --dur-quick:    200ms;
  --dur-base:     280ms;
  --dur-emphasis: 380ms;
  --dur-exit:     180ms;

  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in:     cubic-bezier(0.55, 0, 1, 0.45);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease: var(--ease-out);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-body);
  color: var(--midnight);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-size: 16px;
  line-height: 1.55;
}

a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
img, video { display: block; max-width: 100%; }

::selection { background: var(--blurple); color: white; }

/* Type scale */
.h-display {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.02;
  font-size: clamp(48px, 7vw, 96px);
}
.h-1 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.05;
  font-size: clamp(40px, 5.4vw, 72px);
}
.h-2 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
  font-size: clamp(32px, 3.6vw, 48px);
}
.h-3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  font-size: clamp(22px, 2vw, 28px);
}
.eyebrow {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate);
}
.body-lg { font-size: 18px; line-height: 1.55; color: var(--slate); }
.body { font-size: 16px; line-height: 1.6; color: var(--slate); }
.body-sm { font-size: 14px; line-height: 1.55; color: var(--slate); }
.mono { font-family: var(--font-mono); }

/* Containers */
.container { max-width: var(--max); margin: 0 auto; padding: 0 24px; }
.container-narrow { max-width: var(--max-narrow); margin: 0 auto; padding: 0 24px; }

/* Buttons. Base weight bumped 500 → 600 so primary CTAs read with
   more visual confidence — easier to spot scan and closer to the
   weight most ecom UIs ship today. Per-variant weights below
   inherit unless they override. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 20px;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.005em;
  border: 1px solid transparent;
  white-space: nowrap;
  transition:
    background-color var(--dur-micro) var(--ease-out),
    border-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out),
    transform var(--dur-micro) var(--ease-out);
  will-change: transform;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.97); transition-duration: var(--dur-instant); }
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
  transform: none;
}
.btn-primary {
  background: var(--blurple);
  color: white;
  border-color: var(--blurple);
}
.btn-primary:hover { background: #4A3FE6; }
.btn-secondary {
  background: white;
  color: var(--midnight);
  border-color: var(--hairline-strong);
}
.btn-secondary:hover { border-color: var(--midnight); }
.btn-ghost {
  background: transparent;
  color: var(--midnight);
  border-color: transparent;
}
.btn-ghost:hover { background: var(--surface); }
.btn-on-dark {
  background: white;
  color: var(--midnight);
}
.btn-on-dark:hover { background: rgba(255,255,255,.9); }
.btn-outline-on-dark {
  background: transparent;
  color: white;
  border-color: rgba(255,255,255,.32);
}
.btn-outline-on-dark:hover { background: rgba(255,255,255,.08); }
.btn-lg { padding: 14px 24px; font-size: 16px; }
.btn-sm { padding: 8px 14px; font-size: 13px; }

/* Chips */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--surface);
  color: var(--slate);
  border: 1px solid var(--card-border);
}
.chip-blurple { background: rgba(99,91,255,.08); color: var(--blurple); border-color: rgba(99,91,255,.18); }
.chip-cyan    { background: rgba(0,160,200,.08); color: #007a9a; border-color: rgba(0,160,200,.18); }
.chip-magenta { background: rgba(255,93,177,.1); color: #c43a87; border-color: rgba(255,93,177,.22); }
.chip-sunset  { background: rgba(255,142,0,.1); color: #c46a00; border-color: rgba(255,142,0,.22); }
.chip-yellow  { background: rgba(255,212,0,.16); color: #8a6b00; border-color: rgba(255,212,0,.32); }
.chip-on-dark { background: rgba(255,255,255,.08); color: white; border-color: rgba(255,255,255,.16); }

/* Cards */
.card {
  background: white;
  border: 1px solid var(--card-border);
  border-radius: var(--r-lg);
}

/* Hairline divider */
.hr { height: 1px; background: var(--hairline); border: 0; margin: 0; }

/* RUO banner */
.ruo-banner {
  background: var(--midnight);
  color: rgba(255,255,255,.78);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 24px;
  text-align: center;
  position: relative;
  z-index: 50;
}
.ruo-banner b { color: white; font-weight: 600; }

/* Section padding */
.section { padding: 96px 0; }
.section-sm { padding: 64px 0; }
.section-lg { padding: 128px 0; }

@media (max-width: 720px) {
  .section { padding: 64px 0; }
  .section-lg { padding: 80px 0; }
}

/* Utility */
.text-midnight { color: var(--midnight); }
.text-slate { color: var(--slate); }
.text-white { color: white; }
.bg-surface { background: var(--surface); }
.bg-midnight { background: var(--midnight); color: white; }
.bg-white { background: white; }

.flex { display: flex; }
.grid { display: grid; }
.gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; } .gap-6 { gap: 24px; } .gap-8 { gap: 32px; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }

/* v2 status-dot blink (used on stock indicators, live badges) */
@keyframes blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

/* ── Reveal-on-scroll primitives ───────────────────────────────────
 * Mark any element with `data-anim="rise"` and it stays at opacity 0
 * + 8px down until scripts/reveal.js adds `.is-revealed`. Stagger
 * children by adding `data-anim-stagger` to the parent — children
 * inherit the rise + a 50ms cascading delay (capped at 6 items so
 * a long list doesn't waterfall forever).
 * GPU-only (transform + opacity). Skipped wholesale by the
 * reduced-motion block above. */
[data-anim="rise"] {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
  will-change: transform, opacity;
}
[data-anim="rise"].is-revealed {
  opacity: 1;
  transform: translateY(0);
}
[data-anim-stagger] > * {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}
[data-anim-stagger].is-revealed > * { opacity: 1; transform: translateY(0); }
[data-anim-stagger].is-revealed > *:nth-child(1) { transition-delay: 0ms; }
[data-anim-stagger].is-revealed > *:nth-child(2) { transition-delay: 50ms; }
[data-anim-stagger].is-revealed > *:nth-child(3) { transition-delay: 100ms; }
[data-anim-stagger].is-revealed > *:nth-child(4) { transition-delay: 150ms; }
[data-anim-stagger].is-revealed > *:nth-child(5) { transition-delay: 200ms; }
[data-anim-stagger].is-revealed > *:nth-child(n+6) { transition-delay: 250ms; }

/* Product-page accent palette — scoped to .pp-wrap so it stays out of :root.
   Used by the molecule renderer + mechanism tabs on product pages.
   Decorative-only. Don't lift any of these into a "dark surface" — see color-rules.md §1. */
.pp-wrap {
  --acc-primary: var(--blurple);
  --acc-complement: #F4FF35;
  --acc-analogous: #5BA8FF;
  --acc-contrast: #1F2BD9;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─── Chrome layout reserve (anti-CLS) ────────────────────────────────
 * chrome.js is loaded via <script defer>, so the page renders without
 * the announcement bar first, then chrome.js executes and injects it
 * into [data-chrome-top]. Without this min-height, everything below
 * jumps down by ~33px the moment chrome runs.
 *
 * Height derived from chrome.js's own CSS for .announcement-bar
 * (13px font + 9px*2 padding + 1px border ≈ 33px). Only fires when
 * the page opts in via `data-announcement="true"`.
 *
 * The earlier RUO-banner reservation rules have been removed —
 * chrome.js hardcodes `showRuo = false` site-wide (the banner is no
 * longer rendered) and the reservation was creating a phantom 32px
 * gap above the nav on every page that hadn't explicitly opted out
 * with `data-ruo-banner="false"`.
 * ────────────────────────────────────────────────────────────────── */
[data-chrome-top][data-announcement="true"] { min-height: 33px; }

/* ─── First-paint stabilizer + page transition ────────────────────────
 * The page chrome (announcement bar, hero header, footer) is injected
 * by chrome.js into placeholder elements at the END of the DOM. Even
 * with a synchronous <script> tag, the browser renders the rest of
 * the body BEFORE chrome.js's mount() fires — visible as: page
 * appears without chrome → ~50-200ms later chrome pops in. User
 * reads that pop as a "weird animation."
 *
 * Fix: hide the body entirely until chrome has mounted, then fade
 * it in. chrome.js adds `chrome-ready` to <body> at the end of
 * mount(); an inline <script> in <head> sets a 1200ms safety
 * fallback so the page never stays permanently invisible if JS
 * fails for any reason.
 *
 * That opacity-hide is gone now (see the note below @view-transition),
 * and so is the branded loader overlay that used to cover the gap.
 * What remains: the browser paints the body as soon as the HTML is
 * ready, chrome.js slides the nav/footer in behind it, and internal
 * navigation crossfades via View Transitions.
 * ────────────────────────────────────────────────────────────────── */
html { background: #fff; }

/* Cross-document View Transitions — modern browsers (Chrome 126+, Edge
   126+, Safari 18+) render a smooth crossfade between same-origin page
   navigations. Replaces the old opacity-hide pattern, which was
   producing visible white flashes between every page load (body hidden
   at opacity:0 → navigation → next body opacity:0 → eventually fades
   in). The View Transitions crossfade is implemented in the browser's
   compositor, runs over the actual page pixels, and produces NO white
   intermediate frame.
   Older browsers (older Safari, Firefox without flag) ignore the at-
   rule entirely and fall back to the browser's default navigation —
   which is still a brief paint-hold, but no longer the multi-step
   opacity-hide that was producing the flash. */
@view-transition {
  navigation: auto;
}

/* No more body opacity-hide on initial load. The old pattern
   (body { opacity: 0 } → JS flips chrome-ready → 240ms fade-in) was
   the SOURCE of the white-flash bug: between pages, the browser saw
   body opacity:0 on the new page until chrome.js finished mounting,
   producing a visible white gap. Letting the browser paint the body
   as soon as the HTML is ready means the new page's content appears
   instantly. Chrome.js still injects the nav/footer ~50-200ms later;
   that's a quieter "nav slides in" effect, not a full-page flash.
   .chrome-ready class is still applied (downstream selectors may
   still target it) — it just no longer gates body visibility. */
body.is-navigating {
  /* No-op on modern browsers (View Transitions runs the crossfade).
     On older browsers without view-transitions support, fall through
     to a quick fade-out so the navigation still feels intentional. */
  opacity: 0.001; /* preserves layout for transitions but lets the browser optimize repaint */
  transition: opacity 120ms ease-in;
}
@supports (view-transition-name: none) {
  /* Browser supports View Transitions — skip the manual opacity fade
     entirely and let the browser-driven crossfade handle the visual. */
  body.is-navigating { opacity: 1; transition: none; }
}

/* ─── Page loader — REMOVED 2026-08-10 ────────────────────────────────
 * There was a full-viewport white splash overlay here (.pl-loader:
 * peptriva wordmark + three pulsing peptide-chain dots) that
 * page-transitions.js injected whenever chrome-ready hadn't flipped
 * within 250ms. It was cut for looking cheesy — and because body
 * visibility is no longer gated on chrome-ready (see above), it was
 * curtaining content the browser had already painted.
 *
 * Do NOT reintroduce a sitewide splash or spinner. A pending state
 * belongs to the component that is actually waiting (cart drawer,
 * checkout submit), not to the whole viewport.
 * ────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  body { transition: none; }
  body.is-navigating { transition: none; }
}
/* Respect user's reduced-motion preference: skip even the brief
   invisible window (page renders incomplete-but-static; better than
   any perceived motion). */
@media (prefers-reduced-motion: reduce) {
  body { opacity: 1; }
}

/* ─── Mobile: respect iOS safe-area-inset for sticky elements ──────
 * Any element fixed/sticky at the bottom (sticky checkout button,
 * floating cart pill, mobile drawer) needs padding-bottom inset for
 * the iOS home-indicator strip — otherwise the indicator overlaps
 * the button and the user can't tap it.
 *
 * Components opt in by adding `safe-area-bottom` class OR by reading
 * the --safe-bottom CSS var directly in their own rules.
 * ────────────────────────────────────────────────────────────────── */
:root {
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-top: env(safe-area-inset-top, 0px);
}
.safe-area-bottom {
  padding-bottom: max(16px, var(--safe-bottom));
}

/* ─── Minimum touch target sitewide (a11y + mobile) ─────────────────
 * 44×44 px is the iOS HIG / WCAG minimum touch target. Apply by
 * default to all interactive elements; opt-out via `.touch-small`
 * for dense data tables / chip groups where 44px is too generous.
 *
 * This list covers anchors-styled-as-buttons that don't carry the
 * `.btn` class — homepage card CTAs, end-of-page CTA banner buttons,
 * footer login, cart line remove, suggest-add, etc. New click-target
 * anchor classes MUST extend this list (or use the `.btn` class) so
 * the touch-target floor stays enforced across all pages.
 * ────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  button:not(.touch-small),
  a.btn:not(.touch-small),
  a.lg-pill:not(.touch-small),
  .ac-btn:not(.touch-small),
  .od-btn:not(.touch-small),
  input[type="submit"]:not(.touch-small),
  /* Anchor-CTAs that don't carry `.btn` — kept opt-in by class name
     so unrelated text links (article inline links, footnote refs)
     stay un-padded. */
  a.hp-blend-card__cta,
  a.ctabanner__btn,
  a.site-footer__login,
  a.site-footer__refer,
  a.cart-suggest__add,
  a.rw-cta-product,
  .cart-line__remove,
  .site-drawer__link,
  .site-header__login,
  .site-header__refer {
    min-height: 44px;
  }
  /* Square icon-buttons need BOTH dimensions ≥ 44px — min-height alone
     leaves a tall-thin click target. Listed separately so the rule
     reads explicitly. */
  .site-header__apps,
  .coa-modal__close,
  .site-drawer__close,
  .stack-modal-close,
  .va-toast-close,
  .assistant__icon-btn,
  .verify-modal__close {
    min-width: 44px;
    min-height: 44px;
    aspect-ratio: 1 / 1;
  }
  /* Small inline close-× buttons (chip removals, dropdown clears).
     Opt out of the 44px floor so the round × stays a small square
     inside its parent chip — but lock aspect-ratio:1 + matching
     min-width/min-height so the (pointer:coarse) min-height bump
     from the base rule above can't stretch them into a vertical
     bar. Visible size bumped to 24px for a comfortable tap, while
     the parent chip remains the primary touch zone.
     NOTE: selectors are prefixed with `button` (specificity 0,1,1) so they
     TIE `button:not(.touch-small)` (also 0,1,1) and, being later in source,
     WIN — without the prefix the bare class (0,1,0) lost to the 44px rule and
     the × stretched to 24×44 (a vertical oval that ballooned the pill). */
  button.rs-active-chip__x,
  button.cat-active-pill__remove {
    min-width: 24px;
    min-height: 24px;
    width: 24px;
    height: 24px;
    aspect-ratio: 1 / 1;
    flex-shrink: 0;
  }
  /* Footer + legal anchor rows — text-link styling means the global
     button rule doesn't catch them. Pad the click area without
     changing the visual baseline (padding is symmetrical, so the
     row spacing stays visually identical). */
  .site-footer__legal a,
  .site-footer__cols a,
  .footer-social-link {
    padding-top: max(10px, env(safe-area-inset-bottom, 0px) * 0);
    padding-bottom: 10px;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* ─── iOS Safari zoom-on-focus prevention ──────────────────────────────
 * Safari iOS auto-zooms the viewport whenever a form input has a
 * font-size below 16px. The visible result on phones is a jarring
 * zoom-in the moment the buyer taps an email / address / promo field
 * during checkout, plus a manual zoom-out afterwards. This kills
 * checkout conversion on mobile (#1 root cause behind cart drop-off
 * for ecom Safari users).
 *
 * Fix: force every interactive input to ≥16px on touch devices. Done
 * via the same (pointer: coarse) media query the touch-target rule
 * uses, so desktop styling is untouched.
 * ────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  input[type="text"]:not(.touch-small),
  input[type="email"]:not(.touch-small),
  input[type="password"]:not(.touch-small),
  input[type="tel"]:not(.touch-small),
  input[type="url"]:not(.touch-small),
  input[type="search"]:not(.touch-small),
  input[type="number"]:not(.touch-small),
  input[type="date"]:not(.touch-small),
  input:not([type]):not(.touch-small),
  textarea:not(.touch-small),
  select:not(.touch-small) {
    font-size: 16px;
  }
}

/* ─── Critical modal-offscreen positioning (anti-flash) ──────────────
 * .coa-modal markup is hardcoded inline in some pages (e.g. index.html
 * line 3527) — BEFORE chrome.js's <script> tag near the bottom. The
 * full modal styles (position: fixed, transform offscreen, transitions,
 * etc.) are injected by chrome.js at mount() via injectStyles(), but
 * that runs AFTER the inline modal markup has already been parsed.
 *
 * Result: ~50-200ms window where the .coa-modal renders in normal
 * document flow as a giant card — visible to the user as a sudden
 * slide-in / appearance when chrome.js's styles arrive and snap the
 * modal offscreen.
 *
 * Fix: declare ONLY the minimum CSS needed to keep the modal offscreen
 * (position + transform) here in the synchronous stylesheet. The
 * cosmetic + transition rules stay in chrome.js's injectStyles — they
 * don't matter for first-paint stability.
 *
 * Same problem applies to .verify-modal (RUO consent gate).
 * ────────────────────────────────────────────────────────────────── */
.coa-modal,
.verify-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
}
.coa-modal__panel {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, 100%);
}
.coa-modal[aria-hidden="false"] {
  pointer-events: auto;
}
.coa-modal[aria-hidden="false"] .coa-modal__panel {
  transform: translate(-50%, 0);
}
/* verify-modal is hard-hidden when not active. Its visible/anim styles
   live in chrome.js's injectStyles. Without the pointer-events flip the
   inherited `pointer-events: none` from the base rule above blocks every
   click inside the modal — the RUO checkbox can't be ticked. */
.verify-modal[aria-hidden="true"] {
  display: none;
}
.verify-modal[aria-hidden="false"] {
  pointer-events: auto;
}


/* ───────────────────────────────────────────────────────────────
   .pp-research — static "Continue researching" section injected
   into every /product/{slug}/index.html AFTER </main>. Lives
   outside <main> because product-flagship.js does main.innerHTML
   = ... on init, which would wipe descendant content. Loaded
   sitewide via tokens.css; selectors are scoped to .pp-research
   so they don't leak.
─────────────────────────────────────────────────────────────── */
.pp-research {
  border-top: 1px solid var(--hairline);
  padding: 64px 40px 80px;
  background: var(--surface);
}
.pp-research__inner { max-width: 1100px; margin: 0 auto; }
.pp-research__eyebrow {
  font-family: var(--font-meta);
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate-2);
  margin: 0 0 14px;
}
.pp-research__h {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ink);
  margin: 0 0 32px;
}
.pp-research__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 32px;
}
.pp-research__list > li { padding: 0; margin: 0; }
.pp-research__list a {
  display: grid;
  gap: 4px;
  padding: 20px 0;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--hairline);
  transition: opacity .15s;
}
.pp-research__list a:hover { opacity: 0.72; }
.pp-research__type {
  font-family: var(--font-meta);
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--slate-2);
}
.pp-research__title {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.3;
  color: var(--ink);
}
.pp-research__list a:hover .pp-research__title {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.pp-research__dek {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--slate);
}
.pp-research__more {
  margin: 28px 0 0;
  font-size: 14px;
}
.pp-research__more a {
  color: var(--ink);
  text-decoration: none;
  font-weight: 500;
  border-bottom: 1px solid var(--ink);
  padding-bottom: 1px;
}
.pp-research__more a:hover { opacity: 0.7; }

@media (max-width: 999px) {
  .pp-research { padding: 48px 28px 64px; }
  .pp-research__h { font-size: 24px; margin-bottom: 24px; }
  .pp-research__list { grid-template-columns: 1fr; gap: 0; }
}
@media (max-width: 599px) {
  .pp-research { padding: 40px 20px 56px; }
  .pp-research__h { font-size: 22px; }
  .pp-research__title { font-size: 15px; }
  .pp-research__dek { font-size: 13px; }
}

/* ── PDP-plan Phase 4c — commerce lock (D-10, D-13) ────────────────────
   The inline head gate (auth-gate v4) sets BOTH `is-authed` (has a session)
   and `is-shop-open` (may see prices) on <html> PRE-PAINT, so these rules
   resolve before first paint with no flash in any state. CSS is the second
   line of defence — every commerce renderer is also guarded in JS on
   window.PEPTRIVA_GATE. Do NOT use the `hidden` attribute on #add-btn
   (author `display` beats UA [hidden]) — the class pair below is
   deterministic: html.is-shop-open #add-btn (0,1,1,1) beats .pg-hide.

   BOTH classes are matched on purpose. In account mode the gate sets them
   together, so this is unchanged behaviour. In age-gate mode `is-shop-open`
   is what an age-verified guest carries. And renewal.html runs its OWN
   older price-gate script (#peptriva-price-gate) that sets only
   `is-authed` — keying solely on is-shop-open would silently hide prices
   from signed-in visitors on that page. */
.pg-hide { display: none; }
html.is-authed .pg-lock,
html.is-shop-open .pg-lock { display: none; }
/* Age-gate mode has no sign-in requirement, so the "Sign in for pricing"
   affordance (baked into PDPs by prerender-pdp-lib and hand-written into
   /renewal's cards) is a false statement there — hide it in that mode
   whether or not the visitor has consented yet. The price VALUE is still
   withheld until shopOpen, so this reveals nothing early. */
html.is-age-gate .pg-lock { display: none; }
html.is-authed #add-btn,
html.is-shop-open #add-btn { display: inline-flex; }
/* D-10: suppress the price element itself when the shop is closed to this
   visitor — its .99 fallback text is never edited, so the no-reflow rule
   survives intact. SCOPED to /renewal cards only: the homepage keeps its own
   long-standing price-gate treatment by owner decision (2026-08-08 — "not on
   the signup page or homepage"), and catalog/PDP/drawer prices are
   suppressed in JS. */
html:not(.is-authed):not(.is-shop-open) .rn-card [data-bp-price],
html:not(.is-authed):not(.is-shop-open) .rn-card [data-bp-price-x],
html:not(.is-authed):not(.is-shop-open) .rn-card [data-bp-price-discount],
html:not(.is-authed):not(.is-shop-open) .rn-card [data-bp-saved],
html:not(.is-authed):not(.is-shop-open) .rn-card [data-bp-price-per-vial] { display: none; }
/* Locked-out mobile: the sticky add bar has no login affordance of its own —
   hide it; the in-page buybox lock is the CTA. */
html:not(.is-authed):not(.is-shop-open) .va-mobile-cta { display: none; }

/* The lock affordance itself (baked by prerender-pdp.mjs / catalog render) */
.pg-lock[data-price-lock], .pg-lock--card {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 16px 18px;
  background: #fff;
}
.pg-lock__h { margin: 0 0 10px; font-size: 14px; font-weight: 500; color: var(--ink); }
.pg-lock__cta {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 20px; min-height: 44px; box-sizing: border-box;
  background: var(--ink); color: #fff; border-radius: 999px;
  font-size: 14px; font-weight: 500; text-decoration: none;
  appearance: none; -webkit-appearance: none;
}
.pg-lock__meta { margin: 10px 0 0; font-size: 12px; color: var(--slate); }
.pg-lock--card { padding: 10px 12px; }
.pg-lock--card .pg-lock__cta { padding: 8px 14px; min-height: 36px; font-size: 13px; }
a.va-add-btn--locked { text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }
