/*
 * woocommerce.css — the Woo → design-system map, and the product page's own
 * layout system.
 *
 * Why this file exists separately from commerce.css:
 *   commerce.css holds the design system's own commerce vocabulary
 *   (.nk-product-card, .nk-price, .nk-stock, .nk-gallery …) and is used on the
 *   homepage rails and category pages, where Woo's stylesheet is absent. This
 *   file holds the rules that translate WooCommerce's own markup
 *   (.woocommerce div.product, .single_add_to_cart_button, .variations …) into
 *   that vocabulary, plus the panels the PDP is composed from. Keeping the two
 *   apart means a Woo upgrade never forces an edit to a file the rest of the
 *   store depends on.
 *
 * Cascade position: last of the component files, immediately before motion.css
 * — so it can out-specify any of our own components on a commerce route
 * without reaching for !important. It still has to beat WooCommerce's own
 * stylesheets, which are enqueued separately and win on specificity in places.
 * Raise specificity or dequeue the offending Woo style; never !important.
 *
 * ---------------------------------------------------------------------------
 * TWO SELECTOR TRAPS ON THIS ROUTE, both of which fail silently:
 *
 * 1. `.woocommerce` is a class on <body>, not a wrapper inside it. So
 *    `body.single-product .woocommerce …` matches nothing at all. The
 *    template's own container is `.nk-product-main`.
 *
 * 2. WooCommerce Blocks' legacy-template compat sheet ships selectors like
 *      .woocommerce :where(div[data-block-name="woocommerce/legacy-template"])
 *      div.product form.cart div.quantity { display: inline-block }
 *    `:where()` contributes nothing, but the rest is (0,4,3) and beats an
 *    ordinary (0,2,1) rule of ours. Out-specify it on the type count.
 *
 * ---------------------------------------------------------------------------
 * THE PAGE IS BUILT FROM PANELS.
 *
 * Laid out as a continuous column of headings and paragraphs the page read
 * like a newspaper — nothing told a scanning parent where "choosing" ended and
 * "reading" began. Every major block is a raised surface with its own padding
 * and shadow (`.nk-panel`), and the one place a purchase decision happens is
 * the only block on an accent ground (`.nk-buy-panel`). Shadow does the
 * separating; `nk_divider()` marks the single seam where the product ends and
 * the shop resumes.
 *
 * Registered as 'nk-woocommerce' in the $styles array in functions.php.
 */

/* =======================================================================
 * SHELL
 * ===================================================================== */

body.single-product {
  background-color: var(--nk-surface-page);
  color: var(--nk-text-default);
}

body.single-product .nk-product-main {
  inline-size: min(100%, var(--nk-container-max));
  margin-inline: auto;
  padding-inline: var(--nk-gutter);
  padding-block: var(--nk-space-3) var(--nk-section-gap);
}

/*
 * Woo's notices are styled in `feedback.css`, unscoped, mapped onto
 * `.nk-alert`. They lived here scoped to `body.single-product` until
 * 2026-08-23, which is why the cart and the checkout showed Woo's own grey bar
 * — the rules were right and simply did not reach the other routes. The only
 * product-page-specific part left is the space below one.
 */
body.single-product .woocommerce-notices-wrapper { margin-block-end: var(--nk-space-5); }

/* Below 768px the fold is spent on the price, not the category path — hidden
   outright rather than kept to one scrolling line. Visual only: the markup
   still renders server-side (`nk_product_breadcrumb_defaults()` in
   `inc/product.php`), and WooCommerce's BreadcrumbList JSON-LD is a separate
   script on `wp_footer` keyed off the same trail data, not this `<nav>` — so
   hiding it here does not touch the schema. `display: none` also drops the
   reserved space rather than leaving an empty gap. From 768px, where the
   fold pressure is gone, it is one scrolling line, never two: a wrapped
   breadcrumb cost 34px there too, and the mask fades the overflowing end
   instead of cutting a word in half. */
body.single-product .woocommerce-breadcrumb.nk-breadcrumb {
  display: none;
}

@media (min-width: 768px) {
  body.single-product .woocommerce-breadcrumb.nk-breadcrumb {
    display: block;
    margin-block-end: var(--nk-space-3);
  }
}

body.single-product .woocommerce-breadcrumb .nk-breadcrumb__list {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--nk-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  mask-image: linear-gradient(to left, #000 88%, transparent 100%);
}

body.single-product .woocommerce-breadcrumb .nk-breadcrumb__list::-webkit-scrollbar { display: none; }
body.single-product .woocommerce-breadcrumb .nk-breadcrumb__item { white-space: nowrap; }

body.single-product div.product {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--nk-space-5);
}

body.single-product div.product::after,
body.single-product div.product::before { display: none; }

/* =======================================================================
 * PANEL — the page's one layout primitive
 * ===================================================================== */

.nk-panel {
  padding: var(--nk-space-5);
  border-radius: var(--nk-radius-xl);
  background-color: var(--nk-surface-raised);
  box-shadow: var(--nk-shadow-sm);
  border: var(--nk-border-hairline);
}

.nk-panel__head {
  display: flex;
  align-items: center;
  gap: var(--nk-space-3);
  margin-block-end: var(--nk-space-5);
}

/* The circular brand badge. It is the storefront's own mark, so a section
   heading on the product page is signed rather than merely titled. */
.nk-panel__mark {
  inline-size: 44px;
  block-size: 44px;
  border-radius: var(--nk-radius-circle);
  flex: none;
}

.nk-panel__title {
  margin: 0;
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-xl);
  font-weight: var(--nk-weight-bold);
  color: var(--nk-text-strong);
  line-height: var(--nk-leading-tight);
}

.nk-panel__subtitle {
  margin: 0;
  font-size: var(--nk-text-sm);
  color: var(--nk-text-muted);
}

/* =======================================================================
 * GALLERY
 * ===================================================================== */

body.single-product div.product .woocommerce-product-gallery.nk-gallery {
  position: relative;
  inline-size: 100%;
  margin: 0;
  min-inline-size: 0;
}

/*
 * The stage is an aspect-ratio box sized from the INLINE axis.
 *
 * commerce.css already gives .nk-gallery__stage `aspect-ratio:
 * var(--nk-ratio-product)`. Adding a `block-size` here made the ratio derive
 * the *width from the height* instead — the image rendered 180px wide inside a
 * 328px column at 360px, and 300px inside 736px at 768px, with dead space
 * beside it. Sizing from the inline axis is also what keeps the box stable
 * while the image loads, so there is no layout jump between slides.
 *
 * There is deliberately no `max-block-size` here any more. Capping the height
 * of an aspect-ratio box does not scale it — it clips it: at 360px the stage
 * computed 328×400 (a 0.82 box, not 3:4) while the slides inside kept their
 * own height, so `overflow: hidden` cut ~92px off the bottom of every
 * photograph. Clothing is shot full-length; the cap was removing the shoes.
 * The stage is now a true 3:4 portrait at every width, which costs the 360px
 * fold ~37px and is the correct trade for a fashion catalogue.
 */
body.single-product .nk-gallery .nk-gallery__stage {
  position: relative;
  inline-size: 100%;
  aspect-ratio: var(--nk-ratio-product);
  border-radius: var(--nk-radius-xl);
  background-color: var(--nk-surface-sunken);
  box-shadow: var(--nk-shadow-sm);
  overflow: hidden;
}

/* `grid-auto-rows: 100%` is load-bearing. The wrapper's own height is definite
   (100% of the 3:4 stage), but the implicit ROW was content-sized, so a slide
   asking for `block-size: 100%` resolved against an indefinite row, fell back
   to the image's intrinsic height and overflowed the stage — which is how a
   492px-tall photo ended up inside a 400px frame. Pinning the row makes every
   percentage below it resolve, and object-fit can finally do its job. */
body.single-product .nk-gallery .woocommerce-product-gallery__wrapper {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  grid-auto-rows: 100%;
  block-size: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

body.single-product .nk-gallery .woocommerce-product-gallery__wrapper::-webkit-scrollbar { display: none; }

body.single-product .nk-gallery .woocommerce-product-gallery__image {
  block-size: 100%;
  /* A grid item's default `min-block-size: auto` floors it at the content's
     intrinsic height, which would reintroduce the overflow the row fixes. */
  min-block-size: 0;
  min-inline-size: 0;
  overflow: hidden;
  background-color: var(--nk-surface-sunken);
  scroll-snap-align: center;
}

/*
 * Pressing and dragging must move the rail, not the picture.
 *
 * An `<img>` is natively draggable and its `<a>` wrapper is natively
 * selectable, so a press-and-drag on the photograph started a browser drag
 * carrying a ghost image, and a press-and-drag across it painted a selection
 * over the link. Neither changes the layout, but both look like the gallery
 * has come apart under the cursor. `product.js` sets `draggable="false"` for
 * Firefox, which ignores `-webkit-user-drag`.
 */
body.single-product .nk-gallery .woocommerce-product-gallery__image,
body.single-product .nk-gallery .woocommerce-product-gallery__image a,
body.single-product .nk-gallery__thumb {
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
 * The link needs this as much as the image does.
 *
 * An `<a>` is natively draggable in its own right, and dragging one carries
 * the image it wraps as the ghost — so suppressing the drag on the `<img>`
 * alone left the exact behaviour it was meant to remove. Measured: the image
 * was `draggable=false` with `-webkit-user-drag: none` while its link was
 * still `draggable=true` / `auto`.
 */

/*
 * Snap fights a pointer drag, and it fights the settle afterwards.
 *
 * Two states, because they end at different moments. `is-dragging` lasts while
 * the pointer is down. `is-settling` lasts until the release animation has
 * finished — restoring snap any earlier hands the browser a rail parked
 * between two slides, and it immediately snaps to the *nearest* one, which
 * silently overrides where the drag committed to. That is what made a
 * two-thirds drag spring back.
 */
body.single-product .nk-gallery .woocommerce-product-gallery__wrapper.is-dragging,
body.single-product .nk-gallery .woocommerce-product-gallery__wrapper.is-settling {
  scroll-snap-type: none;
}

body.single-product .nk-gallery .woocommerce-product-gallery__wrapper.is-dragging {
  scroll-behavior: auto;
}

body.single-product .nk-gallery .woocommerce-product-gallery__wrapper.is-dragging,
body.single-product .nk-gallery .woocommerce-product-gallery__wrapper.is-dragging a {
  cursor: grabbing;
}

body.single-product .nk-gallery .woocommerce-product-gallery__image a {
  position: relative;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  cursor: zoom-in;
}

/*
 * contain, not cover: these are full-body shots and a crop takes the head or
 * the shoes off. The sunken ground makes the letterboxing deliberate.
 *
 * `div.product` is in the selector to win, not to describe. Woo ships
 * `.woocommerce div.product div.images img { height: auto }` — (0,3,3), which
 * beat the (0,3,2) this rule used to be, so `block-size: 100%` never applied
 * and the image kept its intrinsic height and overflowed the slide. This is
 * (0,4,3). Per .claude/rules/css.md: beat Woo on specificity, never with
 * !important.
 */
body.single-product div.product .nk-gallery .woocommerce-product-gallery__image img {
  /*
   * Absolutely positioned against the link, not sized with percentages.
   *
   * `block-size: 100%` was resolving to `auto` for the featured image — it
   * carries width/height attributes, so the UA aspect-ratio won and it
   * rendered 328×492 inside a 437px slide while the other two slides obeyed
   * the same rule. Setting it inline reproduced it, so this is percentage
   * resolution against the grid-stretched link, not the cascade. `inset: 0`
   * takes the link's padding box directly: no percentage to resolve, and every
   * slide is identical whatever attributes Woo prints on it.
   */
  position: absolute;
  inset: 0;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
  object-position: center;
  background-color: var(--nk-surface-sunken);
}

/*
 * Thumbnails, at every width.
 *
 * These rules used to target `.flex-control-thumbs`, which FlexSlider builds —
 * and FlexSlider is disabled on this page, so that class never rendered and
 * the real markup was left unstyled and auto-placed into a second grid row
 * under the image.
 *
 * commerce.css hides the rail below 1024px for the gallery's other uses. On
 * the PDP it is shown on a phone too, deliberately: a single large image reads
 * as the only photograph there is, and a shopper who does not know more exist
 * will not swipe for them.
 */
body.single-product .nk-gallery__thumbs {
  display: flex;
  gap: var(--nk-space-2);
  margin-block-start: var(--nk-space-3);
  padding-block-end: var(--nk-space-1);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

body.single-product .nk-gallery__thumbs::-webkit-scrollbar { display: none; }

body.single-product .nk-gallery__thumb {
  flex: 0 0 var(--nk-space-12);
  aspect-ratio: var(--nk-ratio-product);
  padding: 0;
  border: var(--nk-border-width-thick) solid var(--nk-border-subtle);
  border-radius: var(--nk-radius-md);
  background-color: var(--nk-surface-sunken);
  overflow: hidden;
  cursor: pointer;
  scroll-snap-align: start;
  opacity: 0.62;
  transition: border-color var(--nk-transition-base), opacity var(--nk-transition-base),
    transform var(--nk-transition-base);
}

body.single-product .nk-gallery__thumb img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* The selected frame is unmistakable — full opacity, the accent border and a
   lift. A border alone at 48px wide was not enough to find at a glance. */
body.single-product .nk-gallery__thumb[aria-current="true"] {
  border-color: var(--nk-border-accent);
  opacity: 1;
  box-shadow: var(--nk-shadow-sm);
}

@media (hover: hover) {
  body.single-product .nk-gallery__thumb:hover { opacity: 1; border-color: var(--nk-border-default); }
}
body.single-product .nk-gallery__thumb:active { transform: translateY(var(--nk-space-hair)); }
body.single-product .nk-gallery__thumb:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  opacity: 1;
}

/* Woo only builds this trigger when jQuery Zoom is on; ours is .nk-gallery__zoom. */
body.single-product .woocommerce-product-gallery__trigger { display: none; }

body.single-product .nk-gallery__controls {
  position: absolute;
  inset-inline: var(--nk-space-3);
  inset-block-end: var(--nk-space-3);
  z-index: var(--nk-z-raised);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--nk-space-2);
  pointer-events: none;
}

body.single-product .nk-gallery__controls > * { pointer-events: auto; }
body.single-product .nk-gallery__zoom { margin-inline-start: auto; }

/*
 * The controls sit on photography, so they carry their own ground — a bare
 * stroke icon disappears against a pale garment.
 *
 * The *visual* circle is 32px, not the 44px `.nk-icon-btn` ships. Three 44px
 * discs and a pill across the foot of a 3:4 portrait read as a toolbar bolted
 * to the picture; the garment is the product and the chrome should be quiet.
 * The touch target is **not** reduced with it: `::after` outsets the hit area
 * back to the full 44px, so the control is smaller to look at and exactly as
 * easy to hit. Logical insets, so it stays symmetric in RTL.
 */
body.single-product .nk-gallery__controls .nk-icon-btn {
  /* The BUTTON keeps its full 44px. Only the painted disc shrinks — shrinking
     the element instead would have cut the real touch target to 32px and made
     every automated target check right to complain. */
  background-color: transparent;
  color: var(--nk-text-inverse);
}

/* The visible disc: 32px, centred in the 44px target. */
body.single-product .nk-gallery__controls .nk-icon-btn::before {
  content: "";
  position: absolute;
  inset: calc((var(--nk-touch-target) - var(--nk-space-8)) / 2);
  border-radius: var(--nk-radius-pill);
  background-color: var(--nk-surface-overlay);
  backdrop-filter: blur(4px);
  transition: background-color var(--nk-transition-base);
}

/* Positioned, so it paints above the disc — a positioned ::before otherwise
   covers the button's own content. */
body.single-product .nk-gallery__controls .nk-icon {
  position: relative;
  inline-size: var(--nk-icon-sm);
  block-size: var(--nk-icon-sm);
}

@media (hover: hover) {
  body.single-product .nk-gallery__controls .nk-icon-btn:hover { background-color: transparent; }
}
@media (hover: hover) {
  body.single-product .nk-gallery__controls .nk-icon-btn:hover::before { background-color: var(--nk-navy-800); }
}

/* The focus ring must clear the disc, not the invisible target. */
body.single-product .nk-gallery__controls .nk-icon-btn:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

/* Inline-end, not inline-start: the model is framed centre-to-start in a 3:4
   portrait, and a start-aligned badge landed on her face. */
body.single-product .nk-gallery__badges {
  position: absolute;
  inset-block-start: var(--nk-space-3);
  inset-inline-end: var(--nk-space-3);
  z-index: var(--nk-z-raised);
  display: grid;
  gap: var(--nk-space-2);
  justify-items: end;
}

body.single-product .nk-gallery__badges:empty { display: none; }

/* Sized down with the arrows it sits between — see the note on
   `.nk-gallery__controls .nk-icon-btn`. It is a label, not a control, so it has
   no touch target to preserve. */
body.single-product .nk-gallery__counter {
  position: static;
  padding-inline: var(--nk-space-2);
  background-color: var(--nk-surface-overlay);
  color: var(--nk-text-inverse);
  font-size: var(--nk-text-2xs);
  backdrop-filter: blur(4px);
}

/* =======================================================================
 * IDENTITY — brand, category, title, rating, price
 * ===================================================================== */

body.single-product div.product .summary {
  inline-size: 100%;
  margin: 0;
  min-inline-size: 0;
  display: grid;
  gap: var(--nk-space-5);
}

.nk-product-identity { display: grid; gap: var(--nk-space-3); }

/*
 * TaxKey — the category+brand control in the identity row.
 *
 * One surface, not two chips with a gap: a parent shopping for children's
 * clothes shops *by label* as much as by department, and showing them as one
 * card says they are two facets of the same fact rather than two separate
 * choices. Category leads — rightmost in the RTL row, "what it is" before
 * "who made it" — and each half's thumbnail sits at the control's outer
 * edge, so both names read inward toward the seam that divides them.
 *
 * `overflow: hidden` on the card is why each half has no radius of its own:
 * the card's own corners clip them for free, including the thumbnail sitting
 * flush in the corner.
 */
.nk-tax-key {
  position: relative;
  display: flex;
  align-items: stretch;
  border: var(--nk-border-hairline);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-raised);
  box-shadow: var(--nk-shadow-xs);
  overflow: hidden;
}

.nk-tax-key__part {
  display: flex;
  align-items: center;
  flex: 1 1 0;
  gap: var(--nk-space-3);
  min-inline-size: 0;
  padding: var(--nk-space-3) var(--nk-space-4);
  color: var(--nk-text-strong);
  text-decoration: none;
  transition: background-color var(--nk-transition-base);
}

.nk-tax-key__part--category { justify-content: flex-start; }
.nk-tax-key__part--brand { justify-content: flex-end; }

.nk-tax-key__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--nk-space-12);
  block-size: var(--nk-space-12);
  flex: none;
  border-radius: var(--nk-radius-md);
  overflow: hidden;
  color: var(--nk-text-muted);
}

.nk-tax-key__thumb img { inline-size: 100%; block-size: 100%; }

/* Category art is a photograph — crop it. Brand art is a wordmark on the
   white the build flattens it onto — never crop a logo, and a coloured mark
   behind a black wordmark is how the label gets misrepresented. Either side
   with no artwork yet falls to its Lucide glyph, centred on the same tinted
   mark rather than left as a gap. */
.nk-tax-key__part--category .nk-tax-key__thumb { background-color: var(--nk-surface-sunken); }
.nk-tax-key__part--category .nk-tax-key__thumb img { object-fit: cover; }
.nk-tax-key__part--brand .nk-tax-key__thumb { background-color: var(--nk-white); }
.nk-tax-key__part--brand .nk-tax-key__thumb img { object-fit: contain; }

.nk-tax-key__text {
  display: grid;
  min-inline-size: 0;
  line-height: var(--nk-leading-tight);
}
.nk-tax-key__part--brand .nk-tax-key__text { text-align: end; }

.nk-tax-key__text small { font-size: var(--nk-text-2xs); color: var(--nk-text-subtle); }
.nk-tax-key__text strong {
  overflow: hidden;
  font-size: var(--nk-text-sm);
  font-weight: var(--nk-weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (hover: hover) {
  .nk-tax-key__part:hover { background-color: var(--nk-surface-accent); }
}
.nk-tax-key__part:active { background-color: var(--nk-surface-sunken); }
.nk-tax-key__part:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: calc(-1 * var(--nk-focus-ring-width) - var(--nk-focus-ring-offset));
}

/* A soft angled seam, not a hard rule: the two halves stay one card while
   still reading as distinct facts. Sized off the card's own block size so it
   never has to be retuned by hand if the control's padding changes. */
.nk-tax-key__divider {
  position: absolute;
  inset-block: var(--nk-space-3);
  inset-inline-start: 50%;
  inline-size: 1px;
  background: linear-gradient(to bottom, transparent, var(--nk-border-default), transparent);
  transform: translateX(-50%) rotate(8deg);
  pointer-events: none;
}

.nk-product-title {
  margin: 0;
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-2xl);
  font-weight: var(--nk-weight-bold);
  line-height: var(--nk-leading-tight);
  color: var(--nk-text-strong);
}

.nk-product-meta-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--nk-space-3);
  font-size: var(--nk-text-sm);
  color: var(--nk-text-muted);
}

.nk-product-meta-link {
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-2);
  min-block-size: var(--nk-touch-target);
  /*
   * Always the row's left in RTL — sales sits right of it when shown, and
   * rating still holds the far end alone when there is no sales history.
   * The element itself carries dir="ltr" (star → value → count reads
   * left-to-right), so its own logical properties resolve against *that*
   * direction: inline-end is the physical right in LTR, same physical side
   * "inline-start" meant back when this element was still RTL. Flip this
   * back to margin-inline-start only if the dir="ltr" attribute ever comes
   * off the markup.
   */
  margin-inline-end: auto;
  color: var(--nk-text-muted);
  text-decoration: none;
}

@media (hover: hover) {
  .nk-product-meta-link:hover { color: var(--nk-text-accent); }
}
.nk-product-meta-link:active { color: var(--nk-action-primary-active); }
.nk-product-meta-link:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-xs);
}

/*
 * THE SALES BADGE
 *
 * The pill stays neutral — sunken grey ground, default near-black text — and
 * **only the glyph is coral**. This is deliberate and was chosen over a fully
 * coral pill at the shop owner's direction: coral text on a coral wash is the
 * one combination in this row that has a contrast argument to lose (coral-500
 * measures 2.9:1 on cream, and even coral-700 on coral-50 is a narrower pass
 * than plain text on grey), and the badge is social proof rather than an
 * action, so it should not compete with the price or the CTA for the accent.
 *
 * The glyph alone carries the colour and the beat: coral-500 is allowed there
 * because the icon is aria-hidden decoration whose meaning is already in the
 * visible «فروش» label beside it — the same exemption the star rating relies
 * on — so no contrast requirement rides on it.
 *
 * The glyph's pulse lives in motion.css with the theme's other loops, not here.
 */
.nk-product-sold {
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-1);
  padding-inline: var(--nk-space-3);
  padding-block: var(--nk-space-1);
  border-radius: var(--nk-radius-pill);
  background-color: var(--nk-surface-sunken);
  color: var(--nk-text-default);
  font-size: var(--nk-text-xs);
}

.nk-product-sold__icon { color: var(--nk-coral-500); }

/*
 * WooCommerce's native star-rating widget (wc_get_rating_html(), printed by
 * nk_rating() in components.php), repainted only — the markup, the
 * accessible label and the fill percentage are all Woo's own, computed from
 * the real average and review count. Scoped under .nk-rating (0,2,0) rather
 * than a bare .star-rating (0,1,0): Woo's own plugin stylesheet is only
 * dequeued on the single-product page (see nk_product_dequeue_legacy_styles()
 * in inc/product.php), so on the shop archive and homepage rails — where
 * nk_product_card() calls nk_rating() too — both stylesheets are present and
 * this file cannot rely on load order alone to win.
 *
 * The star row is a single Lucide star glyph, masked onto two identical
 * layers — .star-rating itself (muted, the empty track) underneath, its own
 * child span (coral, Woo's own width:% overlay) on top — instead of five
 * separate icon elements. One mask asset, tiled with mask-repeat: repeat-x
 * at exactly --nk-star-w per tile with **no gap**: Woo computes that width
 * as (rating / 5) * 100 against a plain contiguous 5-star bar, so a gap here
 * would land that percentage on the wrong pixel — a 4.3 rating would stop
 * somewhere other than exactly 4 full stars + 30% of the fifth. Both layers
 * share the same tile size and 0 0 origin, so the coral overlay's mask lines
 * up star-for-star with the muted track beneath it at any width.
 *
 * The mask source is `assets/icons/star-mask.svg`, not a `sprite.svg#nk-star`
 * fragment: a browser will not paint a bare SVG `<symbol>` as a mask/background
 * image (a `<symbol>` only renders when instantiated by `<use>`), so the sprite
 * itself cannot be referenced this way. `tools/build-star-mask.js` derives that
 * standalone file from the same `nk-star` symbol, so the two can never drift
 * onto two different star shapes — this is the one place a Lucide glyph is
 * consumed outside `nk_icon()`, alongside `assets/icons/brands.svg`.
 */
.nk-rating .star-rating {
  --nk-star-w: var(--nk-icon-sm);
  position: relative;
  display: inline-block;
  vertical-align: middle;
  inline-size: calc(var(--nk-star-w) * 5);
  block-size: var(--nk-star-w);
  /*
   * Stars read left-to-right on this site regardless of the page's own
   * direction — star 1 is always the leftmost star and the fill grows
   * rightward from it, the same convention the reviews' score column
   * already used. Woo's inline width:% on the child span is a physical
   * value either way, but this decides which edge it clips *from*.
   */
  direction: ltr;
  background-color: var(--nk-navy-200);
  -webkit-mask-image: url("../../icons/star-mask.svg");
  mask-image: url("../../icons/star-mask.svg");
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  -webkit-mask-size: var(--nk-star-w) var(--nk-star-w);
  mask-size: var(--nk-star-w) var(--nk-star-w);
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
}

/*
 * Woo's own overlay span: absolutely positioned, width set inline by
 * wc_get_star_rating_html() to (rating / 5) * 100%. overflow: hidden plus
 * the identical mask is the whole "overlay clipped to a fraction of a star"
 * mechanism — no per-star markup, no JS, nothing computed twice. The span's
 * own text ("Rated 4.3 out of 5 based on 3 customer ratings") stays in the
 * DOM as the no-CSS/no-JS fallback — the parent's role="img" + aria-label is
 * what a screen reader actually announces — so it is hidden with
 * font-size/color rather than removed.
 */
.nk-rating .star-rating span {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  block-size: 100%;
  overflow: hidden;
  background-color: var(--nk-rating-color);
  color: transparent;
  font-size: 0;
  line-height: 0;
  -webkit-mask-image: url("../../icons/star-mask.svg");
  mask-image: url("../../icons/star-mask.svg");
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  -webkit-mask-size: var(--nk-star-w) var(--nk-star-w);
  mask-size: var(--nk-star-w) var(--nk-star-w);
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
}

.nk-product-excerpt { color: var(--nk-text-muted); line-height: var(--nk-leading-normal); }
.nk-product-excerpt > :last-child { margin-block-end: 0; }
.nk-product-features { margin-block-start: var(--nk-space-1); }

/* =======================================================================
 * PRICE BLOCK
 *
 * Lives inside the buy panel now, not a standalone accented card above it —
 * a customer read the two boxes as two separate decisions ("is it worth it"
 * then "how do I buy it") when they are one. So this prints as a plain
 * divider row between the panel's head and its form, not a card of its own:
 * no background, no border-radius, just the same rule the head already uses
 * to separate itself from what follows.
 *
 * A grid, not a flex row, for the two lines it holds. The row this replaces
 * squeezed the stock line into 38% of the column and it wrapped into the
 * struck-through price at 360px. Here the current price owns a line at
 * display size and the stock line is a second row. Every part is replaced
 * wholesale by `found_variation`, so a variation's sale price and its
 * struck-through original can never update out of step.
 * ===================================================================== */

.nk-price-block {
  display: grid;
  gap: var(--nk-space-2);
  justify-items: start;
  margin-block-end: var(--nk-space-5);
  padding-block-end: var(--nk-space-4);
  border-block-end: var(--nk-border-hairline);
}

.nk-price-block__main {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--nk-space-2) var(--nk-space-3);
  min-inline-size: 0;
}

/* display: contents, so the current price and the struck-through original
   become direct items of the row above. As one flex child, .nk-price either
   fitted or wrapped whole, which orphaned the original price onto its own
   line. */
.nk-price-block__main .nk-price { display: contents; }

.nk-price-block .nk-price__current {
  font-size: var(--nk-text-3xl);
  line-height: var(--nk-leading-tight);
  color: var(--nk-text-accent);
}

.nk-price-block .nk-price__old { font-size: var(--nk-text-base); }

/* The discount has one home on this page: the badge on the image. Printed
   beside the price too, the same number had two voices in one glance — and at
   360px the chip wrapped onto a line of its own, orphaned. */
.nk-price-block .nk-price__off { display: none; }
.nk-price-block__stock { font-size: var(--nk-text-sm); }

/* =======================================================================
 * BUY PANEL
 *
 * The one accented surface on the page. A customer could not tell where
 * "reading about it" stopped and "buying it" started; one box now says so,
 * and a second accent would say nothing.
 * ===================================================================== */

.nk-buy-panel {
  padding: var(--nk-space-5);
  border-radius: var(--nk-radius-xl);
  background-color: var(--nk-surface-raised);
  border: var(--nk-border-width) solid var(--nk-coral-200);
  box-shadow: var(--nk-shadow-md);
}

.nk-buy-panel__head {
  display: flex;
  align-items: center;
  gap: var(--nk-space-3);
  margin-block-end: var(--nk-space-5);
  padding-block-end: var(--nk-space-4);
  border-block-end: var(--nk-border-hairline);
}

.nk-buy-panel__icon {
  display: grid;
  place-items: center;
  inline-size: var(--nk-touch-target);
  block-size: var(--nk-touch-target);
  border-radius: var(--nk-radius-circle);
  background-color: var(--nk-action-primary);
  color: var(--nk-text-on-accent);
  flex: none;
}

.nk-buy-panel__title {
  margin: 0;
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-lg);
  font-weight: var(--nk-weight-bold);
  color: var(--nk-text-strong);
}

.nk-buy-panel__hint { margin: 0; font-size: var(--nk-text-sm); color: var(--nk-text-muted); }

body.single-product form.cart { display: grid; gap: var(--nk-space-5); }

body.single-product form.cart .variations {
  display: block;
  margin: 0;
  border: 0;
}

body.single-product form.cart .variations tbody,
body.single-product form.cart .variations tr,
body.single-product form.cart .variations th,
body.single-product form.cart .variations td { display: block; }

body.single-product form.cart .variations tr + tr { margin-block-start: var(--nk-space-5); }

/* Woo's own <th> label is replaced by .nk-variation-group__head, which can
   also carry the chosen value and the size-guide link on one line. */
body.single-product form.cart .variations .label { display: none; }
body.single-product form.cart .variations .value { position: relative; padding: 0; }

.nk-variation-group__head {
  display: flex;
  align-items: baseline;
  gap: var(--nk-space-2);
  margin-block-end: var(--nk-space-3);
}

.nk-variation-group__label {
  color: var(--nk-text-strong);
  font-size: var(--nk-text-md);
  font-weight: var(--nk-weight-semibold);
}

/* Echoing the choice beside its label is what makes a swatch row legible to
   someone who cannot tell "یاسی" from "صورتی" by the circle alone. */
.nk-variation-group__value { color: var(--nk-text-accent); font-size: var(--nk-text-sm); }
.nk-variation-group__value:empty { display: none; }

.nk-variation-group__aside {
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-1);
  min-block-size: var(--nk-touch-target);
  padding: 0;
  border: 0;
  background: none;
  color: var(--nk-text-muted);
  font-size: var(--nk-text-sm);
  text-decoration: underline;
  text-underline-offset: var(--nk-space-1);
  cursor: pointer;
}

@media (hover: hover) {
  .nk-variation-group__aside:hover { color: var(--nk-text-accent); }
}
.nk-variation-group__aside:active { color: var(--nk-action-primary-active); }
.nk-variation-group__aside:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-xs);
}

/* Woo's <select> stays in the DOM and stays authoritative — it is simply not
   where anyone points. Also used to park Woo's legacy #rating select. */
.nk-native-variation-select {
  position: absolute;
  inline-size: var(--nk-space-hair);
  block-size: var(--nk-space-hair);
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Woo hides this link with inline `visibility: hidden`, which keeps its 44px
   touch target in flow — 52px of blank column above the quantity stepper
   before anything is selected. product.js flags the form once a choice
   exists; until then the link is out of flow entirely. */
body.single-product .reset_variations { display: none; }

body.single-product form.cart.nk-has-selection .reset_variations {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--nk-touch-target);
  color: var(--nk-text-muted);
  font-size: var(--nk-text-sm);
}

@media (hover: hover) {
  body.single-product .reset_variations:hover { color: var(--nk-text-accent); }
}
body.single-product .reset_variations:active { color: var(--nk-action-primary-active); }
body.single-product .reset_variations:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

.nk-color-select { align-items: flex-start; }
.nk-color-select__option {
  block-size: auto;
  min-block-size: var(--nk-touch-target);
  inline-size: auto;
  min-inline-size: var(--nk-touch-target);
  gap: var(--nk-space-1);
  cursor: pointer;
}
.nk-color-select__name {
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
  white-space: nowrap;
}
.nk-color-select__input:checked ~ .nk-color-select__name {
  color: var(--nk-text-strong);
  font-weight: var(--nk-weight-semibold);
}
@media (hover: hover) {
  .nk-color-select__option:hover .nk-color-select__swatch { transform: scale(1.04); }
}
.nk-color-select__option:active .nk-color-select__swatch { transform: scale(0.96); }
.nk-color-select__input:disabled ~ .nk-color-select__name,
.nk-size-select__input:disabled + .nk-size-select__label { color: var(--nk-action-disabled-text); }

body.single-product .single_variation_wrap { display: grid; gap: var(--nk-space-4); }
/* The grid gap is the spacing; Woo's own bottom margin left ~19px of blank
   above the quantity stepper even when no variation description exists. */
body.single-product .single_variation_wrap .woocommerce-variation { margin: 0; }
body.single-product .woocommerce-variation-description { color: var(--nk-text-muted); }

/* Woo prints the selected variation's price and availability again here, in
   English ("in stock 2"), from strings this theme cannot translate. The price
   block already mirrors both from the same variation object, in Persian. One
   statement of each, not two in two languages. */
body.single-product .woocommerce-variation-price,
body.single-product .woocommerce-variation-availability { display: none; }

/* Quantity and add-to-cart share one row — the quantity stepper takes only
   the width its digits need, the button fills what is left. Quick buy
   (the grid's third child) spans both columns on the row below; see its own
   rule. */
body.single-product .woocommerce-variation-add-to-cart,
body.single-product form.cart:not(.variations_form) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--nk-space-3);
}

/*
 * Four classes and four types on purpose — see trap 2 in the file header.
 * The (0,2,1) rule that was here lost to Woo Blocks' compat sheet and the
 * stepper stacked into a vertical column.
 */
body.single-product div.product form.cart div.quantity,
body.single-product .quantity {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  justify-self: start;
  border: var(--nk-border-width) solid var(--nk-border-default);
  border-radius: var(--nk-radius-md);
  overflow: hidden;
  background-color: var(--nk-surface-raised);
}

/* Same four-class, four-type shape as the rule above, for the same reason:
   the legacy-template sheet's `div.product form.cart div.quantity .input-text`
   sets a padding *and* Woo's `.qty` sets `margin-right`, which in RTL opened an
   8px gap between the input and the «+» button inside the stepper's border. */
body.single-product div.product form.cart div.quantity input.qty,
body.single-product .quantity .qty {
  inline-size: var(--nk-touch-target-lg);
  block-size: var(--nk-touch-target);
  margin: 0;
  padding: 0;
  border: 0;
  border-inline: var(--nk-border-hairline);
  border-radius: 0;
  background: transparent;
  text-align: center;
  font-family: var(--nk-font-numeric);
  font-size: var(--nk-text-md);
  font-weight: var(--nk-weight-semibold);
  appearance: textfield;
}

body.single-product .quantity .qty::-webkit-inner-spin-button,
body.single-product .quantity .qty::-webkit-outer-spin-button { appearance: none; margin: 0; }

body.single-product .quantity .qty:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: calc(var(--nk-focus-ring-offset) * -1);
}

body.single-product .single_add_to_cart_button,
body.single-product .nk-quick-buy {
  inline-size: 100%;
  min-block-size: var(--nk-touch-target-lg);
}

/* Full-width on its own row below the quantity + add-to-cart line, not a
   third column squeezed beside them. */
body.single-product .nk-quick-buy { grid-column: 1 / -1; }

body.single-product .single_add_to_cart_button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--nk-space-2);
  padding-inline: var(--nk-space-6);
  border: 0;
  border-radius: var(--nk-radius-md);
  background-color: var(--nk-action-primary);
  color: var(--nk-text-on-accent);
  box-shadow: var(--nk-shadow-accent);
  font-family: var(--nk-font-ui);
  font-size: var(--nk-text-md);
  font-weight: var(--nk-weight-semibold);
  cursor: pointer;
  transition: background-color var(--nk-transition-base), transform var(--nk-transition-base),
    box-shadow var(--nk-transition-base);
}

@media (hover: hover) {
  body.single-product .single_add_to_cart_button:hover:not(:disabled):not(.disabled) { background-color: var(--nk-action-primary-hover); }
}
body.single-product .single_add_to_cart_button:active:not(:disabled):not(.disabled) { background-color: var(--nk-action-primary-active); transform: translateY(var(--nk-space-hair)); }
body.single-product .single_add_to_cart_button:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}
body.single-product .single_add_to_cart_button:disabled,
body.single-product .single_add_to_cart_button.disabled {
  background-color: var(--nk-action-disabled-bg);
  color: var(--nk-action-disabled-text);
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

/* Quick buy is the second action, and has to read as one. Outlined rather than
   the solid navy it was: two full-width solid blocks made the page look like
   it had two primary buttons, which is exactly what the design system forbids. */
body.single-product .nk-quick-buy {
  background-color: transparent;
  border: var(--nk-border-width) solid var(--nk-border-strong);
  color: var(--nk-text-strong);
  box-shadow: none;
}

@media (hover: hover) {
  body.single-product .nk-quick-buy:hover:not(:disabled) {
    background-color: var(--nk-surface-sunken);
    border-color: var(--nk-navy-800);
  }
}
body.single-product .nk-quick-buy:disabled {
  border-color: var(--nk-border-subtle);
  color: var(--nk-action-disabled-text);
  cursor: not-allowed;
}

.nk-product-sku {
  margin: var(--nk-space-4) 0 0;
  color: var(--nk-text-subtle);
  font-size: var(--nk-text-xs);
}

/* =======================================================================
 * TRUST PANEL
 *
 * A 2×2 grid at every width, not the horizontal scroll rail it was. A
 * scroller hides half its contents by design, and these four claims are the
 * answer to "can I trust this shop" — the one thing that must not be behind a
 * swipe. Each card opens a sheet, so the card itself stays one line.
 * ===================================================================== */

.nk-trust-panel { padding: var(--nk-space-4); }

.nk-trust-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--nk-space-3);
}

/* Stacked below 480px. Side by side, a two-word Persian title plus a chevron
   left about 90px for the text in a half-width card and «جنس باکیفیت» broke
   across two lines in every card. Centred under its icon it fits on one. */
.nk-trust-card {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--nk-space-2);
  min-block-size: var(--nk-touch-target-lg);
  padding: var(--nk-space-4) var(--nk-space-2);
  text-align: center;
  border: var(--nk-border-hairline);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-raised);
  box-shadow: var(--nk-shadow-xs);
  text-align: start;
  cursor: pointer;
  transition: box-shadow var(--nk-transition-base), border-color var(--nk-transition-base),
    transform var(--nk-transition-base);
}

.nk-trust-card__icon {
  display: grid;
  place-items: center;
  inline-size: var(--nk-touch-target);
  block-size: var(--nk-touch-target);
  border-radius: var(--nk-radius-circle);
  background-color: var(--nk-surface-accent);
  color: var(--nk-action-primary);
  flex: none;
}

.nk-trust-card__text { display: grid; gap: var(--nk-space-hair); min-inline-size: 0; }
.nk-trust-card__more { display: none; }
.nk-trust-card__text strong {
  font-size: var(--nk-text-sm);
  font-weight: var(--nk-weight-semibold);
  color: var(--nk-text-strong);
}
.nk-trust-card__text small { font-size: var(--nk-text-xs); color: var(--nk-text-muted); }


@media (hover: hover) {
  .nk-trust-card:hover {
    box-shadow: var(--nk-shadow-sm);
    border-color: var(--nk-border-accent);
  }
}
.nk-trust-card:active { transform: translateY(var(--nk-space-hair)); }
.nk-trust-card:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

/* =======================================================================
 * INFO PANEL — accordion on mobile, tabs on desktop
 *
 * One content source. The panels are printed once and carry both affordances;
 * only their visibility changes. Dropping the accordion later is these two
 * declarations, not a template rewrite.
 * ===================================================================== */

.nk-info-panel__tabs { display: none; }

.nk-info-section + .nk-info-section { border-block-start: var(--nk-border-hairline); }

.nk-info-section__toggle {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  inline-size: 100%;
  min-block-size: var(--nk-touch-target-lg);
  padding: 0;
  border: 0;
  background: none;
  color: var(--nk-text-strong);
  font-family: var(--nk-font-ui);
  font-size: var(--nk-text-md);
  font-weight: var(--nk-weight-semibold);
  text-align: start;
  cursor: pointer;
}

.nk-info-section__name { flex: 1; }
.nk-info-section__toggle .nk-icon { color: var(--nk-text-accent); flex: none; }
.nk-info-section__chevron { color: var(--nk-text-subtle); transition: transform var(--nk-transition-base); }
.nk-info-section.is-active .nk-info-section__chevron { transform: rotate(180deg); }

@media (hover: hover) {
  .nk-info-section__toggle:hover { color: var(--nk-text-accent); }
}
.nk-info-section__toggle:active { color: var(--nk-action-primary-active); }
.nk-info-section__toggle:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-xs);
}

.nk-info-section__body {
  display: none;
  padding-block-end: var(--nk-space-4);
  line-height: var(--nk-leading-relaxed);
  color: var(--nk-text-default);
}

.nk-info-section.is-active .nk-info-section__body { display: block; }
.nk-info-section__body > :first-child { margin-block-start: 0; }
.nk-info-section__body > :last-child { margin-block-end: 0; }

/* =======================================================================
 * REVIEWS
 * ===================================================================== */

.nk-reviews-layout { display: grid; gap: var(--nk-space-4); }

/* No ratings yet, so no summary column — see nk_product_render_reviews(). The
   modifier has to restate one column at every width, because the two
   breakpoints below give `.nk-reviews-layout` a two-column track. */
.nk-reviews-layout--bare { grid-template-columns: minmax(0, 1fr); }

/*
 * The score column is `direction: ltr`.
 *
 * It is a stack of numerals, stars and bars with no prose in it at all, and in
 * RTL the "۴٫۳ / از ۵" pairing and the 5→1 bar ladder both read backwards. The
 * label text inside stays RTL by inheritance where it exists. This is one of
 * the documented direction overrides — see the count in the jensik-ui skill's
 * mobile-rtl-a11y topic.
 */
.nk-review-summary {
  direction: ltr;
  display: grid;
  gap: var(--nk-space-3);
  padding: var(--nk-space-4);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-sunken);
  align-self: start;
}

/*
 * Mobile: the score and the 5→1 ladder sit side by side, not stacked.
 *
 * Stacked they were the single tallest thing in the panel — a 48px numeral, a
 * caption, a star row, a second caption and then five bars, ~290px before the
 * first review. Side by side the same information is ~130px and the reader
 * reaches an actual review inside one screenful, which is the whole point of
 * the section.
 */
.nk-review-summary__score {
  display: grid;
  justify-items: center;
  gap: var(--nk-space-hair);
  text-align: center;
}

.nk-review-summary__average {
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-4xl);
  font-weight: var(--nk-weight-bold);
  line-height: 1;
  color: var(--nk-text-strong);
}

.nk-review-summary__outof { font-size: var(--nk-text-xs); color: var(--nk-text-subtle); }
.nk-review-summary__total {
  direction: rtl;
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
}

.nk-review-summary__bars { display: grid; gap: var(--nk-space-1); align-content: center; }

@media (max-width: 767px) {
  .nk-review-summary {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--nk-space-4);
  }
  /* The "بر پایه N دیدگاه" line repeats the panel subtitle directly above it;
     at this width the column cannot afford to say it twice. */
  .nk-review-summary__total { display: none; }
}

.nk-rating-bar__star {
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-hair);
  min-inline-size: var(--nk-space-8);
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
}
.nk-rating-bar__star .nk-icon { color: var(--nk-rating-color); }
.nk-rating-bar__count {
  min-inline-size: var(--nk-space-6);
  text-align: end;
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
}
.nk-rating-bar__fill { display: block; }

/* --- Toolbar: how many, and in what order --------------------------- */

.nk-reviews-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--nk-space-3);
  margin-block-end: var(--nk-space-3);
}

.nk-reviews-toolbar__count {
  margin: 0;
  font-size: var(--nk-text-sm);
  font-weight: var(--nk-weight-semibold);
  color: var(--nk-text-muted);
}

/* The trigger states the order in force rather than the word «مرتب‌سازی», so
   the toolbar answers "how is this sorted" without being opened. `min-inline-size: 0`
   plus the ellipsis below keep «بیشترین امتیاز» from widening the row at 360px. */
.nk-reviews-sort__trigger { min-inline-size: 0; }
.nk-reviews-sort__current {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The sort sheet is short; a centred card at 84vh would be mostly empty. The
   420px width is `.nk-sheet--sort`'s, set with the other card widths in
   feedback.css — only the height ceiling is specific to this one. */
@media (min-width: 768px) {
  .nk-reviews-sort-sheet > .nk-sheet__panel { max-block-size: none; }
}

/* --- The list ------------------------------------------------------- */

.nk-review-list { margin: 0; padding: 0; list-style: none; }
.nk-review-list ol,
.nk-review-list ul { margin: 0; padding: 0; list-style: none; }

/*
 * Everything past the tenth top-level review is hidden, not absent.
 *
 * The markup holds the whole list either way, which is what lets the sheet
 * *borrow* it rather than render a second copy — a copy would duplicate every
 * `id="review-N"` anchor and every vote button in the document. `> li` and
 * `:nth-of-type` together count only top-level reviews, so a shop reply nested
 * inside the tenth review does not push the eleventh out of sight.
 */
.nk-review-list--capped > li:nth-of-type(n + 11) { display: none; }

/*
 * There is no `--rest` mask and no reserved gap any more, deliberately.
 *
 * The popup used to borrow the *whole* list and mask the first ten; that left
 * an empty hole in the page behind the scrim and shortened the document by
 * ~3200px, which made the browser clamp `scrollY` and drop the reader at the
 * footer on close. It now moves only the rows this rule already hides, so
 * nothing visible leaves the page and its height does not change. Both the
 * second mask and the measured spacer that compensated for the height loss
 * were deleted rather than kept "just in case" — see components-reviews.md.
 */

body.single-product .nk-review { border-block-end: 0; padding-block: 0; }
body.single-product .nk-review + .nk-review { margin-block-start: var(--nk-space-3); }

.nk-review__card {
  padding: var(--nk-space-3);
  border: var(--nk-border-hairline);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-raised);
  box-shadow: var(--nk-shadow-xs);
}

.nk-review__head { align-items: flex-start; margin-block-end: var(--nk-space-2); }
.nk-review__who { display: grid; gap: var(--nk-space-hair); min-inline-size: 0; }
.nk-review__author { display: flex; align-items: center; flex-wrap: wrap; gap: var(--nk-space-2); }
.nk-review__rating { margin-inline-start: auto; }

/* A reply from the shop is visually a different voice: indented, on the accent
   wash, with the storefront's own mark instead of an initial. */
.nk-review--reply { margin-block-start: var(--nk-space-3); }
.nk-review--reply .nk-review__card {
  margin-inline-start: var(--nk-space-8);
  border-inline-start: var(--nk-border-width-thick) solid var(--nk-coral-300);
  background-color: var(--nk-surface-accent);
  border-color: var(--nk-coral-200);
}

.nk-review__avatar--shop { background-color: var(--nk-white); border-color: var(--nk-coral-300); }

.nk-review__role {
  padding-inline: var(--nk-space-2);
  padding-block: var(--nk-space-hair);
  border-radius: var(--nk-radius-pill);
  background-color: var(--nk-action-primary);
  color: var(--nk-text-on-accent);
  font-size: var(--nk-text-2xs);
  font-weight: var(--nk-weight-semibold);
}

/* `comment_text()` runs through wpautop, so the body arrives as <p> elements
   carrying the document's own paragraph rhythm. Inside a card that is one
   paragraph tall it read as an empty line above the photo row. */
.nk-review__body { line-height: var(--nk-leading-relaxed); }
.nk-review__body > :first-child { margin-block-start: 0; }
.nk-review__body > :last-child { margin-block-end: 0; }

.nk-review__media {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nk-space-2);
  margin-block-start: var(--nk-space-3);
}

.nk-review__thumb {
  inline-size: var(--nk-space-16);
  block-size: var(--nk-space-16);
  border-radius: var(--nk-radius-md);
  overflow: hidden;
  background-color: var(--nk-surface-sunken);
}

.nk-review__thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.nk-review__thumb:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

/* --- Helpfulness vote ----------------------------------------------- */

.nk-review__foot {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  margin-block-start: var(--nk-space-3);
  padding-block-start: var(--nk-space-2);
  border-block-start: var(--nk-border-hairline);
}

.nk-review__vote-label {
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
}

.nk-review__votes {
  display: flex;
  align-items: center;
  gap: var(--nk-space-1);
  margin-inline-start: auto;
}

/*
 * A quiet pair, not two more buttons.
 *
 * The row already carries a rating and a card border; a bordered control here
 * would be the third box in 40px. The target is still the full 44px — the
 * padding is what grows, the ink does not.
 */
.nk-review-vote {
  display: inline-flex;
  align-items: center;
  gap: var(--nk-space-1);
  min-block-size: var(--nk-touch-target);
  min-inline-size: var(--nk-touch-target);
  justify-content: center;
  padding-inline: var(--nk-space-2);
  border: 0;
  border-radius: var(--nk-radius-pill);
  background-color: transparent;
  color: var(--nk-text-muted);
  font-size: var(--nk-text-xs);
  font-family: inherit;
  cursor: pointer;
  transition: color var(--nk-transition-base), background-color var(--nk-transition-base);
}

.nk-review-vote__count { font-variant-numeric: tabular-nums; }

@media (hover: hover) {
  .nk-review-vote:hover { background-color: var(--nk-surface-sunken); color: var(--nk-text-default); }
}

.nk-review-vote:focus-visible {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

.nk-review-vote:active { transform: scale(0.94); }

/* `aria-pressed` drives the pressed style, the way `.nk-chip` already does, so
   the accessible state and the visual state cannot drift apart. */
.nk-review-vote[aria-pressed="true"] { background-color: var(--nk-surface-accent); }
.nk-review-vote--up[aria-pressed="true"] { color: var(--nk-action-primary); }
.nk-review-vote--down[aria-pressed="true"] { color: var(--nk-status-danger-text); }

.nk-review-vote[disabled] { color: var(--nk-action-disabled-text); cursor: not-allowed; }

/* --- Overflow control ----------------------------------------------- */

.nk-reviews-more { margin-block-start: var(--nk-space-3); }

/* --- Nothing here yet ------------------------------------------------ */

/*
 * One line, not `nk_empty_state()`.
 *
 * The shared component's icon well, title and paragraph stack to ~220px, and
 * on a product with no reviews that was the tallest block in the panel — the
 * shop owner reported it as taking more room than the form under it. The rule
 * "an empty state without an action is a dead end" is still honoured: the way
 * forward is the review form directly below, not a button that scrolls to it.
 */
.nk-reviews-empty {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  margin: 0;
  padding: var(--nk-space-3);
  border: var(--nk-border-hairline);
  border-radius: var(--nk-radius-md);
  background-color: var(--nk-surface-sunken);
  color: var(--nk-text-muted);
  font-size: var(--nk-text-sm);
}
.nk-reviews-empty .nk-icon { color: var(--nk-text-subtle); flex: 0 0 auto; }

/* --- The form ------------------------------------------------------- */

.nk-review-form {
  margin-block-start: var(--nk-space-6);
  padding: var(--nk-space-4);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-sunken);
}

.nk-review-form .comment-reply-title {
  display: block;
  margin-block-end: var(--nk-space-4);
  font-family: var(--nk-font-display);
  font-size: var(--nk-text-lg);
  font-weight: var(--nk-weight-bold);
}

.nk-review-form .form-submit { margin-block-start: var(--nk-space-4); margin-block-end: 0; }

/* The signed-out submit is an <a>, so it does not inherit the button element's
   own margin collapse; state the same rhythm for it. */
.nk-review-form__gate { margin-block-start: var(--nk-space-4); }

/* Why the sign-in is being asked for, under the control that asks. */
.nk-review-form__note {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  margin-block: var(--nk-space-3) 0;
  font-size: var(--nk-text-xs);
  color: var(--nk-text-muted);
}
.nk-review-form__note .nk-icon { flex: 0 0 auto; color: var(--nk-text-subtle); }

/* The sticky add-to-cart bar retreats while a control in this form has focus
   (product.js, initStickyCta) — which is why this number is the nav alone and
   not the nav plus the bar. The bottom nav is global and does not retreat. It
   is fixed, so it takes no space in flow and the browser happily scrolls a
   focused field to a position it then covers — the last field and the submit
   button most of all. Reserving the bar's height as scroll margin is what
   makes "tap the field" land somewhere you can read. */
.nk-review-form :is(input, textarea, select, button) {
  scroll-margin-block-end: calc(var(--nk-bottom-nav-space) + var(--nk-space-4));
}

/* Woo Blocks' compat sheet sets `.woocommerce-page select` to the preset
   `small` size — 13px. Under 16px, iOS zooms the whole page the moment the
   field takes focus and leaves the customer scrolled sideways mid-review. */
body.single-product .nk-select,
body.single-product .nk-input,
body.single-product .nk-textarea { font-size: var(--nk-text-md); }

/*
 * Five real radios behind five stars, reading 1 → 5 from the left.
 *
 * Woo's own widget is a row of <a href="#"> driving a hidden <select> — not
 * reachable in a form without a mouse, and labelled in English.
 *
 * Three declarations, and all three are load-bearing:
 *
 *   • The DOM order is 5→1 (see nk_product_star_inputs()) because CSS has no
 *     previous-sibling combinator: `~` can only light the stars that come
 *     *after* the hovered one, so the lower ratings have to be the later ones.
 *   • `row-reverse` therefore puts 1 on the visual left and 5 on the right.
 *   • `direction: ltr` is what makes that true in *this* document. Flex
 *     direction resolves against the writing direction, so in the page's RTL
 *     context `row-reverse` ran left→right and put 5 — the highest score — at
 *     the left edge, the exact opposite of the intent. It shipped that way and
 *     the shop owner reported it on 2026-08-22. This is one of the documented
 *     direction overrides: the row is a scale, not prose, and a scale ascends
 *     left to right here the same way the review summary's own score column
 *     does.
 */
.nk-star-input {
  direction: ltr;
  display: flex;
  flex-direction: row-reverse;
  /* `flex-start` on a reversed row is the *right* edge — the reading side, so
     the cluster stays under its own label instead of stranded across the panel
     from it. The order inside the cluster is still 1 → 5 leftwards to right. */
  justify-content: flex-start;
  gap: var(--nk-space-1);
}

.nk-star-input__option { cursor: pointer; }
.nk-star-input__option input {
  position: absolute;
  inline-size: var(--nk-space-hair);
  block-size: var(--nk-space-hair);
  opacity: 0;
}

.nk-star-input__star {
  display: grid;
  place-items: center;
  inline-size: var(--nk-touch-target);
  block-size: var(--nk-touch-target);
  color: var(--nk-navy-200);
  transition: color var(--nk-transition-base), transform var(--nk-transition-base);
}

.nk-star-input__star .nk-icon { inline-size: var(--nk-icon-lg); block-size: var(--nk-icon-lg); }

.nk-star-input__option:has(input:checked) .nk-star-input__star,
.nk-star-input__option:has(input:checked) ~ .nk-star-input__option .nk-star-input__star {
  color: var(--nk-rating-color);
}
@media (hover: hover) {
  .nk-star-input__option:hover .nk-star-input__star,
  .nk-star-input__option:hover ~ .nk-star-input__option .nk-star-input__star {
    color: var(--nk-rating-color);
  }
}

.nk-star-input__option:active .nk-star-input__star { transform: scale(0.92); }
.nk-star-input__option:has(input:focus-visible) .nk-star-input__star {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
  border-radius: var(--nk-radius-sm);
}

.nk-review-upload__drop {
  display: grid;
  justify-items: center;
  gap: var(--nk-space-2);
  padding: var(--nk-space-5);
  border: var(--nk-border-width-thick) dashed var(--nk-border-default);
  border-radius: var(--nk-radius-lg);
  background-color: var(--nk-surface-raised);
  color: var(--nk-text-muted);
  cursor: pointer;
  transition: border-color var(--nk-transition-base), background-color var(--nk-transition-base);
}

@media (hover: hover) {
  .nk-review-upload__drop:hover { border-color: var(--nk-border-accent); background-color: var(--nk-surface-accent); }
}
.nk-review-upload__drop .nk-icon { color: var(--nk-action-primary); }
.nk-review-upload__hint { font-size: var(--nk-text-xs); }
.nk-review-upload__input { position: absolute; inline-size: var(--nk-space-hair); block-size: var(--nk-space-hair); opacity: 0; }
.nk-review-upload__drop:focus-within {
  outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
  outline-offset: var(--nk-focus-ring-offset);
}

.nk-review-upload__preview { display: flex; flex-wrap: wrap; gap: var(--nk-space-2); margin-block-start: var(--nk-space-3); }
.nk-review-upload__item { margin: 0; inline-size: var(--nk-space-16); block-size: var(--nk-space-16); border-radius: var(--nk-radius-md); overflow: hidden; }
.nk-review-upload__item img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.nk-review-upload__error { margin-block-start: var(--nk-space-2); color: var(--nk-status-danger-text); font-size: var(--nk-text-sm); }

.nk-review-upload__locked {
  display: flex;
  align-items: center;
  gap: var(--nk-space-2);
  margin: 0;
  padding: var(--nk-space-3);
  border-radius: var(--nk-radius-md);
  background-color: var(--nk-surface-raised);
  color: var(--nk-text-muted);
  font-size: var(--nk-text-sm);
}

/* =======================================================================
 * RAILS — moved
 * `.nk-rail-section` now lives in commerce.css, rendered by nk_product_rail(),
 * because the homepage grew rows of its own and a second copy would have been
 * a second set of RTL scroll bugs. Nothing PDP-specific was left behind.
 * ===================================================================== */

/* =======================================================================
 * SHEETS
 * ===================================================================== */

.nk-sheet--compact .nk-sheet__body { line-height: var(--nk-leading-relaxed); color: var(--nk-text-default); }
.nk-sheet--compact .nk-sheet__body p { margin: 0; }

/* The list is borrowed from the page, so it arrives with the page's own
   `margin-block-start` between cards and nothing else to reset. */
.nk-reviews-sheet .nk-sheet__body { padding-block-end: calc(var(--nk-space-5) + var(--nk-safe-bottom)); }

.nk-login-sheet__lead {
  margin-block: 0 var(--nk-space-4);
  font-size: var(--nk-text-sm);
  line-height: var(--nk-leading-relaxed);
  color: var(--nk-text-muted);
}
.nk-login-sheet__form .nk-field { margin-block-end: 0; }
.nk-login-sheet .nk-error { display: flex; align-items: center; gap: var(--nk-space-1); }
.nk-login-sheet .nk-error[hidden] { display: none; }
.nk-sheet__title { display: flex; align-items: center; gap: var(--nk-space-2); }
.nk-sheet__title .nk-icon { color: var(--nk-text-accent); }

/* Only 768–1023 ever sees this sheet as a centred dialog — its opener is the
   sticky CTA, which is `display: none` from 1024 — and at 768 the default
   `--nk-container-narrow` card would be the whole viewport wide. */
@media (min-width: 768px) { .nk-buy-sheet { --nk-sheet-width: 520px; } }

/* The head is a flex row; without these the product block grows past the close
   button and the button itself stretches to the row's full height — a 44px
   icon rendered as a 180px circle. */
.nk-buy-sheet .nk-sheet__head { align-items: flex-start; }
.nk-buy-sheet .nk-sheet__head > .nk-icon-btn { flex: none; align-self: center; }

.nk-buy-sheet__product {
  display: flex;
  align-items: center;
  gap: var(--nk-space-3);
  flex: 1;
  min-inline-size: 0;
}
.nk-buy-sheet__thumb {
  inline-size: var(--nk-space-12);
  block-size: var(--nk-space-16);
  border-radius: var(--nk-radius-md);
  background-color: var(--nk-surface-sunken);
  overflow: hidden;
  flex: none;
}
.nk-buy-sheet__thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.nk-buy-sheet__meta { display: grid; gap: var(--nk-space-1); min-inline-size: 0; }
.nk-buy-sheet__meta .nk-sheet__title { font-size: var(--nk-text-md); }
.nk-buy-sheet__price .nk-price { display: flex; align-items: baseline; gap: var(--nk-space-2); }
.nk-buy-sheet__price .nk-price__current { font-size: var(--nk-text-lg); color: var(--nk-text-accent); }
.nk-buy-sheet__price .nk-price__old { font-size: var(--nk-text-xs); }
/* The discount lives on the image badge; a third copy in the sheet head is
   noise in a box whose whole job is the two choices below it. */
.nk-buy-sheet__price .nk-price__off { display: none; }

.nk-buy-sheet__notice { margin: 0 0 var(--nk-space-3); color: var(--nk-status-danger-text); font-size: var(--nk-text-sm); }
.nk-buy-sheet__notice:empty { display: none; }
.nk-buy-sheet .nk-sheet__foot { display: block; }

/* The borrowed controls carry their own panel styling into the sheet; inside
   it the surrounding card would be a box within a box. */
.nk-buy-sheet [data-nk-purchase] .single_add_to_cart_button,
.nk-buy-sheet [data-nk-purchase] .nk-quick-buy { display: none; }

/* =======================================================================
 * STICKY CTA
 * ===================================================================== */

body.nk-has-sticky-cta {
  padding-block-end: calc(var(--nk-bottom-nav-space) + var(--nk-space-20));
}

body.single-product .nk-sticky-cta {
  min-inline-size: 0;
  gap: var(--nk-space-4);
  box-shadow: var(--nk-shadow-xl);
}

/* The price gets a whole readable line, not 42% of the bar. It was
   `max-inline-size: 42%` with the currency and the struck-through original
   fighting over it, which at 360px produced two clipped fragments. */
body.single-product .nk-sticky-cta__price {
  display: grid;
  gap: 0;
  min-inline-size: 0;
  flex: 1;
}

body.single-product .nk-sticky-cta__price .nk-price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--nk-space-1) var(--nk-space-2);
}

body.single-product .nk-sticky-cta__price .nk-price__current {
  font-size: var(--nk-text-lg);
  color: var(--nk-text-accent);
  white-space: nowrap;
}

body.single-product .nk-sticky-cta__price .nk-price__old { font-size: var(--nk-text-xs); }
body.single-product .nk-sticky-cta__price .nk-price__off { display: none; }

body.single-product .nk-sticky-cta__btn { flex: 0 0 auto; min-inline-size: calc(var(--nk-space-20) * 2); }

/* =======================================================================
 * BREAKPOINTS
 * ===================================================================== */

@media (min-width: 480px) {
  .nk-panel { padding: var(--nk-space-6); }

  /* Room for the row layout returns, and with it the affordance that says the
     card opens something. */
  .nk-trust-card {
    grid-template-columns: auto minmax(0, 1fr) auto;
    justify-items: start;
    align-items: center;
    gap: var(--nk-space-3);
    padding: var(--nk-space-3);
    text-align: start;
  }
  .nk-trust-card__more { display: block; color: var(--nk-text-subtle); }

  .nk-buy-panel { padding: var(--nk-space-6); }
  .nk-product-title { font-size: var(--nk-text-3xl); }
}

@media (min-width: 768px) {
  /* Tablet is still one column, and a 720px-wide 3:4 box would be 960px tall.
     Constrain the portrait on the INLINE axis and centre it — capping the
     block axis instead would clip the slides rather than scale them. */
  body.single-product .nk-gallery .nk-gallery__stage {
    max-inline-size: calc(var(--nk-space-20) * 6);
    margin-inline: auto;
  }

  body.single-product .nk-gallery__thumbs { justify-content: center; }

  .nk-trust-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .nk-reviews-layout { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); }
  .nk-reviews-layout--bare { grid-template-columns: minmax(0, 1fr); }
  .nk-review-form { padding: var(--nk-space-6); }
}

@media (min-width: 1024px) {
  body.single-product .nk-product-main { padding-block-start: var(--nk-space-5); }

  /*
   * Two equal columns, separated by exactly one design-system step.
   *
   * They were 1.15fr / 0.85fr — 681px against 503px at 1280px. Combined with
   * the stage's inline cap that produced the page's worst piece of geometry:
   * the gallery column was 681 wide holding a 560px stage, and because the
   * stage is pinned to its column's inline start, all 121px of the remainder
   * collected on the summary side. Measured separation between photograph and
   * buy card was 32px of grid gap plus 121px of dead track — 153px of empty
   * page down the middle, which reads as a layout fault rather than a gutter.
   *
   * `div.product` is capped at 1216px by `.nk-product-main`, so 1280, 1440 and
   * 1920 all resolve to the same 592px columns; only 1024 is narrower.
   */
  body.single-product div.product {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--nk-space-8);
    align-items: start;
  }

  /*
   * The thumbnails are a horizontal rail **below** the stage at every width.
   *
   * They used to be an 80px column beside it, which cost the gallery 96px of
   * its own inline space and gave that space to three small frames — on a
   * 1280px viewport the stage lost ~14% of its width to a rail that is only
   * ever three items long. A row underneath costs ~120px of block space that
   * the buy column (the taller of the two) already occupies, so it is space the
   * layout was wasting anyway. It also makes the gallery one component at every
   * width instead of two, which is why this block is now so short.
   */
  body.single-product div.product .woocommerce-product-gallery.nk-gallery {
    grid-row: 1;
    grid-column: 1;
    display: block;
  }

  /*
   * The width freed by moving the rail is NOT given to the image.
   *
   * Uncapped, the stage took the whole 681px column and a 3:4 portrait became
   * 908px tall — taller than a 900px laptop viewport, so the garment no longer
   * fit the fold and the thumbnail rail we had just moved below it was pushed
   * off-screen entirely, which defeats the reason for moving it. Capped, the
   * image stays the size it always was and the reclaimed space becomes a gutter
   * between the photograph and the buy card. Inline axis only — capping the
   * block axis clips an aspect-ratio box rather than scaling it.
   */
  /*
   * Centred in its own column, not pinned to its inline start.
   *
   * The cap is kept — it is what stops a 3:4 portrait growing taller than a
   * laptop viewport — but with `margin-inline: 0` every pixel the cap held
   * back piled up on one side, and that side is the one facing the buy card.
   * `auto` splits the remainder, so the slack frames the photograph instead of
   * pretending to be a gutter. At the equal columns above it is 592 − 560 = 32px
   * total, i.e. 16px a side; at 1024 the column is narrower than the cap and
   * this does nothing at all.
   */
  body.single-product .nk-gallery .nk-gallery__stage {
    max-inline-size: calc(var(--nk-space-20) * 7);
    margin-inline: auto;
  }

  /* Same cap and the same centring, so the rail keeps the stage's edges and the
     two read as one block rather than a picture with a stray row beneath it. */
  body.single-product .nk-gallery__thumbs {
    max-inline-size: calc(var(--nk-space-20) * 7);
    margin-inline: auto;
  }

  /*
   * `flex-direction` and the scroll axis are reset explicitly. commerce.css
   * still lays the rail out as a 1024px-and-up *column* for the gallery's other
   * uses, and nothing in this block competed with it — the frames came out
   * 681px wide and 64px tall, because in a column container `flex-basis` sizes
   * the block axis. Start-aligned, so the rail's edge lines up with the stage's
   * own inline start — the right-hand edge in RTL, without naming a side.
   */
  body.single-product .nk-gallery__thumbs {
    flex-direction: row;
    justify-content: flex-start;
    margin-block-start: var(--nk-space-3);
    max-block-size: none;
    overflow-x: auto;
    overflow-y: hidden;
  }

  /* Roomier frames than mobile's 48px: there is width to spend here and a
     64px frame is legible at arm's length on a desktop screen. */
  body.single-product .nk-gallery__thumb { flex: 0 0 var(--nk-space-16); }

  /*
   * The buy column is deliberately NOT sticky.
   *
   * It was, and it escaped its grid area completely: measured still pinned
   * 3300px into the document, painted over the details, the reviews and the
   * recommendation rails. An explicit `grid-row` did not constrain it —
   * Chrome resolves a sticky grid item against the grid container here, and
   * the container is the whole page-length product grid. It also bought
   * nothing: the buy column is the taller of the two, so a sticky one pins
   * immediately and never travels. Mobile's .nk-sticky-cta does that job.
   */
  body.single-product div.product .summary { grid-row: 1; grid-column: 2; }

  /* Everything below the fold spans both columns. */
  body.single-product .nk-panel,
  body.single-product .nk-rail-section,
  body.single-product .nk-divider { grid-column: 1 / -1; }

  /* Tabs replace the accordion. Removing the accordion permanently is these
     two declarations moved out of the media query — the markup already
     carries both affordances and the panels are never duplicated. */
  .nk-info-panel__tabs {
    display: flex;
    gap: var(--nk-space-2);
    margin-block-end: var(--nk-space-5);
    border-block-end: var(--nk-border-hairline);
  }

  .nk-info-tab {
    display: inline-flex;
    align-items: center;
    gap: var(--nk-space-2);
    min-block-size: var(--nk-touch-target);
    padding-inline: var(--nk-space-4);
    border: 0;
    border-block-end: var(--nk-border-width-thick) solid transparent;
    background: none;
    color: var(--nk-text-muted);
    font-family: var(--nk-font-ui);
    font-size: var(--nk-text-md);
    font-weight: var(--nk-weight-medium);
    cursor: pointer;
    transition: color var(--nk-transition-base), border-color var(--nk-transition-base);
  }

  @media (hover: hover) {
    .nk-info-tab:hover { color: var(--nk-text-accent); }
  }
  .nk-info-tab[aria-selected="true"] {
    color: var(--nk-text-accent);
    border-block-end-color: var(--nk-action-primary);
    font-weight: var(--nk-weight-semibold);
  }
  .nk-info-tab:focus-visible {
    outline: var(--nk-focus-ring-width) solid var(--nk-focus-ring-color);
    outline-offset: var(--nk-focus-ring-offset);
    border-radius: var(--nk-radius-xs);
  }

  .nk-info-section { display: none; border: 0; }
  .nk-info-section.is-active { display: block; }
  .nk-info-section + .nk-info-section { border-block-start: 0; }
  .nk-info-section__toggle { display: none; }
  .nk-info-section__body { display: block; padding-block: 0; }

  .nk-reviews-layout { grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: var(--nk-space-8); }
  .nk-reviews-layout--bare { grid-template-columns: minmax(0, 1fr); }

  /*
   * The score column follows the reader down the list.
   *
   * It is the reference the reviews are being read *against* — "is this a
   * 4.3-star complaint or a 2-star one" — and once the list is ten or twenty
   * cards long it had scrolled away by the second card, leaving a tall empty
   * track beside the reviews. Sticky only from 1024, where the two-column
   * layout exists at all; below that it is a band above the list and there is
   * nothing to stick to.
   *
   * The offset is `--nk-header-stuck`, the height ui.js measures and publishes,
   * **not** `--nk-header-height` — that token is only `.nk-header__bar`, and
   * the sticky header also carries the announcement strip and the category
   * nav. Measured at 1280 the bar is 80px and the real sticky block is 174px,
   * so the token alone pinned the panel 78px under the chrome. The fallback in
   * the `var()` is what keeps the no-JS case merely imperfect rather than
   * broken; the archive's category rail learned this the same way.
   */
  .nk-review-summary {
    position: sticky;
    inset-block-start: calc(var(--nk-header-stuck, var(--nk-header-height)) + var(--nk-space-4));
    max-block-size: calc(100vh - var(--nk-header-stuck, var(--nk-header-height)) - var(--nk-space-8));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  body.nk-has-sticky-cta { padding-block-end: 0; }
}
