/* ============================================================
   FABBRUCCIO: THE PDP (section, single product)

   Ported from beta2 site.css:65-148 (hero, gallery, buybox, accordion),
   :208-219 (the sticky buy-bar), :221-238 (the size-guide dialog),
   :241-257 (the two breakpoints) and :427-442 (THE MOBILE CAROUSEL).

   Markup: the fabbruccio/pdp-* dynamic blocks in inc/pdp-render.php,
   ordered by templates/single-product.html.
   Behaviour: assets/js/fab-site.js sections 5, 6, 8 and 9.

   The storytelling spreads, the editorial portrait, the press row and the
   reviews are NOT here: they are the shared primitives in fab-craft.css,
   fab-ugc.css and fab-press.css, which the section map loads on this route.

   beta2 -> here
     .pdp        -> .fab-pdp            .crumb      -> .fab-crumb
     .pdp-hero   -> .fab-pdp__hero      .gallery    -> .fab-gallery
     .shot       -> .fab-shot           .shot--hero -> .fab-shot--hero
     .shot-hint  -> .fab-shot__hint     .swapped    -> .is-swapped
     .buybox     -> .fab-buybox         .bb-*       -> .fab-buybox__*
     .mto        -> .fab-buybox__mto    .opt-row    -> .fab-buybox__opt
     .sizes      -> .fab-sizes          .sel        -> .is-selected
     .cta        -> .fab-cta (fab-base) .bnpl       -> .fab-buybox__bnpl
     .lining     -> .fab-buybox__lining .swatch     -> .fab-swatch
     .returns    -> .fab-buybox__returns
     .assure     -> .fab-assure         .acc/.spec  -> .fab-acc/.fab-spec
     .buybar     -> .fab-buybar         .docked     -> .is-docked
     .sg-ov/.sg  -> .fab-sg/.fab-sg__panel
     .gal-count  -> .fab-gal-count
   ============================================================ */

/* ------------------------------------------------------------
   LANDMINE 3, AND ON THIS ROUTE IT IS THE ONE THAT MATTERS MOST

   instawhew/assets/css/layout.css:22 sets `body { overflow-x: hidden }`.
   Per CSS Overflow 3, a non-visible value on one axis forces the other
   axis to compute to `auto`, which makes BODY A SCROLL CONTAINER. A
   position:sticky element sticks only against its nearest SCROLLING
   ancestor, so the buy box below would pin to body's scrollport, and
   body's scrollport never scrolls (the viewport does). The buy box would
   simply sit at the top of its column and never move: sticky, silently
   dead, with no error and nothing in the console.

   `clip` is the fix, and the distinction is exact: the same spec rule
   only forces `auto` when the other value is "not visible nor clip".
   `overflow-x: clip` with `overflow-y: visible` is a legal pair, so body
   stops being a scroll container and sticky works again, while horizontal
   overflow is still contained so the no-horizontal-scroll gate is
   unaffected. It does not clip the fixed nav or the fixed buy-bar either:
   overflow does not create a containing block for fixed descendants.

   Scoped to single products, because this file also loads on
   cart/checkout/account, which must keep the parent's behaviour untouched.
   THIS IS THE STICKY-BUYBOX TEST. If someone removes this line, the buy
   box stops sticking and the buy-bar is the only thing left holding the
   price on screen.
   ------------------------------------------------------------ */
body.single-product { overflow-x: clip; }

/* The parent wraps the WooCommerce single product in
   .wp-block-group.woocommerce.product > .site-main (woocommerce.css:1080).
   .fab-main zeroes the padding and escapes the wrapper gutter; this makes
   sure the outer group cannot re-introduce either. */
.wp-block-group.woocommerce.product { padding: 0; margin: 0; }

.fab-pdp { padding-top: clamp(10px, 2vw, 22px); }

/* THE BREADCRUMB. Plain text, caption size, secondary ink, with the
   current page in INK AT WEIGHT 400. Not 500: the current page is not
   emphasis, it is just the one that is not a link. */
.fab-crumb {
  padding: 0 0 22px;
  font-size: var(--fab-size-caption);
  color: var(--fab-ink-secondary);
}
.fab-crumb a { color: inherit; text-decoration: none; }
.fab-crumb a:hover { color: var(--fab-ink); }
.fab-crumb b { color: var(--fab-ink); font-weight: var(--fab-weight-regular); }

/* ============================================================
   THE HERO ROW. Gallery left, buy box right. minmax(0, ...) on the first
   track is what stops a wide image from pushing the grid past its
   container; minmax(320px, ...) on the second is the buy box's floor.
   ============================================================ */
.fab-pdp__hero {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(320px, .92fr);
  gap: clamp(26px, 4vw, 76px);
  align-items: start;
}

/* THE GALLERY, DESKTOP. A flex COLUMN, 12px gaps. Not a grid, not a
   thumbnail rail, no magnifier: SCROLLING IS THE GALLERY. Five or six
   angles, one per row. The product reads as a specimen. */
.fab-gallery {
  display: flex;
  flex-direction: column;
  gap: var(--fab-space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* CROP DISCIPLINE. The angles on this install are 2000x1500, exactly 4:3,
   so a 4/3 stage with object-fit: CONTAIN crops nothing and the stage
   colour is never actually visible. Contain, never cover. */
.fab-shot {
  position: relative;
  margin: 0;
  background: var(--fab-stage-grey);
  aspect-ratio: var(--fab-stage-aspect);
  overflow: hidden;
}
.fab-shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* THE LINING REVEAL. The hero cell carries a second picture and a hint
   chip. Hover on a fine pointer, tap on a coarse one, Enter or Space from
   the keyboard: all three toggle .is-swapped. See fab-site.js section 5. */
.fab-shot--hero { cursor: pointer; }
.fab-shot--hero:focus-visible { outline: 2px solid var(--fab-ink); outline-offset: 3px; }
.fab-shot__swap {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--fab-swap-duration) var(--fab-ease);
}
@media (hover: hover) and (pointer: fine) {
  .fab-shot--hero:hover .fab-shot__swap { opacity: 1; }
  .fab-shot--hero:hover .fab-shot__base {
    opacity: 0;
    transition: opacity var(--fab-swap-duration) var(--fab-ease);
  }
}
.fab-shot.is-swapped .fab-shot__swap { opacity: 1; }
.fab-shot.is-swapped .fab-shot__base { opacity: 0; }

.fab-shot__hint {
  position: absolute;
  left: 16px;
  bottom: 14px;
  padding: 5px 9px;
  background: var(--fab-veil-hint);
  backdrop-filter: var(--fab-blur-chip);
  -webkit-backdrop-filter: var(--fab-blur-chip);
  color: var(--fab-ink-secondary);
  font-size: var(--fab-size-micro);
  letter-spacing: var(--fab-track-upper-wide);
  text-transform: uppercase;
  transition: opacity .3s;
}

/* ============================================================
   THE BUY BOX. Minimal: name and price at the SAME SIZE, one CTA, the
   proof stacked under it, the specs behind native accordions.

   `top` is the nav's height plus a 20px breath, so it never docks under
   the fixed bar. It goes static below 900px, where a sticky column in a
   single-column layout would just be a jumping block of text.
   ============================================================ */
.fab-buybox {
  position: sticky;
  top: calc(var(--fab-nav-height) + 20px);
  min-width: 0;
}
.fab-buybox__eyebrow { margin: 0 0 14px; }

/* NAME AND PRICE ARE BOTH --fab-size-product (1.32rem). The name is
   weight 500, the price weight 400, and that difference is the ONLY
   hierarchy between them. NEVER MAKE THE PRICE LARGER THAN THE NAME:
   that is the identity's minimal-hierarchy rule made testable, and it is
   checked by comparing computed font sizes, not by eye. */
.fab-buybox__name {
  margin: 0;
  font-size: var(--fab-size-product);
  font-weight: var(--fab-weight-medium);
  line-height: var(--fab-leading-product);
  letter-spacing: var(--fab-track-heading-s);
}
.fab-buybox__color {
  margin: 3px 0 0;
  font-size: .9rem;
  color: var(--fab-ink-secondary);
}
.fab-buybox__price {
  margin: var(--fab-space-md) 0 0;
  font-size: var(--fab-size-product);
  font-weight: var(--fab-weight-regular);
  letter-spacing: var(--fab-track-heading-s);
}
/* WooCommerce's price markup, neutralised so it inherits ours. The
   structure is kept: it carries the microdata and the RTL isolation. */
.fab-buybox__price .woocommerce-Price-amount,
.fab-buybox__price bdi,
.fab-buybox__price .woocommerce-Price-currencySymbol {
  font: inherit;
  color: inherit;
}

.fab-buybox__rating {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin: 12px 0 0;
  font-size: var(--fab-size-detail);
  color: var(--fab-ink-secondary);
}

/* THE MADE-TO-ORDER LINE. A 2px oxblood LEFT RULE, which is one of the
   four sanctioned uses of the accent. It is a rule, not a fill: the
   moment this gets a background it has become a field. */
.fab-buybox__mto {
  margin: var(--fab-space-lg) 0 0;
  padding-left: var(--fab-space-sm);
  border-left: 2px solid var(--fab-accent-oxblood);
  font-size: .84rem;
  color: var(--fab-ink-secondary);
}
.fab-buybox__mto b { color: var(--fab-ink); font-weight: var(--fab-weight-medium); }

.fab-buybox__opt {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--fab-space-sm);
  margin: var(--fab-space-xl) 0 11px;
  font-size: var(--fab-size-label);
  letter-spacing: var(--fab-track-upper);
  text-transform: uppercase;
  color: var(--fab-ink-secondary);
}
/* The nudge state. An inline error colour on ONE label is the fifth
   sanctioned use of the accent, and it is the only one that is not
   permanent. */
.fab-buybox__opt .is-error { color: var(--fab-accent-oxblood); }

.fab-sizes {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 7px;
}
/* Genuinely out of stock, from real variation data. Rendered visibly
   disabled rather than quietly missing, so a shopper can tell the
   difference between "we do not make it" and "it is gone". */
.fab-sizes button:disabled {
  opacity: .38;
  cursor: not-allowed;
  text-decoration: line-through;
}
.fab-sizes button:disabled:hover { border-color: var(--fab-hairline); }

.fab-buybox__size-note {
  margin: var(--fab-space-sm) 0 0;
  font-size: .8rem;
  color: var(--fab-ink-secondary);
}

.fab-buybox__form { margin: 22px 0 0; }
.fab-buybox__bnpl {
  margin: 13px 0 0;
  font-size: .8rem;
  line-height: var(--fab-leading-compact);
  color: var(--fab-ink-secondary);
}
.fab-buybox__bnpl b { color: var(--fab-ink); font-weight: var(--fab-weight-medium); }

/* THE SIGNATURE. A 26px circular cherry swatch with an inset ring, plus
   one sentence, above a hairline. THE SWATCH IS THE ONLY ROUND THING IN
   THE ENTIRE SYSTEM, and it is a product swatch rather than a UI shape,
   which is the only reason it is allowed to be round. */
.fab-buybox__lining {
  display: flex;
  align-items: center;
  gap: var(--fab-space-sm);
  margin-top: 22px;
  padding-top: var(--fab-space-lg);
  border-top: 1px solid var(--fab-hairline);
}
.fab-swatch {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: var(--fab-radius-round);
  background: var(--fab-accent-cherry);
  box-shadow: inset 0 0 0 1px var(--fab-swatch-ring);
}
.fab-buybox__lining p {
  margin: 0;
  font-size: var(--fab-size-detail);
  line-height: var(--fab-leading-lining);
  color: var(--fab-ink-secondary);
}
.fab-buybox__lining b { color: var(--fab-ink); font-weight: var(--fab-weight-medium); }

.fab-buybox__returns {
  margin: 18px 0 0;
  font-size: var(--fab-size-detail);
  line-height: var(--fab-leading-compact);
  color: var(--fab-ink-secondary);
}
.fab-buybox__returns b { color: var(--fab-ink); font-weight: var(--fab-weight-medium); }

/* THREE TINY PROOF PAIRS. No icons, no badges, no seals: proof is a
   sentence. One column below 560px. */
.fab-assure {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-top: 22px;
  padding-top: var(--fab-space-lg);
  border-top: 1px solid var(--fab-hairline);
}
.fab-assure span {
  font-size: var(--fab-size-caption);
  line-height: var(--fab-leading-assure);
  color: var(--fab-ink-secondary);
}
.fab-assure b {
  display: block;
  margin-bottom: var(--fab-space-xxs);
  font-size: .8rem;
  font-weight: var(--fab-weight-medium);
  color: var(--fab-ink);
}

/* ============================================================
   THE SPEC ACCORDION. Native <details>/<summary>: no JavaScript, no ARIA
   polyfill, keyboard operable for free, and it still works with JS off.

   The list is bounded by hairlines and the group has no box of its own.
   The marker is a "+" that becomes a minus sign when open. beta2 carries
   `transition: transform .2s` on the marker and it has NO EFFECT (only
   `content` changes, and content is not animatable), so per
   design-language/motion.md it is DROPPED here rather than reproduced.
   Height is deliberately not animated.
   ============================================================ */
.fab-acc { margin-top: 24px; }
.fab-spec { border-top: 1px solid var(--fab-hairline); }
.fab-spec:last-of-type { border-bottom: 1px solid var(--fab-hairline); }
.fab-spec > summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 0;
  list-style: none;
  cursor: pointer;
  font-size: .68rem;
  letter-spacing: var(--fab-track-upper);
  text-transform: uppercase;
  color: var(--fab-ink);
}
.fab-spec > summary::-webkit-details-marker { display: none; }
.fab-spec > summary::after {
  content: "+";
  color: var(--fab-ink-secondary);
  font-size: .95rem;
  line-height: 1;
}
.fab-spec[open] > summary::after { content: "\2212"; }
.fab-spec__body {
  padding: 0 0 18px;
  max-width: var(--fab-measure-intro);
  font-size: var(--fab-size-compact);
  line-height: var(--fab-leading-spec);
  color: var(--fab-ink-secondary);
}

/* ============================================================
   THE STICKY BUY-BAR. Docked once the in-page CTA has scrolled out of
   view ABOVE the viewport, undocked as soon as the footer arrives. z-index
   55, one below the nav's 60.

   TRANSFORM ONLY. It is never faded and never scaled, so the transition
   touches one composited property and cannot cause layout or paint work
   on scroll.
   ============================================================ */
.fab-buybar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 55;
  background: var(--fab-veil-buybar);
  backdrop-filter: var(--fab-blur-chrome);
  -webkit-backdrop-filter: var(--fab-blur-chrome);
  border-top: 1px solid var(--fab-hairline);
  transform: translateY(100%);
  transition: transform var(--fab-buybar-duration) var(--fab-ease);
}
.fab-buybar.is-docked { transform: translateY(0); }

/* Capped at the page measure with the page gutter, so the bar's contents
   line up with the grid above them rather than floating at the edges. */
.fab-buybar__in {
  display: flex;
  align-items: center;
  gap: 18px;
  max-width: var(--fab-wrap);
  margin: 0 auto;
  padding: 11px var(--fab-gutter);
}
.fab-buybar__name { font-size: .88rem; font-weight: var(--fab-weight-medium); }
.fab-buybar__price { font-size: .88rem; color: var(--fab-ink-secondary); }
.fab-buybar__price .woocommerce-Price-amount,
.fab-buybar__price bdi { font: inherit; color: inherit; }
.fab-buybar__size {
  margin-left: auto;
  font-size: var(--fab-size-small);
  color: var(--fab-ink-secondary);
}
/* The same .fab-cta class in a different box: width auto, tighter
   padding, no top margin. One component, two placements. */
.fab-buybar .fab-cta {
  width: auto;
  flex: 0 0 auto;
  margin: 0;
  padding: 12px 26px;
}

/* ============================================================
   THE SIZE-GUIDE DIALOG. [hidden] drives visibility, so the dialog is
   inert and out of the accessibility tree when closed without needing a
   display rule for each state.
   ============================================================ */
.fab-sg {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: var(--fab-veil-modal);
  backdrop-filter: var(--fab-blur-modal);
  -webkit-backdrop-filter: var(--fab-blur-modal);
}
.fab-sg[hidden] { display: none; }
body.fab-noscroll { overflow: hidden; }

.fab-sg__panel {
  position: relative;
  width: 100%;
  max-width: 620px;
  max-height: 88vh;
  overflow: auto;
  padding: clamp(26px, 4vw, 42px);
  background: var(--fab-ground);
  border: 1px solid var(--fab-hairline);
}
.fab-sg__close {
  position: absolute;
  top: 12px;
  right: 16px;
  border: 0;
  background: none;
  color: var(--fab-ink-secondary);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.fab-sg__close:hover { color: var(--fab-ink); }
.fab-sg__panel h2 {
  margin: 6px 0 8px;
  font-size: 1.4rem;
  font-weight: var(--fab-weight-medium);
}
.fab-sg__sub {
  margin: 0 0 22px;
  max-width: var(--fab-measure-wide);
  font-size: var(--fab-size-compact);
  color: var(--fab-ink-secondary);
}
.fab-sg__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fab-size-detail);
}
.fab-sg__table th {
  padding: var(--fab-space-xs);
  border-bottom: 1px solid var(--fab-ink);
  text-align: left;
  font-size: .6rem;
  font-weight: var(--fab-weight-medium);
  letter-spacing: var(--fab-track-upper-tight);
  text-transform: uppercase;
  color: var(--fab-ink-secondary);
}
.fab-sg__table td {
  padding: var(--fab-space-xs);
  border-bottom: 1px solid var(--fab-hairline);
  color: var(--fab-ink-secondary);
}
.fab-sg__table td:first-child {
  color: var(--fab-ink);
  font-weight: var(--fab-weight-medium);
}
.fab-sg__note {
  margin: 18px 0 0;
  padding-top: var(--fab-space-md);
  border-top: 1px solid var(--fab-hairline);
  font-size: var(--fab-size-detail);
  line-height: var(--fab-leading-spec);
  color: var(--fab-ink-secondary);
}
.fab-sg__note b { color: var(--fab-ink); font-weight: var(--fab-weight-medium); }

/* ============================================================
   RESPONSIVE
   ============================================================ */

/* The mobile counter is hidden above 900px, where the gallery is a
   vertical stack and a position readout would be meaningless. */
.fab-gal-count { display: none; }

@media (max-width: 900px) {

  .fab-pdp__hero { grid-template-columns: 1fr; gap: 30px; }

  /* Static below 900px: a sticky column in a one-column layout is just a
     block of text that jumps. */
  .fab-buybox { position: static; }

  /* ==========================================================
     THE MOBILE GALLERY IS A SWIPEABLE CAROUSEL, NOT A STACK.
     THIS IS A HARD RULE IN design-language/components.md SECTION 7
     BECAUSE THE STACK SHIPPED TO PHONES ONCE.

     WHY: the desktop vertical image-stack pushed the name, the price, the
     size selector and Add to bag ROUGHLY SIX SCREENS DOWN on a phone,
     which is wrong for commerce. The carousel puts the gallery at about
     263px tall and the whole buy box inside the first screen. Pattern
     source: locomotive-store's counter gallery, the reference the brand
     owner singled out.

     The SAME MARKUP becomes the carousel: flex-direction row, x-mandatory
     scroll snap, each shot at flex: 0 0 100%. fab-site.js injects the
     "3 / 5" readout after the gallery, which is hidden above 900px.

     DO NOT ADD A NEGATIVE-MARGIN FULL-BLEED ESCAPE HERE. Making the
     carousel run edge to edge with margin-inline: calc(-1 * gutter) is
     the obvious next idea and it caused a real horizontal-overflow bug
     earlier in this project. The carousel stays inside the container.
     ========================================================== */
  .fab-gallery {
    flex-direction: row;
    gap: var(--fab-space-xs);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .fab-gallery::-webkit-scrollbar { display: none; }
  .fab-gallery > .fab-shot {
    flex: 0 0 100%;
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }

  .fab-gal-count {
    display: block;
    margin-top: 10px;
    text-align: center;
    font-size: var(--fab-size-caption);
    letter-spacing: var(--fab-track-link-underline);
    color: var(--fab-ink-secondary);
    font-variant-numeric: tabular-nums;
  }
}

@media (max-width: 560px) {
  .fab-assure { grid-template-columns: 1fr; gap: 12px; }
  /* Name plus CTA only. There is not room for four items at 390px, and
     the price is already two lines up the page in the buy box. */
  .fab-buybar__price,
  .fab-buybar__size { display: none; }
}
