/*
 * Single product page (Phase 2, Milestone 1 cleanup pass) — restyles WooCommerce's own default
 * gallery/summary/tabs markup (still WooCommerce's real templates/hooks; the only theme
 * overrides are the .container wrapper in single-product.php and the tabs→sections template,
 * see woocommerce/single-product/tabs/tabs.php) using the site's existing tokens/spacing scale.
 * Related Products / Up-Sells reuse the same real ProductCard component as the shop grid
 * (content-product.php override) and the same grid breakpoints as shop.css, duplicated here
 * rather than shared across a stylesheet dependency — small enough to not be worth the coupling.
 */

/* Compaction pass follow-up (2026-09-12, explicit request): reduced again from --space-8 (48px) —
   the page should read as "one compact block", not just inside the earlier 40-56px range. */
.product-page { padding-top: var(--space-7); }

/* Editorial-hierarchy pass: the shared .container (base.css) caps at --breakpoint-xl (1280px) —
   correct for the homepage/shop grids, but on wide screens it left the product hero looking like
   a modest block floating in a much wider page instead of the image being the dominant visual
   anchor ("closer to the reference"). Widened HERE ONLY, scoped to .product-page, so the shared
   base component and every other page keep their original width. */
.container.product-page { max-width: 1600px; }

/* ================= Gallery + summary =================
 * .product-hero (theme override, woocommerce/content-single-product.php) wraps ONLY the gallery
 * + summary hooks — tabs/related/up-sells are siblings after it, not grid items inside it.
 * Fixed-width summary column (not a fr-ratio) — the reading measure that's comfortable for title/
 * price/story text doesn't get wider just because the viewport does, so the gallery absorbs 100%
 * of the extra room on a wide screen and actually grows into the primary focal point instead of
 * both columns just stretching together. */
.product-hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  margin-bottom: var(--space-9);
}
@media (min-width: 1024px) {
  .product-hero { grid-template-columns: 1fr minmax(360px, 440px); gap: var(--space-8); align-items: start; }
}
@media (min-width: 1100px) {
  .product-hero { grid-template-columns: 1fr minmax(380px, 460px); gap: var(--space-9); }
}
/* Editorial layout (all products): three grid items — gallery (col 1, row 1), `.product-hero__aside`
   (purchase summary + "Наш підхід" card, col 2, spanning both rows) and `.product-hero__content`
   (description + accordions, col 1, row 2, under the gallery). Row 2 is `1fr` so a taller aside's
   extra height lands below the content instead of being spread into the gallery row and opening a
   gap between photo and text. Below 1024px the grid is one column: the aside is `display: contents`
   so its two children become grid items too, and `order` puts them gallery → purchase summary →
   description/accordions → brand card (the description never sits between title/price and Add to
   Cart). */
@media (min-width: 1024px) {
  .product-hero { grid-template-rows: auto 1fr; row-gap: 0; }
  .product-hero > .woocommerce-product-gallery { grid-column: 1; grid-row: 1; }
  .product-hero > .product-hero__aside { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; gap: var(--space-7); }
  .product-hero > .product-hero__content { grid-column: 1; grid-row: 2; margin-top: var(--space-8); }
}
@media (min-width: 1024px) {
  /* Aligned with the main image, not the thumbnail rail (88px rail + --space-4 gap, see the gallery
     block below), and no wider than the image itself. */
  .product-hero > .product-hero__content { margin-left: calc(88px + var(--space-4)); max-width: 560px; }
}
@media (max-width: 1023px) {
  .product-hero__aside { display: contents; }
  .product-hero__content { order: 1; }
  .product-standards { order: 2; }
  /* Single column on tablet: keep the gallery a medium-sized, centered frame instead of full-bleed. */
  .product-hero > .woocommerce-product-gallery { max-width: 560px; width: 100% !important; margin-inline: auto !important; }
}
/* Density fix: found the real cause of the large gap before "Опис" — both grid children
   (.woocommerce-product-gallery and .summary.entry-summary) carried a leaked margin-bottom, so
   the grid row sized itself to the taller column PLUS that leaked margin. Root cause finally
   identified (editorial-hierarchy pass): Storefront's own woocommerce.css ships a legacy
   float-based 2-column layout — `.images{width:39.13%;float:left;margin-right:4.35%;
   margin-bottom:3.706325903em}` and `.summary{width:56.52%;float:right;margin-bottom:3.71em}` —
   targeting the exact classes WooCommerce puts on the gallery (`images`) and summary
   (`.summary.entry-summary`) wrappers. Our .product-hero grid was never actually stretching
   these two items to their full grid-cell width: Storefront's explicit `width` percentage (and
   `float`) pre-empts CSS Grid's own stretch default, quietly capping both columns to ~39%/56% of
   their track and floating them — the same unidentified-source-leak pattern documented elsewhere
   in this project (.site-header/.comment-respond/.storefront-sorting), just tracked down this
   time. Reset in full below, not just margin-bottom. */
.product-hero > .woocommerce-product-gallery,
.product-hero__aside > .summary.entry-summary {
  width: auto !important;
  float: none !important;
  margin-right: 0 !important;
  margin-bottom: 0 !important;
}
/* Real bug, found live at 1280px: grid items default to min-width:auto (their own min-content
   size), not 0. The gallery's min-content width (driven by the flex thumbnail rail + image) was
   wider than the 1fr track's fair share at narrower desktop widths, so the grid track refused to
   shrink and the whole .product-hero (and the Add to Cart button inside .summary) overflowed past
   the container — silently clipped by body's overflow-x:hidden rather than scrolling, which is
   what made the CTA text look cut off instead of producing a visible horizontal scrollbar. */
.product-hero > .woocommerce-product-gallery {
  min-width: 0;
}

/* Bug fix: WooCommerce's sale-flash badge (.onsale) is a plain, unpositioned <span> and a direct
   sibling of the gallery inside .product-hero — without absolute positioning it became a 3rd
   grid item (auto-placed before the gallery), pushing gallery/summary out of their intended
   columns. Positioned as an overlay instead, same corner-badge treatment as our own
   .product-card__badge (homepage.css). */
.woocommerce-product-gallery { position: relative; }
/* Visual pass: matched to the unified badge language below (same pill shape/size/tracking as
   the shop grid's New/Bestseller/Sold-Out tags) — outline style specifically, distinguishing
   "sale" as a marketing tag rather than a hard-fact status like New/Sold-Out. */
.product-hero > .onsale {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  background: var(--color-background-primary);
  color: var(--color-accent-terracotta);
  border: 1px solid color-mix(in srgb, var(--color-accent-terracotta) 55%, transparent);
  font-size: 9px;
  font-weight: var(--font-weight-emphasis);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
}

/* Compact-gallery pass: the previous fixed aspect-ratio (4/5) + object-fit:cover let the hero
   image grow as tall as the column allowed, which is exactly what pushed Add to Cart below the
   first viewport. Replaced with a max-height clamp (viewport-relative, so it adapts instead of
   assuming one screen size) + object-fit:contain, so the whole product is always visible and the
   image can never grow taller than the summary column next to it needs to be. */
.woocommerce-product-gallery__image {
  border-radius: var(--radius-medium);
  overflow: hidden;
  /* Explicit request: no grey/beige frame — the space around a portrait photo should disappear
     into the page background instead of reading as side bars. */
  background: transparent;
  /* Real bug, found live 2026-08-14: this was `max-height`, not `height` — a flex container
     with only max-height has no *definite* height, so the img's `height: 100%` below had
     nothing valid to resolve against (percentage heights require a definite parent height per
     spec) and was silently ignored. The image then scaled purely from width:100% + its own
     aspect ratio, growing far taller than intended (confirmed live: rendered ~870px inside a
     538px-tall box) and getting cropped by overflow:hidden — visible as a small, oddly-
     positioned sliver instead of the full photo. `height` (not max-height) gives both the flex
     layout and the percentage-height img a real value to size against.
     Explicit request (slightly portrait gallery): height comes from a 10/11 aspect-ratio against
     the frame's width (capped at ~560px on desktop, see .flex-viewport in the ≥1024px block below),
     with a max-height so it stays a medium-sized frame — not the earlier fixed clamp (a wide frame
     around a vertical photo) and not a tall 4/5 block. */
  aspect-ratio: 10 / 11;
  max-height: 620px;
  /* !important verified necessary, not a guess: FlexSlider's own JS sets an inline
     `display: block` (confirmed live via getAttribute('style')) on every slide to manage its
     carousel mechanic — inline styles beat any external stylesheet regardless of specificity,
     so plain `display: flex` here was silently losing and the image was never actually centered. */
  display: flex !important;
  align-items: center;
  justify-content: center;
}
/* Real bug, found live 2026-08-16: width:auto/height:auto + max-width/max-height doesn't reliably
   clamp to the container in both dimensions — it depends on the *loaded* image resource's own
   intrinsic size, not the container. Confirmed live on this product: three of its four gallery
   images resolved (via srcset) to a small already-fits source and rendered at their tiny native
   size, leaving large empty space around them; the fourth resolved to a source file whose actual
   pixel dimensions (696×923, confirmed via img.naturalWidth/Height) don't match what its filename/
   registered srcset metadata claim (416×552) — a mismatched WordPress-generated thumbnail, not a
   file this fix touches. With auto-sizing, that image's width resolved to fill the container
   (max-width:100%) before max-height was applied, rendering 923px tall inside the 640px-tall box
   and overflowing right past the frame (clipped by the container's overflow:hidden). Sizing the
   img to exactly 100%/100% of its already-fixed-size parent and letting object-fit:contain (a
   property purpose-built for this) do the actual fit-without-crop math is immune to both failure
   modes: the frame never changes size regardless of the active image's real dimensions, and
   object-fit:contain scales content to fill the frame (up or down) rather than only shrinking.
   That 100%/100% only works if EVERY ancestor up to the definite-height container also has a
   definite height — confirmed live this wasn't true: WooCommerce wraps each slide's <img> in a
   bare <a> (the PhotoSwipe zoom trigger), which is a flex item of .woocommerce-product-gallery__
   image under `align-items: center` — center (not stretch) means a flex item without its own
   height sizes to its own content instead of filling the cross axis, so the <a> stayed
   content-sized (= the img's natural aspect-ratio height) rather than 640px. img height:100% of a
   non-definite parent resolves as if 'auto' per spec (confirmed: computed max-height came back
   'none'), which is exactly the failure mode this rule closes off. */
.woocommerce-product-gallery__image > a { display: block; width: 100%; height: 100%; }
.woocommerce-product-gallery__image img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* "Editorial photography, not app chrome": the zoom trigger (WooCommerce/PhotoSwipe default —
   a solid white circle with a magnifying-glass emoji) was the one piece of visible UI chrome
   competing with the image itself. Faded to near-invisible at rest, appearing only on hover —
   present for the person who wants it, otherwise the image stands alone. */
.woocommerce-product-gallery__wrap { position: relative; }
.woocommerce-product-gallery__wrap:hover .woocommerce-product-gallery__trigger { opacity: 1; }
.woocommerce-product-gallery__trigger {
  opacity: 0;
  background: color-mix(in srgb, var(--color-background-primary) 85%, transparent) !important;
  border-radius: 50%;
  transition: opacity var(--animation-duration) var(--animation-easing);
}
/* Visual-polish pass: the gallery column is genuinely wide now (container/float-leak fixes
   earlier this phase), so the previous 72px/space-5 filmstrip reads sparse under it — sized up
   and tightened to feel like a considered set of thumbnails rather than a thin strip. This is the
   MOBILE/TABLET (<1024px) treatment — a horizontal row below the main image, deliberately kept
   (touch-friendly horizontal scroll, per explicit request) rather than forcing the desktop
   vertical rail onto small screens. */
.woocommerce-product-gallery .flex-control-thumbs {
  display: flex;
  gap: var(--space-3);
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
}
/* !important verified necessary, not a guess: Storefront's own woocommerce.css ships several
   responsive `.flex-control-thumbs li { width: X%; float: left }` rules for its own default
   thumbnail grid (2/3/4/5-column tiers) at equal selector specificity, loaded after this file —
   confirmed live via getComputedStyle (li rendered at 9.77px = exactly 88px × 11.11%, Storefront's
   5-column tier, not our intended 88px). */
.woocommerce-product-gallery .flex-control-thumbs li { width: 88px !important; flex-shrink: 0; }
/* Fine-tuning pass: active state reads via a hairline underline instead of relying on opacity
   alone — quieter than a border around the thumbnail, and legible without the jump-to-full-
   opacity feeling like a UI toggle. */
.woocommerce-product-gallery .flex-control-thumbs img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-small);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity var(--animation-duration) var(--animation-easing);
}
.woocommerce-product-gallery .flex-control-thumbs li { padding-bottom: var(--space-2); border-bottom: 2px solid transparent; }
.woocommerce-product-gallery .flex-control-thumbs img:hover { opacity: 0.85; }
.woocommerce-product-gallery .flex-control-thumbs img.flex-active { opacity: 1; }
.woocommerce-product-gallery .flex-control-thumbs li:has(img.flex-active) { border-bottom-color: var(--color-background-accent); }

/* Desktop (≥1024px, this project's existing nav-collapse breakpoint): thumbnails move to a
   vertical rail to the LEFT of the main image, both to save vertical space (the real goal — a
   horizontal strip under a tall image adds height the first viewport doesn't have) and to match
   the requested Flamingo-Estate-style compact layout. Pure CSS reflow on WooCommerce's own
   FlexSlider-generated DOM (.woocommerce-product-gallery > .flex-viewport + ol.flex-control-thumbs,
   confirmed live) — FlexSlider's own thumbnail click/keyboard handling is untouched, only their
   position changes. The zoom trigger (position:absolute) doesn't participate in this flex layout. */
@media (min-width: 1024px) {
  .woocommerce-product-gallery {
    display: flex;
    flex-direction: row-reverse;
    gap: var(--space-4);
  }
  .woocommerce-product-gallery .flex-viewport {
    flex: 1;
    min-width: 0;
    /* Medium-sized frame: ~560px wide at most (→ ~610px tall at 10/11). */
    max-width: 560px;
  }
  /* The frame is narrower than the gallery column now — pack thumbnails + frame to the column's
     left edge (row-reverse: flex-end is the left side) instead of leaving the gap on that side. */
  .woocommerce-product-gallery { justify-content: flex-end; }
  /* Gallery column sized to thumbnails + frame (88 + 16 + 560) instead of absorbing all spare
     width, summary pushed to the right edge — same summary track widths as the rules above. */
  .product-hero { grid-template-columns: minmax(0, 664px) minmax(360px, 440px); justify-content: space-between; }
  .woocommerce-product-gallery .flex-control-thumbs {
    flex: 0 0 auto;
    flex-direction: column;
    width: 88px;
    max-height: 620px;
    overflow-y: auto;
    margin: 0;
  }
  /* Same Storefront-override reasoning as the mobile rule above. */
  .woocommerce-product-gallery .flex-control-thumbs li { width: 100% !important; flex-shrink: 0; }
}
@media (min-width: 1100px) {
  .product-hero { grid-template-columns: minmax(0, 664px) minmax(380px, 460px); }
}

/* Visual pass: deliberate rhythm from category → badge → title → price → selectors → story →
   notes → CTA → trust → meta — each step a clearly different size/weight/distance. Editorial
   hierarchy pass (Milestone 1): the right column now carries far more real content than the old
   flat title/price/excerpt/CTA stack, so gaps below are tighter than that first pass used —
   "information-rich, not mostly empty" only reads correctly if the rhythm is dense enough that
   each block still feels like part of one continuous column, not isolated islands. */
/* Compaction pass (2026-09-12, explicit request): padding-top dropped entirely (was --space-3,
   12px) — the category/badge row should sit flush with the gallery's own top edge (the grid's
   align-items:start already lines the two columns up there), not offset below it. */
.summary.entry-summary { max-width: 460px; }

/* Visual alignment pass 2026-08-13: rating summary, grouped with name/price/variation per the
   reference structure. Reuses the sitewide .rating component as-is, just adds link affordance. */
.product-editorial__rating-link { display: inline-block; margin: var(--space-2) 0 var(--space-4); text-decoration: none; }
.product-editorial__rating-link:hover .rating { color: var(--color-text-primary); }

/* Compaction pass (2026-09-12, explicit request): tightened --space-4 -> --space-3. */
.product-editorial__eyebrow { display: flex; align-items: center; gap: var(--space-4); margin-bottom: var(--space-3); }
.product-editorial__category {
  font-size: var(--font-size-ui-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-decoration: none;
}
.product-editorial__category:hover { color: var(--color-background-accent); }

/* Compaction pass (2026-09-12, explicit request — "dramatically oversized"): font-size dropped
   ~50%, --font-size-h1 (64px) -> the dedicated --font-size-product-title token (32px,
   ui-tokens.css, so no other H1 use sitewide is affected); line-height tightened 1.15 -> 1.05 so
   a real two-line name like "Гідролат лаванди" doesn't leave a large gap between its own lines.
   margin-bottom (title -> price) widened slightly, --space-3 (12px) -> --space-4 (16px), to land
   inside the requested 16-20px range now that the title itself is so much smaller. */
.summary.entry-summary .product_title {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-product-title);
  line-height: 1.05;
  margin: 0 0 var(--space-4);
}
/* Fragrance titles split into name (this line's own size/weight) + concentration type — a much
   smaller secondary label directly underneath, own line, well clear of the button-language sizes
   elsewhere on the page. See gb_split_fragrance_title(), inc/product-editorial.php. */
.product-editorial__title-type {
  display: block;
  font-family: var(--font-body);
  font-weight: var(--font-weight-body);
  font-size: var(--font-size-meta);
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-top: var(--space-2);
}
/* Compaction pass (2026-09-12, explicit request): margin-bottom (price -> purchase controls)
   widened --space-5 (20px) -> --space-7 (32px) — a "moderate" gap, deliberately more than the
   title -> price one above, landing at the top of the requested 28-32px range. For a variable
   product the variation selectors (their own margin, .variation-selectors below) sit between
   this and the purchase controls — real content, not blank space, so untouched here.
   !important verified necessary, not a guess (same Storefront-load-order fight documented
   elsewhere in this file/checkout.css): WooCommerce's own `<p class="price">` picks up
   Storefront's own generic `.summary p` margin shorthand (~2em top AND bottom), which loads
   AFTER this stylesheet and was silently winning over a plain margin-bottom here — confirmed
   live, computed margin-top read ~32px with nothing in this file ever setting one. `margin`
   shorthand zeroes that unwanted top margin explicitly instead of leaving it to chance. */
.summary.entry-summary .price {
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-body);
  color: var(--color-text-primary);
  display: block;
  margin: 0 0 var(--space-7) !important;
}
.summary.entry-summary .price ins { text-decoration: none; }
.summary.entry-summary .price del { color: var(--color-text-muted); margin-right: var(--space-2); }

/* Stock status (rendered inside .single_variation_wrap / .woocommerce-variation-availability for
   variable products, or as a plain .stock line for simple ones) sits with the CTA, not stacked
   above it as its own line — it's a purchase-decision fact, not narrative copy. */
.stock { display: block; font-size: var(--font-size-meta); margin: var(--space-3) 0 0; }
.stock.in-stock { color: var(--color-text-muted); }
.stock.out-of-stock { color: var(--color-accent-terracotta); }

/* ================= Variation selectors (custom segmented buttons) =================
 * The real <select> WooCommerce renders (inside .variation-selector__hidden-select) stays in
 * the DOM for add-to-cart-variation.js to read/write — visually hidden here, not display:none,
 * since hidden form controls are still perfectly valid for that JS to operate on; kept off-screen
 * rather than removed from layout so no browser silently excludes it from anything. */
.variation-selector__hidden-select {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.variation-selectors { display: flex; flex-direction: column; gap: var(--space-6); margin-bottom: var(--space-6); }
/* Auto-hide rule (inc/woocommerce-variations.php): a single-option attribute renders only the
   hidden <select>, no button group — nothing left in this row worth showing the shopper, so the
   whole label+control row disappears rather than leaving an empty label sitting above nothing. */
.variation-selector:has(.variation-selector__hidden-select--single) { display: none; }
.variation-selector__label {
  display: block;
  margin-bottom: var(--space-3);
  font-size: var(--font-size-ui-label);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.variation-selector__buttons { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.variation-selector__button {
  min-height: 40px;
  padding: 0 var(--space-5);
  background: var(--color-background-primary);
  color: var(--color-text-primary);
  border: var(--border-width) solid var(--color-border-default);
  border-radius: var(--radius-small);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  cursor: pointer;
  transition: border-color var(--animation-duration) var(--animation-easing), color var(--animation-duration) var(--animation-easing);
}
.variation-selector__button:hover:not(:disabled) { border-color: var(--color-background-accent); }
.variation-selector__button.is-active {
  border-color: var(--color-background-accent);
  color: var(--color-background-accent);
  font-weight: var(--font-weight-emphasis);
}
.variation-selector__button:disabled { opacity: 0.4; cursor: not-allowed; text-decoration: line-through; }

/* ================= Fragrance guide link ================= */
.product-editorial__guide-link {
  display: inline-block;
  margin-bottom: var(--space-7);
  font-size: var(--font-size-meta);
  color: var(--color-background-accent);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--color-background-accent) 40%, transparent);
}
.product-editorial__guide-link:hover { border-bottom-color: var(--color-background-accent); }

/* ================= Description (under the gallery) =================
 * The full WooCommerce description, open by default, headed "Опис" — first block of
 * .product-hero__content, above the info accordions. */
.product-editorial__story { margin-bottom: var(--space-7); }
.product-editorial__story-heading {
  margin: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-h3);
  line-height: var(--line-height-heading);
  color: var(--color-text-primary);
}
.product-editorial__story-content {
  font-size: var(--font-size-body);
  color: color-mix(in srgb, var(--color-text-muted) 92%, transparent);
  line-height: 1.75;
}
.product-editorial__story-content p { margin: 0 0 var(--space-4); }
.product-editorial__story-content p:last-child { margin-bottom: 0; }

/* ================= "Наш підхід" card (right column, under the purchase summary) =================
 * Soft off-white surface, no border/shadow/dividers; icons are plain dark line drawings (no colored
 * circles), text on the right. Same five lines on every product (gb_product_standards_card()). */
.product-standards {
  padding: var(--space-7);
  background: var(--color-background-surface);
  border-radius: var(--radius-large);
}
.product-standards__heading {
  margin: 0 0 var(--space-6);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-h3);
  line-height: var(--line-height-heading);
  color: var(--color-text-primary);
}
.product-standards__list { display: flex; flex-direction: column; gap: var(--space-5); margin: 0; padding: 0; list-style: none; }
.product-standards__item { display: flex; align-items: center; gap: var(--space-5); }
.product-standards__icon { flex: none; display: inline-flex; color: var(--color-text-primary); }
.product-standards__copy { display: flex; flex-direction: column; gap: 2px; font-size: var(--font-size-meta, 13px); line-height: 1.5; color: var(--color-text-muted); }
.product-standards__copy strong { font-size: var(--font-size-body); font-weight: var(--font-weight-emphasis); color: var(--color-text-primary); }

/* ================= Fragrance notes (top / heart / base) ================= */
.product-editorial__notes { margin-bottom: var(--space-7); }
.product-editorial__notes-heading {
  font-size: var(--font-size-ui-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  font-weight: var(--font-weight-body);
  margin: 0 0 var(--space-4);
}
.fragrance-notes { display: flex; flex-direction: column; gap: var(--space-3); margin: 0; }
.fragrance-notes__tier { display: flex; gap: var(--space-4); padding-bottom: var(--space-3); border-bottom: var(--border-width) solid color-mix(in srgb, var(--color-border-default) 40%, transparent); }
.fragrance-notes__tier:last-child { border-bottom: none; padding-bottom: 0; }
.fragrance-notes__tier dt {
  flex: 0 0 110px;
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
}
.fragrance-notes__tier dd { margin: 0; font-size: var(--font-size-body); color: var(--color-text-primary); }

/* ================= Trust indicators =================
 * Art-direction pass 2026-08-13: rebuilt as a 4-icon row per the mockup (was a dot-bulleted text
 * list — see gb_product_trust_indicators() for the markup/content change). Two columns on mobile,
 * all four in one row once there's room.
 * Global cleanup pass (2026-09-12, explicit request): the top/bottom rules that used to enclose
 * this row like a boxed strip are gone — request was specifically for whitespace instead of
 * horizontal rules between purchase controls and this row, and below it. margin-top widened
 * --space-4 (16px) -> --space-7 (32px), inside the requested 28-36px range, now that it's doing
 * the full job of separating the two areas on its own instead of a border+margin combo. The
 * padding this rule used to carry (space-5 top AND bottom, to clear the borders) is gone too;
 * margin-bottom keeps a quieter, non-zero gap to whatever real content follows (fragrance-guide
 * link / story) so this doesn't collide with it now that the border is gone.
 * Real bug found live: base.css's global `ul[class] { margin: 0; padding: 0; }` reset has HIGHER
 * specificity than a bare single-class selector (element+attribute beats one class), so it was
 * silently winning over this rule's margin/padding regardless of stylesheet order — computed
 * margin-top read 0px live no matter what value was set here. Selector scoped to
 * `.summary.entry-summary` (two classes) to legitimately outrank it instead of reaching for
 * !important. */
.summary.entry-summary .product-editorial__trust {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5) var(--space-4);
  list-style: none;
  margin: var(--space-7) 0 var(--space-5);
  padding: 0;
}
@media (min-width: 640px) {
  .product-editorial__trust { grid-template-columns: repeat(4, 1fr); }
}
.product-editorial__trust li {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}
.product-editorial__trust svg {
  width: 22px;
  height: 22px;
  color: var(--color-background-accent);
}
.product-editorial__trust span {
  font-size: var(--font-size-meta);
  line-height: 1.4;
  color: var(--color-text-muted);
}

/* form.cart is NOT flexed here — for variable products it's the SAME <form> that stays open
   from the volume/fragrance selectors all the way through the story/notes content (see
   template-parts/product/variation-selectors.php), so making the form itself a flex container
   would turn all of that prose into flex items too. Quantity + Add to Cart get their own row via
   .product-editorial__purchase-row instead — a wrapper injected around exactly those two
   elements via woocommerce_before/after_add_to_cart_button (inc/product-editorial.php), which
   fire in the same bracketing positions in both WooCommerce's simple.php and
   variation-add-to-cart-button.php templates, so one rule covers both product types. */
/* Real bug found live (compaction pass, 2026-09-12): this rule already existed to zero
   Storefront's own form.cart default spacing, but Storefront's own woocommerce.css loads AFTER
   this stylesheet — same load-order fight documented elsewhere in this file — so a plain
   margin-bottom:0 was silently losing (computed margin-bottom read ~25.9px live) and a
   ~16px padding-top nobody had ever addressed was adding on top of that between price and the
   quantity/Add to Cart row. Both zeroed with !important now the actual values are confirmed, not
   guessed.
   Global cleanup pass (2026-09-12, explicit request): same fight, different property — Storefront
   also sets a ~16px padding-bottom on this same element, which was silently eating into the
   purchase-row -> trust-row gap this pass just widened (measured live: the two rows sat only
   16px apart no matter what .product-editorial__trust's own margin-top was set to, because this
   form's padding-bottom is a sibling-adjacent gap that stacks with it, not a value that rule
   could ever override). Zeroed for the same reason as padding-top above. */
.summary.entry-summary form.cart { margin-bottom: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important; }
.product-editorial__purchase-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* Quantity stepper — real WooCommerce <input> (class "qty" is WC's own default), custom +/-
   buttons injected around it via the same hooks (class "qty__btn"). components.css's Quantity
   selector component is intentionally kept at its frozen 36px spec for the cart drawer/styleguide
   (states-spec.md) — this scoped override gives the product-page instance more visual weight
   (48px, matching the Add to Cart button's own height) without touching that shared definition,
   since the wrapper WooCommerce renders here is <div class="quantity">, not <div class="qty">. */
.summary.entry-summary .quantity {
  display: inline-flex;
  align-items: stretch;
  height: 48px;
  margin: 0;
  border: var(--border-width) solid var(--color-border-default);
  border-radius: var(--radius-medium);
  overflow: hidden;
  transition: border-color var(--animation-duration) var(--animation-easing);
}
.summary.entry-summary .quantity:focus-within { border-color: var(--color-action-primary); }
.summary.entry-summary .quantity .qty__btn {
  width: 44px;
  height: 48px;
  font-size: 16px;
}
.summary.entry-summary .quantity .qty__btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-action-primary) 8%, transparent);
  color: var(--color-action-primary);
}
.summary.entry-summary .quantity .qty {
  width: 52px;
  height: 48px;
  padding: 0 var(--space-2);
  text-align: center;
  border: none;
  border-left: var(--border-width) solid var(--color-border-default);
  border-right: var(--border-width) solid var(--color-border-default);
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  -moz-appearance: textfield;
}
.summary.entry-summary .quantity .qty::-webkit-inner-spin-button,
.summary.entry-summary .quantity .qty::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.summary.entry-summary .quantity .qty:focus-visible { outline: none; box-shadow: none; }

/* CTA hierarchy: the add-to-cart button is the one solid-fill, full-attention control on the
   page — taller than the shop grid's own buttons/inputs, flex:1 so it's clearly the dominant
   element in its row rather than a same-size peer of the quantity field. */
.summary.entry-summary button.single_add_to_cart_button {
  flex: 1;
  min-width: 200px;
  min-height: 48px;
  padding: 0 var(--space-7);
  background: var(--color-background-accent);
  color: var(--color-text-on-accent);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--font-size-button);
  letter-spacing: var(--letter-spacing-button);
  cursor: pointer;
  transition: background var(--animation-duration) var(--animation-easing);
}
.summary.entry-summary button.single_add_to_cart_button:hover { background: var(--color-background-accent-hover); }
/* Real bug, found live during the add-to-cart AJAX fix: once the fragment-refresh bug (see
   assets/js/add-to-cart.js) was fixed, WooCommerce core's own AddToCartHandler.updateButton()
   started actually running — which inserts its own unstyled "View cart" link after the button on
   every successful add. This project's own compact toast (assets/js/add-to-cart.js) already
   offers the same action, so this default WC link is a redundant, unstyled duplicate that breaks
   the "purchase area stays visually compact" requirement — hidden rather than restyled, since the
   toast already covers the same need. */
.summary.entry-summary .added_to_cart { display: none; }
/* Global cleanup pass (2026-09-12, explicit request): the frontend SKU/Category/Tags block
   (.product_meta) this rule used to style is no longer rendered at all — the call to
   woocommerce_template_single_meta() was removed at the source in
   woocommerce/content-single-product.php, not just hidden here. Removed as dead CSS along with
   it; SKU/categories/tags remain fully intact in wp-admin/the database. */
/* WordPress's own admin-only "Edit" link (edit_post_link(), visible to logged-in editors only —
   never shown to real customers) was adding its own default paragraph margin-top on top of
   everything else, inflating the measured gap to "Опис" during logged-in QA. Real customers
   never see this element at all; compacted here so it doesn't distort the layout even in
   admin preview. */
.summary.entry-summary .edit-link { margin-top: var(--space-2); font-size: var(--font-size-meta); }

/* ================= Product sections (was: left-nav tabs) =================
 * Visual pass: more air between sections (space-9 instead of space-7) so each reads as its own
 * editorial "chapter" rather than a dense stacked list; the separator itself is a hairline
 * (border-color mixed toward transparent) so it recedes instead of drawing a hard grid line
 * across the page; the eyebrow-style uppercase heading gives each section the same small-caps
 * "chapter label" treatment used for section eyebrows elsewhere in the design system, promoted
 * to sit alongside the existing serif heading rather than replacing it. */
/* Fine-tuning pass: hairline faded further (70% -> 45% of the border color) so it's barely
   there — a whisper of separation, not a rule. Heading promoted to h2 size for more authority as
   a chapter title, while the body copy below gets quieter (faded color, wider line-height) —
   deliberately widening the contrast between the two instead of leaving them close in weight. */
/* ================= Story block with photography =================
 * Full-width editorial break between the hero and the Ingredients/Usage/Reviews sections —
 * image + one short paragraph, sitting outside .product-hero's grid so it isn't constrained to
 * either column. Renders only when a product has both _gb_story_text and _gb_story_image_id
 * (see gb_product_story_block(), inc/product-editorial.php) — most products won't have this
 * block at all, which is expected, not a bug. */
.product-story {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: center;
  max-width: 960px;
  margin: var(--space-9) 0;
}
@media (min-width: 768px) {
  .product-story { grid-template-columns: 5fr 7fr; gap: var(--space-8); }
}
.product-story__image { border-radius: var(--radius-medium); overflow: hidden; }
.product-story__image img { display: block; width: 100%; height: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.product-story__text {
  font-family: var(--font-heading);
  font-style: italic;
  font-size: var(--font-size-h3);
  line-height: 1.5;
  color: color-mix(in srgb, var(--color-text-primary) 88%, transparent);
  margin: 0;
}

/* Visual alignment pass 2026-08-13: converted from always-open stacked sections to real
   accordions (.accordion component, inc/fields.php) — .product-section/__heading are gone, the
   accordion component already provides its own divider (border-bottom) and summary/heading
   styling. .product-section__content is kept as the inner-panel class so the extensive
   attributes-table and reviews-form styling below didn't need to change at all. */
.product-sections { max-width: 720px; margin-top: var(--space-9); }
/* Inside the hero content column (under the gallery): sized by that column, closer to the description. */
.product-hero__content .product-sections { max-width: none; margin-top: var(--space-7); }
.product-sections .accordion:first-child { border-top: var(--border-width) solid var(--color-border-default); }
.product-sections .accordion > summary {
  padding-block: var(--space-6);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-h3);
}
.product-sections .accordion__panel { padding-bottom: var(--space-8); }
.product-section__content {
  font-size: var(--font-size-body);
  color: color-mix(in srgb, var(--color-text-muted) 90%, transparent);
  line-height: 1.8;
  max-width: 60ch;
}
/* Editorial specification list instead of a bordered table — no cell borders/background/zebra
   stripes, label and value sit side by side on a shared baseline (same dt/dd pattern as the
   fragrance-notes pyramid above, for visual consistency between the two spec-style blocks on
   this page) with generous vertical rhythm and a barely-there hairline, rather than reading as a
   default WooCommerce/spreadsheet table. */
.product-section__content table.woocommerce-product-attributes {
  width: 100%;
  border-collapse: collapse;
}
.product-section__content table.woocommerce-product-attributes tr {
  display: flex;
  align-items: baseline;
  gap: var(--space-6);
  padding: var(--space-5) 0;
  background: none;
  border-bottom: var(--border-width) solid color-mix(in srgb, var(--color-border-default) 35%, transparent);
}
.product-section__content table.woocommerce-product-attributes tr:first-child { padding-top: 0; }
.product-section__content table.woocommerce-product-attributes tr:last-child { border-bottom: none; padding-bottom: 0; }
.product-section__content table.woocommerce-product-attributes tbody tr:nth-child(even) td,
.product-section__content table.woocommerce-product-attributes tbody tr:nth-child(odd) td { background: none; }
.product-section__content table.woocommerce-product-attributes th,
.product-section__content table.woocommerce-product-attributes td {
  display: block;
  text-align: left;
  padding: 0;
  background: none;
  border-bottom: none;
}
.product-section__content table.woocommerce-product-attributes th {
  flex: 0 0 140px;
  font-size: var(--font-size-meta);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  font-weight: var(--font-weight-body);
}
.product-section__content table.woocommerce-product-attributes td {
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
}

/* Reviews section: native WC comment form, restyled to match the rest of the form language.
   Fine-tuning pass: hairline borders (faded, matching the section separators above instead of
   the full-strength border-default), more internal padding on inputs, more room between fields
   (label->field, field->field), a quieter empty state. */
.product-section--reviews .comment-form-rating select,
.product-section--reviews textarea,
.product-section--reviews input[type="text"],
.product-section--reviews input[type="email"] {
  width: 100%;
  max-width: 420px;
  padding: var(--space-4);
  border: var(--border-width) solid color-mix(in srgb, var(--color-border-default) 55%, transparent);
  border-radius: var(--radius-small);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  background: none;
  transition: border-color var(--animation-duration) var(--animation-easing);
}
.product-section--reviews textarea:focus,
.product-section--reviews input:focus { border-color: var(--color-background-accent); }
/* Bug fix: Storefront applies its own generic padding (computed ~39px, the same leaked value
   found on .site-header earlier this project — an unidentified competing rule a plain override
   doesn't beat) and a near-invisible background to .comment-respond, boxing the review form in a
   way that read as a "default plugin form" card. !important justified the same way as the
   .site-header fix (header.css). */
.product-section--reviews .comment-respond { padding: 0 !important; background: none !important; }
.product-section--reviews textarea { max-width: 100%; min-height: 140px; }
.product-section--reviews label { display: block; margin-bottom: var(--space-3); font-size: var(--font-size-ui-label); letter-spacing: 0.05em; text-transform: uppercase; color: var(--color-text-muted); }
/* Rating stars — WooCommerce's default gold (#ffb900) swapped for the same terracotta used for
   review stars elsewhere in the design system (homepage .review-card__stars). */
.product-section--reviews .comment-form-rating { margin-bottom: var(--space-6); }
.product-section--reviews .stars a { color: var(--color-accent-terracotta); }
.product-section--reviews p { margin: 0 0 var(--space-6); }
.product-section--reviews #submit {
  min-height: 44px;
  margin-top: var(--space-2);
  padding: 0 var(--space-7);
  background: var(--color-background-accent);
  color: var(--color-text-on-accent);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--font-size-button);
  letter-spacing: var(--letter-spacing-button);
  cursor: pointer;
  transition: background var(--animation-duration) var(--animation-easing);
}
.product-section--reviews #submit:hover { background: var(--color-background-accent-hover); }
.product-section--reviews .commentlist { list-style: none; margin: 0 0 var(--space-7); padding: 0; }
.product-section--reviews .commentlist li { padding-bottom: var(--space-6); margin-bottom: var(--space-6); border-bottom: var(--border-width) solid color-mix(in srgb, var(--color-border-default) 40%, transparent); }
.product-section--reviews .commentlist li:last-child { padding-bottom: 0; margin-bottom: 0; border-bottom: none; }
/* Each review: round avatar + name/date/stars/text, laid out as a row rather than WooCommerce's
   default (a floated image with text wrapping awkwardly around it). */
.product-section--reviews .comment_container { display: flex; gap: var(--space-5); align-items: flex-start; }
.product-section--reviews .comment_container .avatar { width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0; }
.product-section--reviews .comment-text { flex: 1; min-width: 0; }
.product-section--reviews .woocommerce-review__author {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-body);
  color: var(--color-text-primary);
  font-style: normal;
}
.product-section--reviews .woocommerce-review__published-date {
  display: block;
  font-size: var(--font-size-meta);
  color: var(--color-text-muted);
  margin-top: 2px;
}
.product-section--reviews .comment-text .description { margin-top: var(--space-3); }
.product-section--reviews .comment-text .description p { margin: 0; color: color-mix(in srgb, var(--color-text-muted) 92%, transparent); line-height: 1.7; }
/* Displayed rating (past reviews) is a different element than the interactive .stars widget in
   the form above — WooCommerce renders it via a font-glyph star-rating span, colored dark gray
   by Storefront by default; recolored here to the same terracotta used for the form's stars. */
.product-section--reviews .star-rating { margin-top: var(--space-2); font-size: 14px; }
.product-section--reviews .star-rating::before { color: var(--color-border-default); }
/* The filled-star glyph's color lives on span::before, not the span itself (confirmed via
   computed style — some other WooCommerce stylesheet sets an explicit color on the ::before that
   plain inheritance from .star-rating span doesn't reach, landing on WooCommerce's own brand
   purple #7f54b3 instead of our terracotta) — targeted directly, !important to beat it. */
.product-section--reviews .star-rating span,
.product-section--reviews .star-rating span::before { color: var(--color-accent-terracotta) !important; }
/* Editorial empty state — italic serif, matching the pull-quote treatment already used for real
   review copy elsewhere in the design system (homepage/related .review-card__quote), instead of
   a plain sentence that reads as an unstyled plugin default. */
.product-section--reviews .woocommerce-noreviews {
  background: none;
  font-family: var(--font-heading);
  font-style: italic;
  font-size: var(--font-size-h3);
  color: color-mix(in srgb, var(--color-text-muted) 80%, transparent);
  padding: 0;
  margin: 0 0 var(--space-7);
}
/* Duplicates our own section heading above (same tab title) — see the "Reviews (%d)" note in
   inc/woocommerce-hooks.php. */
.woocommerce-Reviews-title { display: none; }

/* ================= Related products / up-sells (same ProductCard as the shop grid) =================
 * Fine-tuning pass: the filled panel from the previous round now reads as its own separate
 * container — dropped the background fill (was --color-background-surface) and the rounded
 * panel edges entirely. What's left is generous top spacing and one hairline (matching the
 * product-detail sections' own, much-faded separator) — the section continues the page's story
 * instead of announcing a new one, while still getting real breathing room above it.
 */
.related.products,
.upsells.products {
  margin-top: var(--space-9);
  padding-top: var(--space-8);
  border-top: var(--border-width) solid color-mix(in srgb, var(--color-border-default) 45%, transparent);
}
.related.products > h2,
.upsells.products > h2 {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-heading);
  font-size: var(--font-size-h2);
  text-align: center;
  margin: 0 0 var(--space-7);
}
/* Visual-polish pass (2026-07-23): back to align-items:stretch, deliberately — the earlier
   density fix removed stretch because unclamped titles/descriptions of wildly different lengths
   made the margin-top:auto gap look artificial. Now that both the title and descriptor areas
   below have a fixed, clamped height (reserved whether or not a descriptor exists — see
   .product-card__name::after), every card's content above the footer is the same height by
   construction, so stretch + margin-top:auto only ever absorbs a small, consistent remainder —
   not an arbitrary one. Net effect: identical card heights, price/CTA baselines aligned across
   every card in a row, exactly as requested. */
.related.products ul.products,
.upsells.products ul.products {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-8) var(--space-6);
  align-items: stretch;
  list-style: none;
  margin: 0;
  padding: 0;
}
.related.products ul.products li.product,
.upsells.products ul.products li.product { width: auto; margin: 0; padding: 0; float: none; }
/* Same clearfix-becomes-a-grid-item bug as shop.css — see that file's comment for the full
   explanation. */
.related.products ul.products::before,
.related.products ul.products::after,
.upsells.products ul.products::before,
.upsells.products ul.products::after { content: none; }
@media (min-width: 640px) {
  .related.products ul.products,
  .upsells.products ul.products { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1100px) {
  .related.products ul.products,
  .upsells.products ul.products { grid-template-columns: repeat(4, 1fr); }
}

/* ================= ProductCard + Badges — Editorial tile (2026-09-12) =================
 * Full redesign per an explicit visual reference — see homepage.css's own version of this same
 * pass for the full reasoning (flat, no card surface, no CTA, portrait image, tight left-aligned
 * text stack). Duplicated rather than shared across a stylesheet dependency, matching this file's
 * own established convention.
 */
:is(.related.products, .upsells.products) .product-card {
  display: grid;
  grid-template-rows: auto auto auto auto auto;
  text-decoration: none;
  color: inherit;
  /* Real bug found live (editorial-tile pass, 2026-09-12) — see shop.css's own version of this
     same fix: Storefront's default `ul.products li.product { text-align: center }` was inherited
     straight down into every text row here, center-aligning them instead of matching the
     reference's left-aligned rating/title/descriptor/price. */
  text-align: left;
}
/* Vertical-rhythm pass (2026-09-12, explicit request, reference: "Бестселери" screenshot) — see
   homepage.css's own version of this same fix: image -> title target is 16-20px total, this
   margin is the only source of it now that the empty rating box below collapses via :empty. */
:is(.related.products, .upsells.products) .product-card__image { position: relative; margin-bottom: var(--space-4); }
/* Real bug found live (editorial-tile pass follow-up, 2026-09-12) — see homepage.css's own
   version of this same fix: .responsive-image's base border-radius/background (components.css)
   need explicitly squaring off/clearing now that this card has no surface of its own. */
:is(.related.products, .upsells.products) .product-card__image .responsive-image { aspect-ratio: 4 / 5; border-radius: 0; background: none; }
/* object-fit: contain — see shop.css's own note on the same rule. */
:is(.related.products, .upsells.products) .product-card__image .responsive-image img { object-fit: contain; }
:is(.related.products, .upsells.products) .product-card__image--wide .responsive-image img { object-fit: cover; }
:is(.related.products, .upsells.products) .product-card__image .responsive-image,
:is(.related.products, .upsells.products) .product-card__image .responsive-image img { transition: transform var(--animation-duration-slow) var(--animation-easing); }
:is(.related.products, .upsells.products) .product-card:hover .product-card__image .responsive-image img { transform: scale(1.035); }
:is(.related.products, .upsells.products) .product-card__badge { position: absolute; top: var(--space-3); left: var(--space-3); z-index: 1; }
:is(.related.products, .upsells.products) .product-card--sold-out .product-card__image .responsive-image { opacity: 0.5; }
:is(.related.products, .upsells.products) .product-card--sold-out .product-card__name,
:is(.related.products, .upsells.products) .product-card--sold-out .product-card__descriptor,
:is(.related.products, .upsells.products) .product-card--sold-out .product-card__price { opacity: 0.5; }
/* Rating slot — collapses to zero footprint when empty (see homepage.css's own version of this
   same fix) so image -> title stays a fixed 16-20px for an unrated product. */
:is(.related.products, .upsells.products) .product-card__rating {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-1);
}
:is(.related.products, .upsells.products) .product-card__rating:empty { display: none; margin: 0; }
/* Real bug fixed live (compaction pass, 2026-09-12, explicit request) — see homepage.css's own
   version of this same fix: `min-height: calc(1.1em * 2)` reserved 2 lines regardless of actual
   title length, leaving dead space below any real 1-line name before the descriptor started.
   Dropped; -webkit-line-clamp:2 alone still caps a long name at 2 lines. */
/* Real bug fixed live (2026-09-15, explicit request) — see homepage.css's own version of this
   same fix for the full measurement: line-height:1.1 clipped descenders (g/y/p/q/j) against this
   rule's own overflow:hidden (needed for -webkit-line-clamp). 1.2 is the smallest bump that fully
   clears it, confirmed live at 4x zoom on "Fig Milk". +2.1px per rendered line only. */
:is(.related.products, .upsells.products) .product-card__name {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-body);
  font-size: var(--font-size-product-card-title);
  line-height: 1.2;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--color-text-primary);
  /* Vertical-rhythm pass (2026-09-12, explicit request): title -> descriptor target is 4-6px. */
  margin-bottom: var(--space-1);
}
/* Scent/botanical descriptor ("Матча · лайм · мак") — a dedicated meta field
   (_gb_product_descriptors, inc/woocommerce-integration.php), not the WooCommerce short
   description. A short phrase that fits one line at every column width this card renders at, so
   both the reservation and the line-clamp ceiling stay at 1 line. */
:is(.related.products, .upsells.products) .product-card__descriptor {
  min-height: calc(1.3em * 1);
  font-size: var(--font-size-product-card-descriptor);
  font-weight: var(--font-weight-body);
  color: color-mix(in srgb, var(--color-text-muted) 85%, transparent);
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Vertical-rhythm pass (2026-09-12, explicit request): descriptor -> price target is 12-16px. */
  margin: 0 0 var(--space-3);
}
/* Price — one explicit single-line typography rule, not inherited: identical height whether the
   text is "450 ₴" or a struck-through "1 200 ₴ 950 ₴", since neither wraps at this card's width.
   Last element in the card now (no CTA below it) — no margin-bottom of its own; the grid's own
   row-gap controls the gap to the next row. */
:is(.related.products, .upsells.products) .product-card__price {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-emphasis);
  line-height: 1.4;
}
:is(.related.products, .upsells.products) .badge {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 3px 9px; border-radius: var(--radius-pill);
  font-family: var(--font-body); font-size: 9px; font-weight: var(--font-weight-emphasis);
  letter-spacing: 0.07em; text-transform: uppercase; line-height: 1;
}
:is(.related.products, .upsells.products) .badge--new { background: var(--color-background-accent); color: var(--color-text-on-accent); border: none; }
:is(.related.products, .upsells.products) .badge--bestseller {
  background: var(--color-background-primary); color: var(--color-background-accent);
  border: 1px solid color-mix(in srgb, var(--color-background-accent) 55%, transparent); padding: 2px 8px;
}
:is(.related.products, .upsells.products) .badge--sold-out {
  position: static; width: auto; height: auto; border-radius: var(--radius-pill);
  background: var(--color-border-default); border: none; color: var(--color-text-muted);
  padding: 3px 9px; font-size: 9px;
}
:is(.related.products, .upsells.products) .badge--sold-out::before { content: none; }
