/*
 * RahkarTheme — single-product page styles.
 *
 * Loaded ONLY on is_product() (inc/enqueue.php). All layout for the PDP
 * is here in one stylesheet so the page hits a single CSS request.
 *
 * Speed-relevant decisions:
 *   - Sticky purchase column on desktop = pure CSS, no scroll listener.
 *   - Variation switching updates DOM in place via JS — no class-toggle
 *     cascade, no expensive selectors.
 *   - Tabs use `[hidden]` (display:none) to swap — single attr change
 *     per click, no animation, no recompute storm.
 *   - Modal uses native <dialog>: free focus trap, ESC handling, and
 *     backdrop styling via ::backdrop pseudo. Zero JS library cost.
 */

/* ----------------------------------------------------------------------
 *  Tokens
 * ---------------------------------------------------------------------- */
.rh-product {
	--rh-pd-fg:      var(--wp--preset--color--text, #1a1d23);
	--rh-pd-muted:   var(--wp--preset--color--text-muted, #5b6271);
	--rh-pd-bg:      var(--wp--preset--color--base, #fff);
	--rh-pd-surface: var(--wp--preset--color--surface, #f7f7f8);
	--rh-pd-border:  var(--wp--preset--color--border, #e2e4e9);
	--rh-pd-primary: var(--wp--preset--color--primary, #0a66c2);

	color: var(--rh-pd-fg);
}

/* ----------------------------------------------------------------------
 *  Breadcrumb (above the grid)
 *
 *  white-space:nowrap + overflow-x:auto = naturally scrollable when a
 *  Persian category chain runs longer than the viewport. Scrollbar hidden
 *  so it doesn't visually intrude — touch / wheel scroll still works.
 *
 *  No JS, no extra request. Schema markup is microdata on the existing
 *  HTML (see rahkar_product_render_breadcrumb), zero render cost.
 * ---------------------------------------------------------------------- */
.rh-breadcrumb {
	margin: 0 0 18px;
	overflow-x: auto;
	overflow-y: hidden;
	overscroll-behavior-x: contain;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	/* Reserve height to prevent CLS even when the chain is empty. */
	min-height: 22px;
}
.rh-breadcrumb::-webkit-scrollbar {
	display: none;
}
.rh-breadcrumb__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
	color: var(--rh-pd-muted);
	white-space: nowrap;
}
.rh-breadcrumb__item {
	flex: 0 0 auto;
}
.rh-breadcrumb__link {
	color: var(--rh-pd-muted);
	text-decoration: none;
	transition: color 120ms linear;
}
.rh-breadcrumb__link:hover,
.rh-breadcrumb__link:focus-visible {
	color: var(--rh-pd-primary);
	outline: none;
}
.rh-breadcrumb__sep {
	flex: 0 0 auto;
	color: var(--rh-pd-border);
	font-size: 14px;
	line-height: 1;
}

@media (max-width: 900px) {
	/* Breadcrumb pins right under the sticky mini-header on mobile —
	 * styled "as-is" (no full-bleed background, no divider line). The
	 * summary card has a higher z-index, so as the user scrolls the
	 * card cleanly slides over BOTH the gallery and the breadcrumb;
	 * before the card reaches them, breadcrumb and gallery stay in
	 * place together. The page background showing through is fine
	 * because the page body is white, so the visual stays uncluttered
	 * — no extra chrome above the image. */
	.rh-breadcrumb {
		position: sticky;
		top: var(--rh-page-header-h, 56px);
		/* margin: 0 eliminates the visible "gap" that would otherwise
		 * close the moment sticky engages — when both the breadcrumb
		 * and the gallery sticky-pin at the same `top: 56px`, the
		 * 12px bottom margin would visually collapse and the user
		 * would see the breadcrumb "shift up" by 12px. Going flush
		 * makes the transition imperceptible. */
		margin: 0;
		background: var(--rh-pd-bg);
	}
	.rh-breadcrumb__list {
		font-size: 12px;
		gap: 6px;
	}
}

/* ----------------------------------------------------------------------
 *  Grid (5:4:3 desktop, stack on mobile)
 *
 *  Single row: gallery (col 1) | summary (col 2) | purchase (col 3).
 *  Sections live BELOW the grid (see .rh-product__lower) so that the
 *  upsell slider between them can break out to full-bleed AND so a
 *  separate sticky "mirror" purchase box can sit alongside the sections.
 * ---------------------------------------------------------------------- */
.rh-product__grid {
	display: grid;
	grid-template-columns: 5fr 4fr 3fr;
	gap: 28px;
	align-items: start;
}
.rh-product__col--gallery  { grid-column: 1; }
.rh-product__col--summary  { grid-column: 2; }
.rh-product__col--purchase { grid-column: 3; }

/* Mobile-only fixed cart-bar ("dock"). Hidden on desktop — the in-flow bar
   inside the purchase column serves desktop. Becomes the visible fixed bar
   ≤900px (see the mobile block), where the in-grid bar is hidden instead. */
.rh-product__dock { display: none; }

/* ----------------------------------------------------------------------
 *  Tab nav (mobile only)
 *
 *  Sits at the very start of the sections column and becomes sticky as
 *  the user scrolls past the gallery + summary card above. Each tab is
 *  a normal anchor link to a section id — without JS, clicking jumps
 *  to the section; with JS, product-single.js intercepts for a smooth
 *  scroll and keeps the active tab in sync with the currently-visible
 *  section via IntersectionObserver.
 *
 *  Hidden on desktop because desktop already has the sticky purchase
 *  column + always-visible sections layout.
 * ---------------------------------------------------------------------- */
.rh-product__tabs {
	display: none;
}
@media (max-width: 900px) {
	.rh-product__tabs {
		display: flex;
		gap: 0;
		position: sticky;
		/* Lock under the mini-header — the tabs slide in to REPLACE the
		 * breadcrumb in the sticky stack (same `top`, higher z-index),
		 * so when the user reaches the sections the breadcrumb is
		 * hidden behind the tabs and the page reads as "header → tabs".
		 * That matches the Digikala UX in the reference screenshots. */
		top: var(--rh-page-header-h, 56px);
		z-index: 5;
		background: var(--rh-pd-bg);
		/* Full-bleed: extend to viewport edges so the bottom-border
		 * actually reaches the screen sides instead of stopping at the
		 * content-padding column. Same full-bleed trick as the
		 * breadcrumb above. */
		width: 100vw;
		margin: 12px calc(50% - 50vw);
		padding: 0 14px;
		border-bottom: 1px solid var(--rh-pd-border);
		box-sizing: border-box;
		overflow-x: auto;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}
	.rh-product__tabs::-webkit-scrollbar { display: none; }

	.rh-product__tab {
		flex: 0 0 auto;
		padding: 14px 12px;
		color: var(--rh-pd-muted);
		text-decoration: none;
		font-size: 13.5px;
		font-weight: 600;
		white-space: nowrap;
		border-bottom: 2px solid transparent;
		margin-bottom: -1px;        /* overlap the parent's bottom-border so the underline sits on top */
		transition: color 120ms linear, border-color 120ms linear;
	}
	.rh-product__tab.is-active {
		color: var(--rh-pd-fg);
		border-bottom-color: var(--rh-pd-primary);
	}
	.rh-product__tab:hover,
	.rh-product__tab:focus-visible {
		color: var(--rh-pd-fg);
		outline: none;
	}
}

/* ----------------------------------------------------------------------
 *  Upsell slider — site content width
 *
 *  Spans the full width of the constrained block container (i.e. the same
 *  width as the gallery+summary+purchase row above and the lower layout
 *  below — NOT the viewport edge-to-edge). The slider itself handles its
 *  internal scroll-snap rail, so cards still flow naturally without us
 *  having to break out of the page gutter.
 * ---------------------------------------------------------------------- */
.rh-product__upsell {
	width: 100%;
	margin-block: 40px 32px;
}

/* "کالاهای مشابه" — sibling of .rh-product__lower, paints after it.
 * Same width rule as .rh-product__upsell so the two sliders read as one
 * pattern. Larger top margin because reviews + the sticky aside end
 * above; extra breathing room separates the slider from the dense
 * review list visually. */
.rh-product__related {
	width: 100%;
	margin-block: 48px 24px;
}

/* ----------------------------------------------------------------------
 *  Lower layout: sections + sticky mirror purchase aside (desktop only)
 *
 *  Two-column layout below the upsell. The aside re-renders the cart-bar
 *  markup (`[data-cart-bar]`, `[data-price-area]`, …) so product-single.js
 *  updates both the top-of-page purchase column AND this mirror in lockstep
 *  via querySelectorAll. The aside is `position: sticky` so the buy CTA
 *  stays in view while the user reads the long product description.
 *
 *  Mobile: aside is hidden (the existing fixed bottom cart-bar serves the
 *  same role on small screens) and the sections column takes full width.
 * ---------------------------------------------------------------------- */
.rh-product__lower {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 320px;
	gap: 28px;
	align-items: start;
	margin-top: 8px;
}
.rh-product__lower-main {
	min-width: 0;
}
.rh-product__lower-aside {
	min-width: 0;
}

/* ----------------------------------------------------------------------
 *  Cart bar — DESKTOP defaults (must come BEFORE the mobile media query
 *  below so its rules properly override these on small screens).
 *
 *  Desktop: vertical column (price above, button below)
 *  Mobile (max-width:900px below): row-reverse sticky bar
 * ---------------------------------------------------------------------- */
.rh-product__cart-bar {
	display: flex;
	flex-direction: column;
	gap: 14px;
	align-items: stretch;
	margin-top: 14px;
}

/* Cart UI cluster (button + qty counter). Full-width row on desktop. */
.rh-product__cart {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
}

/* Add-to-cart button.
 *
 *  Desktop default                        → full-width text+icon
 *  Desktop + cart-bar.is-in-cart          → shrinks to icon-only 48px
 *  Mobile (set in media query below)      → icon-only or text variant
 */
.rh-product__add-btn {
	flex: 1;
	position: relative; /* anchor for the loading spinner */
	min-height: var(--wp--custom--button--h--lg, 52px);
	border: 0;
	border-radius: var(--wp--custom--button--radius, 10px);
	background: var(--rh-pd-primary);
	color: #fff;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--wp--custom--button--gap, 8px);
	cursor: pointer;
	padding: 0 var(--wp--custom--button--px--lg, 22px);
	font-weight: var(--wp--custom--button--fw, 600);
	font-size: var(--wp--custom--button--fs--lg, 1rem);
	font-family: inherit;
	transition: background-color 120ms linear, transform 80ms linear, flex-basis 200ms ease;
}
/* The explicit display above outranks the UA [hidden] rule, so when product-
 * single.js hides add-to-cart for an OOS variation (standing the notify button
 * in its place) the dead «ناموجود» button would otherwise stay visible. Re-assert
 * hidden at class+attribute specificity — same pattern as .rh-product__qty below. */
.rh-product__add-btn[hidden] {
	display: none;
}
.rh-product__add-btn-icon {
	display: inline-flex;
	align-items: center;
	flex: 0 0 auto;
}
.rh-product__add-btn:hover {
	background: var(--wp--preset--color--primary-dark, #074a8d);
}
.rh-product__add-btn:focus-visible {
	outline: 2px solid var(--rh-pd-primary);
	outline-offset: 2px;
}
.rh-product__add-btn:active {
	transform: translateY(1px);
}
.rh-product__add-btn[aria-busy="true"] {
	color: transparent; /* hides icon + text (both use currentColor) */
	pointer-events: none;
}
.rh-product__add-btn[aria-busy="true"]::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 20px;
	height: 20px;
	margin: -10px 0 0 -10px;
	border: 2px solid rgba(255, 255, 255, 0.45);
	border-top-color: #fff;
	border-radius: 50%;
	animation: rh-add-spin 0.7s linear infinite;
}
@keyframes rh-add-spin {
	to { transform: rotate(360deg); }
}
.rh-product__add-btn.is-disabled,
.rh-product__add-btn[disabled] {
	background: var(--rh-pd-surface);
	color: var(--rh-pd-muted);
	cursor: not-allowed;
}

/* In-cart: button shrinks to icon-only, qty counter takes its place. */
.rh-product__cart-bar.is-in-cart .rh-product__add-btn {
	flex: 0 0 48px;
	min-height: 48px;
	padding: 0;
	width: 48px;
}
.rh-product__cart-bar.is-in-cart .rh-product__add-btn-text {
	display: none;
}

/* Qty counter — hidden until JS toggles `[hidden]` off. */
.rh-product__qty {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 4px;
	padding: 4px;
	border: 1px solid var(--rh-pd-border);
	border-radius: 10px;
	background: var(--rh-pd-bg);
	flex: 1;
	min-height: 48px;
	transition: opacity 160ms linear;
}
.rh-product__qty[hidden] {
	display: none;
}
.rh-product__qty.is-busy {
	opacity: 0.55;
	pointer-events: none;
}
.rh-product__qty-btn {
	width: 36px;
	height: 38px;
	border: 0;
	background: transparent;
	color: var(--rh-pd-fg);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	padding: 0;
	border-radius: 8px;
	transition: background-color 120ms linear, color 120ms linear;
}
.rh-product__qty-btn:hover,
.rh-product__qty-btn:focus-visible {
	background: var(--rh-pd-surface);
	outline: none;
}
/* Dec button morphs minus ⇄ trash. Both icons are in the DOM; we show one.
 * Default (qty >= 2) = minus; the cart-bar gets `is-single` at qty 1 = trash.
 * Red hover only reads as "delete" in the trash state. */
.rh-product__qty-ico {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.rh-product__qty-ico--trash {
	display: none;
}
.rh-product__cart-bar.is-single .rh-product__qty-ico--minus {
	display: none;
}
.rh-product__cart-bar.is-single .rh-product__qty-ico--trash {
	display: inline-flex;
}
.rh-product__cart-bar.is-single .rh-product__qty-btn--dec:hover {
	color: #c0392b;
}
.rh-product__qty-num {
	min-width: 26px;
	text-align: center;
	font-weight: 700;
	font-size: 14px;
}

@media (max-width: 900px) {
	/* Pull the product page FLUSH against the sticky mini-header by
	 * cancelling EVERY source of vertical spacing above the breadcrumb.
	 * The Digikala-style sticky stack only feels right when breadcrumb +
	 * image sit DIRECTLY under the header; even a few pixels of gap
	 * pushes the breadcrumb's natural y below the sticky `top: 56px`,
	 * forcing "scroll-before-sticky-engages" the first time the user
	 * scrolls — that is the visible shift the user keeps reporting.
	 *
	 * Sources of pre-header gap that we shut down here:
	 *   1. <main>'s padding-top from the template's inline style
	 *      (theme.json constrained-layout value). !important is needed
	 *      because inline style beats normal CSS — but !important author
	 *      rules beat inline style.
	 *   2. <main>'s margin-top (set by the global block layout).
	 *   3. .rh-product's own top spacing (just in case future CSS adds any).
	 *   4. .rh-breadcrumb's own top spacing (same).
	 *
	 * Scoped by body.single-product so we only override `<main>` on
	 * product pages, never anywhere else on the site. */
	body.single-product main.wp-block-group {
		padding-top: 0 !important;
		padding-bottom: 0 !important;
		margin-top: 0 !important;
	}
	body.single-product .rh-product {
		margin-top: 0;
		padding-top: 0;
	}
	body.single-product .rh-breadcrumb {
		margin-top: 0;
		padding-top: 0;
	}

	/* Switch from grid to block on mobile so the gallery can be made a
	 * direct child of `.rh-product__grid` (via display:contents below).
	 * That puts its sticky containing block at the grid level, which is
	 * tall enough (gallery + summary + purchase) for the sticky effect
	 * to last while the user reads the summary card.
	 *
	 * Once the user has scrolled past the entire grid (gallery + summary
	 * + purchase), the gallery releases and scrolls off naturally.
	 */
	.rh-product__grid {
		display: block;
		gap: 0;
	}

	/* Dissolve the gallery column on mobile — its only child (.rh-gallery)
	 * is hoisted to be a direct child of the grid, so the gallery can
	 * stick within the grid's full height. Summary and purchase columns
	 * keep their own boxes (they need backgrounds + paddings for the
	 * "card" overlay effect). */
	.rh-product__col--gallery {
		display: contents;
	}

	/* Defensive sizing for grid children — CSS grid's default
	 * `min-width: auto` lets items grow to fit min-content, which on
	 * RTL pages with long Persian words or nested <table>/<dl> content
	 * silently pushes the cell beyond its column width and overflows
	 * the page. Forcing `min-width: 0` prevents that. */
	.rh-product__col--summary,
	.rh-product__col--purchase {
		min-width: 0;
		max-width: 100%;
	}
	.rh-product__col--summary > *,
	.rh-product__col--purchase > * {
		min-width: 0;
		max-width: 100%;
	}

	/* ── Sticky gallery + summary "card" overlay (Digikala-style) ──────
	 *
	 *   • The sticky offset --rh-page-header-h matches the height of the
	 *     mobile mini-header (.rh-product-mini, 56px), so the gallery
	 *     and the tab nav both lock under the header — not on top of it.
	 *     One variable, one source of truth: if the header height ever
	 *     changes, both follow.
	 *   • Gallery sticks below the header while the summary card slides
	 *     up over it. As the user scrolls, the card visually rises out
	 *     of the bottom of the gallery, with a small drag-handle bar
	 *     hinting at the interaction.
	 *   • z-index ordering (REVISED — deterministic, fixes the iOS/Android
	 *     compositor bleed): the grid is an isolated stacking context
	 *     (`isolation: isolate`), and the three columns get EXPLICIT z within
	 *     it — gallery:0, purchase:1, summary:2. On real devices a sticky
	 *     element is always layer-promoted; without an isolated context the
	 *     promoted gallery layer floated ambiguously relative to the
	 *     (non-composited) content at the ROOT stacking context, so during
	 *     fling scroll it flashed OVER the summary/variants. Isolating the
	 *     grid pins the gallery's layer BELOW the content layers inside ONE
	 *     subtree the compositor orders as a unit — no more bleed.
	 *   • The fixed cart-bar can't live inside this isolated grid (its z:50
	 *     would be trapped beneath the tab nav). On mobile it's rendered as
	 *     `.rh-product__dock`, a direct child of .rh-product OUTSIDE the grid;
	 *     the in-grid bar is hidden ≤900px. The tab nav (z:5) and dock (z:50)
	 *     resolve at the page stacking context as before.
	 *   • The card has a 16px negative top margin so its rounded top
	 *     corners overlap the gallery's bottom edge from the start.
	 *   • The drag-handle bar is a CSS pseudo-element — purely visual,
	 *     no JS gesture handling (native page scroll is the actual
	 *     drag affordance).
	 */
	.rh-product {
		--rh-page-header-h:     56px;
		/* Approximate breadcrumb height on mobile (single-line Persian
		 * text + min-height). Used to pin the gallery RIGHT BELOW the
		 * breadcrumb so its rounded slide corners never end up hidden
		 * behind the breadcrumb's bottom edge. */
		--rh-page-breadcrumb-h: 22px;
	}
	/* Bumping specificity past the default `.rh-gallery { position: relative }`
	 * rule that sits LATER in this file (line ~633). Because both rules
	 * have single-class specificity, the later one would otherwise win,
	 * leaving the gallery stuck on `position: relative` and breaking the
	 * sticky overlay. `.rh-product .rh-gallery` is (0,2,0) vs (0,1,0). */
	/* Isolated stacking context for the three columns so the explicit
	   z-indexes below order deterministically on real devices' compositors,
	   not just in desktop responsive mode. Safe: the fixed cart-bar is NOT in
	   this grid on mobile (it's .rh-product__dock, outside), so nothing is
	   trapped. */
	.rh-product__grid {
		isolation: isolate;
	}
	.rh-product .rh-gallery {
		position: sticky;
		top: calc(var(--rh-page-header-h) + var(--rh-page-breadcrumb-h));
		/* Explicit floor of the stack inside the isolated grid — below the
		   summary (2) and purchase (1) columns, so the pinned image can never
		   composite over scrolling content. */
		z-index: 0;
		/* Pre-promote to a STABLE composited layer. On iOS the sticky layer is
		   otherwise created/destroyed during momentum scroll (churn), which is
		   what produces the white-flash + transient bleed. A persistent layer
		   removes the churn; z:0 within the isolated grid keeps it the floor. */
		transform: translateZ(0);
		-webkit-transform: translateZ(0);
	}

	.rh-product__col--summary {
		position: relative;
		z-index: 2;
		/* Promote the card to its OWN compositor layer so it composites
		 * deterministically ABOVE the sticky gallery's layer.
		 *
		 * Why: on real iOS/Android (not desktop responsive mode) a
		 * position:sticky element is always layer-promoted. The card was
		 * left in the non-composited document layer, so during async/fling
		 * scrolling the GPU re-pinned the gallery layer before the main
		 * thread re-rastered the card — the gallery image flashed over the
		 * card (worse on fast scroll). Making the card a layer too means the
		 * compositor orders the two by z-index (card z:2 > gallery auto) at
		 * GPU level, with no raster-timing gap. Safe here: the summary has no
		 * position:fixed descendant, so translateZ(0) creating a containing
		 * block changes nothing. */
		transform: translateZ(0);
		-webkit-transform: translateZ(0);
		background: var(--rh-pd-bg);
		margin-top: -5px;
		margin-left: -16px;
    	margin-right: -16px;
		padding: 4px 16px 16px;
		border-radius: 18px 18px 0 0;
		/* Shadow is drawn by ::after as an absolutely-positioned strip ABOVE
		 * the card (see below). A plain box-shadow with -2 offset still
		 * bleeds onto the sides and (especially) the bottom — the bottom
		 * bleed was visible as a faint band above the variants section. */
	}
	.rh-product__col--summary::before {
		content: "";
		display: block;
		width: 38px;
		height: 4px;
		background: var(--rh-pd-border);
		border-radius: 2px;
		margin: 0 auto 8px;
	}
	/* Top-only shadow: a thin gradient strip parked just above the card.
	 * `bottom: 100%` puts the strip's bottom edge exactly at the card's
	 * top edge, so the shadow appears to rise out of the card without
	 * touching the sides or bottom. */
	.rh-product__col--summary::after {
		content: "";
		position: absolute;
		bottom: 100%;
		left: 0;
		right: 0;
		height: 14px;
		background: linear-gradient(to top, rgba(15, 17, 21, 0.08), rgba(15, 17, 21, 0));
		pointer-events: none;
	}

	/* Purchase column extends the same visual card — no rounded corners
	 * (already given by summary above), no extra background break.
	 *
	 * z-index:1 here is now SAFE (and needed): the fixed cart-bar no longer
	 * lives in this column on mobile — it's .rh-product__dock, outside the
	 * grid — so giving the column a stacking context can't trap it. The
	 * in-grid bar (inside this column) is display:none ≤900px, so its z is
	 * moot. z:1 keeps this column ABOVE the gallery (z:0) inside the isolated
	 * grid, so the pinned image never composites over the variants. */
	.rh-product__col--purchase {
		position: relative;
		z-index: 1;
		/* Promote to its OWN composited layer (now safe — the fixed cart-bar
		 * moved to .rh-product__dock, so this column has no fixed descendant).
		 * With ALL three columns composited (gallery/summary/purchase), the
		 * compositor orders them by the explicit z above and never falls back
		 * to overlap-testing — which is what iOS Safari fails during fling,
		 * letting the pinned gallery bleed over the variants. */
		transform: translateZ(0);
		-webkit-transform: translateZ(0);
		background: var(--rh-pd-bg);
		padding-inline: 16px;
		margin-left: -16px;
		margin-right: -16px;
	}
	/* On mobile the in-grid cart-bar is replaced by the fixed .rh-product__dock
	   (outside the isolated grid). Hide the in-grid copy so we don't paint two. */
	.rh-product__col--purchase .rh-product__cart-bar {
		display: none;
	}
	/* The dock IS the visible fixed bar ≤900px. */
	.rh-product__dock {
		display: block;
	}

	/* Lower layout collapses to single column — and the sticky-aside
	 * mirror is suppressed entirely. The fixed bottom cart-bar (rendered
	 * by the main purchase column) already fills the "always-visible buy"
	 * role on small screens, so showing the mirror would be a duplicate
	 * affordance. */
	.rh-product__lower {
		grid-template-columns: 1fr;
		gap: 20px;
		position: relative;
		background: var(--rh-pd-bg, #fff);
	}
	.rh-product__lower-aside {
		display: none;
	}

	/* Upsell margins tighten on mobile to match the rest of the spacing. */
	.rh-product__upsell {
		margin-block: 24px 20px;
	}
	.rh-product__related {
		margin-block: 28px 16px;
	}

	/* Purchase card frame is desktop-only (see the @media (min-width:901px)
	 * block far below). On mobile the wrapper is just a pass-through — no
	 * border, no padding, no background — so variants sit flush in the
	 * column and the page can breathe.
	 *
	 * Flex column + display:contents on the inner <form> flattens the form
	 * children up to the purchase-inner level so we can reorder stock and
	 * variants with plain `order` — without restructuring PHP (and without
	 * changing desktop, which doesn't enter this media query).
	 *
	 * Why display:contents on a form works: the form element stays in the
	 * DOM, only its rendering box dissolves. Submission, validation and
	 * fieldset semantics are unaffected. Browser support is ~95% (Safari
	 * fixed its old a11y tree bug in 15.4). */
	.rh-product__purchase-inner {
		display: flex;
		flex-direction: column;
	}
	.rh-product__purchase-inner > form {
		display: contents;
	}
	.rh-variations         { order: 1; }
	.rh-product__stock     { order: 2; margin-top: 6px; margin-bottom: 10px; }
	.rh-product__attrs--mobile { order: 3; }
	/* Cart bar is position:fixed on mobile (see below) — leaves the flow
	 * entirely, so `order` is decorative for it. Set it anyway so any
	 * future un-fixing keeps the visual sequence sane. */
	.rh-product__cart-bar  { order: 4; }

	/* Section dividers — 8px grey strips between major content blocks for
	 * clearer visual separation in the long mobile scroll. Run full-bleed
	 * to the viewport edges via negative inline margin that cancels the
	 * parent column's 16px padding.
	 *
	 * Anchor points are picked to GROUP the visually-related blocks:
	 *   summary card → purchase    : on purchase::before (NOT summary::after,
	 *                                 which is now the top-only drop shadow)
	 *   variants + stock           : one group; strip goes after stock
	 *   attrs                      : standalone specs; strip after attrs */
	.rh-product__col--purchase::before,
	.rh-product__stock::after,
	.rh-product__attrs--mobile::after {
		content: "";
		display: block;
		height: 8px;
		background: var(--wp--preset--color--surface, #f7f7f8);
	}
	.rh-product__col--purchase::before {
		margin: 0 -16px 16px;
	}
	.rh-product__stock::after,
	.rh-product__attrs--mobile::after {
		margin: 16px -16px 0;
	}

	/* Reserve bottom space so the sticky cart-bar doesn't cover content. */
	.rh-product {
		padding-bottom: 72px;
	}

	/* The cart bar leaves the natural flow and pins to the viewport bottom.
	 * Position:fixed escapes the form's parent stacking — the form still
	 * submits because the DOM tree is unchanged.
	 *
	 *  Layout (per explicit UX): cart UI on the PHYSICAL RIGHT (RTL start),
	 *  price on the PHYSICAL LEFT (RTL end). DOM order stays [price, cart]
	 *  for semantics; `flex-direction: row-reverse` flips the visual order
	 *  so the user gets cart-on-right without rewriting markup.
	 *
	 *  Add-button always shows icon + text initially. When in-cart, the
	 *  shared `.is-in-cart` rule shrinks it to icon-only and the qty
	 *  counter appears beside it.
	 */
	.rh-product__cart-bar {
		position: fixed;
		inset-inline: 0;
		inset-block-end: 0;
		flex-direction: row-reverse;
		justify-content: space-between;
		align-items: center;
		gap: 10px;
		margin: 0;
		padding: 8px 12px;
		padding-bottom: max(env(safe-area-inset-bottom, 0), 8px);
		background: var(--rh-pd-bg);
		border-top: 1px solid var(--rh-pd-border);
		box-shadow: 0 -2px 10px rgba(15, 17, 21, 0.06);
		z-index: 50;
	}
	/* Price block: natural width hugged to the LEFT edge of the bar. */
	.rh-product__price {
		text-align: end;
		flex: 0 1 auto;
		min-width: 0;
		gap: 2px;
	}
	.rh-product__price-top {
		justify-content: flex-end; /* end = left in RTL */
		gap: 6px;
	}
	.rh-product__price-current {
		font-size: 15px;
		line-height: 1.3;
	}
	.rh-product__price-old {
		font-size: 12px;
	}
	.rh-product__discount {
		font-size: 10px;
		padding: 1px 6px;
	}

	/* Cart cluster: compact 40px button + qty group. */
	.rh-product__cart {
		width: auto;
		flex: 0 0 auto;
		gap: 8px;
	}
	.rh-product__add-btn {
		flex: 0 0 auto;
		min-height: var(--wp--custom--button--h--md, 44px);
		padding: 0 var(--wp--custom--button--px--md, 20px);
		font-size: var(--wp--custom--button--fs--sm, 0.875rem);
		gap: 6px;
		border-radius: var(--wp--custom--button--radius, 10px);
	}
	.rh-product__add-btn-icon svg {
		width: 18px;
		height: 18px;
	}
	.rh-product__cart-bar.is-in-cart .rh-product__add-btn {
		flex: 0 0 var(--wp--custom--button--h--md, 44px);
		width: var(--wp--custom--button--h--md, 44px);
		padding: 0;
	}
	.rh-product__qty {
		flex: 0 0 auto;
		min-height: 40px;
		padding: 2px;
		gap: 0;
		border-radius: 9px;
	}
	.rh-product__qty-btn {
		width: 30px;
		height: 32px;
		border-radius: 7px;
	}
	.rh-product__qty-btn svg {
		width: 15px;
		height: 15px;
	}
	.rh-product__qty-num {
		font-size: 13px;
		min-width: 22px;
	}
}

/* Purchase column sticks on desktop so the buy button stays in view on
 * long descriptions. `align-self: start` is required for sticky to work
 * inside grid. Top offset clears the sticky site header (~64px) + air.
 *
 * Same treatment for the mirror aside below the upsell — it sticks while
 * the user reads sections (intro/attrs/reviews) on the side. When the
 * user scrolls past `.rh-product__lower`, the mirror naturally scrolls
 * off-screen (sticky is scoped to its parent).
 */
@media (min-width: 901px) {
	.rh-product__col--purchase {
		position: sticky;
		top: 80px;
		align-self: start;
	}
	.rh-product__lower-aside {
		position: sticky;
		top: 80px;
		align-self: start;
	}
}

/* ----------------------------------------------------------------------
 *  Gallery (col 1)
 *
 *  Same DOM on desktop and mobile; behaviour diverges at 900px:
 *
 *    Desktop (>= 901px)
 *      .rh-gallery__track    stacks slides; only .is-active is display:block
 *      .rh-gallery__thumbs   visible, horizontal scroll if many
 *      .rh-gallery__dots     hidden
 *      .rh-gallery__counter  hidden
 *      .rh-zoom-lens         (created by JS on first hover, position:absolute
 *                             over the slide, shows high-res background-image
 *                             that follows the cursor)
 *
 *    Mobile (<= 900px)
 *      .rh-gallery__track    horizontal flex row; viewport scrolls X with
 *                            scroll-snap; native lazy loading defers off-
 *                            screen <img>s until swiped near
 *      .rh-gallery__thumbs   hidden
 *      .rh-gallery__dots     visible bottom-center
 *      .rh-gallery__counter  visible top-end
 * ---------------------------------------------------------------------- */
.rh-gallery {
	position: relative;
}
.rh-gallery__viewport {
	position: relative;
	aspect-ratio: 1 / 1;
	background: var(--rh-pd-surface);
	border-radius: 12px;
	overflow: hidden;
	cursor: zoom-in;
}
.rh-gallery__track {
	width: 100%;
	height: 100%;
}
.rh-gallery__slide {
	width: 100%;
	height: 100%;
}
.rh-gallery__main-img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	display: block;
}

/* Expand affordance — small icon button bottom-end of viewport. */
.rh-gallery__expand {
	position: absolute;
	inset-block-end: 10px;
	inset-inline-end: 10px;
	width: 34px;
	height: 34px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 0;
	border-radius: 8px;
	background: rgba(255, 255, 255, 0.85);
	color: var(--rh-pd-fg);
	cursor: pointer;
	padding: 0;
	box-shadow: 0 2px 6px rgba(15, 17, 21, 0.12);
	backdrop-filter: blur(4px);
	transition: background-color 120ms linear;
	z-index: 2;
}
.rh-gallery__expand:hover,
.rh-gallery__expand:focus-visible {
	background: #fff;
	outline: none;
}

/* Counter pill — "[icon] 13" total-only, mobile only.
   Lives INSIDE the first slide (the big A-block image) so it sits on
   that image in the mosaic. Positioned bottom-start; physical `left`
   keeps it visually anchored to the left edge regardless of RTL/LTR. */
.rh-gallery__counter {
	display: none;
	position: absolute;
	inset-block-end: 10px;
	left: 10px;
	padding: 4px 10px;
	gap: 6px;
	font-size: 12px;
	font-weight: 600;
	color: #fff;
	background: rgba(15, 17, 21, 0.55);
	border-radius: 999px;
	backdrop-filter: blur(4px);
	z-index: 2;
	pointer-events: none;
	align-items: center;
	line-height: 1;
}
.rh-gallery__counter-icon {
	display: block;
	flex: 0 0 auto;
}
.rh-gallery__counter-num {
	display: inline-block;
	min-width: 0.5em;
	text-align: center;
}

/* Dots — mobile only. */
.rh-gallery__dots {
	display: none;
	gap: 6px;
	justify-content: center;
	margin-top: 10px;
}
.rh-gallery__dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--rh-pd-border);
	transition: width 160ms ease, background-color 160ms ease;
}
.rh-gallery__dot.is-active {
	width: 18px;
	background: var(--rh-pd-primary);
	border-radius: 999px;
}

/* Thumbs strip — desktop only.
 *
 *  6.5 thumbs visible by default; the rest reached via prev/next arrows.
 *  Math: viewport width V, gap G, thumb size T → V = 6.5T + 6G
 *  → T = (V - 6G) / 6.5  (CSS calc with --rh-thumb-gap variable)
 *
 *  Scrollbar is hidden (arrows are the only navigation). Native scroll
 *  is still active — wheel + drag still work; arrows are an additional
 *  affordance.
 */
.rh-gallery__thumbs-wrap {
	position: relative;
	margin-top: 12px;
	--rh-thumb-gap: 8px;
}
.rh-gallery__thumbs {
	display: flex;
	gap: var(--rh-thumb-gap);
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x proximity;
	scroll-behavior: smooth;
	scrollbar-width: none;
	padding-block: 2px;
	-webkit-overflow-scrolling: touch;
}
.rh-gallery__thumbs::-webkit-scrollbar {
	display: none;
}
.rh-gallery__thumb {
	flex: 0 0 calc((100% - 6 * var(--rh-thumb-gap)) / 6.5);
	aspect-ratio: 1 / 1;
	padding: 0;
	border: 1px solid var(--rh-pd-border);
	border-radius: 8px;
	background: var(--rh-pd-bg);
	cursor: pointer;
	overflow: hidden;
	transition: border-color 120ms linear;
	scroll-snap-align: start;
}
.rh-gallery__thumb:hover,
.rh-gallery__thumb:focus-visible {
	border-color: var(--rh-pd-primary);
	outline: none;
}
.rh-gallery__thumb.is-active {
	border-color: var(--rh-pd-primary);
	box-shadow: 0 0 0 1px var(--rh-pd-primary);
}
.rh-gallery__thumb-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Floating arrow buttons. Hidden unless the strip is actually overflowing
 * (JS adds .is-visible). Positioned half outside the strip so they overlay
 * the first/last thumb edge — common product-page pattern. */
.rh-gallery__thumbs-nav {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	width: 28px;
	height: 28px;
	display: none;
	align-items: center;
	justify-content: center;
	border: 0;
	border-radius: 50%;
	background: #fff;
	color: var(--rh-pd-fg);
	cursor: pointer;
	padding: 0;
	box-shadow: 0 2px 8px rgba(15, 17, 21, 0.18);
	z-index: 2;
	transition: opacity 140ms linear, background-color 120ms linear;
}
.rh-gallery__thumbs-nav.is-visible {
	display: inline-flex;
}
.rh-gallery__thumbs-nav:hover {
	background: var(--rh-pd-surface);
}
.rh-gallery__thumbs-nav[disabled] {
	opacity: 0;
	pointer-events: none;
}
/* "prev" = goes to LOWER indexes = visually on the RIGHT in RTL.
 * "next" = goes to HIGHER indexes = visually on the LEFT in RTL.
 * Logical inset-inline anchors do this automatically with `dir="rtl"`. */
.rh-gallery__thumbs-nav--prev { inset-inline-start: -6px; }
.rh-gallery__thumbs-nav--next { inset-inline-end:   -6px; }

/* Zoom lens — created by JS on first desktop hover. Background-image of the
 * 'full' size pans with the cursor. Pointer-events:none so it doesn't block
 * the click → lightbox handler underneath. */
.rh-zoom-lens {
	position: absolute;
	inset: 0;
	background-repeat: no-repeat;
	background-size: 220% auto;
	opacity: 0;
	transition: opacity 120ms linear;
	pointer-events: none;
	z-index: 1;
}
.rh-zoom-lens.is-on {
	opacity: 1;
}

/* Mosaic block wrappers are INVISIBLE to layout by default — slides
   appear as direct children of `.rh-gallery__track` everywhere they're
   rendered. The mobile breakpoint below promotes the blocks to real
   grid containers; desktop keeps `display: contents` so the existing
   one-active-slide-at-a-time behavior is unchanged. */
.rh-gallery__block {
	display: contents;
}

/* ---- Desktop slide swap: only active slide is rendered (saves bytes:
 *      browser doesn't fetch <img> of display:none slides). ---- */
@media (min-width: 901px) {
	.rh-gallery__slide {
		display: none;
	}
	.rh-gallery__slide.is-active {
		display: block;
	}
}

/* ---- Mobile: Digikala-style horizontal-scroll mosaic gallery ----------
 *
 * Horizontal scroll-snap rail. Each "block" is a unit of 1–3 images that
 * snaps as one page; the user swipes between blocks. Inside each block
 * the big image is ALWAYS a square; smalls / B / C cells are also
 * squares. Block heights are all equal (the big square's edge), so the
 * rail has a consistent vertical footprint as the user swipes.
 *
 *   single (1-img product)        ┌──────────────┐
 *                                 │              │
 *                                 │     S0       │   square, ~92% of viewport
 *                                 │              │
 *                                 └──────────────┘
 *
 *   pair (2-img product)          ┌──────┬──────┐
 *                                 │  P0  │  P1  │   two squares side by side
 *                                 └──────┴──────┘
 *
 *   A (3 imgs)   ┌──────────┬────┐
 *                │          │ A1 │   big = square = block height.
 *                │   A0     ├────┤   smalls = each half the big edge,
 *                │          │ A2 │   so two stacked = big edge.
 *                └──────────┴────┘
 *
 *   B (1 img)    ┌────────┐
 *                │   B0   │   single square = block height.
 *                └────────┘
 *
 *   C (2 imgs)   ┌────────┬────────┐
 *                │   C0   │   C1   │   two squares side by side, both
 *                └────────┴────────┘   = block height tall.
 *
 * The CSS variable `--rh-block-h` sets the block height in viewport
 * units (default 68vw); changing one variable retunes every block size
 * — widths follow because every slot is a square and the block height
 * is the square edge.
 * ---- */
@media (max-width: 900px) {
	.rh-gallery {
		/* The block-height CSS variable — change this one value to
		   rescale the whole mosaic. 68vw is tuned so block A (1.5×
		   block height wide) leaves a small peek of the next block,
		   inviting a swipe without obscuring product visibility. */
		--rh-block-h: 75vw;
	}

	.rh-gallery__viewport {
		/* Free horizontal scroll rail — NO scroll-snap. The user reported
		   the mandatory-snap version felt jumpy on touch (every swipe
		   forced a full-block jump). Free scroll lets the finger drag
		   the rail any distance it wants and matches the looser feel
		   of horizontal carousels on mobile commerce apps.
		   The IntersectionObserver in product-single.js still drives
		   dot/counter updates from whichever slide is most visible,
		   which works the same with or without snap.
		   `max-width: 100vw` is a defensive cap: if any sibling on the
		   page causes horizontal overflow (which would push the gallery
		   column wider), the viewport still tops out at the actual
		   browser viewport width — otherwise viewport would expand to
		   match its parent and the inner track would fit without
		   needing to scroll. */
		overflow-x: auto;
		overflow-y: hidden;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
		overscroll-behavior-x: contain;
		aspect-ratio: auto;
		background: transparent;
		border-radius: 0;
		cursor: default;
		max-width: 100vw;
	}
	.rh-gallery__viewport::-webkit-scrollbar { display: none; }

	.rh-gallery__track {
		display: flex;
		flex-direction: row;
		gap: 8px;
		height: auto;
		width: max-content;     /* let blocks size by their own widths */
		padding-inline: 4px;    /* tiny gutter so first/last block don't kiss the edge */
	}

	/* Every block: fixed height, sized to its own width.
	   `flex: 0 0 auto` keeps each block at its declared width without
	   shrinking to fit the viewport. No scroll-snap — the rail scrolls
	   freely under the finger (see .rh-gallery__viewport above). */
	.rh-gallery__block {
		display: grid;
		gap: 6px;
		height: var(--rh-block-h);
		flex: 0 0 auto;
	}

	/* A: 1 big square + 2 stacked smalls.
	   Columns 2fr 1fr → big (2fr) is twice the small column width.
	   Big spans both rows → big's height = block height, width = 2/3
	   of block width. For big to be square: 2/3 × block_w = block_h →
	   block_w = 1.5 × block_h. */
	.rh-gallery__block--a {
		width: calc( var(--rh-block-h) * 1.5 + 6px );
		grid-template-columns: 2fr 1fr;
		grid-template-rows: 1fr 1fr;
	}
	.rh-gallery__block--a > .rh-gallery__slide:first-child {
		grid-column: 1;
		grid-row: 1 / 3;
	}

	/* B: single full square. Block width = block height. */
	.rh-gallery__block--b {
		width: var(--rh-block-h);
	}

	/* C: 2 side-by-side squares. block_w = 2 × block_h + gap. */
	.rh-gallery__block--c {
		width: calc( var(--rh-block-h) * 2 + 6px );
		grid-template-columns: 1fr 1fr;
	}

	/* Single-image product edge case — fill nearly the whole viewport
	   with one big square, no scroll affordance needed. */
	.rh-gallery__block--single {
		width: 92vw;
		height: 92vw;
	}

	/* Two-image product edge case — two squares side by side, no third
	   block to scroll to. */
	.rh-gallery__block--pair {
		width: 92vw;
		height: calc( (92vw - 6px) / 2 );
		grid-template-columns: 1fr 1fr;
	}

	/* Slides become positioned cards so the counter pill on the first
	   one anchors correctly. */
	.rh-gallery__slide {
		position: relative;
		width: auto;
		height: auto;
		overflow: hidden;
		border-radius: 12px;
		background: var(--rh-pd-surface);
	}
	.rh-gallery__slide .rh-gallery__main-img {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.rh-gallery__thumbs-wrap,
	.rh-gallery__expand,
	.rh-gallery__dots {
		display: none;
	}
	.rh-gallery__counter {
		display: inline-flex;
	}
}

/* ----------------------------------------------------------------------
 *  Lightbox (<dialog id="rh-lightbox">)
 *
 *  Desktop (>= 901px)
 *    Anchored to the PHYSICAL LEFT edge as a side panel covering ~50vw
 *    (clamped). Slides in from off-screen left. Light background (white)
 *    so it reads as a peer panel to the product page, not a media viewer.
 *    Layout:
 *       header   title + close (close on physical LEFT in RTL)
 *       stage    main image (white bg, prev/next chevrons)
 *       counter  "تصویر X از Y"
 *       thumbs   4-COLUMN VERTICAL GRID, scrolls vertically when many
 *
 *  Mobile (<= 900px)
 *    Bottom sheet that slides UP from the bottom. Full viewport.
 *    Header height locked to 56px to PERFECTLY OVERLAY the product
 *    mini-header (inc/product-mini-header.css) — when the dialog
 *    appears, the close button visually replaces the back button at
 *    the SAME physical position (right in RTL). Thumbs rendered as a
 *    horizontal scrollable row (vertical grid would be cramped on a
 *    phone). No dots.
 *
 *  Content is injected by JS on first open (lightbox-open class on
 *  documentElement disables body scroll while open).
 * ---------------------------------------------------------------------- */
.rh-lightbox {
	border: 0;
	padding: 0;
	margin: 0;
	max-width: none;
	max-height: none;
	background: #fff;
	color: var(--rh-pd-fg);
	overflow: hidden;
	box-shadow: 0 0 60px rgba(15, 17, 21, 0.18);
}
.rh-lightbox::backdrop {
	background: rgba(15, 17, 21, 0.55);
	opacity: 0;
	transition: opacity 220ms ease-out, overlay 220ms allow-discrete, display 220ms allow-discrete;
}
.rh-lightbox[open]::backdrop {
	opacity: 1;
}
@starting-style {
	.rh-lightbox[open]::backdrop {
		opacity: 0;
	}
}

.rh-lightbox__inner {
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
}

/* Header — title on the start (right in RTL), close on the end (left in RTL).
 * Screenshot puts the X on the physical LEFT in RTL → that's inline-end. */
.rh-lightbox__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 16px 20px;
	flex: 0 0 auto;
	border-bottom: 1px solid var(--rh-pd-border);
}
.rh-lightbox__title {
	margin: 0;
	font-size: 17px;
	font-weight: 700;
	color: var(--rh-pd-fg);
}
.rh-lightbox__close {
	width: 34px;
	height: 34px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 0;
	border-radius: 8px;
	background: transparent;
	color: var(--rh-pd-fg);
	cursor: pointer;
	transition: background-color 120ms linear;
}
.rh-lightbox__close:hover,
.rh-lightbox__close:focus-visible {
	background: var(--rh-pd-surface);
	outline: none;
}

/* Stage — holds the swipeable image track. flex:1 → grows to fill. */
.rh-lightbox__stage {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	background: #fff;
}
.rh-lightbox__track {
	display: flex;
	width: 100%;
	height: 100%;
	overflow-x: auto;
	overflow-y: hidden;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
	scroll-behavior: smooth;
	overscroll-behavior-x: contain;
}
.rh-lightbox__track::-webkit-scrollbar {
	display: none;
}
.rh-lightbox__slide {
	flex: 0 0 100%;
	height: 100%;
	scroll-snap-align: start;
	scroll-snap-stop: always;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 12px;
	box-sizing: border-box;
}
.rh-lightbox__slide img {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
	display: block;
	user-select: none;
	-webkit-user-drag: none;
}

/* Prev/Next chevrons — desktop only, floating over the stage. */
.rh-lightbox__nav {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	width: 38px;
	height: 38px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 0;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.9);
	color: var(--rh-pd-fg);
	cursor: pointer;
	box-shadow: 0 2px 8px rgba(15, 17, 21, 0.12);
	transition: background-color 120ms linear, opacity 120ms linear;
	z-index: 2;
}
.rh-lightbox__nav:hover {
	background: #fff;
}
.rh-lightbox__nav--prev { inset-inline-start: 12px; }
.rh-lightbox__nav--next { inset-inline-end:   12px; }
.rh-lightbox__nav[disabled] {
	opacity: 0;
	pointer-events: none;
}

/* Counter "تصویر ۱ از ۲۱" — sits between stage and footer. The number is
 * accented (primary color) to match the reference screenshot. */
.rh-lightbox__counter {
	flex: 0 0 auto;
	text-align: center;
	padding: 14px 16px 6px;
	font-size: 14px;
	font-weight: 700;
	color: var(--rh-pd-primary);
}

/* Footer — desktop: 4-col vertical grid; mobile: horizontal scroll. */
.rh-lightbox__footer {
	flex: 0 0 auto;
	padding: 8px 14px 18px;
	min-height: 0;
}

/* Default = desktop 4-col vertical grid. Container scrolls vertically
 * when there are more than 4 rows of thumbs. */
.rh-lightbox__thumbs {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 10px;
	overflow-y: auto;
	overflow-x: hidden;
	padding: 4px 2px;
	scrollbar-width: thin;
}
.rh-lightbox__thumbs::-webkit-scrollbar {
	width: 6px;
}
.rh-lightbox__thumbs::-webkit-scrollbar-thumb {
	background: var(--rh-pd-border);
	border-radius: 3px;
}

.rh-lightbox__thumb {
	aspect-ratio: 1 / 1;
	padding: 4px;
	border: 2px solid var(--rh-pd-border);
	border-radius: 10px;
	background: var(--rh-pd-bg);
	cursor: pointer;
	overflow: hidden;
	transition: border-color 140ms ease;
}
.rh-lightbox__thumb:hover {
	border-color: var(--rh-pd-primary);
}
.rh-lightbox__thumb img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	display: block;
}
.rh-lightbox__thumb.is-active {
	border-color: var(--rh-pd-primary);
	box-shadow: 0 0 0 1px var(--rh-pd-primary);
}

/* Dots — always hidden (user reverted: mobile lightbox now uses thumbs). */
.rh-lightbox__dots {
	display: none;
}

/* ---- Desktop: side panel anchored to physical LEFT, slides in from left.
 *
 * Native <dialog> with showModal() forces inset:0 + margin:auto centering by
 * default. We override with explicit physical anchoring + transform. The
 * transform is in physical coords (translateX(-100%) = off-screen left)
 * regardless of writing direction.
 * ---- */
@media (min-width: 901px) {
	.rh-lightbox {
		width: min(640px, 52vw);
		min-width: 420px;
		height: 100vh;
		height: 100dvh;
		max-height: none;
		top: 0;
		bottom: 0;
		left: 0;
		right: auto;
		margin: 0;
		border-radius: 0;
		transform: translateX(-100%);
		transition:
			transform 280ms cubic-bezier(0.2, 0, 0, 1),
			overlay 280ms allow-discrete,
			display 280ms allow-discrete;
	}
	.rh-lightbox[open] {
		transform: translateX(0);
	}
	@starting-style {
		.rh-lightbox[open] {
			transform: translateX(-100%);
		}
	}
}

/* ---- Mobile: bottom sheet, thumbs as horizontal scroll, 56px header. ---- */
@media (max-width: 900px) {
	.rh-lightbox {
		width: 100vw;
		width: 100dvw;
		height: 100vh;
		height: 100dvh;
		max-width: none;
		max-height: none;
		top: 0;
		bottom: 0;
		left: 0;
		right: 0;
		margin: 0;
		border-radius: 0;
		transform: translateY(100%);
		transition:
			transform 280ms cubic-bezier(0.2, 0, 0, 1),
			overlay 280ms allow-discrete,
			display 280ms allow-discrete;
	}
	.rh-lightbox[open] {
		transform: translateY(0);
	}
	@starting-style {
		.rh-lightbox[open] {
			transform: translateY(100%);
		}
	}

	/* Mobile drops the stage prev/next arrows — swipe is the only nav. */
	.rh-lightbox__nav {
		display: none;
	}

	/* Header: matches .rh-product-mini__inner dimensions (56px tall, 8px
	 * horizontal padding, 40x40 buttons) so the dialog header sits at
	 * the same physical place as the page mini-header.
	 *
	 * Natural RTL flex order (no row-reverse): title is FIRST → goes to
	 * start (right in RTL); close is LAST → end (left in RTL). */
	.rh-lightbox__header {
		min-height: 56px;
		padding: 0 8px;
		padding-top: max(env(safe-area-inset-top, 0), 0);
	}
	.rh-lightbox__close {
		width: 40px;
		height: 40px;
		border-radius: 8px;
	}
	.rh-lightbox__title {
		font-size: 15px;
	}

	/* Thumbs: horizontal scroll on mobile (4-col grid would be cramped). */
	.rh-lightbox__thumbs {
		display: flex;
		grid-template-columns: none;
		gap: 8px;
		overflow-x: auto;
		overflow-y: hidden;
		overscroll-behavior-x: contain;
		scroll-snap-type: x proximity;
		scrollbar-width: none;
	}
	.rh-lightbox__thumbs::-webkit-scrollbar {
		display: none;
	}
	.rh-lightbox__thumb {
		flex: 0 0 auto;
		width: 64px;
		aspect-ratio: 1 / 1;
		scroll-snap-align: start;
	}

	.rh-lightbox__footer {
		padding: 6px 12px;
		padding-bottom: max(env(safe-area-inset-bottom, 0), 12px);
	}
	.rh-lightbox__counter {
		padding: 10px 16px 6px;
	}
}

/* Body scroll lock while lightbox / desc-modal open. */
html.rh-lightbox-open,
html.rh-lightbox-open body,
html.rh-desc-open,
html.rh-desc-open body {
	overflow: hidden;
}

/* ----------------------------------------------------------------------
 *  Summary (col 2)
 * ---------------------------------------------------------------------- */
.rh-product__title {
	margin: 0 0 8px;
	font-size: clamp(15px, 1.3vw, 17px);
	font-weight: 700;
	line-height: 1.7;
}

/* Brand link under the title — secondary, brand-blue, on its own line. */
.rh-product__brand {
	display: inline-block;
	margin: 0 0 12px;
	font-size: 13px;
	font-weight: 600;
	color: var(--rh-pd-primary);
	text-decoration: none;
}
.rh-product__brand:hover,
.rh-product__brand:focus-visible {
	text-decoration: underline;
}

/* Rating row — pill badge.
 *
 *  Visual reading (RTL, right→left):
 *     ┌─────────────────────────────────────────┐
 *     │  ۴  ★  (۵۰۵)  │  ۷۸ دیدگاه ›          │
 *     └─────────────────────────────────────────┘
 *
 *  Single pill background draws the eye to the rating cluster. The
 *  reviews link inside has its own hover-bg highlight so it still feels
 *  individually clickable.
 *
 *  ".is-empty" variant: quiet text-link CTA, no badge background, used
 *  when the product has zero ratings (don't shout "no reviews").
 */
/* Row that hosts the rating pill (or empty-state CTA) on the inline-start
 * side and the favorite / add-to-list icons on the inline-end side. Lives
 * inside the summary column. Bottom margin owned by this row so changing
 * row contents doesn't double-stack spacing — the old margin-bottom on
 * .rh-product__rating moved here. */
.rh-product__meta-row {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 14px;
	flex-wrap: wrap;
}

.rh-product__rating {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 5px 12px;
	background: var(--rh-pd-surface);
	border: 1px solid var(--rh-pd-border);
	border-radius: 999px;
	font-size: 13px;
	color: var(--rh-pd-fg);
	max-width: 100%;
	flex-wrap: wrap;
}
.rh-product__rating-score {
	font-weight: 700;
	font-size: 14px;
}
.rh-product__star {
	color: #f4b400;          /* warm gold — independent of theme primary */
	flex: 0 0 auto;
}
.rh-product__rating-count {
	color: var(--rh-pd-muted);
	font-size: 12px;
}
.rh-product__rating-sep {
	color: var(--rh-pd-border);
	margin: 0 2px;
}
.rh-product__rating-reviews {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	color: var(--rh-pd-primary);
	text-decoration: none;
	font-weight: 600;
	padding: 2px 6px;
	margin: -2px -6px -2px 0;
	border-radius: 999px;
	transition: background-color 120ms linear, color 120ms linear;
}
.rh-product__rating-reviews:hover,
.rh-product__rating-reviews:focus-visible {
	background: rgba(0, 0, 0, 0.04);
	color: var(--wp--preset--color--primary-dark, #074a8d);
	outline: none;
}
.rh-product__rating-chev {
	line-height: 1;
	font-size: 14px;
}

/* No-reviews layout — when there's no rating badge to show, the action
   icons (heart + bookmark) move up onto the title's own line instead of
   sitting in an empty meta-row. Title hugs the inline-start, icons the
   inline-end (via .rh-list-actions margin-inline-start:auto). */
.rh-product__title-row {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 8px;
}
.rh-product__title-row .rh-product__title {
	margin: 0;
	flex: 1 1 auto;
}

/* Short description.
 *
 *  CSS line-clamps to 5 lines so initial paint never exceeds that height —
 *  no CLS when JS later measures. If JS finds the text actually overflows,
 *  it un-hides the "see more" button; if not, the button stays hidden so
 *  the page doesn't grow a useless link.
 *
 *  Custom dot bullets in primary color, no default UL indent — gets rid of
 *  the awkward right-side padding that browsers add by default to <ul>.
 */
/* Short description — clamp + fade come from the shared .rh-fade
 * component (fade-block.css). Only the typography overrides specific
 * to the short-desc context (muted color, inline list markers) live
 * here; everything else moved into the shared CSS. */
.rh-product__short-desc {
	font-size: 14px;
	line-height: 1.9;
	color: var(--rh-pd-muted);
}
.rh-product__short-desc p {
	margin: 0 0 6px;
}
.rh-product__short-desc p:last-child {
	margin-bottom: 0;
}
.rh-product__short-desc ul,
.rh-product__short-desc ol {
	margin: 0;
	padding: 0;
	list-style: none;
}
.rh-product__short-desc li {
	position: relative;
	padding-inline-start: 14px;
	margin-bottom: 4px;
}
.rh-product__short-desc li::before {
	content: '';
	position: absolute;
	inset-block-start: 0.75em;
	inset-inline-start: 4px;
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var(--rh-pd-primary);
}

/* Short attributes — minimal 2-column card grid.
 *
 *  Each card stacks label (small, muted) over value (bold), with a
 *  chevron on the end side pointing to the full attribute panel. The
 *  whole card is an <a> so it's a single tap target on mobile.
 *  Layout: grid-template-columns repeat(2, minmax(0, 1fr)) — the
 *  minmax(0,…) lets long Persian values truncate cleanly inside narrow
 *  cells instead of overflowing the column.
 */
.rh-product__attrs {
	margin-top: 18px;
}
.rh-product__attrs-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	margin-bottom: 10px;
}
.rh-product__attrs-title {
	margin: 0;
	font-size: 14px;
	font-weight: 700;
}
.rh-product__attrs-more {
	color: var(--rh-pd-primary);
	text-decoration: none;
	font-size: 12px;
	font-weight: 500;
	transition: color 120ms linear;
	white-space: nowrap;
}
.rh-product__attrs-more:hover,
.rh-product__attrs-more:focus-visible {
	color: var(--wp--preset--color--primary-dark, #074a8d);
	outline: none;
}
/* Two copies of the attrs block are rendered — one in the summary column
 * (desktop) and one in the purchase column after variants (mobile). Each
 * viewport shows exactly one. Saves a CSS-`order` dance across two
 * parents and keeps the same HTML on both. */
.rh-product__attrs--mobile  { display: none; }
@media (max-width: 900px) {
	.rh-product__attrs--desktop { display: none; }
	.rh-product__attrs--mobile  { display: block; }
}

.rh-attrs-grid {
	display: grid;
	/* 2-column compact grid on both viewports — desktop summary column is
	 * narrow but two short labels still fit; mobile keeps two columns for
	 * scanability. Hard cap on rendered items is 4 (see PHP renderer), so
	 * this always produces a tidy 2×2. */
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
}
@media (max-width: 900px) {
	.rh-attrs-grid {
		gap: 6px;
	}
}
.rh-attrs-card {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 0 12px;
	min-height: 56px;
	background: var(--rh-pd-bg);
	border: 1px solid var(--rh-pd-border);
	border-radius: 10px;
	color: var(--rh-pd-fg);
	text-decoration: none;
	transition: background-color 120ms linear, border-color 120ms linear;
	min-width: 0;
	background: #e2e4e973;
}
.rh-attrs-card:hover,
.rh-attrs-card:focus-visible {
	background: var(--wp--preset--color--surface, #f7f7f8);
	border-color: var(--wp--preset--color--text-muted, #c7cbd2);
	outline: none;
}
/* Mobile: borderless and padless spec cells — pure typography with the
 * chevron as the only visual affordance. Keeps the wider DESKTOP card
 * styling intact via the @media gate. Typography is dialled DOWN here so
 * the attribute block reads as supporting info, not a primary feature —
 * label muted+small, value medium-weight (not bold) at body size. */
@media (max-width: 900px) {
	.rh-attrs-card {
		padding: 0;
		min-height: 0;
		background: transparent;
		border: 0;
		border-radius: 0;
	}
	.rh-attrs-card:hover,
	.rh-attrs-card:focus-visible {
		background: transparent;
		border-color: transparent;
	}
	.rh-attrs-card__label {
		font-size: 11px;
		color: var(--rh-pd-muted);
	}
	.rh-attrs-card__value {
		font-size: 13px;
		font-weight: 500;
	}
}
.rh-attrs-card__body {
	display: flex;
	flex-direction: column;
	gap: 2px;
	flex: 1 1 auto;
	min-width: 0;
}
.rh-attrs-card__label {
	color: var(--rh-pd-muted);
	font-size: 11px;
	font-weight: 400;
	line-height: 1.3;
}
.rh-attrs-card__value {
	color: var(--rh-pd-fg);
	font-size: 13px;
	font-weight: 500;
	line-height: 1.4;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	min-width: 0;
}
.rh-attrs-card__chev {
	color: var(--rh-pd-muted);
	flex: 0 0 auto;
	font-size: 16px;
	line-height: 1;
}

/* ----------------------------------------------------------------------
 *  Purchase (col 3)
 *
 *  Structure:
 *    .rh-product__purchase-inner
 *      .rh-product__stock        — in-stock / out-of-stock indicator
 *      <form class="cart">       — variations + hidden qty + cart-bar
 *        .rh-variations          — variable products only
 *        .rh-product__cart-bar   — price + cart UI; sticky bottom on mobile
 *          .rh-product__price
 *            .rh-product__price-top      (discount badge + strikethrough)
 *            .rh-product__price-current  (final price, bold + large)
 *          .rh-product__cart
 *            .rh-product__add-btn        (cart icon button)
 *            .rh-product__qty            (trash, qty number, plus — hidden
 *                                          until item is in cart)
 * ---------------------------------------------------------------------- */
/* Desktop-only card frame around the purchase column. Scoped explicitly
 * to >900px because the unscoped version would otherwise win the cascade
 * against the mobile reset (which appears earlier in this file) and
 * re-draw the desktop border around the mobile stack. */
@media (min-width: 901px) {
	.rh-product__purchase-inner {
		border: 1px solid var(--rh-pd-border);
		border-radius: 12px;
		padding: 18px;
		background: var(--rh-pd-bg);
	}
}
.rh-product__stock {
	font-size: 13px;
	margin-bottom: 14px;
	min-height: 18px;
}
.rh-product__stock .stock.in-stock,
.rh-product__stock .in-stock {
	color: #1a8745;
}
.rh-product__stock .stock.out-of-stock,
.rh-product__stock .out-of-stock {
	color: #c0392b;
}

/* Price block.
 *
 * On DESKTOP the cart-bar is a vertical stack (column) — price sits above
 * the full-width add-to-cart button, end-aligned (physical LEFT in RTL).
 * On MOBILE the cart-bar is a horizontal sticky row — price on start side,
 * cart UI on end side, naturally start-aligned.
 *
 * Alignment is controlled at the cart-bar level via flex direction; the
 * price block itself uses `text-align: end` so inline content (current
 * price) follows whichever end the parent demands.
 */
.rh-product__price {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
	text-align: end; /* desktop default — flipped on mobile */
}
.rh-product__price-top {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
	color: var(--rh-pd-muted);
	justify-content: flex-end; /* end = left physical in RTL */
}
.rh-product__discount {
	display: inline-flex;
	align-items: center;
	background: var(--rh-pd-primary);
	color: #fff;
	padding: 2px 8px;
	border-radius: 999px;
	font-size: 11px;
	font-weight: 700;
	line-height: 1.6;
}
.rh-product__price-old {
	color: var(--rh-pd-muted);
	font-weight: 400;
	font-size: 13px;
	text-decoration: line-through;
}
.rh-product__price-old .woocommerce-Price-amount,
.rh-product__price-old bdi {
	font-size: inherit;
	font-weight: inherit;
	color: inherit;
}
.rh-product__price-current {
	font-size: 20px;
	font-weight: 700;
	color: var(--rh-pd-fg);
	line-height: 1.3;
}
.rh-product__price-current .woocommerce-Price-amount,
.rh-product__price-current bdi {
	font-size: inherit;
	font-weight: inherit;
	color: inherit;
}

/* Variations */
.rh-variations {
	margin-bottom: 16px;
}
.rh-variations__row {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-bottom: 12px;
}
.rh-variations__row:last-child {
	margin-bottom: 0;
}
.rh-variations__label {
	font-size: 13px;
	font-weight: 600;
}
.rh-variations__select {
	height: 40px;
	padding: 0 12px;
	font: inherit;
	color: inherit;
	border: 1px solid var(--rh-pd-border);
	border-radius: 8px;
	background: var(--rh-pd-bg);
	cursor: pointer;
	appearance: none;
	-webkit-appearance: none;
	background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position: calc(0% + 14px) 50%, calc(0% + 19px) 50%;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	padding-inline-start: 32px;
}
.rh-variations__select:focus-visible {
	border-color: var(--rh-pd-primary);
	outline: none;
}

/* ----------------------------------------------------------------------
 *  Variation swatches (color / button modes)
 *
 *  Picked over a native <select> for the two most common UX cases (color,
 *  size) because:
 *    - users see *all* options without opening a dropdown
 *    - we can mark unavailable combinations visually (strikethrough)
 *    - selected state can carry a brand-color border, matching the comp
 *
 *  Markup (rendered by rahkar_product_render_variation_row):
 *
 *    .rh-variations__row.rh-variations__row--color
 *      .rh-variations__label                  ← "رنگ: مشکی"
 *        .rh-variations__label-text
 *        .rh-variations__selected-name        ← JS-populated
 *      <input type="hidden" data-attr-select> ← invisible value carrier
 *      .rh-swatch-group.rh-swatch-group--color
 *        .rh-swatch.rh-swatch--color[.is-selected][.is-unavailable][.is-sold-out]
 *          .rh-swatch__dot                    ← colored circle (--rh-swatch-color)
 *          .rh-swatch__name                   ← human-readable label
 *      .rh-swatch-toast[hidden]               ← shown by JS on conflict
 *
 *  All visual state is class-driven (.is-selected / .is-unavailable /
 *  .is-sold-out) so JS only toggles classes — no per-element style writes,
 *  no layout thrash on attribute switches.
 * ---------------------------------------------------------------------- */

/* Label row: text + dynamic selected name inline ("رنگ: مشکی") */
.rh-variations__row--color .rh-variations__label,
.rh-variations__row--button .rh-variations__label {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px;
	margin-bottom: 0px !important;
}
.rh-variations__selected-name {
	font-weight: 500;
	color: var(--rh-pd-muted);
}

/* Swatch group: wrap pills, leave room between rows */
.rh-swatch-group {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	position: relative; /* anchor for .rh-swatch-toast */
}

/* Common base for all swatch buttons.
 *
 * Sized to match the comp: slim, single-line pills. Height intentionally
 * matches a standard input control so swatches and the price/CTA below
 * read as one tight vertical rhythm. */
.rh-swatch {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 0 10px;
	min-width: 38px;
	height: 34px;
	font: inherit;
	color: inherit;
	background: var(--rh-pd-bg);
	border: 1.5px solid var(--rh-pd-border);
	border-radius: 8px;
	cursor: pointer;
	user-select: none;
	-webkit-tap-highlight-color: transparent;
	transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}
.rh-swatch:hover:not(.is-selected):not(:disabled) {
	border-color: color-mix(in srgb, var(--rh-pd-primary) 40%, var(--rh-pd-border));
}
.rh-swatch:focus-visible {
	outline: 2px solid var(--rh-pd-primary);
	outline-offset: 2px;
}

/* Color mode: small disk + name inside the same pill */
.rh-swatch--color {
	padding-inline: 8px 12px;
}
.rh-swatch__dot {
	display: block;
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: var(--rh-swatch-color, #ccc);
	box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
	flex-shrink: 0;
}
/* Missing hex affordance: a small raked stripe so admin can spot it at a
 * glance without opening DevTools. */
.rh-swatch__dot--missing {
	background: repeating-linear-gradient(
		45deg,
		#ddd 0 4px,
		#fff 4px 8px
	);
}

/* Button mode: rectangular pill (no dot) */
.rh-swatch--button {
	min-width: 48px;
	padding-inline: 12px;
}

.rh-swatch__name {
	font-size: 13px;
	font-weight: 500;
	line-height: 1;
	white-space: nowrap;
}

/* ----- States ----- */

/* Selected: primary-color border, slightly heavier weight */
.rh-swatch.is-selected {
	border-color: var(--rh-pd-primary);
	color: var(--rh-pd-primary);
	box-shadow: inset 0 0 0 0.5px var(--rh-pd-primary);
}

/* Unavailable / sold-out: a clean, minimal "muted + struck-through name"
 * treatment — no diagonal slash through the pill (which read as visual
 * noise at this size). The dot also goes flat-grey via opacity, and the
 * border softens. The pill stays clickable for unavailable (JS handles
 * auto-clearing the conflicting other-attribute), while sold-out is
 * marked not-allowed so users don't try to add a permanently-OOS option. */
.rh-swatch.is-unavailable,
.rh-swatch.is-sold-out {
	color: var(--rh-pd-muted);
	border-color: var(--rh-pd-border);
	background: var(--rh-pd-surface);
}
.rh-swatch.is-unavailable .rh-swatch__name,
.rh-swatch.is-sold-out .rh-swatch__name {
	text-decoration: line-through;
	text-decoration-color: var(--rh-pd-muted);
	text-decoration-thickness: 1px;
}
.rh-swatch.is-unavailable .rh-swatch__dot,
.rh-swatch.is-sold-out .rh-swatch__dot {
	opacity: 0.5;
	filter: saturate(0.4);
}
.rh-swatch.is-sold-out {
	cursor: not-allowed;
}
/* Selected wins over unavailable visually (e.g. user just picked an
 * out-of-stock option) — the primary border restores. */
.rh-swatch.is-selected.is-unavailable,
.rh-swatch.is-selected.is-sold-out {
	border-color: var(--rh-pd-primary);
	color: var(--rh-pd-primary);
	background: var(--rh-pd-bg);
}

/* Toast: shown below the group when JS auto-deselects a conflicting choice.
 * One fade-in / fade-out cycle, ~3s on screen — JS hides it again. */
.rh-swatch-toast {
	margin-top: 8px;
	padding: 8px 12px;
	font-size: 12.5px;
	line-height: 1.5;
	color: #6a4500;
	background: #fff6e3;
	border: 1px solid #f3d18a;
	border-radius: 8px;
	opacity: 0;
	transform: translateY(-2px);
	transition: opacity 180ms ease, transform 180ms ease;
}
.rh-swatch-toast.is-visible {
	opacity: 1;
	transform: translateY(0);
}
.rh-swatch-toast[hidden] {
	display: none;
}

/* ----------------------------------------------------------------------
 *  Sections (replaces the old tabs)
 *
 *  Stacked panels rendered in row 2 of the product grid (alongside the
 *  sticky purchase column). Each section: heading on the start side,
 *  body below, optional "see more" expand link for the description.
 * ---------------------------------------------------------------------- */
.rh-sections {
	margin-top: 32px;
}
.rh-section {
	padding-block: 24px;
	border-top: 1px solid var(--rh-pd-border);
	/* Mobile-only sticky stack is: header (56px) + tab nav (~48px) =
	   ~104px. scroll-margin-top pushes a tab-click target past BOTH
	   so the section heading lands directly under the tabs instead of
	   being hidden behind them. Desktop has no sticky tab bar so the
	   extra margin only ever shows up after a fragment-link scroll,
	   which is the correct place for it there too. */
	scroll-margin-top: 108px;
}
.rh-section:first-child {
	border-top: 0;
	padding-top: 0;
}
.rh-section__title {
	margin: 0 0 16px;
	font-size: 18px;
	font-weight: 700;
	text-align: start; /* right in RTL */
	color: var(--rh-pd-fg);
}
.rh-section__body {
	font-size: 15px;
	line-height: 1.9;
	color: var(--rh-pd-fg);
}
.rh-section__body p {
	margin: 0 0 12px;
}
.rh-section__body p:last-child {
	margin-bottom: 0;
}
.rh-section__body img {
	max-width: 100%;
	height: auto;
	border-radius: 8px;
}

/* The long description's clamp + fade + "بیشتر/کمتر" toggle moved to
 * the shared `.rh-fade` component (see assets/css/fade-block.css). The
 * intro section just sets the wrapping <section> typography; the body
 * itself is rendered via rahkar_fade_block() inside rahkar_product_section_intro(). */

/* Table-bodied sections (attributes). No truncation needed — the table is
 * usually short and the alternating-row look reads better in full. */
.rh-section__body--table {
	padding: 0;
}

/* Attributes table inside the attrs section. Alternating row backgrounds
 * for scan-ability. Both columns are right-aligned in RTL — explicit
 * direction:rtl guards against a UA / theme stylesheet flipping the
 * default on <table>, which would otherwise put label on the left. */
.rh-attrs-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 14px;
	direction: rtl;
	text-align: right;
}
.rh-attrs-table tbody tr:nth-child(odd) {
	background: var(--rh-pd-surface);
}
.rh-attrs-table td {
	padding: 12px 16px;
}
.rh-attrs-table__label {
	color: var(--rh-pd-muted);
	font-weight: 400;
	width: 40%;
	text-align: start;
}
.rh-attrs-table__value {
	color: var(--rh-pd-fg);
	font-weight: 600;
	text-align: start;
}

@media (max-width: 900px) {
	.rh-sections {
		margin-top: 24px;
	}
	.rh-section {
		padding-block: 18px;
	}
	.rh-section__title {
		font-size: 16px;
	}
	.rh-section__body {
		font-size: 14px;
		line-height: 1.85;
	}
	.rh-attrs-table {
		font-size: 13px;
	}
	.rh-attrs-table td {
		padding: 10px 12px;
	}
}

/* ----------------------------------------------------------------------
 *  Variant-selection modal (native <dialog>)
 *
 *  Desktop:  centered card, 480px max
 *  Mobile:   bottom sheet, slide up from edge, drag-handle visual
 *
 *  Animation uses @starting-style + transition-behavior: allow-discrete
 *  so the open/close transitions work on native <dialog>. Browsers
 *  without support skip animation and the modal just appears — fine,
 *  zero JS branch needed.
 * ---------------------------------------------------------------------- */
.rh-var-modal {
	/* The modal renders as a sibling of .rh-product, so it does NOT inherit
	 * the design tokens defined on .rh-product. Re-declaring them here
	 * means the swatch styles (which read --rh-pd-*) render identically
	 * inside the modal and on the page — same border, same selected
	 * color, same unavailable treatment. Without these, the swatch pills
	 * resolve their var() reads to `unset`, which makes the border and
	 * background go transparent and the buttons look like plain text. */
	--rh-pd-fg:      var(--wp--preset--color--text, #1a1d23);
	--rh-pd-muted:   var(--wp--preset--color--text-muted, #5b6271);
	--rh-pd-bg:      var(--wp--preset--color--base, #fff);
	--rh-pd-surface: var(--wp--preset--color--surface, #f7f7f8);
	--rh-pd-border:  var(--wp--preset--color--border, #e2e4e9);
	--rh-pd-primary: var(--wp--preset--color--primary, #0a66c2);

	border: 0;
	padding: 0;
	border-radius: 14px;
	width: 92vw;
	max-width: 480px;
	box-shadow: 0 16px 40px rgba(15, 17, 21, 0.18);
	color: var(--wp--preset--color--text, #1a1d23);
	background: var(--wp--preset--color--base, #fff);

	/* Native <dialog> with showModal() does NOT vertically center by
	 * default — browsers stick it to the top. inset:0 + margin:auto
	 * forces both-axis centering on a fixed-dimension element. */
	inset: 0;
	margin: auto;

	opacity: 0;
	transform: translateY(8px);
	transition:
		opacity 180ms ease-out,
		transform 220ms cubic-bezier(0.2, 0, 0, 1),
		overlay 220ms allow-discrete,
		display 220ms allow-discrete;
}
.rh-var-modal[open] {
	opacity: 1;
	transform: translateY(0);
}
@starting-style {
	.rh-var-modal[open] {
		opacity: 0;
		transform: translateY(8px);
	}
}
.rh-var-modal::backdrop {
	background: rgba(15, 17, 21, 0.45);
	opacity: 0;
	transition:
		opacity 200ms ease-out,
		overlay 200ms allow-discrete,
		display 200ms allow-discrete;
}
.rh-var-modal[open]::backdrop {
	opacity: 1;
}
@starting-style {
	.rh-var-modal[open]::backdrop {
		opacity: 0;
	}
}

.rh-var-modal__handle {
	display: none; /* Desktop: no drag-handle visual */
}
.rh-var-modal__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	padding: 14px 18px;
	border-bottom: 1px solid var(--wp--preset--color--border, #e2e4e9);
}
.rh-var-modal__title {
	margin: 0;
	font-size: 16px;
	font-weight: 700;
	flex: 1 1 auto;
	min-width: 0;
}
.rh-var-modal__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border: 0;
	border-radius: 8px;
	background: transparent;
	color: var(--wp--preset--color--text, #1a1d23);
	cursor: pointer;
	flex: 0 0 auto;
	transition: background-color 120ms linear;
}
.rh-var-modal__close:hover,
.rh-var-modal__close:focus-visible {
	background: var(--wp--preset--color--surface, #f7f7f8);
	outline: none;
}

.rh-var-modal__body {
	padding: 18px;
	overflow-y: auto;
	max-height: 60vh;
}
@media (max-width: 900px) {
	.rh-var-modal__body {
		max-height: none;
	}
}
.rh-var-modal__attrs .rh-variations__row:last-child {
	margin-bottom: 0;
}
.rh-var-modal__price {
	margin-top: 18px;
	padding-top: 14px;
	border-top: 1px dashed var(--wp--preset--color--border, #e2e4e9);
	font-size: 18px;
	font-weight: 700;
	text-align: start;
	min-height: 24px;
}
.rh-var-modal__price del {
	color: var(--wp--preset--color--text-muted, #5b6271);
	font-weight: 400;
	font-size: 14px;
	margin-inline-end: 6px;
}
.rh-var-modal__price ins {
	text-decoration: none;
}

.rh-var-modal__footer {
	padding: 12px 18px 16px;
	border-top: 1px solid var(--wp--preset--color--border, #e2e4e9);
}
.rh-var-modal__add {
	width: 100%;
	height: 48px;
	position: relative; /* anchor for the loading spinner */
	border: 0;
	border-radius: 10px;
	background: var(--wp--preset--color--primary, #0a66c2);
	color: #fff;
	font: inherit;
	font-weight: 700;
	font-size: 15px;
	cursor: pointer;
	transition: background-color 120ms linear, transform 80ms linear;
}
.rh-var-modal__add:hover {
	background: var(--wp--preset--color--primary-dark, #074a8d);
}
.rh-var-modal__add:active {
	transform: translateY(1px);
}
.rh-var-modal__add[aria-busy="true"] {
	color: transparent;
	pointer-events: none;
}
.rh-var-modal__add[aria-busy="true"]::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 20px;
	height: 20px;
	margin: -10px 0 0 -10px;
	border: 2px solid rgba(255, 255, 255, 0.45);
	border-top-color: #fff;
	border-radius: 50%;
	animation: rh-add-spin 0.7s linear infinite;
}
/* Validation prompt — shown when "افزودن" is tapped with no attribute picked. */
.rh-var-modal__msg {
	margin: 0 0 10px;
	min-height: 0;
	font-size: 13px;
	font-weight: 600;
	color: #c0392b;
	text-align: center;
	opacity: 0;
	transition: opacity 120ms linear;
}
.rh-var-modal__msg.is-visible {
	opacity: 1;
}

/* Empty-attr shake feedback on add click */
@keyframes rh-var-shake {
	0%, 100% { transform: translateX(0); }
	20%, 60% { transform: translateX(-6px); }
	40%, 80% { transform: translateX(6px); }
}
.rh-var-shake {
	animation: rh-var-shake 320ms ease-in-out;
	border-color: #c0392b !important;
	box-shadow: 0 0 0 1px #c0392b;
}

/* ---- Mobile: bottom sheet ---- */
@media (max-width: 900px) {
	.rh-var-modal {
		/* Explicit position:fixed — the UA default for <dialog> is
		 * position:absolute. On mobile RHSheet opens this sheet with
		 * dialog.show() (NOT showModal) and moves it to <body>, so without
		 * `fixed` it anchors to the document bottom (inset: …0…) and SCROLLS
		 * away with the page instead of staying pinned to the viewport. Mirror
		 * of the .rh-desc-modal sheet fix. */
		position: fixed !important;
		width: 100%;
		max-width: none;
		inset: auto 0 0 0;
		margin: 0;
		border-radius: 16px 16px 0 0;
		max-height: 88vh;
		overflow-y: auto;
		padding-bottom: env(safe-area-inset-bottom, 0px);

		transform: translateY(100%);
		transition:
			transform 240ms cubic-bezier(0.2, 0, 0, 1),
			opacity 200ms ease-out,
			overlay 240ms allow-discrete,
			display 240ms allow-discrete;
	}
	.rh-var-modal[open] {
		transform: translateY(0);
	}
	@starting-style {
		.rh-var-modal[open] {
			transform: translateY(100%);
		}
	}

	.rh-var-modal__close { display: none; }
	.rh-var-modal__handle {
		display: block;
		width: 36px;
		height: 4px;
		border-radius: 2px;
		background: var(--wp--preset--color--border, #e2e4e9);
		margin: 8px auto 0;
		flex: 0 0 auto;
	}
	.rh-var-modal__header {
		padding: 8px 18px 12px;
	}
}

/* ----------------------------------------------------------------------
 *  Short-description modal (<dialog id="rh-desc-modal">)
 *
 *  Same animation playbook as the variation modal (centered card on
 *  desktop, bottom sheet on mobile) — kept as a separate selector so
 *  the two can evolve independently. Could be factored into a generic
 *  .rh-modal base later if a third modal appears.
 * ---------------------------------------------------------------------- */
.rh-desc-modal {
	/* RHSheet relocates this dialog to <body> on mobile so position:fixed
	 * isn't broken by ancestor transforms. But the --rh-pd-* tokens are
	 * scoped to .rh-product, so once relocated the background/color/border
	 * vars resolve to nothing and the sheet renders transparent. Redefine
	 * the tokens locally so the dialog is self-contained wherever it lives
	 * in the DOM. */
	--rh-pd-fg:      var(--wp--preset--color--text, #1a1d23);
	--rh-pd-muted:   var(--wp--preset--color--text-muted, #5b6271);
	--rh-pd-bg:      var(--wp--preset--color--base, #fff);
	--rh-pd-surface: var(--wp--preset--color--surface, #f7f7f8);
	--rh-pd-border:  var(--wp--preset--color--border, #e2e4e9);
	--rh-pd-primary: var(--wp--preset--color--primary, #0a66c2);

	border: 0;
	padding: 0;
	border-radius: 14px;
	width: 92vw;
	max-width: 560px;
	box-shadow: 0 16px 40px rgba(15, 17, 21, 0.18);
	color: var(--rh-pd-fg);
	background: var(--rh-pd-bg);
	inset: 0;
	margin: auto;

	opacity: 0;
	transform: translateY(8px);
	transition:
		opacity 180ms ease-out,
		transform 220ms cubic-bezier(0.2, 0, 0, 1),
		overlay 220ms allow-discrete,
		display 220ms allow-discrete;
}
.rh-desc-modal[open] {
	opacity: 1;
	transform: translateY(0);
}
@starting-style {
	.rh-desc-modal[open] {
		opacity: 0;
		transform: translateY(8px);
	}
}
.rh-desc-modal::backdrop {
	background: rgba(15, 17, 21, 0.45);
	opacity: 0;
	transition:
		opacity 200ms ease-out,
		overlay 200ms allow-discrete,
		display 200ms allow-discrete;
}
.rh-desc-modal[open]::backdrop {
	opacity: 1;
}
@starting-style {
	.rh-desc-modal[open]::backdrop {
		opacity: 0;
	}
}

.rh-desc-modal__handle {
	display: none;
}
.rh-desc-modal__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	padding: 14px 18px;
	border-bottom: 1px solid var(--rh-pd-border);
}
.rh-desc-modal__title {
	margin: 0;
	font-size: 16px;
	font-weight: 700;
}
.rh-desc-modal__close {
	width: 32px;
	height: 32px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 0;
	border-radius: 8px;
	background: transparent;
	color: var(--rh-pd-fg);
	cursor: pointer;
	transition: background-color 120ms linear;
}
.rh-desc-modal__close:hover,
.rh-desc-modal__close:focus-visible {
	background: var(--rh-pd-surface);
	outline: none;
}
.rh-desc-modal__body {
	padding: 18px;
	overflow-y: auto;
	max-height: 65vh;
	font-size: 14px;
	line-height: 1.9;
	color: var(--rh-pd-fg);
}
.rh-desc-modal__body p {
	margin: 0 0 8px;
}
.rh-desc-modal__body p:last-child {
	margin-bottom: 0;
}
.rh-desc-modal__body ul,
.rh-desc-modal__body ol {
	margin: 0 0 8px;
	padding: 0;
	list-style: none;
}
.rh-desc-modal__body li {
	position: relative;
	padding-inline-start: 16px;
	margin-bottom: 4px;
}
.rh-desc-modal__body li::before {
	content: '';
	position: absolute;
	inset-block-start: 0.75em;
	inset-inline-start: 4px;
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var(--rh-pd-primary);
}

/* Mobile: bottom sheet, drag-handle visual. */
@media (max-width: 900px) {
	.rh-desc-modal {
		/* Explicit position:fixed — the UA default for <dialog> is
		 * position:absolute, which when the sheet is moved to <body> by
		 * RHSheet ends up anchored to the document, not the viewport. On
		 * any scrolled page that puts the sheet wherever the document's
		 * bottom is, mid-page. Mirrors .rh-rev-modal which already has
		 * this and works correctly. !important guards against any UA
		 * stylesheet or browser-specific rule that re-asserts absolute
		 * on <dialog>. */
		position: fixed !important;
		width: 100%;
		max-width: none;
		inset: auto 0 0 0 !important;
		margin: 0;
		border-radius: 16px 16px 0 0;
		/* Floor the height so a sheet with just one or two rows doesn't
		 * render as a thin strip stuck to the bottom — looks broken on
		 * mobile. 40vh gives the panel real presence without dominating
		 * the screen when content is light. */
		min-height: 40vh;
		max-height: 85vh;

		transform: translateY(100%);
		transition:
			transform 240ms cubic-bezier(0.2, 0, 0, 1),
			opacity 200ms ease-out,
			overlay 240ms allow-discrete,
			display 240ms allow-discrete;
	}
	.rh-desc-modal[open] {
		transform: translateY(0);
	}
	@starting-style {
		.rh-desc-modal[open] {
			transform: translateY(100%);
		}
	}
	.rh-desc-modal__close { display: none; }
	.rh-desc-modal__handle {
		display: block;
		width: 36px;
		height: 4px;
		border-radius: 2px;
		background: var(--rh-pd-border);
		margin: 8px auto 0;
		flex: 0 0 auto;
	}
	.rh-desc-modal__header {
		padding: 8px 18px 12px;
	}
	.rh-desc-modal__body {
		max-height: 70vh;
		padding-bottom: max(env(safe-area-inset-bottom, 0), 18px);
	}
}
