/*
 * Fivany shop surfaces — product cards, listing, PDP, cart, checkout.
 * Ported from Opnar's `.pcard` family in components/home/home.css.
 */

/* ══════════════════════════════════════════════════════════════
   PRODUCT GRID
   ══════════════════════════════════════════════════════════════ */

.woocommerce ul.products {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 16px;
	margin: 0 0 24px;
	padding: 0;
}

.woocommerce ul.products::before,
.woocommerce ul.products::after {
	content: none;
}

@media (max-width: 1023px) {
	.woocommerce ul.products {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (max-width: 767px) {
	.woocommerce ul.products {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 12px;
	}
}

/* ── The card ──────────────────────────────────────────────────── */

.woocommerce ul.products li.product {
	background: var(--fv-cream);
	border: 0;
	border-radius: var(--fv-radius-lg);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	position: relative;
	box-shadow: var(--fv-shadow-sm);
	transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
	/* Storefront lays the loop out with floats + percentage widths. */
	width: auto !important;
	float: none !important;
	margin: 0 !important;
	padding: 0;
	clear: none !important;
}

.woocommerce ul.products li.product:hover {
	transform: translateY(-2px);
	box-shadow: var(--fv-shadow-md);
	background: var(--fv-cream-hover);
}

.woocommerce ul.products li.product a.woocommerce-loop-product__link {
	display: flex;
	flex-direction: column;
	flex: 1;
	color: inherit;
	text-decoration: none;
}

/* ── Image slot — white inside the cream card ──────────────────── */

.woocommerce ul.products li.product img {
	background: var(--fv-surface);
	aspect-ratio: 1 / 1;
	object-fit: cover;
	width: 100%;
	height: auto;
	margin: 0;
	border-radius: 0;
	transition: transform 0.5s ease;
	display: block;
}

.woocommerce ul.products li.product:hover img {
	transform: scale(1.04);
}

/* The image needs its own clipping box for the zoom to stay inside. */
.woocommerce ul.products li.product a.woocommerce-loop-product__link > img {
	overflow: hidden;
}

/* ── Ribbon ────────────────────────────────────────────────────── */

.woocommerce ul.products li.product .onsale,
.woocommerce span.onsale {
	position: absolute;
	top: 8px;
	left: 8px;
	right: auto;
	margin: 0;
	padding: 3px 7px;
	min-height: 0;
	min-width: 0;
	line-height: 1.4;
	border-radius: var(--fv-radius-md);
	font-size: 10px;
	font-weight: 700;
	font-family: var(--fv-mono);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	background: var(--fv-hot);
	color: #fff;
	z-index: 2;
}

/*
 * The single-product badge is a size up from the card ribbon, matching
 * Opnar's `.gal-badge.disc` (11px / 4px 7px / 10px inset) against its
 * `.ribbon` (10px / 3px 7px / 8px). A badge calibrated for a 260px card
 * reads as a speck on a 570px gallery.
 */
.single-product div.product > .onsale {
	top: 10px;
	left: 10px;
	font-size: 11px;
	padding: 4px 7px;
}

@media (max-width: 767px) {
	/* Below 768 the card is not a grid, so the badge resolves against the
	 * card's padding box instead of the gallery. Offsetting by the card's
	 * own 12px padding puts it back on the image corner. */
	.single-product div.product > .onsale {
		top: 22px;
		left: 22px;
	}
}

.woocommerce ul.products li.product .fv-ribbon-stock {
	position: absolute;
	top: 8px;
	right: 8px;
	padding: 3px 7px;
	border-radius: var(--fv-radius-md);
	font-size: 10px;
	font-weight: 700;
	font-family: var(--fv-mono);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	background: var(--fv-ink);
	color: #fff;
	z-index: 2;
}

/* ── Card body ─────────────────────────────────────────────────── */

.woocommerce ul.products li.product .woocommerce-loop-product__title {
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1.35;
	letter-spacing: -0.005em;
	color: var(--fv-ink);
	padding: 10px 12px 0;
	margin: 0;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	transition: color 0.15s ease;
}

.woocommerce ul.products li.product:hover .woocommerce-loop-product__title {
	color: var(--fv-accent);
}

.woocommerce ul.products li.product .fv-card-meta {
	padding: 2px 12px 0;
	font-size: 10.5px;
	color: var(--fv-ink-3);
	font-family: var(--fv-mono);
	letter-spacing: 0.02em;
	text-transform: uppercase;
}

.woocommerce ul.products li.product .price {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 6px;
	/* Bottom padding raised from 12px: the add-to-cart button used to sit
	 * under the price and give the card its lower breathing room. */
	padding: 6px 12px 16px;
	margin: auto 0 0;
	font-family: var(--fv-mono);
	color: var(--fv-ink);
	font-size: 14px;
	font-weight: 800;
	line-height: 1.3;
}

.woocommerce ul.products li.product .price ins,
.woocommerce ul.products li.product .price > .woocommerce-Price-amount {
	color: var(--fv-hot);
	font-weight: 800;
	font-size: 14px;
	text-decoration: none;
	background: transparent;
	order: 1;
}

.woocommerce ul.products li.product .price del {
	color: var(--fv-ink-3);
	font-size: 11px;
	font-weight: 400;
	text-decoration: line-through;
	opacity: 1;
	order: 2;
}

/* Price ranges on variable products read as one unit, not a sale. */
.woocommerce ul.products li.product .price .from {
	font-size: 10px;
	color: var(--fv-ink-3);
	font-weight: 500;
}

/* ── Add to cart ───────────────────────────────────────────────── */

/* ── Listing chrome ────────────────────────────────────────────── */

.woocommerce .woocommerce-result-count,
.woocommerce .woocommerce-ordering select {
	font-size: 12px;
	color: var(--fv-ink-3);
}

.woocommerce .woocommerce-ordering select {
	font-family: var(--fv-font);
}

.storefront-sorting {
	padding-bottom: 12px;
	border-bottom: 1px solid var(--fv-line);
	margin-bottom: 20px;
}

.woocommerce nav.woocommerce-pagination ul {
	border: 0;
	gap: 6px;
	display: flex;
	justify-content: center;
}

.woocommerce nav.woocommerce-pagination ul li {
	border: 0;
	margin: 0;
}

.woocommerce nav.woocommerce-pagination ul li a,
.woocommerce nav.woocommerce-pagination ul li span {
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-md);
	background: var(--fv-surface);
	font-family: var(--fv-mono);
	font-size: 12px;
	padding: 7px 12px;
	color: var(--fv-ink-2);
}

.woocommerce nav.woocommerce-pagination ul li span.current,
.woocommerce nav.woocommerce-pagination ul li a:hover {
	background: var(--fv-accent);
	border-color: var(--fv-accent);
	color: #fff;
}

/* ══════════════════════════════════════════════════════════════
   PRODUCT PAGE
   ══════════════════════════════════════════════════════════════ */

.single-product div.product {
	background: var(--fv-cream);
	border-radius: var(--fv-radius-xl);
	padding: 16px;
	box-shadow: var(--fv-shadow-sm);
}

/*
 * Grid, not Storefront's floats.
 *
 * Floated columns leave the taller one dictating where everything after
 * it can start: the gallery ran ~550px against a ~420px summary, so the
 * description had to clear the float and opened ~190px of dead cream
 * between the two. Opnar avoids this with a real grid
 * (`grid-template-columns: minmax(0,1.05fr) minmax(0,1fr) 320px`), and the
 * ratio here lands the gallery at ~525px — the same width Opnar's gallery
 * column resolves to at 1440.
 *
 * `align-items: start` stops the short column stretching to match the
 * tall one, and everything after the summary spans both tracks, so the
 * description begins directly under whichever column is shorter.
 */
@media (min-width: 768px) {
	.single-product div.product {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
		gap: 24px;
		align-items: start;
	}

	.single-product div.product > .images {
		grid-column: 1;
		grid-row: 1;
	}

	.single-product div.product > .summary {
		grid-column: 2;
		grid-row: 1;
	}

	/*
	 * Placing the badge in the gallery's grid area is what anchors it to
	 * the image. An absolutely-positioned child of a positioned grid
	 * container resolves its offsets against its GRID AREA, not the
	 * container — so `top/left` here measure from the gallery's corner
	 * rather than the card's, without the badge having to be a descendant
	 * of the gallery in the markup (WooCommerce emits it as a sibling).
	 */
	.single-product div.product > .onsale {
		grid-column: 1;
		grid-row: 1;
	}

	/* Description, related products and anything else hooked after the
	 * summary run the full width beneath both columns. */
	.single-product div.product > *:not(.images):not(.summary):not(.onsale) {
		grid-column: 1 / -1;
	}

	/*
	 * Float and percentage widths are Storefront's; the grid owns both now.
	 *
	 * The `.storefront-full-width-content` variant has to be named
	 * explicitly. Storefront ships
	 *   .storefront-full-width-content.single-product div.product .images
	 * at (0,4,2), which outranks a plain `.single-product div.product
	 * .images`, so the shorter selector alone left the widths in place —
	 * and inside a grid those percentages resolve against the TRACK rather
	 * than the parent, so `39.13%` of a 568px track rendered the gallery at
	 * 222px inside its own column. The layout looked broken; the widths
	 * were simply being measured against a new containing block.
	 */
	.single-product div.product > .images,
	.single-product div.product > .summary,
	.storefront-full-width-content.single-product div.product > .images,
	.storefront-full-width-content.single-product div.product > .summary {
		float: none;
		width: auto;
		margin: 0;
	}
}

@media (max-width: 767px) {
	.single-product div.product {
		padding: 12px;
	}
}

/*
 * The gallery image was rendering at its intrinsic 416px (the
 * `woocommerce_single` size) inside a 529px column, so it sat undersized
 * in an oversized white box. Forcing it to fill the column lets the
 * browser pick a larger candidate from the srcset WooCommerce already
 * emits, and the box then hugs the picture instead of framing it.
 */
.single-product div.product .images {
	background: var(--fv-surface);
	border-radius: var(--fv-radius-lg);
	overflow: hidden;
	/* Storefront gives both columns `margin-bottom: 3.41575em` (~48px).
	 * Above 768px the grid's `gap` handles separation; this is the
	 * stacked-layout value. */
	margin-bottom: 16px;
}

.single-product div.product .images img {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * A real photo fills the frame; the placeholder is a logo, so cropping it
 * would cut the mark. WooCommerce labels its own placeholder, which is
 * exactly the hook needed to treat the two differently.
 */
.single-product div.product .images img.woocommerce-placeholder {
	object-fit: contain;
}

.single-product div.product .summary {
	padding-left: 4px;
	margin-bottom: 16px;
}

/*
 * No thumbnail strip, even on a multi-image product. Note this is the
 * flexslider CONTROL, so hiding it removes the only desktop way to reach
 * the other images — swipe still works on touch. The gallery itself is
 * left intact so the extra images stay attached to the product and in the
 * page source.
 */
.single-product .flex-control-thumbs,
.single-product .flex-control-nav {
	display: none;
}

.single-product div.product .product_title {
	font-size: 22px;
	font-weight: 700;
	letter-spacing: -0.015em;
	line-height: 1.25;
	margin-bottom: 6px;
}

.single-product div.product .summary .price {
	font-family: var(--fv-mono);
	display: flex;
	align-items: baseline;
	gap: 10px;
	flex-wrap: wrap;
	/* Storefront opens the price with `margin-top: 1.618em` (~26px), which
	 * was the single largest gap in the column — the title already carries
	 * its own bottom margin, so this one only compounded it. */
	margin-top: 0;
	margin-bottom: 12px;
}

.single-product div.product .summary .price ins,
.single-product div.product .summary .price > .woocommerce-Price-amount {
	color: var(--fv-hot);
	font-size: 24px;
	font-weight: 800;
	text-decoration: none;
	background: transparent;
}

.single-product div.product .summary .price del {
	color: var(--fv-ink-3);
	font-size: 14px;
	text-decoration: line-through;
	opacity: 1;
}

.single-product div.product .woocommerce-product-details__short-description {
	font-size: 13px;
	color: var(--fv-ink-2);
	line-height: 1.65;
	margin-bottom: 16px;
}

/* ── Variation selector ────────────────────────────────────────── */

.single-product table.variations {
	margin-bottom: 10px;
	border: 0;
}

.single-product table.variations th,
.single-product table.variations td {
	border: 0;
	padding: 3px 0;
	background: transparent;
}

.single-product table.variations th.label label {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	font-family: var(--fv-mono);
	color: var(--fv-ink-2);
}

/*
 * The select is hidden but kept in the layout, not `display: none`.
 * WooCommerce's variation script reads and writes it, and a display-none
 * control cannot receive focus — the clip-rect pattern keeps it reachable
 * for anyone tabbing or using a screen reader while the chips carry the
 * visual job. If the chip script fails to load, `.fv-chip-source` is never
 * applied and the plain select stays visible, so the product is still
 * buyable.
 */
.single-product table.variations select {
	min-width: 180px;
	background: var(--fv-surface);
}

.single-product table.variations select.fv-chip-source {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	min-width: 0;
}

.fv-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.fv-chip {
	min-width: 44px;
	height: 36px;
	padding: 0 12px;
	background: var(--fv-surface);
	border: 1.5px solid var(--fv-line-2);
	border-radius: var(--fv-radius-md);
	font-family: var(--fv-mono);
	font-size: 12px;
	font-weight: 600;
	line-height: 1;
	color: var(--fv-ink-2);
	cursor: pointer;
	transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.fv-chip:hover:not(:disabled) {
	border-color: var(--fv-accent);
	color: var(--fv-accent);
	background: var(--fv-surface);
}

.fv-chip.is-active {
	border-color: var(--fv-accent);
	background: var(--fv-accent);
	color: #fff;
}

/* ── Colour swatches ───────────────────────────────────────────────
 * A swatch is the same chip with a colour disc instead of a label. The
 * ring is drawn on the button, never on the disc, so a white swatch
 * still has a visible edge against a white card.
 */
.fv-chip.fv-swatch {
	min-width: 0;
	width: 36px;
	padding: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/*
 * Square, not a disc. The rest of the theme sits on a near-square radius
 * scale (--fv-radius-sm is 1px), so a full circle here was the one shape
 * on the page that belonged to a different design language. A square also
 * shows more of the colour at the same dimension, which is the swatch's
 * whole job.
 */
.fv-swatch-dot {
	width: 20px;
	height: 20px;
	border-radius: var(--fv-radius-sm);
	/* The swatch's own hairline keeps a light colour readable on the white
	 * chip; the chip's border is the selection state, not the edge. */
	box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.16);
	display: block;
}

.fv-chip.fv-swatch.is-active {
	background: var(--fv-surface);
	border-color: var(--fv-accent);
	box-shadow: 0 0 0 2px var(--fv-accent-soft);
}

.fv-chip.fv-swatch:disabled .fv-swatch-dot {
	opacity: 0.3;
}

/* Struck-through text is meaningless on a swatch — a diagonal bar across
 * the colour carries the same "not available" reading. */
.fv-chip.fv-swatch:disabled {
	position: relative;
	text-decoration: none;
}

.fv-chip.fv-swatch:disabled::after {
	content: '';
	position: absolute;
	left: 6px;
	right: 6px;
	top: 50%;
	height: 1.5px;
	background: var(--fv-ink-3);
	transform: rotate(-45deg);
}

/*
 * A size that exists but is unavailable in the chosen combination stays on
 * screen, struck through — removing it would shift the row and hide the
 * fact that the size is made at all.
 */
.fv-chip:disabled {
	color: var(--fv-ink-soft);
	border-color: var(--fv-line);
	background: var(--fv-muted);
	cursor: not-allowed;
	text-decoration: line-through;
}

.single-product .reset_variations {
	font-size: 11px;
	color: var(--fv-ink-3);
	font-family: var(--fv-mono);
}

.single-product .woocommerce-variation-price {
	margin-bottom: 10px;
}

/* ── Buy row ───────────────────────────────────────────────────── */

.single-product form.cart {
	display: flex;
	align-items: stretch;
	gap: 10px;
	flex-wrap: wrap;
	margin-bottom: 14px;
}

/*
 * The buy row sits inside `.woocommerce-variation-add-to-cart` on a
 * variable product, and Storefront's own margins there opened a ~50px
 * gap between the last attribute row and the quantity box.
 */
.single-product .woocommerce-variation-add-to-cart {
	margin-top: 4px;
}

.single-product .woocommerce-variation {
	margin: 0;
}

.single-product .woocommerce-variation-price:empty,
.single-product .woocommerce-variation-availability:empty,
.single-product .woocommerce-variation-description:empty {
	display: none;
}

.single-product div.product .product_meta {
	margin-top: 12px;
	padding-top: 12px;
	border-top: 1px solid var(--fv-line);
}

.single-product form.cart.variations_form {
	display: block;
}

/*
 * The quantity control and the button are one row, so they are one height.
 *
 * This was 42px against the button's 46 — the wrapper stretched but the
 * control inside did not, leaving its bottom edge 4px shy of the button's and
 * the row looking accidentally ragged. Both now carry the same height rather
 * than relying on the stretch.
 *
 * `select.qty` is listed alongside `input.qty` everywhere the two are styled:
 * the product page renders a dropdown (see inc/quantity.php) and the cart
 * keeps the number input, so both exist in the stylesheet's world.
 */
.single-product form.cart .quantity input.qty,
.single-product form.cart .quantity select.qty {
	width: 72px;
	height: 46px;
	text-align: center;
	font-family: var(--fv-mono);
	font-size: 14px;
	background: var(--fv-surface);
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-md);
}

/*
 * The dropdown, drawn rather than native.
 *
 * A native select renders differently on every platform — a chunky grey
 * control on Android, a blue-tinted one on iOS — and neither sits well beside
 * a flat brand-coloured button. The arrow is a background image so it needs no
 * extra element inside a control that cannot hold one.
 */
.single-product form.cart .quantity select.qty {
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	/* Room for the chevron on the right; the same on the left keeps the
	   number optically centred rather than merely centred. */
	padding: 0 20px 0 10px;
	text-align-last: center;
	cursor: pointer;
	color: var(--fv-ink);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23444' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 7px center;
	background-size: 10px 6px;
}

.single-product form.cart .quantity select.qty:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 2px;
}

/* ── The quantity listbox ──────────────────────────────────────────
 * Once JavaScript has drawn its own control the select is hidden, but it
 * stays in the form — it is what submits, and it is what the variation
 * script reads. Hidden by clipping rather than `display: none`, so it keeps
 * its place in the tab order for anything that still addresses it directly.
 */
.single-product form.cart .quantity.fv-qty-enhanced select.qty {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	overflow: hidden;
	white-space: nowrap;
}

.single-product form.cart .quantity.fv-qty-enhanced {
	position: relative;
	width: 72px;
	flex: 0 0 72px;
}

/* Matches the buy button's height exactly — the two sit on one row. */
.single-product form.cart .fv-qty-toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	width: 100%;
	height: 46px;
	padding: 0 8px 0 12px;
	margin: 0;
	background: var(--fv-surface);
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-md);
	color: var(--fv-ink);
	font-family: var(--fv-mono);
	font-size: 14px;
	line-height: 1;
	cursor: pointer;
	/* The grey flash Android paints on tap fights the open state. */
	-webkit-tap-highlight-color: transparent;
	transition: border-color 0.15s ease;
}

.single-product form.cart .fv-qty-toggle:hover,
.single-product form.cart .quantity.is-open .fv-qty-toggle {
	border-color: var(--fv-ink-soft);
}

.single-product form.cart .fv-qty-toggle:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 2px;
}

/* A single option is not a choice — see quantity.js. */
.single-product form.cart .quantity.is-fixed .fv-qty-toggle {
	cursor: default;
}

.single-product form.cart .quantity.is-fixed .fv-qty-chev {
	display: none;
}

.single-product form.cart .fv-qty-chev {
	width: 7px;
	height: 7px;
	margin-top: -3px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(45deg);
	transition: transform 0.18s ease;
}

.single-product form.cart .quantity.is-open .fv-qty-chev {
	margin-top: 2px;
	transform: rotate(-135deg);
}

/*
 * The panel.
 *
 * Anchored to the control rather than handed to the operating system, which
 * is the whole point of replacing the select. It opens downward because the
 * row sits high in the summary with the trust strip beneath it, and a list of
 * six lines has room there on every screen this shop sees.
 */
.single-product form.cart .fv-qty-list {
	position: absolute;
	z-index: 20;
	top: calc(100% + 4px);
	left: 0;
	right: 0;
	margin: 0;
	padding: 4px;
	list-style: none;
	background: var(--fv-surface);
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-md);
	box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
	/*
	 * No inner scroll. Six lines is the whole range, and a scrollbar inside a
	 * list that short suggests there is more to find when there is not — it
	 * reads as a longer menu the customer has to hunt through. The panel
	 * opens on whichever side has room instead (see quantity.js).
	 */
	display: none;
}

.single-product form.cart .quantity.is-open .fv-qty-list {
	display: block;
}

/* Flipped above the control when there is no room beneath it (quantity.js). */
.single-product form.cart .quantity.is-up .fv-qty-list {
	top: auto;
	bottom: calc(100% + 4px);
	box-shadow: 0 -10px 24px rgba(0, 0, 0, 0.12);
}

.single-product form.cart .fv-qty-option {
	display: flex;
	align-items: center;
	justify-content: center;
	/* A thumb needs the room even though the label is one character. */
	height: 38px;
	border-radius: calc(var(--fv-radius-md) - 1px);
	font-family: var(--fv-mono);
	font-size: 14px;
	color: var(--fv-ink);
	cursor: pointer;
	-webkit-tap-highlight-color: transparent;
}

.single-product form.cart .fv-qty-option:hover {
	background: var(--fv-accent-soft);
}

.single-product form.cart .fv-qty-option[aria-selected='true'] {
	background: var(--fv-accent);
	color: #fff;
	font-weight: 700;
}

@media (prefers-reduced-motion: reduce) {
	.single-product form.cart .fv-qty-chev,
	.single-product form.cart .fv-qty-toggle {
		transition: none;
	}
}

/*
 * One button, and it takes the room.
 *
 * This was a fixed 168px basis because there were two buttons side by side
 * and stretching both read as a pair of banners. There is only one now, so
 * the reasoning inverts: the single thing the page is asking for should be
 * the easiest thing on it to hit. Capped so it does not become a letterbox
 * on a wide screen.
 */
.single-product form.cart .single_add_to_cart_button {
	flex: 1 1 auto;
	max-width: 340px;
	height: 46px;
	/* Matched to the coupon Apply button — see the note in checkout.css. */
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	border-radius: var(--fv-radius-md);
}

.single-product .woocommerce-variation-add-to-cart,
.single-product form.cart {
	justify-content: flex-start;
}

.single-product .woocommerce-variation-add-to-cart {
	display: flex;
	gap: 10px;
	align-items: stretch;
	flex-wrap: wrap;
}

/*
 * Storefront sets `float: left; margin-right: .875em` at
 * `.single-product div.product form.cart .quantity` — (0,4,2). That margin
 * stacked on top of the flex `gap`, giving 21px between the quantity box
 * and Add to Cart against 10px between the two buttons. Matching its
 * specificity is not optional here: a shorter selector silently lost and
 * the row kept its uneven rhythm. The float goes too — it means nothing in
 * a flex row and only confuses the layout.
 */
.single-product div.product form.cart .quantity {
	float: none;
	margin: 0;
}

/* ── Stock + meta ──────────────────────────────────────────────── */

.single-product .stock.in-stock {
	color: var(--fv-good);
	font-family: var(--fv-mono);
	font-size: 11.5px;
	font-weight: 600;
}

.single-product .stock.out-of-stock {
	color: var(--fv-hot);
	font-family: var(--fv-mono);
	font-size: 11.5px;
	font-weight: 600;
}

.single-product .product_meta {
	font-size: 11.5px;
	color: var(--fv-ink-3);
	border-top: 1px solid var(--fv-line);
	padding-top: 12px;
	margin-top: 4px;
}

.single-product .product_meta .sku {
	font-family: var(--fv-mono);
}

/* ── Description ───────────────────────────────────────────────────
 * WooCommerce's tab panel is replaced with a plain block (see
 * inc/woocommerce.php), so what is left to style is a heading and a
 * column of text. The measure is capped: full-bleed body copy across a
 * 1440px rail runs to ~180 characters a line, which is unreadable.
 */
.fv-product-desc {
	margin-top: 20px;
	padding-top: 18px;
	border-top: 1px solid var(--fv-line);
	/* Breathing room where the description ends — related products used to
	 * begin 24px after the last line, which read as part of the same block. */
	margin-bottom: 40px;
}

.fv-product-desc h2 {
	font-family: var(--fv-mono);
	font-size: 12px;
	/* Matches the size guide heading directly above it — the two are
	   sibling section labels and must not drift apart in weight. */
	font-weight: 800;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fv-ink-3);
	margin: 0 0 12px;
}

.fv-product-desc-body {
	font-size: 13.5px;
	line-height: 1.75;
	color: var(--fv-ink-2);
	max-width: 74ch;
}

.fv-product-desc-body > *:first-child {
	margin-top: 0;
}

.fv-product-desc-body > *:last-child {
	margin-bottom: 0;
}

.fv-product-desc-body p {
	margin: 0 0 12px;
}

/* ── Spec list ─────────────────────────────────────────────────────
 * Descriptions are a list of facts now rather than prose, so they are set
 * as data: a mono uppercase label in a fixed column, its value beside it,
 * on the same hairline rows as the size guide that sits directly above.
 * The two blocks then read as one continuous spec sheet instead of two
 * unrelated components stacked together.
 *
 * The value is left-aligned rather than pushed to the right edge the way
 * the size table's figures are. Those are short numbers whose digits line
 * up into a column; these are sentences, and a ragged-left sentence is
 * meaningfully harder to scan.
 *
 * Selectors carry the parent class deliberately — a bare `.fv-spec li` is
 * outranked by woocommerce.css's own list rules.
 */
.fv-product-desc-body .fv-spec {
	list-style: none;
	margin: 0;
	padding: 0;
	max-width: 64ch;
}

.fv-product-desc-body .fv-spec li {
	display: grid;
	/* Wide enough for "Colours", the longest label in use. */
	grid-template-columns: 82px 1fr;
	gap: 16px;
	align-items: baseline;
	margin: 0;
	padding: 9px 0;
	border-bottom: 1px solid var(--fv-line);
	font-size: 13.5px;
	line-height: 1.6;
	color: var(--fv-ink);
	list-style: none;
}

.fv-product-desc-body .fv-spec li:first-child {
	padding-top: 0;
}

.fv-product-desc-body .fv-spec li:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

.fv-product-desc-body .fv-spec strong {
	font-family: var(--fv-mono);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--fv-ink-3);
}

/*
 * On a phone an 82px label column leaves the value about 20 characters
 * wide, which wraps every row into three lines. Stacking is shorter.
 */
@media (max-width: 600px) {
	.fv-product-desc-body .fv-spec li {
		grid-template-columns: 1fr;
		gap: 2px;
		font-size: 13px;
	}
}

/* ── Related products heading ──────────────────────────────────── */

.related.products > h2,
.up-sells > h2,
.cross-sells > h2 {
	/* Storefront centres these; every other heading on the page starts at
	   the content edge, and a centred one over a left-aligned grid reads as
	   a different section belonging to a different design. */
	text-align: left;
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	font-family: var(--fv-mono);
	color: var(--fv-ink);
	padding-bottom: 10px;
	border-bottom: 1px solid var(--fv-line);
	margin-bottom: 18px;
}

/* ══════════════════════════════════════════════════════════════
   CART + CHECKOUT (block-based in WooCommerce 11)
   ══════════════════════════════════════════════════════════════ */

.wp-block-woocommerce-cart,
.wp-block-woocommerce-checkout {
	font-family: var(--fv-font);
	font-size: 13px;
	color: var(--fv-ink);
}

.wc-block-components-title,
.wp-block-woocommerce-cart h1,
.wp-block-woocommerce-checkout h1 {
	font-family: var(--fv-font);
	letter-spacing: -0.01em;
}

.wc-block-components-sidebar,
.wc-block-components-totals-wrapper,
.wc-block-cart__sidebar,
.wc-block-checkout__sidebar {
	background: var(--fv-cream);
	border-radius: var(--fv-radius-lg);
}

.wc-block-components-panel,
.wc-block-components-radio-control__option {
	border-radius: var(--fv-radius-md);
}

.wc-block-components-formatted-money-amount,
.wc-block-components-totals-item__value,
.wc-block-components-product-price {
	font-family: var(--fv-mono);
	font-variant-numeric: tabular-nums;
}

.wc-block-components-totals-footer-item .wc-block-components-totals-item__value {
	color: var(--fv-hot);
	font-weight: 800;
}

.wc-block-components-button,
.wc-block-cart__submit-button,
.wc-block-components-checkout-place-order-button {
	background: var(--fv-accent) !important;
	color: #fff !important;
	border-radius: var(--fv-radius-md) !important;
	font-family: var(--fv-font);
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	font-size: 12.5px;
}

.wc-block-components-button:hover,
.wc-block-cart__submit-button:hover,
.wc-block-components-checkout-place-order-button:hover {
	background: var(--fv-accent-hover) !important;
}

.wc-block-components-text-input input,
.wc-block-components-select select,
.wc-block-components-address-form input {
	border-radius: var(--fv-radius-md);
	border-color: var(--fv-line);
	font-family: var(--fv-font);
}

.wc-block-components-radio-control__option--checked-option-highlighted,
.wc-block-components-radio-control-accordion-option--checked-option-highlighted {
	border-color: var(--fv-accent);
	box-shadow: 0 0 0 1px var(--fv-accent);
}

/* Classic (shortcode) cart table, in case the blocks are swapped out. */
.woocommerce table.shop_table {
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-lg);
	font-size: 12.5px;
}

.woocommerce table.shop_table th {
	font-family: var(--fv-mono);
	font-size: 11px;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--fv-ink-2);
}

/* ══════════════════════════════════════════════════════════════
   Storefront alignment corrections
   ══════════════════════════════════════════════════════════════ */

/*
 * Storefront centres loop text; Fivany's cards are left-aligned.
 *
 * Alignment only. These used to share a rule with the products header below,
 * which meant the header's `padding: 8px 0 0` was also applied to the title,
 * meta and price — wiping out the 12px gutter each of them declares and
 * leaving the text flush against the card's edge.
 */
.woocommerce ul.products li.product,
.woocommerce ul.products li.product .woocommerce-loop-product__title,
.woocommerce ul.products li.product .price,
.woocommerce ul.products li.product .fv-card-meta {
	text-align: left;
}

/* Storefront sets `.storefront-full-width-content .woocommerce-products-header
 * { text-align: center; padding: 0 0 5.9968353111em }` — ~78px of dead space
 * under the title. Match its two-class specificity to win. */
.woocommerce-products-header,
.storefront-full-width-content .woocommerce-products-header {
	padding: 8px 0 0;
	margin: 0 0 10px;
	text-align: left;
}

.woocommerce-products-header .page-title,
.woocommerce-products-header__title {
	text-align: left;
	font-size: 20px;
	letter-spacing: -0.015em;
	margin-bottom: 4px;
}

.woocommerce-products-header .term-description {
	font-size: 12.5px;
	color: var(--fv-ink-3);
	max-width: 70ch;
}

/* Duplicate sorting bar at the foot of the loop adds noise. */
.woocommerce .storefront-sorting:last-of-type {
	border-bottom: 0;
	border-top: 1px solid var(--fv-line);
	margin-top: 8px;
	margin-bottom: 0;
	padding-top: 12px;
	padding-bottom: 0;
}

/* ══════════════════════════════════════════════════════════════
   PDP corrections
   ══════════════════════════════════════════════════════════════ */

/* WooCommerce emits <del> before <ins>; Opnar leads with the price you
 * actually pay and trails the struck original. */
.single-product div.product .summary .price ins,
.single-product div.product .summary .price > .woocommerce-Price-amount {
	order: 1;
}

.single-product div.product .summary .price del {
	order: 2;
}

.single-product div.product .summary .price .screen-reader-text {
	order: 3;
}

/*
 * Clip the gallery so slides cannot bleed past the product card.
 *
 * The wrapper is deliberately NOT in this list. FlexSlider sizes it as a
 * multi-slide track — `width: 800%` for four images — and capping that at
 * `max-width: 100%` squeezed the track back to one slide's width, so the
 * floated slides wrapped into a 1727px vertical stack that `.images` then
 * clipped. It looked like a broken image; it was a wrapped track.
 *
 * Clipping the viewport is what actually contains the slides, and
 * FlexSlider sets `overflow: hidden` there itself.
 */
.woocommerce-product-gallery,
.woocommerce-product-gallery .flex-viewport {
	overflow: hidden;
	max-width: 100%;
}

/* Storefront centres these section headings. */
.related.products > h2,
.up-sells > h2,
.cross-sells > h2 {
	text-align: left;
}

/* Disabled add-to-cart (no variation chosen yet) should read as inert,
 * not as a washed-out brand colour. */
.single-product .single_add_to_cart_button.disabled,
.single-product .single_add_to_cart_button:disabled {
	background: var(--fv-ink-soft);
	color: #fff;
	opacity: 1;
	cursor: not-allowed;
}

/* ══════════════════════════════════════════════════════════════
   LISTING TOOLBAR — category / tag / shop archives
   ══════════════════════════════════════════════════════════════ */

.fv-listing-toolbar {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 6px 2px 0;
	flex-wrap: wrap;
}

.fv-listing-h {
	margin: 0;
	font-size: 18px;
	font-weight: 800;
	letter-spacing: -0.02em;
	color: var(--fv-ink);
	display: flex;
	align-items: center;
	gap: 10px;
	line-height: 1.2;
	min-width: 0;
	flex: 1 1 auto;
}

.fv-listing-h em {
	font-style: normal;
	color: var(--fv-accent);
}

.fv-listing-h .meta {
	font-family: var(--fv-mono);
	font-size: 11px;
	font-weight: 500;
	color: var(--fv-ink-3);
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

.fv-listing-sub {
	margin: 8px 0 0;
	font-size: 12.5px;
	color: var(--fv-ink-3);
	line-height: 1.55;
	max-width: 70ch;
}

.fv-listing-sort {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 34px;
	padding: 0 10px 0 12px;
	border: 1px solid var(--fv-line);
	border-radius: var(--fv-radius-md);
	background: var(--fv-surface);
	color: var(--fv-ink-2);
	font-size: 12.5px;
	box-shadow: var(--fv-shadow-sm);
	flex-shrink: 0;
}

.fv-listing-sort form.woocommerce-ordering {
	margin: 0;
}

.fv-listing-sort select {
	border: 0;
	background: transparent;
	font: inherit;
	font-size: 12.5px;
	font-weight: 600;
	color: var(--fv-ink);
	padding: 0;
	outline: 0;
	box-shadow: none;
	height: auto;
}

.fv-listing-sort select:focus {
	box-shadow: none;
	border: 0;
}

/* ── Subcategory chips ─────────────────────────────────────────── */

.fv-subcat-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 14px 0 18px;
}

.fv-subcat-chip {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	height: 30px;
	padding: 0 14px;
	border: 1px solid var(--fv-line);
	background: var(--fv-surface);
	border-radius: var(--fv-radius-lg);
	color: var(--fv-ink-2);
	font-size: 12.5px;
	font-weight: 600;
	letter-spacing: -0.005em;
	box-shadow: var(--fv-shadow-sm);
	transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.fv-subcat-chip:hover {
	border-color: var(--fv-accent);
	color: var(--fv-accent-hover);
	background: var(--fv-accent-soft);
}

.fv-subcat-chip .ct {
	font-family: var(--fv-mono);
	font-size: 10px;
	color: var(--fv-ink-3);
	letter-spacing: 0.02em;
}

.fv-subcat-chip:hover .ct {
	color: var(--fv-accent);
}

/* ── Account page title ────────────────────────────────────────────
 * Storefront prints "My account" above every screen in the account
 * area. Signed out, the sign-in card already names itself; signed in,
 * the rail says where you are and the screen carries its own heading.
 * Either way it is a second heading saying less.
 *
 * Lives here rather than in auth.css or account.css because those load
 * on opposite sides of the logged-in check — one rule in the sheet that
 * always loads beats the same rule maintained twice.
 */
.woocommerce-account .entry-header,
.woocommerce-account .woocommerce > h2 {
	display: none;
}

/* ── "Adding…" ─────────────────────────────────────────────────────
 * Between the press and the checkout page the browser is navigating, and
 * nothing on screen says so. The button turns white, names what it is doing,
 * and a solid band of brand colour wipes across it.
 *
 * The wipe is a gesture, not a measurement — a form submit reports no
 * progress to animate against — so it is paced to feel like the wait usually
 * is. If the page arrives first the animation simply goes with it.
 *
 * Applies to the main button and the bar's button alike, since either may be
 * the one the customer is looking at.
 */
.single-product div.product form.cart .single_add_to_cart_button.is-adding,
.fv-satc .fv-satc-btn.is-adding {
	position: relative;
	overflow: hidden;
	/*
	 * Longhands, and the image killed explicitly.
	 *
	 * Several themes and WooCommerce itself set this button's background at
	 * varying specificity, some as a gradient `background-image`. A shorthand
	 * alone leaves an inherited image sitting on top of the new colour, and
	 * the white never appears — the button just looks like it did before.
	 * Written at a specificity above everything that targets it so the state
	 * cannot be outranked.
	 */
	background-color: #fff;
	background-image: none;
	border-color: var(--fv-accent);
	color: var(--fv-accent);
	cursor: default;
	/* The press lift would fight the wipe. */
	transform: none;
	box-shadow: none;
	/* Keeps the two label layers stacking against each other and nothing else. */
	isolation: isolate;
}

/*
 * Hover cannot be allowed to repaint it.
 *
 * This is what actually broke it on a phone. A tap leaves `:hover` stuck on
 * mobile browsers, and the hover rule for this button — set further down the
 * file — matched at exactly the same specificity as the adding state did, so
 * the later rule won and put `--fv-accent-hover` back over the white. On a
 * desktop with a real pointer the button is never hovered at the moment of
 * the press, which is why it only ever went wrong on the device.
 *
 * Raising the specificity above fixes it; saying so here means a future
 * reordering of this file cannot quietly undo that.
 */
.single-product div.product form.cart .single_add_to_cart_button.is-adding:hover,
.single-product div.product form.cart .single_add_to_cart_button.is-adding:focus,
.single-product div.product form.cart .single_add_to_cart_button.is-adding:focus-visible,
.single-product div.product form.cart .single_add_to_cart_button.is-adding:active,
.fv-satc .fv-satc-btn.is-adding:hover,
.fv-satc .fv-satc-btn.is-adding:focus,
.fv-satc .fv-satc-btn.is-adding:focus-visible,
.fv-satc .fv-satc-btn.is-adding:active {
	background-color: #fff;
	background-image: none;
	border-color: var(--fv-accent);
	color: var(--fv-accent);
	transform: none;
	box-shadow: none;
}

/*
 * The two label layers, stacked on the same box.
 *
 * `inset: 0` on both means they share the button's geometry exactly, so the
 * letters register on top of each other and the wipe reads as the text
 * changing colour rather than as a second label sliding past.
 */
.is-adding .fv-buy-base,
.is-adding .fv-buy-fill {
	position: absolute;
	/* `inset` is not understood by older in-app webviews; the longhands are. */
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	/* Inherit the button's own type so the two copies cannot drift apart. */
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
}

.is-adding .fv-buy-base {
	color: var(--fv-accent);
	/* Explicit order: the filled layer paints over this one. Relying on DOM
	   order alone left the two at the mercy of any stacking context above. */
	z-index: 1;
}

/*
 * The filled state, revealed left to right.
 *
 * `clip-path` rather than an animated width: the layer keeps the button's
 * full size at all times, so its centred label stays where the base label is
 * instead of being squeezed along with a growing box.
 */
.is-adding .fv-buy-fill {
	z-index: 2;
	background-color: var(--fv-accent);
	background-image: none;
	color: #fff;
	clip-path: inset(0 100% 0 0);
	/*
	 * Fills once, left to right, and stays filled.
	 *
	 * It repeated for a while — sweeping in and back out so the motion never
	 * stopped — and that reads as something scrolling past rather than as a
	 * button filling up. A fill has a destination; a loop does not.
	 *
	 * Timed against the real wait: measured at 398ms from press to the
	 * checkout page, so 450ms leaves the button around three-quarters full as
	 * a fast checkout arrives, and completely full a moment later. On a slower
	 * connection it finishes and holds, which is the right resting state —
	 * the work is done and the page is on its way.
	 *
	 * The 60ms delay is the beat of white: without it the fill starts on the
	 * same frame as the press and nobody registers that the button changed.
	 */
	animation: fv-buy-wipe 0.45s linear 0.06s forwards;
}

@keyframes fv-buy-wipe {
	from {
		clip-path: inset(0 100% 0 0);
	}
	to {
		clip-path: inset(0 0 0 0);
	}
}

/*
 * Without clip-path the filled layer would cover the button from the first
 * frame and the white state would never be seen. Where it is unsupported the
 * layer is simply not drawn, leaving a white button with an accent label —
 * less expressive, but never wrong.
 */
@supports not (clip-path: inset(0 100% 0 0)) {
	.is-adding .fv-buy-fill {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.is-adding .fv-buy-fill {
		animation: none;
		clip-path: inset(0 0 0 0);
	}
}

/* A sweep that never lands needs the label legible at both ends of it, so
   the base copy keeps its own contrast against white. */

/* ── Sticky buy bar ────────────────────────────────────────────────
 * Appears when the real buy button is off screen (assets/js/sticky-atc.js).
 * The bottom edge is free now that the mobile nav is gone, so nothing stacks
 * against it.
 */
.fv-satc {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 60;
	background: rgba(255, 255, 255, 0.96);
	-webkit-backdrop-filter: blur(18px) saturate(1.4);
	backdrop-filter: blur(18px) saturate(1.4);
	border-top: 1px solid var(--fv-line);
	box-shadow: 0 -8px 24px -18px rgba(13, 15, 18, 0.45);
	/* The home indicator on a modern phone sits inside the bar's own space. */
	padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
	transform: translateY(100%);
	transition: transform 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.fv-satc.is-on {
	transform: translateY(0);
}

/* `hidden` alone loses to the display above — the same trap as the exit
   dialog and the cart empty state. */
.fv-satc[hidden] {
	display: none;
}

.fv-satc-inner {
	display: flex;
	align-items: center;
	gap: 10px;
	max-width: 1200px;
	margin: 0 auto;
}

.fv-satc-media {
	flex: 0 0 auto;
	line-height: 0;
}

.fv-satc-thumb,
.fv-satc-media img {
	width: 42px;
	height: 42px;
	object-fit: cover;
	border-radius: var(--fv-radius-md);
	border: 1px solid var(--fv-line);
}

.fv-satc-text {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.fv-satc-name {
	font-size: 12.5px;
	font-weight: 600;
	color: var(--fv-ink);
	/* One line: the bar is a prompt, not a second product page. */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fv-satc-price {
	font-family: var(--fv-mono);
	font-size: 13px;
	font-weight: 700;
	color: var(--fv-ink);
	line-height: 1.1;
}

.fv-satc-price del {
	opacity: 0.5;
	font-weight: 400;
	margin-right: 4px;
}

.fv-satc-price ins {
	text-decoration: none;
}

.fv-satc-btn {
	flex: 0 0 auto;
	min-width: 142px;
	/* The same 46px as the buy button it stands in for. */
	height: 46px;
	padding: 0 18px;
	background: var(--fv-accent);
	color: #fff;
	border: 1px solid var(--fv-accent);
	border-radius: var(--fv-radius-md);
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	cursor: pointer;
	-webkit-tap-highlight-color: transparent;
	transition: background-color 0.16s ease, border-color 0.16s ease,
		transform 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Same reasoning as the main button: a stuck hover on touch is only ever
   wrong, and this button is pressed on a phone more than anywhere. */
@media (hover: hover) {
	.fv-satc-btn:hover {
		background: var(--fv-accent-hover);
		border-color: var(--fv-accent-hover);
	}
}

/* The same press as the main button, for the same reason: a tap gets no
   hover, so the press has to read on its own. */
.fv-satc-btn:active:not(:disabled) {
	transform: scale(0.972);
	background: var(--fv-accent-hover);
}

.fv-satc-btn:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 2px;
}

.fv-satc-btn:disabled {
	background: var(--fv-ink-soft);
	border-color: var(--fv-ink-soft);
	cursor: not-allowed;
}

/* Below 380px the name is the first thing worth losing — the price and the
   button are what the bar is for. */
@media (max-width: 379px) {
	.fv-satc-name {
		display: none;
	}

	.fv-satc-btn {
		min-width: 0;
		flex: 1 1 auto;
	}
}

@media (prefers-reduced-motion: reduce) {
	.fv-satc {
		transition: none;
	}

	.fv-satc-btn {
		transition: none;
	}

	.fv-satc-btn:active:not(:disabled) {
		transform: none;
	}
}

/* ── Size guide link ───────────────────────────────────────────────
 * Sits under the size chips, where the doubt it answers actually happens.
 * Quiet by design: it is a way out of a decision, not one of the actions the
 * page is asking for, so it must not compete with the buy button.
 */
.single-product form.cart .fv-sg-link {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	/* Pulled up against the chips it belongs to, and given clear air before
	   the buy row underneath. */
	margin: -2px 0 14px;
	padding: 6px 2px;
	color: var(--fv-ink-soft);
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1;
	text-decoration: none;
	border-bottom: 1px solid transparent;
	-webkit-tap-highlight-color: transparent;
	transition: color 0.15s ease, border-color 0.15s ease;
}

.single-product form.cart .fv-sg-link:hover,
.single-product form.cart .fv-sg-link:focus-visible {
	color: var(--fv-accent);
}

.single-product form.cart .fv-sg-link span {
	border-bottom: 1px solid currentColor;
	padding-bottom: 1px;
}

.single-product form.cart .fv-sg-link:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 3px;
	border-radius: 2px;
}

.single-product form.cart .fv-sg-link-icon {
	width: 15px;
	height: 15px;
	flex: 0 0 15px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.6;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/*
 * Where the jump lands: on the heading, not under the header.
 *
 * A bare anchor puts the section's top edge at the very top of the viewport,
 * which is underneath the pinned header — the customer arrives at a chart
 * with its title hidden, which reads as having landed in the wrong place.
 *
 * Both values are the measured height of what is actually pinned plus a
 * little air: `.m-top` is 63px on a phone, `.d-top` is 77px on desktop. They
 * are not interchangeable, and guessing one from the other is what put the
 * heading behind the bar the first time.
 */
.single-product .fv-sg-single {
	scroll-margin-top: 76px;
}

@media (min-width: 768px) {
	.single-product .fv-sg-single {
		scroll-margin-top: 96px;
	}
}

/* The section takes focus on arrival (see inc/size-guide-link.php); a focus
   ring around a whole page section reads as an error, so it is suppressed —
   the scroll itself is the feedback. */
.single-product .fv-sg-single:focus {
	outline: none;
}

/* ══════════════════════════════════════════════════════════════
   BUY ROW — Add to Cart + Buy Now
   ══════════════════════════════════════════════════════════════ */

/*
 * The primary action, and the only one.
 *
 * There were two treatments here — filled for Buy Now, outline for Add to
 * Cart — expressing a hierarchy between two buttons. With one button there is
 * no hierarchy to express, and an outline would understate the single thing
 * the page is for, so it takes the filled brand colour.
 *
 * Written against the bare class rather than `:not(.fv-buy-now)`: that
 * selector existed to exclude the other button, and excluding a button that
 * is no longer rendered only makes the rule harder to read.
 */
.single-product form.cart .single_add_to_cart_button {
	background: var(--fv-accent);
	color: #fff;
	border: 1px solid var(--fv-accent);
}

/*
 * Hover belongs to pointers, not to fingers.
 *
 * A tap on a touch screen leaves `:hover` applied until something else is
 * touched, so this button sat in its hover colour long after the press — and
 * while the Adding state was trying to paint it white, this rule was still
 * repainting it `--fv-accent-hover`, which is the muted orange that showed up
 * on the phone instead of the white. Gating on `hover: hover` removes the
 * effect from every device that cannot really hover, which is where it was
 * only ever a bug.
 *
 * `:focus-visible` stays outside the query: keyboard focus is real on any
 * device and must not be hidden with it.
 */
@media (hover: hover) {
	.single-product form.cart .single_add_to_cart_button:hover {
		background: var(--fv-accent-hover);
		border-color: var(--fv-accent-hover);
		box-shadow: 0 6px 16px rgba(255, 107, 0, 0.32);
	}
}

.single-product form.cart .single_add_to_cart_button:focus-visible {
	background: var(--fv-accent-hover);
	border-color: var(--fv-accent-hover);
	box-shadow: 0 6px 16px rgba(255, 107, 0, 0.32);
}

/*
 * The motion on the buy button.
 *
 * The press has to stand on its own. A phone never sends a hover, so a
 * press defined as "undo the hover lift" — which is what this used to be,
 * `translateY(0)` against a resting `none` — computed to the identical
 * matrix and showed the shopper nothing at all on the one device that
 * matters most here. It is now a scale, which reads from the resting state
 * whether or not a hover ever happened, and it is paired with a colour
 * change for the same reason.
 *
 * Timings are deliberately lopsided: the lift eases in, the press snaps.
 * A button that takes as long to go down as it does to come up feels slow
 * under the finger.
 *
 * `will-change` is deliberately absent: two buttons do not need a promoted
 * layer each for a 2px move, and the cost is paid on every product page
 * whether anyone hovers or not.
 */
.single-product form.cart .single_add_to_cart_button {
	transition: background-color 0.16s ease, border-color 0.16s ease,
		color 0.16s ease, box-shadow 0.2s ease,
		transform 0.18s cubic-bezier(0.22, 0.61, 0.36, 1);
	/* The grey flash Android and iOS paint on tap fights the press below. */
	-webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
	.single-product form.cart .single_add_to_cart_button:hover:not(.disabled):not(:disabled) {
		transform: translateY(-2px);
	}
}

.single-product form.cart .single_add_to_cart_button:focus-visible:not(.disabled):not(:disabled) {
	transform: translateY(-2px);
}

/*
 * Pressed. Listed after the hover rules so it wins when both apply, and
 * scaled from wherever the button currently sits rather than to a fixed
 * offset, so a mouse press lands it back level and a tap simply shrinks it.
 */
.single-product form.cart .single_add_to_cart_button:active:not(.disabled):not(:disabled) {
	transform: translateY(0) scale(0.972);
	box-shadow: none;
	/* Down fast, so the feedback arrives with the finger. */
	transition-duration: 0.09s;
}

/* The press carries a colour of its own — the only cue a tap ever gets. */
.single-product form.cart .single_add_to_cart_button:active:not(.disabled):not(:disabled) {
	background: var(--fv-accent-hover);
	border-color: var(--fv-accent-hover);
}

/* Keyboard focus still needs a ring — the lift alone is not a focus state. */
.single-product form.cart .single_add_to_cart_button:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 2px;
}

/*
 * While the request is in flight.
 *
 * The label stays put and the button stops responding — a spinner that
 * replaces the text makes the row jump, and on a fast connection it flashes
 * for 200ms and reads as a glitch.
 */
.single-product form.cart .single_add_to_cart_button.is-loading {
	opacity: 0.72;
	pointer-events: none;
	transform: none;
	box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
	.single-product form.cart .single_add_to_cart_button,
	.single-product form.cart .single_add_to_cart_button:hover,
	.single-product form.cart .single_add_to_cart_button:active {
		transition: none;
		transform: none;
	}
}

/* Disabled has to win over both treatments above, hence the extra class. */
.single-product form.cart .single_add_to_cart_button.disabled,
.single-product form.cart .single_add_to_cart_button:disabled {
	background: var(--fv-ink-soft);
	border-color: var(--fv-ink-soft);
	color: #fff;
	opacity: 1;
	cursor: not-allowed;
}

/* A simple product has no .woocommerce-variation-add-to-cart wrapper, so
 * the row is laid out on the form itself. */
.single-product form.cart {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	align-items: stretch;
}

.single-product form.cart.variations_form {
	display: block;
}

/*
 * Phones keep all three controls on one row. The desktop rule sets
 * min-width: 200px on the button, which at 390px forces each onto its own
 * line — so that floor is dropped and the two buttons split whatever is
 * left after the quantity box. `min-width: 0` is the part that actually
 * lets a flex item shrink below its content width; without it the label
 * keeps the button wide and the row wraps anyway.
 */
@media (max-width: 900px) {
	.single-product form.cart .quantity {
		flex: 0 0 auto;
	}

	/*
	 * The number input narrows here; the dropdown deliberately does not.
	 *
	 * It was 52px because two buttons and a quantity box had to share a
	 * narrow row. There is one button now, and the dropdown is the same
	 * control on every screen — a phone should not get a different-sized
	 * version of it than a desktop. Only `input.qty` is named, so
	 * `select.qty` keeps the 72px set further up.
	 */
	.single-product form.cart .quantity input.qty {
		width: 52px;
		min-width: 0;
		padding-left: 4px;
		padding-right: 4px;
	}

	/* The wrapper wraps by default, which would undo everything below. */
	.single-product .woocommerce-variation-add-to-cart,
	.single-product form.cart {
		flex-wrap: nowrap;
	}

	.single-product form.cart .single_add_to_cart_button {
		/*
		 * Takes whatever the quantity stepper leaves.
		 *
		 * The 200px cap and the shrunk 11.5px type here were both there to
		 * fit two buttons and a stepper on one narrow row. With one button
		 * the row is no longer crowded, so the cap goes and the label returns
		 * to full size — on a phone this is the only thing being asked for,
		 * and it should read like it.
		 */
		flex: 1 1 auto;
		max-width: none;
		min-width: 0;
		padding-left: 10px;
		padding-right: 10px;
		font-size: 14px;
		white-space: nowrap;
	}
}

/* ══════════════════════════════════════════════════════════════
   YOU MAY ALSO LIKE — infinite scroll
   ══════════════════════════════════════════════════════════════ */

/*
 * Reuses `.fv-rail-head` and the shop's own `.products` grid, so this is the
 * same furniture as every other product run on the site. Only the sentinel
 * below the grid is new.
 */

.single-product .fv-alsolike {
	margin-top: 8px;
}

.single-product .fv-alsolike .fv-rail-head {
	margin-bottom: 14px;
}

/* ── Sentinel ──────────────────────────────────────────────────── */

/*
 * A reserved slot below the grid.
 *
 * It keeps its height whether or not the spinner is showing, so appending a
 * batch extends the page downward instead of shifting what the shopper is
 * already looking at.
 */
.single-product .fv-alsolike-more {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	min-height: 52px;
	margin-top: 18px;
}

.single-product .fv-alsolike-more .spin {
	display: none;
	width: 16px;
	height: 16px;
	border: 2px solid var(--fv-line-2);
	border-top-color: var(--fv-accent);
	border-radius: var(--fv-radius-full);
	animation: fv-alsolike-spin 0.7s linear infinite;
}

.single-product .fv-alsolike-more[aria-busy="true"] .spin {
	display: block;
}

@keyframes fv-alsolike-spin {
	to {
		transform: rotate(360deg);
	}
}

/*
 * A spinner is the one thing on the page that must not animate for someone
 * who asked for stillness. It stays visible as a static ring — the busy
 * state is still legible, it just does not move.
 */
@media (prefers-reduced-motion: reduce) {
	.single-product .fv-alsolike-more .spin {
		animation: none;
	}
}

.single-product .fv-alsolike-status {
	margin: 0;
	text-align: center;
	font-family: var(--fv-mono);
	font-size: 11px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--fv-ink-3);
	min-height: 1em;
}

/* ── Product page: keep the buy buttons above the fold ─────────────
 * Measured on a 390x844 phone: Add to Cart sat at 782–824 while the fixed
 * bottom nav covers 774–844 — so the one control the page exists for was
 * completely hidden behind the nav, and nothing on screen suggested it was
 * there. A shopper had to scroll to discover they could buy.
 *
 * The space is reclaimed from the picture (43% of the viewport) plus the
 * accumulated margins under the title, price and stock line. Nothing is
 * removed: the "Clear" link, the size chips and the stock line all stay.
 *
 * Written at the end of the file on purpose — these share specificity with
 * the base rules above and would lose the tie on source order if they sat
 * anywhere earlier.
 */
@media (max-width: 767px) {
	/*
	 * `vh` rather than a pixel cap so the trade scales: on a short phone
	 * the picture gives up more, on a tall one it keeps more. Width goes
	 * auto so the aspect ratio survives the clamp — with `width: 100%` a
	 * max-height would squash the photo instead of scaling it.
	 */
	/*
	 * Full width and square.
	 *
	 * It used to be `width: auto` with a max-height, which kept the aspect
	 * ratio by letterboxing — the picture floated in the middle of a white
	 * box with a band of padding down each side. Filling the column and
	 * cropping to a square instead gives the photograph every pixel of the
	 * page it can have.
	 */
	.single-product div.product .images img {
		width: 100%;
		max-width: 100%;
		max-height: none;
		aspect-ratio: 1 / 1;
		object-fit: cover;
		margin: 0;
	}

	/*
	 * The breadcrumb costs ~36px and earns little on a phone: the category
	 * strip pinned above it already offers every department, and the back
	 * gesture covers the rest. Hidden rather than removed, so the
	 * BreadcrumbList structured data still ships to Google.
	 *
	 * The wrapper goes too. Hiding only the inner element left the
	 * `.storefront-breadcrumb` container holding 12px of empty space — an
	 * invisible band between the category strip and the product.
	 */
	.single-product .woocommerce-breadcrumb,
	.single-product .storefront-breadcrumb {
		display: none;
	}

	.single-product div.product {
		padding-top: 8px;
	}

	/*
	 * The "Clear" link takes a whole row to undo a choice that tapping any
	 * other chip already undoes.
	 */
	.single-product .reset_variations {
		display: none;
	}

	/* photo -> name */
	.single-product div.product .images {
		margin-bottom: 6px;
	}

	/* name -> price */
	.single-product div.product .product_title {
		margin-bottom: 2px;
	}

	/* price -> variant picker */
	.single-product div.product .summary .price {
		margin-bottom: 2px;
	}

	.single-product div.product .summary .stock {
		margin-bottom: 8px;
	}

	/*
	 * The variation form carried 13px of padding top and bottom, another
	 * 13px under the chips row, and the add-to-cart wrapper added 17px more
	 * above the button — about 40px of stacked breathing room between the
	 * price and the one control that matters. Trimmed rather than removed,
	 * so the chips still have room to be tapped.
	 */
	/*
	 * Carries `div.product` deliberately. Storefront styles this as
	 * `.woocommerce div.product form.cart` — three classes and two
	 * elements — so the shorter selector here lost the tie and the 13px
	 * padding and 21px bottom margin stayed put.
	 */
	.single-product div.product form.variations_form.cart {
		padding-top: 0;
		padding-bottom: 0;
		margin-bottom: 6px;
	}

	/* variant picker -> add to cart */
	.single-product div.product table.variations td {
		margin-bottom: 2px;
	}

	.single-product div.product .woocommerce-variation-add-to-cart {
		margin-top: 0;
		padding-top: 0;
	}
}

/*
 * Short phones — a 640-667px viewport spends over 150px on fixed chrome
 * before the product starts, so the picture has to give up more for the
 * buttons to clear the bottom nav. Keyed on viewport height rather than
 * width: it is the height that is scarce.
 */
/*
 * Short phones pay for the square photograph out of the type, not out of
 * the picture. The image stays full width and uncropped at every size; the
 * title, price, stock line and chips give up a few points instead.
 */
@media (max-width: 767px) and (max-height: 700px) {
	.single-product div.product .product_title {
		font-size: 18px;
		line-height: 1.25;
	}

	.single-product div.product .summary .price {
		font-size: 20px;
	}

	.single-product div.product .summary .stock {
		font-size: 11px;
	}

	.single-product table.variations th.label label {
		font-size: 10px;
	}

	.single-product .fv-chip {
		height: 32px;
		font-size: 11px;
	}
}

/*
 * The smallest phones still in wide use here (360x640). Over 150px of the
 * 640 is fixed chrome before the product even begins, so the picture is the
 * only place left to take it from — the alternative is the buy button
 * sitting under the nav, which is the problem this set out to solve.
 */
@media (max-width: 767px) and (max-height: 660px) {
	.single-product div.product .product_title {
		font-size: 16px;
	}

	.single-product div.product .summary .price {
		font-size: 18px;
	}

	.single-product div.product .summary .price ins,
	.single-product div.product .summary .price > .woocommerce-Price-amount {
		font-size: 18px;
	}

	.single-product .fv-chip {
		height: 30px;
	}

	/*
	 * A shorter button on the shortest screens — 5px of clearance above the
	 * nav is not a margin, it is a coincidence.
	 *
	 * `height`, not padding: the button is given a fixed 46px further up,
	 * so padding alone changed nothing.
	 */
	.single-product form.cart .single_add_to_cart_button,
	.single-product form.cart .quantity .qty {
		height: 36px;
	}
}

/*
 * The product page starts where the home page starts.
 *
 * Storefront pads `#content` for a page that opens with a heading; the home
 * page overrides it to zero, so the product page was the odd one out —
 * 16px of dead band under the header at every width, and on desktop that
 * sat on top of the breadcrumb's own spacing for a 44px gap against home's
 * nothing.
 */
.single-product #content.site-content {
	padding-top: 0;
}

/*
 * No white frame around the photograph.
 *
 * `.images` painted a white surface behind a letterboxed picture, so the
 * product sat inside a box rather than filling one. With the image now
 * cropped square to the full column width there is nothing left to show
 * through, and the panel only added a seam against the cream card.
 */
.single-product div.product .images {
	background: transparent;
}

/*
 * Square on desktop too — same reasoning, and it keeps the gallery column
 * a predictable height beside the summary.
 */
.single-product div.product .images img {
	aspect-ratio: 1 / 1;
	object-fit: cover;
}

/*
 * Close the gap under the photograph.
 *
 * FlexSlider gives `.flex-viewport` a 1.618em bottom margin to clear the
 * thumbnail strip beneath it — but this theme hides that strip
 * (`.flex-control-thumbs`), so the margin was 21px of empty space between
 * the picture and the product name, with nothing in it.
 */
.single-product div.product .images .flex-viewport {
	margin-bottom: 0;
}

/* ── Tighter summary type, stock on the price row ──────────────────
 * The title and price were sized for a page that opened with a heading;
 * with the photograph now square and full width they only push the buy
 * controls further down, so both give up a few points.
 */

.single-product div.product .product_title {
	/*
	 * 20px, matching the price. At 19px the product's own name was set
	 * smaller and lighter than its price — the page's subject reading as
	 * less important than one of its attributes. Desktop takes 23px below.
	 */
	font-size: 20px;
	line-height: 1.25;
}

.single-product div.product .summary .price {
	font-size: 17px;
	/* Already flex; these make room for the stock line moved in beside it. */
	display: flex;
	align-items: baseline;
	gap: 10px;
	flex-wrap: wrap;
}

/*
 * The stock line, relocated from under the size picker (see
 * assets/js/variations.js). Pushed to the far end of the price row so the
 * two facts a shopper checks together — what it costs and whether it can
 * be had — sit on one line instead of straddling the picker.
 */
/*
 * The stock line on the price row. Written as a span the script copies in,
 * so the original stays put for WooCommerce and is hidden here.
 */
.single-product div.product .woocommerce-variation-availability {
	display: none;
}

.single-product div.product .summary .price .fv-price-stock {
	margin: 0 0 0 auto;
	font-family: var(--fv-mono);
	font-size: 11.5px;
	font-weight: 600;
	white-space: nowrap;
	color: var(--fv-good);
	/*
	 * The price amount carries `order: 1`, so an unordered element sorts
	 * ahead of it and the stock line landed to the LEFT of the price. An
	 * explicit high order puts it last whatever the amount is set to.
	 */
	order: 9;
}

/*
 * The price itself. The 24px was set when the page opened with a big
 * heading; it is the largest thing in the summary and shrinking it buys
 * back more room than anything else here.
 */
.single-product div.product .summary .price ins,
.single-product div.product .summary .price > .woocommerce-Price-amount {
	font-size: 20px;
}

/* Smaller chips: the picker was 36px tall for a two-character label. */
.single-product .fv-chip {
	height: 32px;
	min-width: 40px;
	padding: 0 11px;
	font-size: 11.5px;
}

.single-product .fv-chip.fv-swatch {
	width: 32px;
	min-width: 0;
	padding: 0;
}

.single-product div.product .summary .price .fv-price-stock.is-out {
	color: var(--fv-hot);
}

/*
 * No smiley on the stock line.
 *
 * Storefront's WooCommerce stylesheet prefixes `.stock` with an icon-font
 * glyph at U+F118 — a smiling face. It reads as a stray emoji next to the
 * price, and on any device without that font loaded it degrades to a
 * tofu box.
 */
.single-product div.product .summary .stock::before,
.single-product div.product .summary .price .fv-price-stock::before,
.single-product div.product .summary .stock::after,
.single-product div.product .summary .price .fv-price-stock::after {
	content: none !important;
	display: none !important;
}

/*
 * No category strip on a product page — phones only.
 *
 * By the time someone is looking at one product the department list has
 * done its job, and on a phone it costs 41px that the buy buttons were
 * being pushed down by. The burger and search in the row above still
 * reach everything.
 *
 * Desktop keeps its row: there is no vertical pressure there, and that row
 * carries the mega menu, which is the main way anyone browses on a large
 * screen.
 */
@media (max-width: 1023px) {
	.single-product .fv-hdr .m-cat {
		display: none;
	}
}

/* ── Product page: consistency pass ────────────────────────────────
 * A review of the finished page against itself, after several rounds of
 * incremental trimming had left the scale uneven.
 */

/*
 * 1. The primary button was SMALLER on a phone than on desktop — 11.5px
 *    against 13px — from a rule written when it had a fixed width and the
 *    row wrapped at ~768px. It uses `flex: 1 1 0` with a max-width now, and
 *    14px, matching the coupon Apply button, which is the reference for
 *    every button in the funnel. Measured to sit on one line without
 *    clipping at 360 and 390 — the most important control on the page
 *    should not shrink on the device most people buy from.
 */
@media (max-width: 900px) {
	.single-product form.cart .single_add_to_cart_button {
		font-size: 14px;
	}
}

/*
 * 2. One gap under the title, not two. It was 6px on desktop and 2px on a
 *    phone purely because the tightening happened in a mobile-only block.
 */
.single-product div.product .product_title {
	margin-bottom: 4px;
}

/*
 * 3. The title gets its size back where there is room for it. At 19px it
 *    was the same size as the "You may also like" heading further down
 *    (18px) and lighter than it, so the page's own subject did not read as
 *    the most important thing on it. Phones keep 19px — that space is
 *    genuinely scarce there.
 */
@media (min-width: 768px) {
	.single-product div.product .product_title {
		font-size: 23px;
	}
}

/*
 * 4. The meta line resolved to 11.375px from an em value — the one size on
 *    the page not on the scale. Matched to the stock line and chips.
 */
.single-product div.product .product_meta {
	font-size: 11.5px;
}

/*
 * Let the loader actually disappear when the scroll is exhausted.
 *
 * The script sets `sentinel.hidden = true` when there is nothing left to
 * fetch, but `[hidden]` is only a UA rule of `display: none` — the flex
 * display above it wins, so the slot kept its 52px min-height and 18px
 * margin after it had finished its job. With the status line's reserved
 * 11px that left 87px of empty cream under the last row of products.
 */
.single-product .fv-alsolike-more[hidden] {
	display: none;
}

/* The status line reserves a line of space so an arriving message does not
   shift the grid. Once the loader is gone there is no message coming. */
.single-product .fv-alsolike-more[hidden] + .fv-alsolike-status {
	min-height: 0;
}

/*
 * The size guide table shares the page's reading width.
 *
 * It was the one block on the product page with no measure: 1326px at
 * 1440, against 630px for the description and 410px for its own note. Size
 * and measurement ended up at opposite ends of the screen with a metre of
 * nothing between them.
 */
@media (min-width: 768px) {
	/*
	 * Only when the guide has the page to itself — sharing a row it is
	 * already inside its track, and a second cap would leave a gap in it.
	 */
	.single-product div.product:not(:has(> .fv-product-desc)) .fv-sg-single .fv-sg-scroll {
		max-width: 640px;
	}
}

/*
 * Left-align the related/up-sell headings for real.
 *
 * The earlier rule lost: Storefront centres these with
 * `.storefront-full-width-content .related > h2:first-child` (0,3,1),
 * which outranks a plain `.related.products > h2` (0,2,1). This carries
 * the body class and the same pseudo-class so it wins outright.
 */
.single-product .related.products > h2:first-child,
.single-product .up-sells > h2:first-child,
.single-product .cross-sells > h2:first-child {
	text-align: left;
}

/*
 * The description block shares the size guide's width.
 *
 * On a 1440px screen the spec list was 545px wide under a heading and rule
 * that ran the full 1345px, so the hairlines under each row stopped a
 * third of the way across and the section read as a table that had failed
 * to finish drawing. The size guide directly above it sits at 640px; the
 * two now line up as one column of content rather than two different
 * measures stacked.
 *
 * `ch` still governs on narrow screens, where it is doing real work.
 */
/*
 * No cap on the description's inner blocks. Sharing a row it is bounded by
 * its grid track; alone it fills the page, and the section frame around it
 * ends at the same edge as its content either way.
 */

/*
 * Section frames match the content they frame.
 *
 * `.fv-product-desc` carries a 1px top rule and was 1345px wide on a 1440
 * screen, over a spec list of 640px — the divider introduced the section
 * across three times the width of anything in it, which is what read as
 * broken. The size guide above had the same split: a full-width heading
 * over a 640px table.
 *
 * Both sections are now the width of their own content, so the product page
 * below the summary is one 640px column instead of full-width chrome
 * wrapped around narrow blocks.
 */
/*
 * A section on its own takes the whole row: the grid rule gives it
 * `grid-column: 1 / -1` and nothing narrows it, so heading, top rule and
 * content all end together at the page edge.
 */

/* ── Size guide and description share a row on desktop ─────────────
 * Both are direct children of the product grid, which already runs two
 * tracks for the gallery and summary above them. Placing one in each track
 * puts the two reference blocks side by side instead of stacked, which on
 * a wide screen is a whole screenful of scrolling saved.
 *
 * The `:has()` guard is what makes the single case work: the pairing only
 * applies when BOTH sections are actually present. A product whose
 * category has no size guide — or one with no description — keeps the
 * full-width rule below and spans the page on its own.
 *
 * Browsers without `:has()` simply fall through to the stacked layout,
 * which is what the page did before.
 */
@media (min-width: 768px) {
	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-sg-single {
		grid-column: 1;
	}

	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-product-desc {
		grid-column: 2;
	}

	/*
	 * Paired, each block is bounded by its own column, so the 640px cap
	 * that keeps a lone section readable would only leave a gap inside the
	 * track. The description also loses its top rule here — side by side
	 * the two headings already read as separate columns, and the rule was
	 * dividing nothing.
	 */
	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-sg-single,
	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-product-desc {
		max-width: none;
	}

	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-product-desc {
		border-top: 0;
		margin-top: 20px;
		/*
		 * A wider gutter than the grid's own 24px.
		 *
		 * That 24px is the right distance between the photograph and the
		 * summary, because a picture and a block of text separate on their
		 * own. These two are both dense left-aligned text with hairline
		 * rules, and at 24px they read as one table that has lost its
		 * middle. The extra padding takes the gutter to ~52px, which is
		 * where they stop competing.
		 */
		padding-left: 28px;
	}

	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-product-desc .fv-product-desc-body,
	.single-product div.product:has(> .fv-sg-single):has(> .fv-product-desc) > .fv-product-desc .fv-spec {
		max-width: none;
	}
}

/* ── Gallery arrows ────────────────────────────────────────────────
 * FlexSlider's own markup is a list with the words "Previous" and "Next"
 * in it, which Storefront leaves to an icon font. Both are replaced here:
 * the text is taken out of the flow (kept for screen readers) and the
 * chevron is drawn in CSS, so it stays crisp at any density and needs no
 * font or image to load.
 *
 * They are on screen from the start at both breakpoints. The gallery used
 * to auto-advance on phones to show that more photographs existed, and the
 * arrows only appeared once it had finished; now that nothing moves on its
 * own, the arrows are the only thing saying there is more, so they cannot
 * wait for a hover that a phone never sends.
 *
 * Resting opacity keeps them quiet over the photograph — findable, not
 * prominent. Desktop still firms them up on hover; a phone simply gets the
 * resting state, which is legible against the blur behind it.
 */

.single-product div.product .images {
	position: relative;
}

.single-product div.product .images .flex-direction-nav {
	display: block;
	margin: 0;
	padding: 0;
	list-style: none;
}

.single-product div.product .images .flex-direction-nav a {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	z-index: 3;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border: 1px solid rgba(255, 255, 255, 0.55);
	border-radius: var(--fv-radius-md);
	/*
	 * Barely there. The photograph is the thing being sold; the arrow only
	 * has to be findable, not prominent. A light blur does the legibility
	 * work that a solid fill would otherwise have to, so the picture still
	 * reads straight through the control.
	 */
	background: rgba(255, 255, 255, 0.22);
	-webkit-backdrop-filter: blur(7px);
	backdrop-filter: blur(7px);
	box-shadow: none;
	color: var(--fv-ink);
	/* The label stays in the accessibility tree; only the glyph shows. */
	font-size: 0;
	text-indent: 0;
	text-decoration: none;
	opacity: 0.5;
	pointer-events: auto;
	transition: opacity 0.18s ease, border-color 0.15s ease,
		color 0.15s ease, background 0.15s ease;
}

/* Chevron, drawn from two borders. */
.single-product div.product .images .flex-direction-nav a::before {
	content: '';
	width: 7px;
	height: 7px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
}

.single-product div.product .images .flex-direction-nav .flex-prev {
	left: 12px;
}

.single-product div.product .images .flex-direction-nav .flex-prev::before {
	transform: rotate(135deg);
	margin-left: 3px;
}

.single-product div.product .images .flex-direction-nav .flex-next {
	right: 12px;
}

.single-product div.product .images .flex-direction-nav .flex-next::before {
	transform: rotate(-45deg);
	margin-right: 3px;
}

.single-product div.product .images .flex-direction-nav a:hover {
	border-color: var(--fv-accent);
	color: var(--fv-accent);
	background: rgba(255, 255, 255, 0.6);
}

.single-product div.product .images .flex-direction-nav a:focus-visible {
	outline: 2px solid var(--fv-accent);
	outline-offset: 2px;
}

/*
 * Both sides are always on screen. At an end FlexSlider marks the arrow
 * `flex-disabled`; it dims further and stops responding, but stays put —
 * a control that vanishes and reappears as you page through is harder to
 * aim at than one that is simply unavailable.
 */
.single-product div.product .images .flex-direction-nav a.flex-disabled {
	opacity: 0.22;
	pointer-events: none;
}

/* A gallery with one image has nothing to page through (see gallery.js). */
.single-product div.product .images.fv-gallery-single .flex-direction-nav {
	display: none;
}

/*
 * On a phone the arrows are the only way through the gallery — there is no
 * hover to reveal anything and nothing advances on its own — so they get a
 * finger-sized target. The control itself stays small, because it sits on
 * top of the photograph; the hit area is grown past it with a transparent
 * `::after`, which costs no pixels. (`::before` is the chevron, and both are
 * out of flow, so neither disturbs the other.)
 */
@media (max-width: 767px) {
	.single-product div.product .images .flex-direction-nav a {
		width: 34px;
		height: 34px;
	}

	.single-product div.product .images .flex-direction-nav a::after {
		content: '';
		position: absolute;
		top: 50%;
		left: 50%;
		width: 44px;
		height: 44px;
		transform: translate(-50%, -50%);
	}
}

/* ── When they firm up ─────────────────────────────────────────── */

@media (min-width: 768px) {
	/* Anywhere over the photograph brings them up a little. */
	.single-product div.product .images:hover .flex-direction-nav a {
		opacity: 0.75;
	}

	/* Full strength only under the cursor. */
	.single-product div.product .images .flex-direction-nav a:hover,
	.single-product div.product .images .flex-direction-nav a:focus-visible {
		opacity: 1;
	}

	/*
	 * An end stop stays dim through all of that. Listed after the two rules
	 * above because it matches them on specificity — without the order it
	 * would lose the moment the pointer entered the gallery.
	 */
	.single-product div.product .images:hover .flex-direction-nav a.flex-disabled,
	.single-product div.product .images .flex-direction-nav a.flex-disabled:hover {
		opacity: 0.25;
	}
}

@media (prefers-reduced-motion: reduce) {
	.single-product div.product .images .flex-direction-nav a {
		transition: none;
	}
}

/* ══════════════════════════════════════════════════════════════
   CARD ADD TO BAG — see inc/card-add-to-cart.php
   ══════════════════════════════════════════════════════════════ */

/*
 * The price used to be the card's last element and carried the lower
 * breathing room itself. With a button beneath it that padding becomes a gap
 * in the middle of the card, so it is handed to the button instead — and
 * only on cards that have one, which is why the selector is scoped to a
 * sibling rather than applied to every price.
 */
.woocommerce ul.products li.product .price:has(+ .fv-card-atc) {
	padding-bottom: 8px;
}

.woocommerce ul.products li.product .fv-card-atc {
	/* `margin: auto` on the price already pins the pair to the card's foot. */
	margin: 0 12px 12px;
	padding: 0 10px;
	min-height: 34px;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	border: 0;
	border-radius: var(--fv-radius-md);
	background: var(--fv-accent);
	color: #fff;
	font-family: var(--fv-font);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	cursor: pointer;
	line-height: 1;
	transition: background 0.15s ease, opacity 0.15s ease;
	-webkit-tap-highlight-color: transparent;
}

.woocommerce ul.products li.product .fv-card-atc:hover {
	background: var(--fv-accent-hover);
}

.woocommerce ul.products li.product .fv-card-atc:focus-visible {
	outline: 2px solid var(--fv-ink);
	outline-offset: 2px;
}

/*
 * Sold out keeps the button rather than losing it, so a row of cards has one
 * footer line throughout — but it is plainly not a thing to press.
 */
.woocommerce ul.products li.product .fv-card-atc.is-out {
	background: var(--fv-muted);
	color: var(--fv-ink-3);
	cursor: not-allowed;
}

/* The spinner slot is empty until the request is in flight. */
.fv-card-atc-sp {
	display: none;
	width: 13px;
	height: 13px;
	border: 2px solid rgba(255, 255, 255, 0.45);
	border-top-color: #fff;
	border-radius: 50%;
	animation: fv-cd-spin 0.6s linear infinite;
}

.fv-card-atc.is-busy {
	opacity: 0.8;
	cursor: progress;
}

.fv-card-atc.is-busy .fv-card-atc-sp {
	display: block;
}

@keyframes fv-cd-spin {
	to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
	.fv-card-atc-sp {
		animation-duration: 1.6s;
	}
}

/* ── The dialog ────────────────────────────────────────────────── */

.fv-cd {
	position: fixed;
	inset: 0;
	/*
	 * Above the sticky header and the mobile bottom bar both. The header
	 * sits at 1000; a dialog that a header overlaps is a dialog nobody can
	 * close on a phone.
	 */
	z-index: 100000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 16px;
}

body.fv-cd-open {
	overflow: hidden;
}

.fv-cd-veil {
	position: absolute;
	inset: 0;
	background: rgba(18, 14, 10, 0.55);
	-webkit-backdrop-filter: blur(2px);
	backdrop-filter: blur(2px);
	opacity: 0;
	transition: opacity 0.18s ease;
}

.fv-cd.is-in .fv-cd-veil {
	opacity: 1;
}

.fv-cd-card {
	position: relative;
	width: 100%;
	max-width: min(360px, 100%);
	max-height: 100%;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	padding: 22px 20px 18px;
	border-radius: var(--fv-radius-xl);
	background: var(--fv-surface);
	box-shadow: 0 24px 60px rgba(18, 14, 10, 0.3);
	opacity: 0;
	transform: translateY(14px) scale(0.97);
	transition: opacity 0.2s ease, transform 0.22s cubic-bezier(0.2, 0.8, 0.3, 1);
}

.fv-cd.is-in .fv-cd-card {
	opacity: 1;
	transform: none;
}

.fv-cd-card:focus {
	outline: none;
}

@media (prefers-reduced-motion: reduce) {
	.fv-cd-veil,
	.fv-cd-card {
		transition-duration: 0.01ms;
	}

	.fv-cd-card {
		transform: none;
	}
}

.fv-cd-x {
	position: absolute;
	top: 8px;
	right: 8px;
	width: 30px;
	height: 30px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	border-radius: var(--fv-radius-full);
	background: transparent;
	color: var(--fv-ink-3);
	cursor: pointer;
	transition: background 0.15s ease, color 0.15s ease;
}

.fv-cd-x:hover {
	background: var(--fv-muted);
	color: var(--fv-ink);
}

.fv-cd-x svg {
	width: 15px;
	height: 15px;
}

/* ── Confirmation ──────────────────────────────────────────────── */

.fv-cd-head {
	display: flex;
	align-items: center;
	gap: 9px;
	margin-bottom: 14px;
	padding-right: 26px;
}

.fv-cd-tick {
	flex: none;
	width: 22px;
	height: 22px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: var(--fv-radius-full);
	background: var(--fv-good);
	color: #fff;
}

.fv-cd-tick svg {
	width: 12px;
	height: 12px;
}

.fv-cd-lede {
	margin: 0;
	font-size: 14px;
	font-weight: 700;
	color: var(--fv-ink);
	line-height: 1.3;
}

.fv-cd-item {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	padding: 12px;
	border-radius: var(--fv-radius-lg);
	background: var(--fv-cream);
}

.fv-cd-pick .fv-cd-item {
	margin-right: 26px;
}

.fv-cd-thumb {
	flex: none;
	width: 62px;
	height: 62px;
	object-fit: cover;
	border-radius: var(--fv-radius-md);
	background: var(--fv-surface);
	display: block;
}

.fv-cd-item-t {
	min-width: 0;
	flex: 1;
}

.fv-cd-name {
	margin: 0;
	font-size: 12.5px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--fv-ink);
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.fv-cd-meta {
	margin: 3px 0 0;
	font-family: var(--fv-mono);
	font-size: 10.5px;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--fv-ink-3);
}

.fv-cd-price {
	margin: 5px 0 0;
	font-family: var(--fv-mono);
	font-size: 13.5px;
	font-weight: 800;
	color: var(--fv-hot);
	line-height: 1.3;
}

/* A price range or a struck-through original arrives as WooCommerce markup. */
.fv-cd-price del {
	color: var(--fv-ink-3);
	font-size: 11px;
	font-weight: 400;
	margin-right: 5px;
}

.fv-cd-price ins {
	text-decoration: none;
	background: transparent;
	color: inherit;
}

.fv-cd-acts {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 8px;
	margin-top: 14px;
}

.fv-cd-btn {
	min-height: 42px;
	display: flex;
	align-items: center;
	justify-content: center;
	/*
	 * Tight enough for "Continue Shopping" to hold one line beside "Start
	 * Checkout" in a 360px dialog. At 12px with wider tracking it wrapped,
	 * which left the two buttons different heights and the pair looking
	 * like a mistake.
	 */
	padding: 0 8px;
	border: 1px solid transparent;
	border-radius: var(--fv-radius-md);
	font-family: var(--fv-font);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.03em;
	text-transform: uppercase;
	white-space: nowrap;
	text-decoration: none;
	cursor: pointer;
	text-align: center;
	line-height: 1.2;
	transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}

.fv-cd-btn.is-wide {
	width: 100%;
	margin-top: 16px;
}

.fv-cd-btn.is-ghost {
	background: transparent;
	border-color: var(--fv-line-2);
	color: var(--fv-ink-2);
}

.fv-cd-btn.is-ghost:hover {
	border-color: var(--fv-ink-3);
	color: var(--fv-ink);
}

.fv-cd-btn.is-go {
	background: var(--fv-accent);
	color: #fff;
}

.fv-cd-btn.is-go:hover {
	background: var(--fv-accent-hover);
	color: #fff;
}

.fv-cd-btn:disabled {
	background: var(--fv-ink-soft);
	color: #fff;
	cursor: not-allowed;
}

.fv-cd-btn.is-busy {
	opacity: 0.75;
	cursor: progress;
}

.fv-cd-btn:focus-visible {
	outline: 2px solid var(--fv-ink);
	outline-offset: 2px;
}

/* ── Size chooser ──────────────────────────────────────────────── */

.fv-cd-attr {
	margin-top: 16px;
}

.fv-cd-attr-l {
	margin: 0 0 7px;
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--fv-ink-3);
}

.fv-cd-opts {
	display: flex;
	flex-wrap: wrap;
	gap: 7px;
}

.fv-cd-opt {
	min-width: 44px;
	min-height: 36px;
	padding: 0 12px;
	border: 1px solid var(--fv-line-2);
	border-radius: var(--fv-radius-md);
	background: var(--fv-surface);
	color: var(--fv-ink);
	font-family: var(--fv-font);
	font-size: 12px;
	font-weight: 600;
	cursor: pointer;
	transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.fv-cd-opt:hover:not(:disabled) {
	border-color: var(--fv-ink-3);
}

.fv-cd-opt.is-on {
	background: var(--fv-ink);
	border-color: var(--fv-ink);
	color: #fff;
}

/*
 * An option no in-stock variation can satisfy is struck through rather than
 * removed: which sizes exist is information, and a list that silently
 * shortens as you choose leaves the customer wondering what they missed.
 */
.fv-cd-opt:disabled {
	color: var(--fv-ink-soft);
	border-color: var(--fv-line);
	background: var(--fv-muted);
	cursor: not-allowed;
	text-decoration: line-through;
}

.fv-cd-opt:focus-visible {
	outline: 2px solid var(--fv-ink);
	outline-offset: 2px;
}

.fv-cd-err {
	margin: 12px 0 0;
	padding: 9px 11px;
	border-radius: var(--fv-radius-md);
	background: var(--fv-accent-soft);
	color: var(--fv-hot-ink);
	font-size: 11.5px;
	line-height: 1.45;
}

.fv-cd-err.is-solo {
	margin-top: 4px;
	padding-right: 26px;
	background: transparent;
	font-size: 13px;
}

@media (max-width: 420px) {
	.fv-cd {
		/* Bottom sheet on a phone: reachable by thumb, not floating mid-screen. */
		align-items: flex-end;
		padding: 0;
	}

	.fv-cd-card {
		max-width: 100%;
		max-height: 88vh;
		border-radius: var(--fv-radius-xl) var(--fv-radius-xl) 0 0;
		padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
		transform: translateY(24px);
	}
}
