/* ═══════════════════════════════════════════════════════════════════════════
   vanta-pdp.css — THE PRODUCT DETAIL PAGE.

   Owner brief, 2026-08-24 (client demo): "make product pages for vanta match
   this styling — only difference with ours is we dont do subscriptions." The
   reference is the client's own number-one reference site, refinedbiolabs.com
   (voice.references[0] in client.json), and the surface being matched is its
   PDP: a tall tinted media plate on the left, and on the right ONE bordered
   card that carries the whole purchase decision in a fixed order —

       category · title + price · attribute chips · one-line description ·
       size · quantity · bundle · COA + add-to-cart · dispatch · details

   WHAT IS MATCHED AND WHAT IS NOT. The reference is taken for STRUCTURE — the
   order of the blocks, the card, the chip and pill idiom, the label register,
   the bundle row, the dispatch panel. It is NOT taken for colour or type:
   Vanta is a monochrome brand (frontend/vanta-mono.css) on Sora/Barlow
   (frontend/vanta-type.css), and the reference's lavender/serif identity has
   no business here. BUILD-DEFAULTS.md's hierarchy of authority reads a
   reference site for structure, never for colour, and this file is that rule
   applied to one page.

   THE SUBSCRIPTION BLOCK IS NOT HERE, and its absence is the owner's own line:
   this engine has no subscription rail, so a "Subscribe & save" panel would be
   a control that does nothing and a price that cannot be charged. It is not
   styled away — it is never emitted (see apply-pdp.mjs).

   ── TWO HOUSE RULES THIS FILE KEEPS ───────────────────────────────────────

   NO HEX IN THIS FILE, AND NO LITERAL FONT FAMILY. Three sessions are on this
   client at once: the palette lives in vanta-mono.css (declared on html:root,
   so it out-specifies every :root on the site) and the type lives in
   vanta-type.css. Every colour and every face below is a var() read from those,
   so the palette can move under this file without it going stale.

   SPECIFICITY, NOT SOURCE ORDER. product.html carries ~170 lines of its own
   inline <style> IN THE BODY — later in document order than this <link> in
   <head> — so an equal-weight rule there would beat one here. Every rule below
   is scoped `#product-main …` (1,1,0 or better), which beats every class rule
   in that block and every option rule in theme.css (0,2,1). Do not "simplify"
   those selectors down to the bare class.

   NOTHING HERE INVENTS A FACT. The dispatch panel's words come from
   client.json `shipping` (Mon–Fri, 2 PM cutoff, 3–5 business days in transit,
   free over $100 else $9.99 — the same numbers checkout.html charges); the
   purity chip renders only when a lot report published one. See vanta-pdp.js.
   ═══════════════════════════════════════════════════════════════════════════ */

#product-main {
  --vp-gap:       clamp(24px, 3.2vw, 52px);
  --vp-pad:       clamp(20px, 2.4vw, 32px);
  --vp-radius:    clamp(16px, 1.5vw, 22px);
  --vp-radius-sm: 12px;
  --vp-hair:      1px solid var(--sf-border-color);
  --vp-label-fs:  0.62rem;

  /* ── THE GLYPHS ──────────────────────────────────────────────────────────
     Four line icons, inline, drawn in the same 24-grid / 2px-stroke idiom the
     master already uses on this page so they do not read as a second icon set.
     They are MASKS, never images: every one is painted with
     `background-color: currentColor`, so it inks from whatever element it
     sits on and follows the palette with no colour written here.

     `stroke='black'` inside these is NOT a palette value and is not the
     exception to this file's no-hex rule — a mask reads only the ALPHA of the
     image it is given, so the stroke colour is geometry. It is a keyword
     rather than a hex so nobody greps this file for a colour and finds one. */
  --vp-ico-cart:  url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='8' cy='21' r='1'/><circle cx='19' cy='21' r='1'/><path d='M2 3h2.5l2.6 12.4a2 2 0 0 0 2 1.6h8.9a2 2 0 0 0 2-1.6L21.5 7H5.2'/></svg>");
  --vp-ico-truck: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2'/><path d='M15 18H9'/><path d='M19 18h2a1 1 0 0 0 1-1v-3.6a1 1 0 0 0-.2-.6l-3.5-4.4A1 1 0 0 0 17.5 8H14'/><circle cx='17' cy='18' r='2'/><circle cx='7' cy='18' r='2'/></svg>");
  --vp-ico-tag:   url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z'/><path d='M7.5 7.5h.01'/></svg>");
  --vp-ico-clock: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><polyline points='12 6 12 12 16 14'/></svg>");
  --vp-ico-doc:   url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z'/><path d='M14 2v4a2 2 0 0 0 2 2h4'/><path d='M16 13H8'/><path d='M16 17H8'/></svg>");
}

/* ── THE TWO COLUMNS ───────────────────────────────────────────────────────
   The media plate is the narrower half. The reference runs ~46/54 and the card
   is the taller of the two, so the plate STICKS: on a long card the vial stays
   beside the block being read instead of scrolling away at the first section.
   The offset clears the floating nav pill AND the research-use strip above it
   (--sf-ruo-h, declared in chrome-refresh.css — another session's file, read
   here as a token with a 0px fallback so this rule is safe without it). */
#product-main .vp-grid { display: grid; grid-template-columns: 1fr; gap: var(--vp-gap); }

@media (min-width: 900px) {
  #product-main .vp-grid { grid-template-columns: minmax(0, 0.86fr) minmax(0, 1fr); align-items: start; }
  #product-main .vp-media { position: sticky; top: calc(var(--sf-nav-h, 64px) + var(--sf-ruo-h, 0px) + 22px); }
}

/* ── THE MEDIA PLATE ───────────────────────────────────────────────────────
   Tall portrait, not square: the subject is a vial, the reference gives it a
   plate about three times taller than it is wide, and the master's `aspect
   1/1 · max-height 480px` was the crop that made a 10ml vial read as a thumb.
   3:4 is as far as this goes — past it the plate outruns a laptop viewport and
   the sticky column stops being able to hold it.

   The ground is a silver WASH rather than a flat fill. The reference tints its
   plate to the product; on a monochrome brand the equivalent is light falling
   off toward the foot of the frame. Both stops resolve from --sf-photo-bg,
   which vanta-mono.css declares per register, so this is correct on the light
   page and would still be correct inside a dark one — and the falloff stops at
   16% ink because the "For Research Use Only" overlay is set on the DARKEST
   corner of it and has to stay legible there. */
#product-main .vp-gallery {
  aspect-ratio: 3 / 4;
  max-height: none;
  border-radius: var(--vp-radius);
  border: var(--vp-hair);
  background:
    radial-gradient(112% 78% at 50% 12%, color-mix(in srgb, var(--sf-elevated) 94%, transparent) 0%, transparent 64%),
    linear-gradient(162deg, var(--sf-photo-bg) 0%, color-mix(in srgb, var(--sf-photo-bg) 84%, var(--sf-text)) 100%);
  box-shadow: var(--sf-shadow-1);
  display: flex; align-items: center; justify-content: center;
}

/* ── THE CARD ──────────────────────────────────────────────────────────────
   One panel, one border, one radius. theme.css's buybox option already makes
   this a card at (0,2,1); this raises it onto --sf-elevated and gives it the
   reference's breathing room. */
#product-main .vp-buybox {
  background: var(--sf-elevated);
  border: var(--vp-hair);
  border-radius: var(--vp-radius);
  box-shadow: var(--sf-shadow-1);
  padding: var(--vp-pad);
  /* A grid item's min-width defaults to AUTO, so this column can never be
     narrower than its own min-content — and the out-of-stock notify row
     (email field + "NOTIFY ME") sets that at ~368px. On the ONE SKU that is
     out of stock the column pushed the whole document 17px past a 375px
     viewport; every in-stock PDP looked fine because it never renders the
     row. The field is already flex:1 1 0 with min-width:0, so letting the
     column shrink is all that was missing. */
  min-width: 0;
}

/* ── 1. CATEGORY ───────────────────────────────────────────────────────────
   The reference sets this in an italic serif. Vanta has no serif, so it takes
   the BODY face in italic — Barlow ships a real italic, so this is a drawn
   face and not a synthesised slant. Same role, same position, Vanta's kit. */
#product-main .vp-cat {
  font-family: var(--sf-font-body);
  font-style: italic;
  font-size: 0.86rem;
  line-height: 1.4;
  color: var(--sf-muted);
  margin: 0 0 6px;
}

/* ── 2. TITLE + PRICE ──────────────────────────────────────────────────────
   One baseline-aligned row. The price column shrinks to its content and is
   right-set, so a long compound name wraps under itself instead of pushing the
   price off the card. */
#product-main .vp-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 18px; margin: 0 0 16px;
}
#product-main .vp-title {
  font-family: var(--sf-font-display);
  font-size: clamp(1.9rem, 3.6vw, 2.6rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.04;
  color: var(--sf-text);
  margin: 0;
}
#product-main .vp-price-col { flex: 0 0 auto; text-align: right; display: flex; flex-direction: column; align-items: flex-end; }
#product-main .vp-from {
  font-size: var(--vp-label-fs);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sf-quiet, var(--sf-muted));
  margin-bottom: 2px;
}
#product-main .vp-price-col #product-price {
  font-family: var(--sf-font-display);
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--sf-text);   /* the price is a headline here, not an accent chip */
}
#product-main .vp-price-col #product-was { font-size: 0.95rem; margin-bottom: 3px; }
#product-main .vp-price-col .disc-badge { margin-top: 6px; }

/* ── 3. ATTRIBUTE CHIPS ────────────────────────────────────────────────────
   Outline pills, uppercase, tracked. The master renders each trust badge with
   a check glyph inside it; the reference's chips carry no icon, and a row of
   ticks reads as a claim rather than as a spec, so the glyph is hidden and the
   words are left to do the work. */
#product-main .vp-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 16px; }
#product-main #product-badges { display: contents; }
#product-main .vp-chip,
#product-main #product-badges > * {
  display: inline-flex; align-items: center;
  padding: 6px 13px;
  border: var(--vp-hair);
  border-radius: 999px;
  background: transparent;
  font-family: var(--sf-font-body);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--sf-muted);
  white-space: nowrap;
}
#product-main #product-badges > * > svg { display: none; }
/* The dose is the one chip that is a MEASUREMENT, so it is the one that is
   inked rather than outlined — the same move the reference uses to make the
   purity figure the first thing read out of the row. */
#product-main .vp-chip.vp-chip-key {
  border-color: transparent;
  background: color-mix(in srgb, var(--sf-text) 7%, transparent);
  color: var(--sf-text);
}

/* ── 4. THE ONE-LINE DESCRIPTION ───────────────────────────────────────────
   p.subtitle from the catalogue — the sentence that already exists, not a new
   one written for this layout. */
#product-main .vp-sub {
  font-size: 0.92rem;
  line-height: 1.62;
  color: var(--sf-muted);
  margin: 0 0 22px;
}

/* ── THE SECTION LABEL ─────────────────────────────────────────────────────
   "Select vial size" / "Quantity" / "Bundle & save". One register for all
   three, and it is the register the master already uses for #vol-pricing's own
   label, so nothing on the card is speaking in a fourth voice.

   These are NOT micro headers in the BUILD-DEFAULTS §2d sense: each one is the
   only label its control has — a stepper with no word beside it is not
   self-describing — which is the exemption that rule states in its own line: a
   label stays when it is the content's only label. */
#product-main .vp-label {
  display: block;
  font-size: var(--vp-label-fs);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sf-quiet, var(--sf-muted));
}
#product-main .vp-label-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }

/* ── 5. SIZE ───────────────────────────────────────────────────────────────
   #variant-row is the master's, written by renderProductPage(); it stays
   hidden while a listing has one size, which is every Vanta SKU today. The
   pills are restyled to the reference's outline idiom for the day a
   multi-size listing exists. */
#product-main #variant-row { margin-bottom: 20px; }
#product-main #variant-row > div:first-child {
  font-size: var(--vp-label-fs) !important;
  letter-spacing: 0.16em !important;
  color: var(--sf-quiet, var(--sf-muted)) !important;
  margin-bottom: 10px !important;
}
#product-main .variant-pill {
  border-radius: 999px;
  padding: 9px 18px;
  background: var(--sf-elevated);
  border-color: var(--sf-border-color);
}
#product-main .variant-pill.active {
  border-color: var(--sf-text);
  background: color-mix(in srgb, var(--sf-text) 5%, transparent);
}

/* ── 6. QUANTITY ───────────────────────────────────────────────────────────
   Label left, stepper right, on one line — the reference's arrangement. The
   stepper itself is the master's #qty-row, unchanged in markup so
   changePageQty() and the coming-soon disabling still reach it. */
#product-main .vp-qty-block {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin: 0 0 22px;
}
#product-main #qty-row { margin: 0; }
#product-main .sf-qty {
  border: var(--vp-hair);
  border-radius: 999px;
  padding: 3px;
  background: var(--sf-elevated);
}
#product-main .sf-qty .qty-btn {
  width: 34px; height: 34px;
  border: none; border-radius: 999px;
  background: transparent;
  color: var(--sf-text);
  font-size: 1.05rem;
}
#product-main .sf-qty .qty-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--sf-text) 8%, transparent); }
#product-main .sf-qty .qty-btn:disabled { opacity: 0.35; cursor: default; }

/* ── 7. BUNDLE & SAVE ──────────────────────────────────────────────────────
   The reference's bundle row: a card per tier, each showing the bottles it
   buys, a floating badge on the ones worth pointing at, and an accent border
   on the one selected. The TIERS THEMSELVES ARE UNTOUCHED — the thresholds and
   the discounts are business data (CLAUDE.md, "we are the web designer, not
   the business owner"), they are what selectVolTier() charges, and a layout is
   not a reason to move a price. The reference shows three because it sells
   three; Vanta sells four, so four are drawn.

   The bottles are one image — the product's own photo — repeated and
   overlapped, painted from --vp-vial-img, which vanta-pdp.js sets once per
   render on the root. A custom property rather than <img> children because
   nothing then has to be rebuilt when the page re-renders on a variant swap.

   The badge stays INSIDE the space the master reserves for it. That reserved
   band is a fix, documented in product.html: the badge is out of flow, and
   floating it onto the card's top edge is what used to drop "POPULAR" 4px into
   "RESEARCH SET" on every client ever built. Its look changes here; its box
   does not. */
#product-main .vol-tiers { gap: 8px; margin: 0 0 8px; }
#product-main .vol-card {
  border: 1.5px solid var(--sf-border-color);
  border-radius: var(--vp-radius-sm);
  background: var(--sf-elevated);
  padding: calc(var(--sf-vol-badge-top) + var(--sf-vol-badge-h) + var(--sf-vol-badge-gap)) 6px 12px;
}
#product-main .vol-card:hover { border-color: color-mix(in srgb, var(--sf-text) 34%, transparent); }
#product-main .vol-card.active {
  border-color: var(--sf-accent);
  background: color-mix(in srgb, var(--sf-accent) 6%, transparent);
}
/* The row keeps its height whether or not a photo resolves, so a SKU still
   awaiting artwork gives a ragged row rather than four different card heights.

   THE BACKGROUND IS ZOOMED, NOT `contain`, and that is the whole reason these
   read as bottles. The source is the square catalogue render, in which the vial
   occupies about 30% of the width and 65% of the height — fitted `contain` into
   a 24px box it comes out ~7px wide and reads as a speck. 230% sizes the SOURCE
   FRAME to ~55px so the vial fills the box vertically while the box crops the
   empty plate at its sides; past ~250% the frame outgrows the box and the crop
   starts taking the cap and the foot off, which is a worse thumbnail than the
   speck. Both figures are tied to the render's own framing, which vial.mjs
   holds constant for every SKU this factory will ever build. */
#product-main .vp-vials { display: flex; align-items: flex-end; justify-content: center; height: 46px; margin-bottom: 9px; }
#product-main .vp-vial {
  display: block; width: 24px; height: 46px;
  background-image: var(--vp-vial-img, none);
  background-size: 230% auto;
  background-position: 50% 50%;
  background-repeat: no-repeat;
}
#product-main .vp-vial + .vp-vial { margin-left: -6px; }
#product-main .vol-card-label { font-size: 0.55rem; letter-spacing: 0.14em; margin-bottom: 4px; }
#product-main .vol-card-qty { font-size: 0.76rem; font-weight: 800; letter-spacing: -0.01em; }
#product-main .vol-card-save { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.02em; }
#product-main .vol-badge {
  border-radius: 999px;
  padding: 0 8px;
  font-size: clamp(9px, 0.5rem, 11px);
  letter-spacing: 0.09em;
  background: var(--sf-accent);
  color: var(--sf-accent-text);
}
#product-main .vol-summary { margin-bottom: 18px; }

/* ── 8. THE ACTION ROW ─────────────────────────────────────────────────────
   COA beside add-to-cart, exactly as the reference has it: the document and
   the purchase are the two things a buyer in this vertical is choosing
   between, and putting the report one click from the button is the whole
   argument of the page. */
#product-main .vp-cta { display: flex; align-items: stretch; gap: 10px; margin: 0 0 18px; }
#product-main .vp-coa {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 0 20px; min-height: 54px;
  border: var(--vp-hair); border-radius: 999px;
  background: var(--sf-elevated);
  color: var(--sf-text);
  font-family: var(--sf-font-body);
  font-size: 0.66rem; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
  text-decoration: none;
  transition: border-color .18s ease, background .18s ease;
}
#product-main .vp-coa:hover { border-color: var(--sf-text); background: color-mix(in srgb, var(--sf-text) 5%, transparent); }
#product-main .vp-coa:focus-visible { outline: 2px solid var(--sf-accent); outline-offset: 3px; }

#product-main #atc-btn.vp-atc {
  flex: 1 1 auto;
  min-height: 54px;
  border-radius: 999px;
  font-size: 0.8rem; font-weight: 800; letter-spacing: 0.1em;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
}
/* THE CART GLYPH IS A PSEUDO-ELEMENT, and that is load-bearing, not a shortcut.
   setComingSoonUI() and the live-stock fetch both set this button's textContent
   — an icon CHILD would be wiped the first time either ran, on exactly the
   states where the button matters most. A ::before survives all of them, and a
   mask takes currentColor, so the glyph re-inks with the button through
   coming-soon, out-of-stock and hover alike with no colour written here. */
#product-main #atc-btn.vp-atc::before {
  content: ""; flex: none; width: 17px; height: 17px;
  background-color: currentColor;
  -webkit-mask: var(--vp-ico-cart) center / contain no-repeat;
          mask: var(--vp-ico-cart) center / contain no-repeat;
}
#product-main #atc-btn.vp-atc:disabled::before { display: none; }

/* ── 9. DISPATCH ───────────────────────────────────────────────────────────
   The reference's shipping panel. Every line in it is read from this client's
   own shipping config by vanta-pdp.js, and the two facts at the foot are the
   two the checkout actually charges. The reference's "shipping protection" and
   "overnight shipping" rows are NOT reproduced: this engine sells neither, and
   a panel offering a service the cart cannot add is worse than a shorter one. */
#product-main .vp-ship {
  border: var(--vp-hair);
  border-radius: var(--vp-radius-sm);
  background: var(--sf-surface);
  padding: 15px 17px;
  margin: 0 0 18px;
}
#product-main .vp-ship-top { display: flex; align-items: flex-start; gap: 11px; }
#product-main .vp-ship-head { font-size: 0.88rem; font-weight: 800; color: var(--sf-text); margin: 0 0 3px; letter-spacing: -0.01em; }
#product-main .vp-ship-sub { font-size: 0.76rem; line-height: 1.5; color: var(--sf-muted); margin: 0; }
#product-main .vp-ship-div { height: 1px; background: var(--sf-border-color); margin: 13px 0 12px; }
#product-main .vp-ship-facts { display: flex; flex-wrap: wrap; gap: 8px 22px; }
#product-main .vp-ship-fact { display: inline-flex; align-items: center; gap: 8px; font-size: 0.76rem; color: var(--sf-muted); }

#product-main .vp-ico {
  flex: none; display: inline-block; width: 15px; height: 15px;
  background-color: currentColor;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
#product-main .vp-ico-lg { width: 19px; height: 19px; margin-top: 1px; color: var(--sf-text); }
#product-main .vp-ico-truck { -webkit-mask-image: var(--vp-ico-truck); mask-image: var(--vp-ico-truck); }
#product-main .vp-ico-tag   { -webkit-mask-image: var(--vp-ico-tag);   mask-image: var(--vp-ico-tag); }
#product-main .vp-ico-clock { -webkit-mask-image: var(--vp-ico-clock); mask-image: var(--vp-ico-clock); }
#product-main .vp-ico-doc   { -webkit-mask-image: var(--vp-ico-doc);   mask-image: var(--vp-ico-doc); }

/* ── STOCK ─────────────────────────────────────────────────────────────────
   Kept, moved, quietened. The master's stock line is a live readout — the
   /api/products fetch rewrites it — so it is not decoration to be dropped for
   a layout. It sits directly above the button, where a buyer looks for it.
   Its COLOUR is vanta-mono.css's business (see that file's STATUS section: in
   stock is monochrome here, low and out keep their hue) and is not set here. */
#product-main .stock-row { margin: 0 0 12px; gap: 10px; }
#product-main .stock-label { font-size: 0.76rem; font-weight: 800; letter-spacing: 0.02em; }
#product-main .stock-note { font-size: 0.72rem; }

/* ── 10. RESEARCH DETAILS ──────────────────────────────────────────────────
   The card's last block, as in the reference. It carries the master's
   #product-specs — the spec table, or the tested-proof panel once a lot report
   is published — under one heading, instead of the unlabelled table the master
   floats above the button. */
#product-main .vp-research { border-top: var(--vp-hair); margin-top: 4px; padding-top: 18px; }
#product-main .vp-research-h {
  font-family: var(--sf-font-display);
  font-size: 1.05rem; font-weight: 800; letter-spacing: -0.02em;
  color: var(--sf-text);
  margin: 0 0 10px;
}
#product-main .vp-research .spec-panel,
#product-main .vp-research .tested-panel { margin: 0; }
#product-main .vp-research .spec-panel { border: none; background: transparent; padding: 0; }
#product-main .vp-research .tested-panel { border-radius: var(--vp-radius-sm); }

/* The research-use notice closes the card. It is the one block on the page
   that is legally load-bearing, so it keeps its frame — quieter, not smaller. */
#product-main .research-warn {
  border-radius: var(--vp-radius-sm);
  border-color: color-mix(in srgb, var(--sf-text) 14%, transparent);
  background: color-mix(in srgb, var(--sf-text) 3%, transparent);
  margin: 16px 0 0;
}
#product-main .research-warn strong { color: var(--sf-text); }

/* ── NARROW ────────────────────────────────────────────────────────────────
   Below 900px the card is the page. The bundle row folds to two-up rather than
   shrinking four cards past legibility, and the action row stacks so the
   button keeps its full width. */
@media (max-width: 620px) {
  #product-main .vp-head { flex-direction: column; align-items: flex-start; gap: 8px; }
  #product-main .vp-price-col { align-items: flex-start; text-align: left; }
  #product-main .vp-cta { flex-direction: column-reverse; }
  #product-main .vp-coa { justify-content: center; }
  #product-main .vol-tiers { grid-template-columns: repeat(2, 1fr); }
}
