/* X Factor Peptides — site-wide safety net v9.5
   Loaded on customer pages. Admin opts out.
   This file does NOT override typography sizing — each page owns its own
   type scale via Tailwind utilities. We only provide layout & a11y safety.
*/

:root {
  --xfp-touch-min: 44px;
  --xfp-pad-x: clamp(12px, 4vw, 28px);
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Prevent any element from busting out of its flex/grid container. */
* { min-width: 0; }

/* Media: never wider than the column they're in. */
img, video, svg, canvas, picture, iframe, object, embed {
  max-width: 100%;
  height: auto;
}

/* Long-word safety on every heading and paragraph — prevents overflow on
   narrow phones for SKU codes, lot numbers, scientific names, URLs. */
h1, h2, h3, h4, p, li, dd {
  overflow-wrap: break-word;
  word-break: normal;
}

/* Touch targets — WCAG 2.5.5 / Apple HIG / Material */
button,
a.btn,
.btn,
[role="button"],
input[type="submit"],
input[type="button"],
select {
  min-height: var(--xfp-touch-min);
}

/* Inputs at >=16px so iOS Safari doesn't auto-zoom on focus. */
input, select, textarea {
  font-size: max(16px, 1em);
}

/* Sticky header gets safe-area on notched devices. */
.sticky-nav,
nav.sticky,
header.sticky {
  padding-top: env(safe-area-inset-top);
}

/* Phone breakpoint */
@media (max-width: 640px) {
  .cart-drawer,
  #cart-drawer,
  [data-cart-drawer] {
    width: 100vw !important;
    max-width: 100vw !important;
    right: 0 !important;
    left: 0 !important;
  }

  .sticky-cta,
  [data-sticky-cta],
  .mobile-sticky-add {
    padding-bottom: max(12px, env(safe-area-inset-bottom));
  }

  table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* Prop 65 warning style — used on every product page. */
.prop65-warning {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 12px 14px;
  margin: 18px 0;
  border: 1px solid #f59e0b;
  background: rgba(245, 158, 11, 0.06);
  color: #fcd34d;
  font-size: 13px;
  line-height: 1.45;
  border-radius: 8px;
}
.prop65-warning a { color: #fde68a; text-decoration: underline; }
.prop65-warning strong { color: #fef3c7; }

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

/* Print-friendly */
@media print {
  nav, footer, .cart-drawer, .sticky-cta, [data-no-print] { display: none !important; }
  body { color: black; background: white; }
}

/* Focus-visible — keyboard-only outline */
:focus-visible {
  outline: 2px solid #C8102E;
  outline-offset: 3px;
  border-radius: 4px;
}
:focus:not(:focus-visible) { outline: none; }

/* Skip-to-content link styling (injected on every page) */
.skip-nav:focus { left: 8px !important; top: 8px !important; }

/* =====================================================================
   v9.6 — responsive polish layer (additive, mobile-scoped & opt-in).
   Safe by design: universal rules below only set defaults that improve
   touch/scroll quality; layout-changing rules live inside @media or
   behind opt-in hooks ([data-stack], .scroll-row) so desktop is untouched.
   ===================================================================== */

/* Smoother momentum + no rubber-band flash on iOS; kill grey tap flash. */
html { overscroll-behavior-y: none; }
* { -webkit-tap-highlight-color: rgba(200,16,46,.18); }

/* Guarantee the PDP hero image always fits its panel (no right-edge crop). */
#vial-img, #vial-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Belt-and-suspenders: no element forces the page wider than the viewport. */
html, body { max-width: 100%; overflow-x: hidden; }

/* Product/media images: consistent crop instead of squish. */
img[data-cover] { width: 100%; height: 100%; object-fit: cover; }

/* Opt-in horizontal scroller for product rails (add class="scroll-row"). */
.scroll-row {
  display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 4px;
}
.scroll-row::-webkit-scrollbar { display: none; }
.scroll-row > * { scroll-snap-align: start; flex: 0 0 auto; }

/* ---------- tablet ---------- */
@media (max-width: 900px) {
  :root { --xfp-pad-x: clamp(14px, 4.5vw, 24px); }
  .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0,1fr)) !important; }
}

/* ---------- phone ---------- */
@media (max-width: 640px) {
  /* Collapse multi-column marketing grids to a single readable column. */
  .grid-2, .grid-3, .grid-4,
  [data-cols] { grid-template-columns: 1fr !important; }

  /* Comfortable side gutters for any centered container that forgot them. */
  .container, main > section, [data-pad] { padding-left: var(--xfp-pad-x); padding-right: var(--xfp-pad-x); }

  /* Stackable data tables (opt-in): table[data-stack] becomes cards. */
  table[data-stack], table[data-stack] tbody, table[data-stack] tr, table[data-stack] td { display: block; width: 100%; }
  table[data-stack] thead { position: absolute; left: -9999px; }
  table[data-stack] tr { border: 1px solid #1f1f24; border-radius: 10px; margin-bottom: 10px; padding: 6px 10px; }
  table[data-stack] td { display: flex; justify-content: space-between; gap: 12px; border: 0; padding: 6px 0; }
  table[data-stack] td::before { content: attr(data-label); color: #9a9aa0; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }

  /* Tighten oversized hero type a touch so it never overflows narrow phones. */
  h1 { line-height: 1.08; }

  /* Buttons go full-width in stacked CTA groups for fat-finger friendliness. */
  .btn-row .btn, .btn-row button, [data-cta-stack] .btn { width: 100%; text-align: center; }

  /* Sticky bottom add-to-cart bar shadow so it reads above content. */
  .sticky-cta, [data-sticky-cta] { box-shadow: 0 -8px 24px rgba(0,0,0,.45); }
}

/* Landscape phones / very short viewports: trim vertical hero padding. */
@media (max-height: 480px) and (orientation: landscape) {
  [data-hero], .hero { padding-top: 16px !important; padding-bottom: 16px !important; }
}

/* Larger fine-pointer screens: cap line length for readability. */
@media (min-width: 1100px) {
  .prose, [data-readable] { max-width: 72ch; }
}

/* PDP product image fit (prevents portrait vial overflow/clipping) */
#vial-img img, #vial-img svg { max-width:100%; max-height:100%; width:auto; height:auto; object-fit:contain; display:block; margin:0 auto; }

/* tactile press feedback (premium micro-interaction) */
.btn-primary:active,.btn-ghost:active,button:active{transform:translateY(1px)}
.btn-primary,.btn-ghost{transition:transform .12s cubic-bezier(.16,1,.3,1),background .2s,border-color .2s,color .2s}

/* Cookie banner ---------------------------------------------------------
   The banner's classes are assigned from JavaScript at runtime, and the
   arbitrary width utility it relied on (w-[calc(100%-2.5rem)]) never took
   effect: at a 360px viewport the banner shrink-wrapped to 222px instead of
   spanning the screen, so the consent text wrapped into a narrow column in
   the middle of the page. Plain CSS, no build step, no JIT timing. */
.xfp-cookie-banner {
  position: fixed;
  left: 1.25rem;
  right: 1.25rem;
  bottom: 1.25rem;
  /* Was z-index:120, which put it ABOVE the cart drawer (z-95), the product
     modal (z-90) and the notify modal (z-120, tied). Pinned to the bottom edge
     full-width on a phone, it landed exactly on top of the cart's checkout
     button: a real browser could not click Checkout at all while the banner was
     up, and Playwright confirmed it - "xfp-cookie-banner intercepts pointer
     events", every retry, until the click timed out.

     A consent notice must never sit above a modal the customer is trying to
     act in. 84 puts it over ordinary page content and the bot panel is at 85,
     while every overlay that takes over the screen now covers it. */
  z-index: 84;
  transform: none;
}
@media (min-width: 640px) {
  .xfp-cookie-banner {
    left: 50%;
    right: auto;
    width: auto;
    max-width: 32rem;
    transform: translateX(-50%);
  }
}

/* MISSING TAILWIND UTILITIES ------------------------------------------------
   assets/tailwind.css is a PREBUILT file (frozen 18 Jul) and is not rebuilt on
   deploy, so any Tailwind class not already inside it simply does not exist.
   That is a live trap: moving /research from md: to lg: column spans silently
   produced no CSS at all, every grid child fell back to one of twelve columns,
   and the page rendered as 43px ribbons 250,000px tall.

   These are the lg: column utilities Tailwind would have emitted. mobile.css
   loads after tailwind.css on every page, so defining them here is additive and
   cannot conflict. If tailwind.css is ever rebuilt properly, this block becomes
   redundant rather than wrong.
   ------------------------------------------------------------------------- */
@media (min-width: 1024px) {
  .lg\:col-span-1 { grid-column: span 1 / span 1; }
  .lg\:col-span-2 { grid-column: span 2 / span 2; }
  .lg\:col-span-3 { grid-column: span 3 / span 3; }
  .lg\:col-span-4 { grid-column: span 4 / span 4; }
  .lg\:col-span-5 { grid-column: span 5 / span 5; }
  .lg\:col-span-6 { grid-column: span 6 / span 6; }
  .lg\:col-span-7 { grid-column: span 7 / span 7; }
  .lg\:col-span-8 { grid-column: span 8 / span 8; }
  .lg\:col-span-9 { grid-column: span 9 / span 9; }
  .lg\:col-span-10 { grid-column: span 10 / span 10; }
  .lg\:col-span-11 { grid-column: span 11 / span 11; }
  .lg\:col-span-12 { grid-column: span 12 / span 12; }
  .lg\:sticky { position: sticky; }
  .lg\:self-start { align-self: flex-start; }
  .lg\:top-\[88px\] { top: 88px; }
}

/* MISSING TAILWIND UTILITIES, SECOND BATCH ---------------------------------
   Found by checking every breakpoint-prefixed layout class used anywhere in the
   HTML against the prebuilt CSS. All of these were in the markup and producing
   no style at all:

     md:hidden                  - the mobile nav strip added to product pages had
                                  no way to hide itself on desktop
     md:grid-cols-[1fr,360px]   - the two-column layout on the five category
                                  pages (growth, healing, longevity, metabolic,
                                  skin). Note the comma: Tailwind arbitrary
                                  values separate tracks with an underscore, so
                                  this was never going to compile even against a
                                  live build
     md:grid-cols-[220px,1fr]   - same mistake on /about
     lg:grid-cols-[380px,1fr]   - same on the label maker
     sm:h-9, md:grid-cols-6,
     sm:grid-cols-5, and the
     admin 4-track grid         - same class of silent no-op
   ------------------------------------------------------------------------- */
@media (min-width: 640px) {
  .sm\:h-9 { height: 2.25rem; }
  .sm\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
  .md\:hidden { display: none; }
  .md\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .md\:grid-cols-\[1fr\,360px\] { grid-template-columns: 1fr 360px; }
  .md\:grid-cols-\[220px\,1fr\] { grid-template-columns: 220px 1fr; }
  .md\:grid-cols-\[1fr\,auto\,auto\,auto\] { grid-template-columns: 1fr auto auto auto; }
}
@media (min-width: 1024px) {
  .lg\:grid-cols-\[380px\,1fr\] { grid-template-columns: 380px 1fr; }
}

/* Mobile tap targets ---------------------------------------------------------
   Audited at 390px: navigation and footer links rendered 16-18px tall. WCAG 2.2
   asks for 24px minimum and Lighthouse wants 48px; links sitting inside a
   sentence are exempt and are left alone. This covers the repeated pattern -
   a link that is the sole content of a list item, which is every footer and
   nav column on the site - plus the section strip added to product pages.
   ------------------------------------------------------------------------- */
@media (max-width: 640px) {
  footer li > a,
  nav li > a,
  aside li > a {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  nav[aria-label="Sections"] a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* Tap targets, second pass: the product-page footer uses <div> columns with
   block links rather than lists, so the li rule above did not reach it, and the
   breadcrumb trail sits at 14px. Breadcrumbs stay inline - making them 44px
   blocks would break the horizontal trail - and get padding instead, which
   grows the hit box without changing the row. */
@media (max-width: 640px) {
  footer a.block {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  nav a { padding-block: 10px; }
  a.inline-flex { min-height: 44px; }
}

/* Tap targets, third pass: footers that use neither lists nor .block links, and
   the citation lists in the research library. Citation links keep their inline
   layout (a reference line is prose, not a menu) and get padding, which grows
   the hit box without reflowing the list. */
@media (max-width: 640px) {
  footer a:not(p a):not(span a) {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  article li > a,
  .doc li > a { padding-block: 6px; }
}

/* xl: utilities the prebuilt file does not contain. The research library's
   nested text+aside grids and the footer split at 1024 left 187-210px columns,
   so they now hold single-column until 1280. */
@media (min-width: 1280px) {
  .xl\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .xl\:col-span-2 { grid-column: span 2 / span 2; }
}

/* Cumulative Layout Shift --------------------------------------------------
   Three pages render their body from a fetch after first paint, so the page
   was drawn short and everything below jumped when the content arrived:
   /compare 0.246, /legal/<policy> 0.180, /learn/ 0.134 - all above Google's
   0.1 "good" threshold and all a Core Web Vitals ranking input. Reserving the
   space the content will occupy removes the shift without changing the design.
   ------------------------------------------------------------------------- */
#indexGrid:empty,
#picker:empty { min-height: 60vh; display: block; }
/* #doc ships a 'Loading...' paragraph, so :empty never matches it; the
   renderer clears this inline once the policy is in the DOM. */
#doc { min-height: 60vh; }

/* Touch target sizing ------------------------------------------------------
   Measured on the live site with a real browser at 360/390/768/834/1024. The
   layout itself is sound - zero horizontal overflow at every width - but the
   navigation links were not tappable to spec: header items came out 17-32px
   wide on a phone ("COAs" was 17x40) and the footer column links were 20px
   TALL on tablets. WCAG 2.2 (2.5.8) asks for 24px minimum; Apple's HIG and
   Material both ask for 44px. Anything under that is a mis-tap, and a mis-tap
   in a footer is a bounced session.

   This is padding and hit-area only. No colour, size, wording or layout
   changes, so nothing here can alter a claim the site makes. */

nav a,
footer a,
.site-footer a,
[class*="footer"] a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

/* Links sitting inside running prose keep their natural inline behaviour -
   turning those into 44px blocks would wreck paragraph rhythm and is not what
   the guidance is asking for. */
p a, li a, td a, span a, small a, .prose a {
  min-height: 0;
  display: inline;
  align-items: initial;
}

/* Give the horizontal nav rows a little breathing room so neighbouring
   targets do not touch, which is the other half of the same guideline. */
nav a { padding-left: 6px; padding-right: 6px; }

/* The prose exemption above is by ancestor, and footers legitimately use a <p>
   as a link row ("Legal hub - Security policy - AI policy" on faq.html is one
   paragraph with three links in it). Those are navigation, not prose, so put
   the target size back for links inside nav/footer regardless of wrapper. */
nav p a, nav li a, nav span a,
footer p a, footer li a, footer span a,
[class*="footer"] p a, [class*="footer"] li a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

/* Contrast ------------------------------------------------------------------
   Measured on the live site by compositing every semi-transparent layer down to
   the page background, which is the only way to get a true figure - reading
   backgroundColor straight off the element reports rgba(200,16,46,0.05) as if it
   were solid #C8102E and invents failures that are not there.

   With that done properly, the site is in good shape: /, /contact and /coa/ have
   no contrast failures at all. One token does fail.

   .text-white/40 over the #050505 page ground is 3.71:1. WCAG 2.2 AA asks 4.5:1
   for text under 24px, and this token is used at text-xs across 79 files. Moving
   it to 50% white gives 5.30:1 and keeps it the dimmest step in the scale, below
   /50's intended slot rather than merging with it visually.

     white/40 -> 3.71:1  FAIL      white/50 -> 5.30:1  PASS
     white/45 -> 4.46:1  FAIL      white/60 -> 7.33:1  PASS

   mobile.css loads after tailwind.css on every page, so this override needs no
   change to the 79 files that use the class. */
.text-white\/40 { color: rgba(255,255,255,.5) !important; }


/* Target size, part 3: buttons and CTAs -------------------------------------
   The responsive sweep (13 widths, 320-1920, 9 pages) found the *link*-shaped
   controls were already fixed by the nav/footer rules above, but the
   button-shaped ones were not: "Home" and "Back" measured 78x40 and 74x40,
   "Full catalog" 144x40, "View" on the catalog cards 58x36. All clear WCAG 2.2
   SC 2.5.8 (24px) but sit under the 44px both Apple HIG and Material ask for,
   and "View" sits next to "Add" on a card foot where a mis-tap adds a vial to
   the cart instead of opening the product. Four pixels of height is the whole
   fix.

   min-height only for <button>, which already honours it. Anchors styled as
   buttons are inline boxes, so they need the display change too or the
   min-height is ignored. */
.btn, .btn-primary, .btn-ghost, .view-btn, .add-btn, .notify-btn { min-height: 44px; }

/* The :not() guard is not cosmetic. Without it this rule sets display on
   .btn-primary with specificity 0,1,1, which outranks Tailwind's .hidden at
   0,1,0 - the header's "Catalog" button is `hidden sm:inline-flex`, and the
   first version of this rule made it render at 320px, on top of a header that
   had no room for it. Any anchor whose class list mentions hidden at all is
   left alone, and its own responsive display utilities decide when it shows. */
a.btn:not([class*="hidden"]),
a.btn-primary:not([class*="hidden"]),
a.btn-ghost:not([class*="hidden"]),
a.view-btn:not([class*="hidden"]),
a.add-btn:not([class*="hidden"]),
a.notify-btn:not([class*="hidden"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Nav rows must wrap, not crush ---------------------------------------------
   faq.html's masthead nav is `flex gap-5` with no wrap. At 320px the five links
   had nowhere to go, so the flex algorithm shrank them instead: "Wholesale" was
   23px wide, one letter per line, and "Home" was 18px. Nothing overflowed, so
   an overflow check sees a clean page - the text was simply unreadable. Links
   keep their words intact and the row wraps to a second line when it must. */
nav { flex-wrap: wrap; }
nav a { white-space: nowrap; flex-shrink: 0; }

/* Checkboxes were 18px on contact/wholesale/compare and browser-default (~13px)
   on the index attestations, all under the 24px minimum. The wrapping <label>
   means the text is part of the target, but the box itself should still be
   hittable. 20px plus the row padding below puts the whole row over 44px. */
/* flex-shrink matters as much as the width here. These boxes sit as flex items
   in a `flex items-start gap-2` label next to a long paragraph of attestation
   text, so a plain width is only a starting size: the browser then shrinks the
   box to make room for the words. Measured after setting width alone, checkbox
   one came out 10px wide at 320px, 12px at 390px and 14px at 768px - narrower
   the smaller the screen, which is exactly backwards. The declared width has to
   be a floor, not a suggestion. */
input[type="checkbox"], input[type="radio"] {
  width: 20px;
  height: 20px;
  min-width: 20px;
  flex: 0 0 20px;
}

label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) { padding-top: 6px; padding-bottom: 6px; }

/* Cookie banner clearance ---------------------------------------------------
   The banner is position:fixed at bottom:1.25rem, so at 1280px and wider it
   sat on top of the footer link row - elementFromPoint at the centre of those
   links returned the banner, not the link, which means they were genuinely
   unclickable until the banner was dismissed. It is removed from the DOM on
   Accept or Reject, so :has() is exact here: the padding exists only while the
   banner does. */
body:has(.xfp-cookie-banner) { padding-bottom: 168px; }

@media (min-width: 768px) {
  body:has(.xfp-cookie-banner) { padding-bottom: 132px; }
}


/* The header search at 320px --------------------------------------------------
   The masthead is hamburger + wordmark + search + cart in one flex row. At
   320px the content box is 288px and the fixed items claim 203 of it, so the
   search wrapper is allotted about 20px - but the input inside it carries
   pl-10 pr-4, 56px of padding, and border-box sizing cannot shrink a box below
   its own padding. So the input rendered at 58px regardless of its container
   and spilled to the right, landing on top of the cart button: a hit test at
   the centre of #open-cart returned #site-search, and the cart was genuinely
   untappable on a 320px screen.

   min-w-0 on the wrapper fixes the flex arithmetic but cannot fix the padding
   floor. Below 360px there is no honest way to fit a usable search field in
   that row, so it steps aside; search is still one tap away in the drawer. */
@media (max-width: 359px) {
  #site-header div:has(> div > #site-search) { display: none; }
}
