/* ==========================================================================
   ULTRA PEP LABS - PDP REFRESH  (TopOpti, 2026-09-15)
   --------------------------------------------------------------------------
   Standalone stylesheet. Loaded from the child theme functions.php as
   'upl-pdp-refresh', with 'chld_thm_cfg_child' as a dependency so it always
   cascades after style.css.

   To disable: comment out the wp_enqueue_style call in functions.php, or
   rename this file. Nothing in style.css was changed.

   Goals:
   1. Bring the PDP layout in line with the reference site's centred, balanced
      two-column hero (theirs: 1240px container, 567px square gallery, 513px
      buy box, 56px gutter). Ours was full-bleed at 1905px with an 853px
      gallery upscaled from a 700px source.
   2. Restore type hierarchy. Our product title was 20px, smaller than its own
      24px price. Theirs is 52px.
   3. Replace the reference site's leftover earthy palette (olive #6B8C47,
      brown #6B6353, beige #D9CEBA, green-tinted shadows) with the Ultra Pep
      navy family. The previous developer changed --ink to navy and left the
      rest, which is why eyebrows render olive and trust text renders brown.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. PALETTE. Navy family, replacing the inherited earth tones.
   -------------------------------------------------------------------------- */
:root {
	--ink:        #002a8f;              /* brand navy, unchanged */
	--ink-2:      #001c5e;              /* deeper navy - was referenced but never defined */
	--ink-soft:   #1a47a8;              /* hover / secondary navy */
	--accent-deep:#1552c7;              /* eyebrows + small caps. WAS olive #6B8C47 */
	--muted:      #5a6478;              /* body secondary text. WAS brown #6B6353 */
	--line:       #d8dfea;              /* light borders. WAS beige #D9CEBA */
	--line-dark:  rgba(255, 255, 255, .14);
	--gr:         #f0f5fa;              /* section wash, unchanged */
	--ok:         #17935a;              /* in-stock green, cleaned up from olive */
	--ok-bg:      rgba(23, 147, 90, .09);
	--ok-bd:      rgba(23, 147, 90, .24);
	--shadow-nav: 0 20px 44px -28px rgba(0, 28, 94, .45);
	--shadow-lg:  0 24px 48px -30px rgba(0, 28, 94, .50);
	--pdp-max:    1240px;
}

/* Re-tint the shadows that were mixed from the old green ink. */
.pdp-specs           { box-shadow: var(--shadow-nav); }
.nlx-coa .coa-card   { box-shadow: var(--shadow-lg); }

/* --------------------------------------------------------------------------
   2. PDP HERO. Centred grid replacing the collapsed float layout.
      The wrapper had height:0 because both children were floated.
   -------------------------------------------------------------------------- */
.single-product div#content .ast-container {
	max-width: 100%;                    /* fixes the stray `//1340px` C-comment, which is not valid CSS */
	margin: 0;
	padding: 0;
}

.single_product_upper_wrapper {
	display: grid;
	grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
	gap: 56px;
	align-items: start;
	max-width: var(--pdp-max);
	margin: 0 auto;
	padding: 34px 24px 0;
}

.single_product_upper_wrapper > .woocommerce-product-gallery,
.single_product_upper_wrapper > .summary.entry-summary {
	float: none !important;
	width: auto !important;
	max-width: none;
	margin: 0 !important;
	padding: 0;
}

/* --------------------------------------------------------------------------
   3. GALLERY. Frame matches the artwork: 4:5, capped so the 700px source is
      never upscaled, framed to match the card language used elsewhere.

   2026-09-18: was `aspect-ratio: 1/1`. That was correct for the original
   930x1526 renders, which were far taller than the frame and had to be cropped
   to something sane. The re-exported artwork is 1122x1402 - exactly 4:5, the
   same ratio as the catalogue card - and is a composed shot: cropping it square
   cut the cap off the top of the vial and the base off the bottom.

   With the frame at 4:5 the `object-fit: cover` below crops nothing at all, so
   the image fills its container exactly on every breakpoint. `cover` is kept
   deliberately rather than swapped for `contain`: if a non-4:5 image is ever
   uploaded, cover fills the frame, where contain would letterbox it against the
   grey wash. Do not reintroduce a fixed square here.
   -------------------------------------------------------------------------- */
.woocommerce-product-gallery {
	position: relative;
	max-width: 560px;
}
/* Astra sets an explicit width with higher specificity, so this needs to win. */
.single_product_upper_wrapper > .woocommerce-product-gallery { max-width: 560px !important; }

.woocommerce-product-gallery__wrapper > a,
.woocommerce-product-gallery__image > a { display: block; height: 100%; }

.woocommerce-product-gallery__wrapper {
	aspect-ratio: 4 / 5;
	overflow: hidden;
	border-radius: var(--r-lg);
	background: var(--gr);
	margin: 0;
}

.woocommerce-product-gallery__wrapper a,
.woocommerce-product-gallery__wrapper div,
.woocommerce-product-gallery__image {
	height: 100%;
}

.woocommerce-product-gallery__wrapper img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	border-radius: var(--r-lg);
}

/* Zoom affordance: keep it, but make it a proper control instead of a bare emoji. */
.woocommerce-product-gallery__trigger {
	top: 14px !important;
	right: 14px !important;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	background: #fff !important;
	box-shadow: 0 4px 14px -4px rgba(0, 28, 94, .35);
	display: flex !important;
	align-items: center;
	justify-content: center;
	font-size: 0;
	z-index: 5;
}
.woocommerce-product-gallery__trigger img { width: 18px; height: 18px; }

/* --------------------------------------------------------------------------
   4. BUY BOX. Type hierarchy, matching the reference site's rhythm.
   -------------------------------------------------------------------------- */
.summary.entry-summary > .woocommerce-breadcrumb {
	font-size: 12.5px;
	color: var(--muted);
	margin: 0 0 14px;
	letter-spacing: .01em;
}
.summary.entry-summary > .woocommerce-breadcrumb a { color: var(--muted); }
.summary.entry-summary > .woocommerce-breadcrumb a:hover { color: var(--ink); }

.single-product .summary h1.product_title {
	font-size: clamp(30px, 3.6vw, 48px) !important;
	line-height: 1.06;
	font-weight: 600;
	letter-spacing: -.03em;
	color: var(--ink);
	margin: 0 0 12px;
}

.single-product .summary .woocommerce-product-rating {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 0 0 18px;
}
.single-product .summary .woocommerce-product-rating .woocommerce-review-link {
	font-size: 13.5px;
	color: var(--muted);
}
/* Stars were gold on an otherwise strictly navy palette. */
.single-product .summary .star-rating span::before,
ul.products .star-rating span::before { color: var(--ink); }
.single-product .summary .star-rating::before,
ul.products .star-rating::before { color: var(--line); }

.single-product .summary p.price,
.single-product .summary .price {
	font-size: 30px !important;
	font-weight: 600;
	color: var(--ink);
	letter-spacing: -.02em;
	margin: 0 0 22px;
	padding-top: 18px;
	border-top: 1px solid var(--line);
}
.single-product .summary .price del { font-size: 20px; opacity: .5; font-weight: 400; }
.single-product .summary .price ins { text-decoration: none; }

/* --------------------------------------------------------------------------
   5. VARIATION FORM + ADD TO CART
   -------------------------------------------------------------------------- */
.single-product .variations th.label,
.single-product .variations label {
	font-size: 11.5px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .1em;
	color: var(--muted);
}

.single-product .variations select {
	width: 100%;
	min-height: 52px;
	padding: 12px 16px;
	border: 1px solid var(--line);
	border-radius: 14px;
	background-color: #fff;
	font-size: 15px;
	color: var(--ink);
	cursor: pointer;
	transition: border-color .18s, box-shadow .18s;
}
.single-product .variations select:hover  { border-color: var(--ink-soft); }
.single-product .variations select:focus  {
	outline: none;
	border-color: var(--ink);
	box-shadow: 0 0 0 3px rgba(0, 42, 143, .12);
}

.single-product .woocommerce-variation-price { margin-bottom: 14px; }
.single-product .woocommerce-variation-price .price { border-top: 0; padding-top: 0; margin: 0; }

.single-product form.cart {
	display: flex;
	flex-wrap: wrap;
	align-items: stretch;
	gap: 12px;
	margin-bottom: 8px;
}
.single-product form.cart .quantity { flex: 0 0 auto; }
.single-product form.cart .quantity .qty,
.single-product form.cart .quantity input[type=number] { height: 54px; }

.single-product .single_add_to_cart_button {
	flex: 1 1 220px;
	min-height: 54px;
	padding: 14px 28px !important;
	font-size: 15.5px !important;
	font-weight: 600 !important;
	letter-spacing: .01em;
	border-radius: 999px !important;
	background-color: var(--ink) !important;
	color: #fff !important;
	border: 1px solid var(--ink);
	transition: background-color .2s, transform .15s cubic-bezier(.2,.7,.3,1), box-shadow .2s;
}
.single-product .single_add_to_cart_button:hover:not(.disabled) {
	background-color: var(--ink-2) !important;
	transform: translateY(-1px);
	box-shadow: 0 12px 24px -14px rgba(0, 28, 94, .6);
}
/* The disabled state was opacity:.5, which read as "broken" rather than
   "choose an option first". Make it legible and explicit. */
.single-product .single_add_to_cart_button.disabled,
.single-product .single_add_to_cart_button:disabled {
	opacity: 1 !important;
	background-color: #e8edf6 !important;
	color: var(--muted) !important;
	border-color: var(--line);
	cursor: not-allowed;
	box-shadow: none;
	transform: none;
}

/* For variable products the quantity + button live inside this div, not
   directly inside form.cart, so the flex context has to be set here too. */
.single-product .woocommerce-variation-add-to-cart {
	width: 100%;
	display: flex;
	flex-wrap: wrap;
	align-items: stretch;
	gap: 12px;
}
.single-product .variations_form .reset_variations {
	font-size: 12.5px;
	color: var(--muted);
	text-transform: lowercase;
}

/* --------------------------------------------------------------------------
   6. TRUST ROW / COA LINK. Slate instead of brown, cleaner in-stock chip.
   -------------------------------------------------------------------------- */
.pdp-trust {
	gap: 10px 18px;
	margin-top: 22px;
	padding-top: 20px;
	border-top: 1px solid var(--line);
}
.pdp-trust-item { color: var(--muted); font-size: 13px; }
.pdp-trust-item svg { color: var(--ok); flex: 0 0 auto; }

span.pdp-trust-item.pdp-instock {
	background: var(--ok-bg);
	border: 1px solid var(--ok-bd);
	color: #0f6b42;
	font-weight: 600;
	padding: 5px 12px;
	border-radius: 999px;
}
.pdp-instock .dot { background: var(--ok); }

.pdp-coa-link {
	color: var(--ink);
	border-color: var(--line);
	font-weight: 600;
}
.pdp-coa-link:hover {
	border-color: var(--ink);
	background-color: var(--gr);
	color: var(--ink);
}

/* Shop-loop stock chip carried the same olive tint. */
.pcard-stock {
	background: var(--ok-bg);
	border: 1px solid var(--ok-bd);
	color: #0f6b42;
}
.pcard-stock .dot { background: var(--ok); box-shadow: 0 0 6px rgba(23, 147, 90, .55); }

/* --------------------------------------------------------------------------
   7. SHOP GRID. Bottom-align the CTAs. Cards stretched to equal height but
      inner content was not pinned, leaving a 65px stagger inside each row.
   -------------------------------------------------------------------------- */
ul.products li.product {
	display: flex;
	flex-direction: column;
}
ul.products li.product > *:last-child { margin-top: auto; }
ul.products li.product .price { margin-bottom: 12px; }

/* --------------------------------------------------------------------------
   8. LOWER PDP SECTIONS
   -------------------------------------------------------------------------- */
/* The fluid size was dropped from the live file, leaving these at a flat 24px. */
.pdp-sec-title {
	font-size: clamp(26px, 3.4vw, 38px) !important;
	line-height: 1.12;
}
.pdp-sec-eyebrow {
	color: var(--accent-deep);
	font-weight: 600;
}

.teamjuih_vertical_tabs {
	max-width: var(--pdp-max);
	margin-left: auto;
	margin-right: auto;
}

.pdp-specs { padding: 8px 28px; }
.spec-row  { grid-template-columns: 220px 1fr; }
.spec-k    { color: rgba(255, 255, 255, .72); }

/* Storage and handling section, added 2026-09-18.
   The DARK panel is .pdp-specs only; this paragraph sits above it on the light
   page background, so it takes the same secondary colour as the rest of the
   body copy. It was briefly white here, which on #eaf0fb was invisible. */
.pdp-storage-lead {
	max-width: 62ch;
	margin: 0 0 22px;
	color: var(--muted);
	font-size: 17px;
	line-height: 1.6;
}
@media (max-width: 767px) {
	.pdp-storage-lead { font-size: 16px; margin-bottom: 18px; }
}

.coa-doc-rows > div,
.coa-doc-top { border-bottom-color: var(--line); }
.coa-doc-top .mono { color: var(--muted); }

.btn-dark:hover { background: var(--ink-2); }

ol.commentlist li { border-color: var(--line) !important; }

/* --------------------------------------------------------------------------
   9. STICKY ADD TO CART
   -------------------------------------------------------------------------- */
.ast-sticky-add-to-cart.bottom {
	border-top: 1px solid var(--line);
	box-shadow: 0 -8px 24px -18px rgba(0, 28, 94, .5);
}
.ast-sticky-add-to-cart a.ast-sticky-add-to-cart-link,
.ast-sticky-add-to-cart .button {
	border-radius: 999px !important;
	font-weight: 600;
	background-color: var(--ink) !important;
	color: #fff !important;
}

/* ==========================================================================
   10. RESPONSIVE
   ========================================================================== */

/* Laptop: keep the two columns but let the gutter breathe less. */
@media (max-width: 1280px) {
	.single_product_upper_wrapper { gap: 44px; padding-left: 32px; padding-right: 32px; }
	.teamjuih_vertical_tabs { padding-left: 8px; padding-right: 8px; }
}

/* Tablet: single column, gallery centred and capped so it never dominates. */
@media (max-width: 991px) {
	.single_product_upper_wrapper {
		grid-template-columns: minmax(0, 1fr);
		gap: 30px;
		padding: 24px 28px 0;
	}
	.woocommerce-product-gallery,
	.single_product_upper_wrapper > .woocommerce-product-gallery {
		max-width: 460px !important;
		margin: 0 auto !important;
	}
	.single-product .summary h1.product_title { font-size: clamp(28px, 5vw, 38px) !important; }
	.single_product_below_wrapper { padding-left: 28px; padding-right: 28px; }
}

/* Phone. This is where the client reported the most trouble. */
@media (max-width: 767px) {
	.single_product_upper_wrapper {
		gap: 24px;
		padding: 16px 20px 0;
	}
	.woocommerce-product-gallery,
	.single_product_upper_wrapper > .woocommerce-product-gallery { max-width: 100% !important; }

	.summary.entry-summary > .woocommerce-breadcrumb { font-size: 12px; margin-bottom: 10px; }

	/* Section rail: "04 Reviews (3)" wrapped to a second line. Single row,
	   swipeable, scrollbar hidden. */
	ul.teamjuih_tabs {
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		padding-bottom: 6px;
	}
	ul.teamjuih_tabs::-webkit-scrollbar { display: none; }
	ul.teamjuih_tabs li { white-space: nowrap; flex: 0 0 auto; }
	aside.pdp-rail { padding: 22px 0 14px; }

	.single-product .woocommerce-variation-add-to-cart { gap: 10px; }

	.single-product .summary h1.product_title {
		font-size: clamp(26px, 7vw, 32px) !important;
		margin-bottom: 10px;
	}
	.single-product .summary p.price,
	.single-product .summary .price {
		font-size: 26px !important;
		padding-top: 14px;
		margin-bottom: 18px;
	}

	/* Full-width CTA, quantity on its own row. Previously a 117x36 button. */
	.single-product form.cart { gap: 10px; }
	.single-product form.cart .quantity { flex: 1 1 100%; }
	.single-product form.cart .quantity .qty,
	.single-product form.cart .quantity input[type=number] { width: 100%; }
	.single-product .single_add_to_cart_button { flex: 1 1 100%; width: 100%; }

	.pdp-trust { margin-top: 18px; padding-top: 16px; gap: 9px 14px; }
	.pdp-trust-item { font-size: 12.5px; }
	.pdp-coa-link { width: 100%; justify-content: center; margin-top: 18px; }

	/* The fixed 70px sticky add-to-cart bar was sitting over page content. */
	.single-product .single_product_below_wrapper { padding-bottom: 94px; }

	/* Specs read better as stacked label/value than as two cramped columns. */
	.spec-row {
		grid-template-columns: 1fr;
		gap: 4px;
		padding: 13px 0;
	}
	.pdp-specs { padding: 6px 20px; }

	.pdp-sec-title { font-size: clamp(24px, 6.4vw, 30px) !important; }
}

/* Small phones. */
@media (max-width: 400px) {
	.single_product_upper_wrapper { padding-left: 16px; padding-right: 16px; }
	.single_product_below_wrapper { padding-left: 16px; padding-right: 16px; }
	.woocommerce-product-gallery__trigger { width: 34px; height: 34px; top: 10px !important; right: 10px !important; }
}

/* --------------------------------------------------------------------------
   11. VARIATION SWATCHES  (added 2026-09-16)
   Works with the free "Variation Swatches for WooCommerce" plugin. Prices are
   injected by pdp-refresh's companion script pdp-swatch-price.js, which reads
   WooCommerce's own data-product_variations blob.

   Selectors are prefixed with body.single-product because this site serves CSS
   inside @layer, where a layered !important beats an unlayered one. Without the
   prefix the plugin's own styling wins.
   -------------------------------------------------------------------------- */
body.single-product ul.variable-items-wrapper {
	display: grid !important;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 10px;
	margin: 0 0 4px !important;
	padding: 0 !important;
	list-style: none;
}

body.single-product ul.variable-items-wrapper li.variable-item {
	margin: 0 !important;
	width: auto !important;
	height: auto !important;
	min-width: 0 !important;
	box-shadow: none !important;
	border: 1px solid var(--line) !important;
	border-radius: 14px !important;
	background: #fff !important;
	padding: 13px 8px !important;
	cursor: pointer;
	transition: border-color .16s, background-color .16s;
}
body.single-product ul.variable-items-wrapper li.variable-item:hover:not(.disabled) {
	border-color: var(--ink-soft) !important;
}
body.single-product ul.variable-items-wrapper li.variable-item.selected {
	background: var(--ink) !important;
	border-color: var(--ink) !important;
	box-shadow: none !important;
}
body.single-product ul.variable-items-wrapper li.variable-item.disabled {
	opacity: .4 !important;
	cursor: not-allowed;
	text-decoration: line-through;
}

body.single-product ul.variable-items-wrapper li.variable-item .variable-item-contents {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
	height: auto !important;
}
body.single-product ul.variable-items-wrapper li.variable-item .variable-item-span {
	font-size: 16px !important;
	font-weight: 600 !important;
	line-height: 1.1 !important;
	color: var(--ink) !important;
	padding: 0 !important;
	height: auto !important;
	width: auto !important;
}
body.single-product ul.variable-items-wrapper li.variable-item .upl-swatch-price,
body.single-product ul.variable-items-wrapper li.variable-item .upl-swatch-price .woocommerce-Price-amount {
	font-size: 13px !important;
	font-weight: 400 !important;
	line-height: 1.2;
	color: var(--muted) !important;
	font-variant-numeric: tabular-nums;
}
body.single-product ul.variable-items-wrapper li.variable-item.selected .variable-item-span {
	color: #fff !important;
}
body.single-product ul.variable-items-wrapper li.variable-item.selected .upl-swatch-price,
body.single-product ul.variable-items-wrapper li.variable-item.selected .upl-swatch-price .woocommerce-Price-amount {
	color: rgba(255, 255, 255, .78) !important;
}

/* Price inside the CTA, as the reference does. */
body.single-product .single_add_to_cart_button .upl-btn-price {
	margin-left: 10px;
	font-weight: 600;
	opacity: .88;
}
body.single-product .single_add_to_cart_button .upl-btn-price .woocommerce-Price-amount {
	color: #fff !important;
}

/* With the price on both the swatch and the CTA, these two become noise.
   Only hidden when the script actually ran (it adds .upl-price-shim-on), so a
   script failure never leaves the page with no price at all. */
body.single-product .variations_form.upl-price-shim-on .woo-selected-variation-item-name,
body.single-product .variations_form.upl-price-shim-on .woocommerce-variation-price {
	display: none !important;
}
body.single-product .variations_form.upl-price-shim-on .woocommerce-variation.single_variation {
	margin-bottom: 0;
}

@media (max-width: 480px) {
	body.single-product ul.variable-items-wrapper {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* --------------------------------------------------------------------------
   12. PDP HEADER + BUY ROW REFINEMENTS  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* Stars before the review count. Astra sets order:2 on .review-rating, which
   pushes the stars AFTER the link; the reference site has stars first. */
body.single-product .summary .woocommerce-product-rating .review-rating {
	order: 0 !important;
}
body.single-product .summary .woocommerce-product-rating .woocommerce-review-link {
	order: 1 !important;
}

/* "FROM" prefix on the variable-product price (see upl_variable_price_from). */
body.single-product .summary .price .upl-price-from {
	display: block;
	font-size: 11.5px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .12em;
	color: var(--muted);
	margin-bottom: 2px;
}

/* Quantity and CTA share one row at every width, as the reference does.
   This intentionally overrides the earlier mobile rule that stacked them. */
body.single-product form.cart .quantity,
body.single-product .woocommerce-variation-add-to-cart .quantity {
	flex: 0 0 auto !important;
	width: auto !important;
}
body.single-product .single_add_to_cart_button {
	flex: 1 1 auto !important;
	width: auto !important;
}

@media (max-width: 767px) {
	body.single-product form.cart .quantity,
	body.single-product .woocommerce-variation-add-to-cart .quantity {
		flex: 0 0 auto !important;
		width: auto !important;
	}
	body.single-product form.cart .quantity .qty,
	body.single-product form.cart .quantity input[type=number] {
		width: 104px !important;
	}
	body.single-product .single_add_to_cart_button {
		flex: 1 1 auto !important;
		width: auto !important;
		min-width: 0;
		padding-left: 18px !important;
		padding-right: 18px !important;
	}
	body.single-product .single_add_to_cart_button .upl-btn-price {
		margin-left: 8px;
	}
}

/* COA link was sitting tight against the stock chip above and the safe-checkout
   panel below. Give it room, and a slightly larger tap target. */
body.single-product .pdp-coa-link {
	margin-top: 26px !important;
	margin-bottom: 26px !important;
	padding: 13px 22px !important;
}

@media (max-width: 767px) {
	body.single-product .pdp-coa-link {
		margin-top: 22px !important;
		margin-bottom: 22px !important;
		padding: 14px 18px !important;
	}
}

/* The "From" prefix also lands on shop-loop cards (the reference does the same).
   Style it there too so it reads as intentional rather than stray text. */
ul.products li.product .price .upl-price-from {
	display: block;
	font-size: 10.5px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .12em;
	color: var(--muted);
	margin-bottom: 1px;
}

/* Quantity stepper: border moved off the input and onto the wrapper so the
   control can take the gallery's radius (--r-lg, 24px) as one rounded pill. */
body.single-product form.cart .quantity,
body.single-product form.cart .quantity.buttons_added {
	display: inline-flex !important;
	align-items: stretch;
	border: 1px solid var(--line) !important;
	border-radius: var(--r-lg) !important;
	overflow: hidden;
	background: #fff;
	height: 54px;
}
body.single-product form.cart .quantity .minus,
body.single-product form.cart .quantity .plus {
	border: 0 !important;
	border-radius: 0 !important;
	background: transparent !important;
	color: var(--ink) !important;
	width: 42px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 18px;
	line-height: 1;
	text-decoration: none;
	transition: background-color .15s;
}
body.single-product form.cart .quantity .minus:hover,
body.single-product form.cart .quantity .plus:hover {
	background: var(--gr) !important;
}
body.single-product form.cart .quantity input.qty,
body.single-product form.cart .quantity input[type=number] {
	border: 0 !important;
	border-radius: 0 !important;
	height: auto !important;
	width: 46px !important;
	min-width: 0 !important;
	padding: 0 !important;
	text-align: center;
	background: transparent !important;
	color: var(--ink) !important;
	font-weight: 600;
	font-size: 15px;
}

@media (max-width: 767px) {
	body.single-product form.cart .quantity,
	body.single-product form.cart .quantity.buttons_added { height: 52px; }
	body.single-product form.cart .quantity .minus,
	body.single-product form.cart .quantity .plus { width: 36px; }
	body.single-product form.cart .quantity input.qty,
	body.single-product form.cart .quantity input[type=number] { width: 38px !important; }
}

/* Keep quantity + CTA on one row. flex-basis:0 rather than auto - with auto the
   button demands its content width, which pushed the total past the container
   at 390px and wrapped it onto its own line. With 0 it simply takes whatever is
   left after the stepper. */
body.single-product .single_add_to_cart_button {
	flex: 1 1 0 !important;
	min-width: 0 !important;
}
body.single-product form.cart,
body.single-product .woocommerce-variation-add-to-cart {
	flex-wrap: nowrap !important;
}

@media (max-width: 767px) {
	body.single-product .single_add_to_cart_button {
		flex: 1 1 0 !important;
		min-width: 0 !important;
		white-space: nowrap;
	}
}

/* --------------------------------------------------------------------------
   13. RULE REMOVAL + STICKY GALLERY  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* The buy column had three horizontal rules (above the price, under the
   variations table, above the trust row). The reference uses spacing alone.
   Replaced with margins. */
body.single-product .summary p.price,
body.single-product .summary .price {
	border-top: 0 !important;
	padding-top: 0 !important;
}
body.single-product .summary > p.price {
	margin: 0 0 26px !important;
}
body.single-product .summary table.variations {
	border-bottom: 0 !important;
	margin-bottom: 0 !important;
}
body.single-product .summary .variations td,
body.single-product .summary .variations th {
	border: 0 !important;
	padding-left: 0 !important;
	padding-right: 0 !important;
}
body.single-product .pdp-trust {
	border-top: 0 !important;
	margin-top: 28px !important;
	padding-top: 0 !important;
}
body.single-product .woocommerce-product-rating { margin-bottom: 22px !important; }
body.single-product .summary .variations_form { margin-top: 4px; }
body.single-product .woocommerce-variation-add-to-cart { margin-top: 22px !important; }

/* WooCommerce wraps each variation's price_html in <span class="price">, so the
   .summary .price rules above were leaking into the swatch and CTA prices and
   drawing stray borders there. Neutralise the nested case. */
body.single-product .variable-item .price,
body.single-product .single_add_to_cart_button .price {
	font-size: inherit !important;
	font-weight: inherit !important;
	margin: 0 !important;
	padding: 0 !important;
	border: 0 !important;
	color: inherit !important;
}

/* Sticky gallery on desktop.
   Two things are required and both are easy to miss:
   1. The gallery is a grid item, so it must STRETCH to the row height,
      otherwise its containing block is its own box and there is no travel.
      Sticky then goes on the inner wrapper, not the item.
   2. body had `overflow-x: hidden`, which makes body a scroll container and
      silently disables sticky. `clip` clips identically WITHOUT creating one.
   Range is naturally limited to (buy column height - image height); it grows
   as products gain variants and copy. */
@media (min-width: 992px) {
	body.single-product { overflow-x: clip !important; }

	body.single-product .single_product_upper_wrapper > .woocommerce-product-gallery {
		position: static !important;
		align-self: stretch !important;
	}
	body.single-product .woocommerce-product-gallery__wrapper {
		position: sticky !important;
		top: 24px !important;
	}
}

/* --------------------------------------------------------------------------
   14. BUY-BOX RHYTHM + CONTROL FIXES  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* Rating row. WooCommerce's .star-rating box is wider than its glyphs, and
   Astra's wrapper added more width on top, leaving a ~25px hole before the
   review link. Box tightened to its natural 5.4em and the row set to a real
   gap so stars and count sit together as on the reference. */
body.single-product .summary .woocommerce-product-rating {
	display: flex !important;
	align-items: center !important;
	gap: 7px !important;
	margin-bottom: 24px !important;
}
body.single-product .summary .woocommerce-product-rating .review-rating {
	width: auto !important;
	display: flex !important;
	align-items: center !important;
	line-height: 1 !important;
}
body.single-product .summary .star-rating {
	width: 5.4em !important;
	letter-spacing: normal !important;
	margin: 0 !important;
	float: none !important;
	line-height: 1 !important;
}
body.single-product .summary .woocommerce-review-link {
	line-height: 1 !important;
	font-size: 13.5px !important;
	position: relative;
	top: 1px;
}

/* Quantity stepper. Astra ships margin-right:-38px on .minus and a matching
   margin-left:38px on the input; they cancel out numerically but leave the
   plus short of the pill's right edge, so its hover painted a square floating
   inside the rounded pill. Margins zeroed and the three children sized
   explicitly so they fill the pill edge to edge. */
body.single-product form.cart .quantity > * {
	margin: 0 !important;
}
body.single-product form.cart .quantity .minus,
body.single-product form.cart .quantity .plus {
	flex: 0 0 40px !important;
	width: 40px !important;
	align-self: stretch;
	height: auto !important;
}
body.single-product form.cart .quantity input.qty,
body.single-product form.cart .quantity input[type=number] {
	flex: 0 0 44px !important;
	width: 44px !important;
	min-width: 0 !important;
	align-self: stretch;
	height: auto !important;
}
body.single-product form.cart .quantity {
	width: auto !important;
	min-width: 0 !important;
}

/* Vertical rhythm, tuned against the reference:
   title 12 / rating 24 / price 26 / label 10 / swatches 26 / trust 26 / coa 26. */
body.single-product .summary table.variations,
body.single-product .summary table.variations tbody,
body.single-product .summary table.variations tr {
	display: block !important;
}
body.single-product .summary table.variations th.label {
	display: block !important;
	margin-bottom: 10px !important;
	padding-bottom: 0 !important;
}
body.single-product .summary table.variations td.value {
	display: block !important;
}
body.single-product .woocommerce-variation-add-to-cart {
	margin-top: 18px !important;
}
body.single-product .summary .woocommerce-variation.single_variation {
	margin: 0 !important;
}
body.single-product .pdp-trust {
	margin-top: 26px !important;
}

/* WooCommerce's "Clear" reset link. The reference has no equivalent, and with
   swatches it adds ~44px of dead space for an action that is redundant once a
   size is chosen. Display-only; delete this rule to bring it back. */
body.single-product .summary .variations_form .reset_variations {
	display: none !important;
}

/* --------------------------------------------------------------------------
   15. VARIABLE-PRODUCT LAYOUT, TOOLTIP, SECTION BAND  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* form.cart must NOT be a flex container on variable products. Section 5 made
   it flex for the simple-product qty+button row, but on a variable product the
   same form wraps the whole variations table, so its children became flex items
   sized to their content. The result: the swatch grid and buy row were
   shrink-to-fit and JUMPED WIDTH (270px -> 324px at 390px) the moment a size
   was chosen, because the shim adds the price to the button and widens it.
   Flex belongs only on the add-to-cart row. */
body.single-product form.cart.variations_form {
	display: block !important;
	gap: 0 !important;
	flex-wrap: initial !important;
}
body.single-product form.cart.variations_form > * {
	width: 100% !important;
}
body.single-product .woocommerce-variation-add-to-cart {
	display: flex !important;
	flex-wrap: nowrap !important;
	align-items: stretch !important;
	gap: 12px !important;
	width: 100% !important;
}

/* Swatch tooltip. It repeats the label that is already printed on the button,
   so it is pure noise - most obviously on touch, where it fires on tap.
   The pseudo-element is the plugin's tooltip; the native `title` attribute is
   stripped in pdp-swatch-price.js because CSS cannot reach it. */
body.single-product .variable-item::before,
body.single-product .variable-item::after,
body.single-product .variable-item .variable-item-contents::before,
body.single-product .variable-item .variable-item-contents::after {
	content: none !important;
	display: none !important;
}

/* Lower PDP section reads as its own band, as on the reference (they use a warm
   beige against white; ours is the navy-family equivalent). The rail sits on a
   slightly deeper tone again so it reads as a distinct panel. */
body.single-product .single_product_below_wrapper {
	background-color: #eaf1fa !important;
}
body.single-product aside.pdp-rail {
	background-color: #dde7f5 !important;
	border-radius: 16px;
	padding: 20px 18px !important;
}
body.single-product ul.teamjuih_tabs li a {
	color: #42506b !important;
}
body.single-product li.teamjuh_active_tab a {
	color: var(--ink) !important;
	font-weight: 600 !important;
}

/* --------------------------------------------------------------------------
   16. SECTION RAIL: INK PANEL + SCROLL FADE  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* Ink panel, white text, tighter than the first pass. */
body.single-product aside.pdp-rail {
	background-color: var(--ink) !important;
	border-radius: 16px;
	padding: 14px 16px !important;
}
body.single-product ul.teamjuih_tabs {
	gap: 14px !important;
}
body.single-product ul.teamjuih_tabs li a {
	color: rgba(255, 255, 255, .74) !important;
}
body.single-product li.teamjuh_active_tab a {
	color: #fff !important;
	font-weight: 600 !important;
}
body.single-product span.rail-n {
	color: rgba(255, 255, 255, .45) !important;
}
/* The active marker was var(--ink) - invisible on an ink panel. */
@media (min-width: 968px) {
	body.single-product li.teamjuh_active_tab { border-left-color: #fff !important; }
}
@media (max-width: 968px) {
	body.single-product li.teamjuh_active_tab { border-bottom-color: #fff !important; }
}

/* Edge fade so the horizontal scroll is discoverable on phones.
   Classes are toggled by railFade() in pdp-swatch-price.js based on real
   scroll position, so the fade only appears on the side that can actually
   move. The rail only overflows below ~568px; a permanent fade would imply
   scrollability that is not there at wider sizes.
   Uses mask-image, so it works regardless of the panel colour; browsers
   without support simply get no fade. */
body.single-product ul.teamjuih_tabs.upl-fade-right {
	-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent 100%);
	        mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent 100%);
}
body.single-product ul.teamjuih_tabs.upl-fade-left {
	-webkit-mask-image: linear-gradient(to right, transparent 0, #000 30px);
	        mask-image: linear-gradient(to right, transparent 0, #000 30px);
}
body.single-product ul.teamjuih_tabs.upl-fade-left.upl-fade-right {
	-webkit-mask-image: linear-gradient(to right, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
	        mask-image: linear-gradient(to right, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
}

/* --------------------------------------------------------------------------
   17. COA BADGE ON PRODUCT CARDS + QUICK-VIEW MODAL  (added 2026-09-16)
   -------------------------------------------------------------------------- */

/* Badge sits over the top-right of the card image.
   NOTE: the badge is rendered as a SIBLING BEFORE Astra's
   .astra-shop-thumbnail-wrap (it has to be, or it would nest inside the
   product <a> and swallow the click), so its positioning context is
   li.product, not the image. li.product carries 20px padding + 1px border,
   which puts the image 21px in from the card edge - hence the offsets below
   are 21px + the intended inset. If the card padding is ever changed, these
   need to move with it. */
.astra-shop-thumbnail-wrap {
	position: relative;
}

a.upl-coa-badge {
	position: absolute;
	top: 34px;   /* 21px card inset + 13px */
	right: 34px;
	z-index: 6;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 30px;
	padding: 0 13px;
	border-radius: 999px;
	background: rgba(255, 255, 255, .96);
	border: 1px solid rgba(0, 42, 143, .22);
	color: var(--ink);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	text-decoration: none;
	line-height: 1;
	box-shadow: 0 5px 14px rgba(0, 28, 94, .12);
	transition: background-color .16s, border-color .16s, transform .16s;
}
a.upl-coa-badge:hover {
	background: #fff;
	border-color: var(--ink);
	color: var(--ink);
	transform: translateY(-1px);
}
a.upl-coa-badge:focus-visible {
	outline: 2px solid var(--ink);
	outline-offset: 2px;
}

@media (max-width: 767px) {
	a.upl-coa-badge {
		top: 31px;   /* 21px card inset + 10px */
		right: 31px;
		height: 27px;
		padding: 0 11px;
		font-size: 10px;
	}
}

/* ---- COA quick-view modal ----------------------------------------------
   Layout follows the reference lightbox: a fixed header strip with the
   document identity on the left and the open/download/close controls on the
   right, a grey document well, and a light footer that works as a small buy
   FORM - pick a size, then commit with the Add to cart button. */
.upl-coa-modal[hidden] { display: none; }
.upl-coa-modal {
	position: fixed;
	inset: 0;
	z-index: 100001;      /* above Astra's cart drawer at 100000 */
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
}
.upl-coa-modal__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0, 20, 60, .55);
	backdrop-filter: blur(2px);
}
.upl-coa-modal__panel {
	position: relative;
	display: flex;
	flex-direction: column;
	width: min(965px, 100%);
	height: min(88vh, 900px);
	background: #fff;
	border-radius: 16px;
	overflow: hidden;
	box-shadow: 0 30px 70px -28px rgba(0, 28, 94, .6);
}

/* Header strip */
.upl-coa-modal__head {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	min-height: 70px;
	padding: 14px 16px;
	border-bottom: 1px solid #efefef;
}
.upl-coa-modal__heading { min-width: 0; }
.upl-coa-modal__title {
	margin: 0;
	font-size: 16px;
	font-weight: 600;
	color: var(--ink);
	line-height: 1.25;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.upl-coa-modal__sub {
	margin: 2px 0 0;
	font-size: 12.5px;
	color: var(--muted);
	line-height: 1.3;
}
.upl-coa-modal__actions {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 8px;
}
.upl-coa-modal__open,
.upl-coa-modal__dl {
	display: inline-flex;
	align-items: center;
	height: 36px;
	padding: 0 14px;
	border: 1px solid var(--line);
	border-radius: 999px;
	background: #fff;
	color: var(--ink) !important;
	font-size: 13px;
	font-weight: 600;
	text-decoration: none;
	white-space: nowrap;
	transition: background-color .15s, border-color .15s;
}
.upl-coa-modal__open:hover,
.upl-coa-modal__dl:hover {
	background: var(--gr);
	border-color: var(--ink);
	color: var(--ink) !important;
}
/* Flex-centred with an SVG glyph. The previous "&times;" text character has
   uneven vertical metrics and sat visibly high inside the circle. */
.upl-coa-modal__close {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	padding: 0;
	border: 1px solid var(--line);
	border-radius: 50%;
	background: #fff;
	color: var(--ink);
	line-height: 0;
	cursor: pointer;
	transition: background-color .15s, border-color .15s;
}
.upl-coa-modal__close svg { display: block; }
.upl-coa-modal__close:hover { background: var(--gr); border-color: var(--ink); }

/* Document well */
.upl-coa-modal__body { flex: 1 1 auto; min-height: 0; background: #f3f3f8; }
.upl-coa-modal__body iframe {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
}

/* ---- Buy row (footer form) ---- */
.upl-coa-modal__buyrow {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 22px;
	padding: 14px 16px;
	background: #fafafa;
	border-top: 1px solid #efefef;
}

.upl-coa-modal__ident {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 12px;
	min-width: 0;
}
.upl-coa-modal__thumb {
	flex: 0 0 auto;
	width: 56px;
	height: 56px;
	border-radius: 10px;
	border: 1px solid var(--line);
	background: #fff;
	/* Product shots are portrait vials on white - cover would slice the vial
	   in half, so fit the whole thing inside the square. */
	object-fit: contain;
	padding: 3px;
}
.upl-coa-modal__thumb[hidden] { display: none; }
.upl-coa-modal__pname {
	margin: 0;
	font-size: 14.5px;
	font-weight: 600;
	color: var(--ink);
	line-height: 1.25;
}
.upl-coa-modal__note {
	margin: 2px 0 0;
	font-size: 12px;
	color: var(--muted);
	line-height: 1.3;
}

.upl-coa-modal__pick {
	flex: 1 1 auto;
	min-width: 0;
	align-self: flex-end;
}
.upl-coa-modal__pick[hidden] { display: none; }
.upl-coa-modal__picklabel {
	margin: 0 0 6px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--muted);
}
.upl-coa-modal__buy {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.upl-coa-modal__submit {
	flex: 0 0 auto;
	display: flex;
	align-items: flex-end;
	justify-content: flex-end;
	flex-wrap: wrap;
	gap: 10px;
	/* Bottom-aligned with .upl-coa-modal__pick so the stepper and the CTA sit
	   on the same line as the size chips rather than floating above them. */
	align-self: flex-end;
}
.upl-coa-modal__submit[hidden] { display: none; }

/* Quantity stepper, same pill as the PDP: the border lives on the wrapper so
   the whole control reads as one rounded control rather than three. */
.upl-coa-modal__qty {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: stretch;
	height: 44px;
	border: 1px solid var(--line);
	border-radius: 999px;
	overflow: hidden;
	background: #fff;
}
body .upl-coa-qty__step {
	flex: 0 0 auto;
	width: 38px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: var(--ink);
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
	transition: background-color .15s;
}
body .upl-coa-qty__step:hover { background: var(--gr); }
/* The theme's own input rules are inside an @layer, and for !important
   declarations the cascade REVERSES layer order - so a layered !important beats
   an unlayered one. A `body` prefix plus !important is what actually wins here;
   without it the input keeps its 230px theme width and blows the row apart. */
body .upl-coa-qty__input {
	width: 44px !important;
	min-width: 0 !important;
	max-width: none !important;
	height: auto !important;
	margin: 0 !important;
	padding: 0 !important;
	border: 0 !important;
	border-radius: 0 !important;
	box-shadow: none !important;
	background: transparent !important;
	color: var(--ink) !important;
	text-align: center;
	font-size: 15px;
	font-weight: 600;
	line-height: 1;
	-moz-appearance: textfield;
	appearance: textfield;
}
body .upl-coa-qty__input:focus { outline: 0; box-shadow: none !important; }
.upl-coa-qty__input::-webkit-outer-spin-button,
.upl-coa-qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.upl-coa-modal__add {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 168px;
	min-height: 44px;
	padding: 10px 26px;
	border: 1px solid var(--ink);
	border-radius: 999px;
	background: var(--ink);
	color: #fff;
	font-size: 14.5px;
	font-weight: 600;
	cursor: pointer;
	transition: background-color .16s, border-color .16s, opacity .16s;
}
.upl-coa-modal__add:hover:not(:disabled) { background: var(--ink-2); border-color: var(--ink-2); }
/* Price inside the CTA, matching .upl-btn-price on the PDP. */
.upl-coa-modal__btnprice {
	margin-left: 10px;
	font-weight: 600;
	opacity: .88;
}
.upl-coa-modal__btnprice:empty { display: none; }
.upl-coa-modal__add.is-busy { opacity: .6; cursor: progress; }
.upl-coa-modal__add.is-added { background: var(--ink-2); border-color: var(--ink-2); }
.upl-coa-modal__status {
	order: -1;
	flex: 1 1 100%;
	margin: 0;
	min-height: 16px;
	font-size: 12px;
	color: var(--muted);
	text-align: right;
}

/* ---- Option chip ----
   Base style is the MODAL form chip: neutral until selected. The sheet
   overrides it below into a tap-to-add tile. */
button.upl-coa-buy {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1px;
	min-width: 84px;
	padding: 8px 16px;
	border: 1px solid var(--line);
	border-radius: 12px;
	background: #fff;
	color: var(--ink);
	cursor: pointer;
	transition: background-color .16s, border-color .16s, color .16s, opacity .16s;
}
button.upl-coa-buy:hover:not(:disabled) { border-color: var(--ink); }
button.upl-coa-buy:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
button.upl-coa-buy .upl-coa-buy__label { font-size: 14px; font-weight: 600; line-height: 1.15; }
button.upl-coa-buy .upl-coa-buy__price { font-size: 11.5px; opacity: .72; line-height: 1.15; }

button.upl-coa-buy.is-selected {
	background: var(--ink);
	border-color: var(--ink);
	color: #fff;
}
button.upl-coa-buy.is-busy { opacity: .6; cursor: progress; }
button.upl-coa-buy.is-added { background: var(--ink-2); border-color: var(--ink-2); color: #fff; }
button.upl-coa-buy:disabled,
button.upl-coa-buy.is-oos {
	background: #eef1f7;
	border-color: var(--line);
	color: var(--muted);
	cursor: not-allowed;
}

body.upl-coa-open { overflow: hidden; }

/* The buy row is a three-column desk layout; below ~900px it stacks. */
@media (max-width: 900px) {
	.upl-coa-modal__buyrow {
		flex-wrap: wrap;
		gap: 12px 16px;
	}
	.upl-coa-modal__pick { flex: 1 1 100%; order: 3; }
	.upl-coa-modal__submit { margin-left: auto; align-items: flex-end; }
}

@media (max-width: 767px) {
	.upl-coa-modal { padding: 0; }
	.upl-coa-modal__panel {
		width: 100%;
		height: 100%;
		border-radius: 0;
	}
	.upl-coa-modal__head { padding: 12px 14px; min-height: 0; }
	.upl-coa-modal__title { font-size: 15px; }
	/* Two text links plus a close button do not fit a phone header. Keep
	   "Open in a new tab" - the useful one on mobile, where the inline PDF
	   viewer is poor - and drop Download; the new tab offers it anyway. */
	.upl-coa-modal__dl { display: none; }
	.upl-coa-modal__open { height: 34px; padding: 0 12px; font-size: 12.5px; }

	.upl-coa-modal__buyrow { padding: 12px 14px 14px; }
	.upl-coa-modal__ident { flex: 1 1 auto; }
	.upl-coa-modal__thumb { width: 44px; height: 44px; }
	.upl-coa-modal__submit {
		flex: 1 1 100%;
		order: 4;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		gap: 12px;
		flex-wrap: wrap;
	}
	.upl-coa-modal__qty { flex: 0 0 auto; }
	.upl-coa-modal__add { flex: 1 1 0; min-width: 0; padding: 10px 14px; }
	.upl-coa-modal__status { text-align: left; }
	.upl-coa-modal__buy button.upl-coa-buy { flex: 1 1 0; min-width: 0; }
}

/* ---- Size sheet (card "Select options") --------------------------------
   Bottom sheet, matching the reference: grab handle, product identity row,
   then a grid of sizes. Unlike the modal this is NOT a form - one tap adds
   that variation straight to the cart, which is what the label promises. */
.upl-sheet[hidden] { display: none; }
.upl-sheet {
	position: fixed;
	inset: 0;
	z-index: 100001;
	display: flex;
	align-items: flex-end;
	justify-content: center;
}
.upl-sheet__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0, 20, 60, .55);
	backdrop-filter: blur(2px);
}
.upl-sheet__panel {
	position: relative;
	width: min(520px, 100%);
	max-height: 88vh;
	overflow-y: auto;
	background: #fff;
	border-radius: 32px 32px 0 0;
	padding: 16px 26px calc(30px + env(safe-area-inset-bottom, 0px));
	box-shadow: 0 -18px 60px -24px rgba(0, 28, 94, .55);
}
.upl-sheet__grab {
	display: block;
	width: 42px;
	height: 4px;
	margin: 0 auto 14px;
	border-radius: 999px;
	background: rgba(0, 42, 143, .18);
}
.upl-sheet__head {
	display: flex;
	align-items: center;
	gap: 14px;
}
.upl-sheet__media {
	flex: 0 0 auto;
	display: block;
	width: 60px;
	height: 60px;
	overflow: hidden;
	border-radius: 10px;
	border: 1px solid var(--line);
	background: #fff;
}
.upl-sheet__media[hidden] { display: none; }
.upl-sheet__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
	padding: 3px;
}
.upl-sheet__ident { min-width: 0; }
.upl-sheet__title {
	margin: 0;
	font-size: 18px;
	font-weight: 600;
	color: var(--ink);
	line-height: 1.2;
}
.upl-sheet__from {
	margin: 3px 0 0;
	font-size: 13.5px;
	color: var(--muted);
	line-height: 1.2;
}
.upl-sheet__close {
	flex: 0 0 auto;
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 38px;
	height: 38px;
	padding: 0;
	border: 1px solid var(--line);
	border-radius: 50%;
	background: #fff;
	color: var(--ink);
	line-height: 0;
	cursor: pointer;
	transition: background-color .15s, border-color .15s;
}
.upl-sheet__close:hover { background: var(--gr); border-color: var(--ink); }
.upl-sheet__close svg { display: block; }

.upl-sheet__label {
	margin: 18px 0 10px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--muted);
}
.upl-sheet__buy {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 10px;
}
.upl-sheet__buy[hidden] { display: none; }

/* Tap-to-add tiles: bigger than the modal chip, and they read as targets
   rather than as an unselected form control. */
.upl-sheet__buy button.upl-coa-buy {
	width: 100%;
	min-width: 0;
	flex-direction: row;
	justify-content: space-between;
	gap: 10px;
	min-height: 54px;
	padding: 10px 18px;
	border-radius: 14px;
}
.upl-sheet__buy button.upl-coa-buy .upl-coa-buy__label { font-size: 15px; }
.upl-sheet__buy button.upl-coa-buy .upl-coa-buy__price { font-size: 14px; opacity: .78; }
/* A "+" after the price says one tap adds it, without a second line of copy.
   Only on rows that can actually be bought - an out-of-stock row reads
   "Out of stock" here. */
.upl-sheet__buy button.upl-coa-buy:not(:disabled) .upl-coa-buy__price::after {
	content: '+';
	margin-left: 9px;
	font-weight: 700;
	opacity: .6;
}
.upl-sheet__buy button.upl-coa-buy:hover:not(:disabled) {
	background: var(--ink);
	border-color: var(--ink);
	color: #fff;
}

.upl-sheet__status {
	margin: 12px 0 0;
	min-height: 18px;
	font-size: 13px;
	color: var(--muted);
	text-align: center;
}
.upl-sheet__status:empty { display: none; }

/* Card quick-add button keeps the stock WooCommerce button styling; only the
   busy state is ours. */
a.upl-quick-add.is-busy { opacity: .6; pointer-events: none; }

@media (max-width: 400px) {
	.upl-sheet__panel { padding-left: 18px; padding-right: 18px; }
}

/* --------------------------------------------------------------------------
   18. SCROLL-TO-TOP vs THE STICKY ADD-TO-CART BAR  (added 2026-09-16)

   On product pages Astra shows BOTH a sticky add-to-cart bar (fixed, full
   width, 61px tall on mobile, z-index 1200) and its scroll-to-top arrow
   (fixed, bottom:30px right:30px, z-index 9999). They appear at the same
   moment - both are revealed on scroll - and on a phone the arrow lands on
   top of the bar's "Select options" button, which is the primary conversion
   action on the page.

   The arrow wins that overlap on z-index (9999 vs 1200), so it is the arrow
   that has to go. Product pages only: the scroll-to-top still works
   everywhere else on the site, and on a product page the sticky bar already
   provides the "get back to the buy box" affordance it was serving.

   !important is required because Astra toggles this element's `display`
   inline from JavaScript as you scroll.

   To restore it and lift it clear of the bar instead, replace this rule with:
       body.single-product #ast-scroll-top { bottom: 100px !important; }
   -------------------------------------------------------------------------- */
body.single-product #ast-scroll-top {
	display: none !important;
}

/* --------------------------------------------------------------------------
   19. PRICE IN THE MOBILE STICKY BAR  (added 2026-09-16)

   Astra hides two separate things in the sticky add-to-cart bar below 544px:
   the TITLE WRAP (thumbnail + product name) and, separately, the PRICE
   (.ast-sticky-add-to-cart-action-price, which lives beside the button in
   .ast-sticky-add-to-cart-action-wrap, NOT inside the title wrap).

   That separation is what makes this easy: the price can come back on its own
   without dragging the thumbnail and product name back with it. The name is
   redundant on a product page anyway; the price is not, and a price next to
   the CTA is a well-established conversion aid.

   Layout is price hard left, button hard right, via margin-right:auto on the
   price rather than justify-content, so the button keeps its own width.

   Breakpoint matches Astra's own 544px exactly - above that Astra already
   shows the price and nothing here should apply.
   -------------------------------------------------------------------------- */
@media (max-width: 544px) {
	body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-content {
		width: 100%;
		align-items: center;
	}
	body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-action-wrap {
		flex: 1 1 auto;
		display: flex;
		align-items: center;
		gap: 12px;
		width: 100%;
	}
	/* !important: this is the declaration Astra hides it with. */
	body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-action-price {
		display: inline-flex !important;
		align-items: baseline;
		gap: 4px;
		margin-right: auto;
		font-size: 15px;
		font-weight: 700;
		line-height: 1.2;
		color: var(--ink);
		white-space: nowrap;
	}
	/* The "From" prefix our variable-price filter adds. Smaller and quieter,
	   so the number itself carries the weight. */
	body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-action-price .upl-price-from {
		font-size: 11px;
		font-weight: 600;
		letter-spacing: .06em;
		text-transform: uppercase;
		color: var(--muted);
	}
	body.single-product .ast-sticky-add-to-cart .single_link_to_cart_button {
		flex: 0 0 auto;
	}
}

/* --------------------------------------------------------------------------
   18. MOBILE: TWO CARDS ACROSS  (added 2026-09-17)

   Astra's Customizer has the shop archive on `mobile-columns-1`, and its rule
   at max-width 544px sets `grid-template-columns: repeat(1, minmax(0,1fr))`.
   That put the catalogue one card per screen on phones, which is nothing like
   the reference layout. The homepage had the same problem from the other
   direction: home-refresh.css carried its own single-column rule under 480px.

   Both are now two across at every phone width. This section owns BOTH grids
   deliberately - the archive and the homepage shortcode loop - so the two
   pages cannot drift apart again. The tidier long-term home for the archive
   half is the Astra Customizer setting (Appearance > Customize > WooCommerce >
   Product Catalog > mobile columns = 2); this override can come out if that
   is ever changed.

   Why !important: the card padding is set by Astra's inline Customizer CSS,
   and this site serves theme CSS inside an @layer, where for !important
   declarations the cascade REVERSES layer order. Measured: a plain rule at
   higher specificity (0,3,3 against their 0,2,2) still lost. Same pattern the
   cart/checkout overrides and the size sheet's quantity input needed.

   Measured at 390px: 159px cards, 133px of content inside them, both cards in
   a row the same height, CTAs aligned, no horizontal overflow.
   -------------------------------------------------------------------------- */

@media (max-width: 544px) {

	/* Shop archive + product category archives, AND the homepage's [products]
	   shortcode loop - which WooCommerce wraps in .woocommerce inside Astra's
	   .ast-container, so Astra's one-column rule catches it too even though
	   the homepage is not a woocommerce-page.

	   Specificity matters here and is the reason this rule is shaped the way
	   it is. Astra's is `.ast-container .woocommerce ul.products:not(...)` at
	   (0,4,1). The homepage rule in home-refresh.css is
	   `body.home .elementor-widget-shortcode ul.products` at (0,3,2) - it
	   LOSES, which is why the homepage stayed one-up when the 480px rule was
	   removed. These selectors are (0,5,2) and (0,4,2), so they win.

	   The :not(.elementor-grid) mirrors Astra's own selector, leaving
	   Elementor's product widgets - which bring their own grid - alone. */
	body.woocommerce .ast-container ul.products:not(.elementor-grid),
	body.woocommerce-page .ast-container ul.products:not(.elementor-grid),
	body.home .ast-container .woocommerce ul.products:not(.elementor-grid) {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 14px;
	}

	/* 20px of padding plus a 1px border took 42px out of a 159px card and
	   left 117px of content. 12px leaves 133px. */
	body.woocommerce ul.products li.product,
	body.woocommerce-page ul.products li.product,
	body.home ul.products li.product {
		padding: 12px !important;
	}

	/* The badge offsets are card-padding + inset, so they move with it:
	   was 21px + 10px, now 12px + 8px. Slightly smaller too - at full size it
	   covered a third of a 159px thumbnail. */
	body a.upl-coa-badge {
		top: 20px !important;
		right: 20px !important;
		height: 22px !important;
		padding: 0 8px !important;
		font-size: 9px !important;
		gap: 4px !important;
	}

	/* "Select options" has to stay on one line in 133px.

	   The card button is an `inline-block` <a> whose label was centred purely
	   by its own vertical padding, and whose line-height resolves to 12px -
	   the same as its font-size. Setting a fixed height and zeroing the
	   padding therefore pinned the text to the TOP of the button with 27px of
	   dead space under it. Centring has to be re-established explicitly, so
	   the button becomes a flex box: that centres the label on both axes
	   whatever the height, padding or line-height happen to be. Same
	   treatment as the hero CTAs in home-refresh.css section 4. */
	body.woocommerce ul.products li.product a.button,
	body.woocommerce-page ul.products li.product a.button,
	body.home ul.products li.product a.button {
		display: flex !important;
		align-items: center !important;
		justify-content: center !important;
		font-size: 12px !important;
		line-height: 1 !important;
		height: 36px !important;
		padding: 0 14px !important;
		text-align: center !important;
		white-space: nowrap;
		width: auto !important;
	}

	/* ---- Card footer: price LEFT, button RIGHT, on one row ----------------
	   Matching the reference card, where the price and the button share the
	   bottom line instead of the button sitting full-width underneath.

	   Astra puts the category, title, rating, price and button in one block
	   element (.astra-shop-summary-wrap) with no wrapper around the last two,
	   so there is nothing to make a flex row out of without adding markup.
	   A two-column grid does it without touching the template: everything
	   spans both columns as before, and only the price and the button are
	   pinned to column 1 and column 2, which lands them on the same row.

	   The block as a whole stays bottom-pinned by section 7's
	   `li.product > *:last-child { margin-top: auto }`, so the footer rows of
	   two cards in the same row still line up even when one card has a rating
	   and the other does not. */
	body.woocommerce .astra-shop-summary-wrap,
	body.woocommerce-page .astra-shop-summary-wrap,
	body.home .astra-shop-summary-wrap {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		align-items: center;
		column-gap: 8px;
		row-gap: 4px;
	}

	/* SAFETY NET, and it earned its place immediately.

	   The one-row footer only fits because the button says "Select" rather
	   than "Select options", and that short label comes from the PHP. The
	   stylesheet is a file, so it goes live instantly; the rendered HTML sits
	   behind WP Engine's page cache for up to 600s. In that window an
	   anonymous visitor gets NEW css against OLD markup: the full label in an
	   auto-width button takes 118px of a 133px row and crushes the price to
	   7px.

	   So the row layout is only applied to a card that actually carries the
	   short label. A card without it falls back to the previous stacked
	   layout, which was fine, and heals itself the moment the cache rolls.
	   Worth keeping permanently - it covers any future deploy where CSS and
	   markup land at different times. */
	body .astra-shop-summary-wrap:not(:has(.upl-cta-short)) {
		display: block;
	}

	body.woocommerce .astra-shop-summary-wrap > *,
	body.woocommerce-page .astra-shop-summary-wrap > *,
	body.home .astra-shop-summary-wrap > * {
		grid-column: 1 / -1;
	}

	body.woocommerce .astra-shop-summary-wrap > .price,
	body.woocommerce-page .astra-shop-summary-wrap > .price,
	body.home .astra-shop-summary-wrap > .price {
		grid-column: 1;
		justify-self: start;
		margin-bottom: 0 !important;   /* section 7 adds 12px under the price */
		margin-top: 6px;
	}

	body.woocommerce .astra-shop-summary-wrap > a.button,
	body.woocommerce-page .astra-shop-summary-wrap > a.button,
	body.home .astra-shop-summary-wrap > a.button {
		grid-column: 2;
		justify-self: end;
		margin: 6px 0 0 !important;
	}

	/* The amount carries the weight, as on the reference card. */
	body ul.products li.product .price .woocommerce-Price-amount {
		font-size: 16px;
		font-weight: 700;
		color: var(--ink);
		line-height: 1.15;
	}

	/* "From" becomes a small label stacked over the amount rather than a word
	   in front of it - at 159px there is no room for it on the same line, and
	   dropping it would misreport a variable product's price as fixed. */
	body ul.products li.product .price .upl-price-from {
		display: block;
		font-size: 9px;
		font-weight: 600;
		letter-spacing: .06em;
		text-transform: uppercase;
		color: var(--muted);
		line-height: 1.2;
	}

	/* Short button label (see upl_loop_quick_add in functions.php). */
	body ul.products li.product .upl-cta-full { display: none; }
	body ul.products li.product .upl-cta-short { display: inline; }

	/* ---- Hide star ratings on mobile cards (Ralph, 2026-09-17) -----------
	   Only 2 of the 12 products carry a rating, and WooCommerce omits the
	   element entirely on the rest rather than reserving space - so the two
	   rated cards run 21px taller than their neighbours and the row falls out
	   of step. At 159px that reads as a mistake rather than as information.

	   Hidden rather than removed: this is presentation only, the markup and
	   its schema data stay in the page, and the rating still shows on the
	   desktop grid and on the product page. Drop this rule to bring it back
	   once enough of the catalogue has reviews for the row to look even. */
	body ul.products li.product .review-rating { display: none; }

	/* ---- Hide the purity line on mobile cards (Ralph, 2026-09-17) --------
	   Same problem, same fix. `.pcard-spec` is on exactly ONE card of twelve
	   - BAC Water, "99.40%+ Purity" - and at 48px tall it left that row 48px
	   deeper than every other row on the page.

	   Note this one is real product data, not a widget: the field simply is
	   not filled in on the other eleven products. If the catalogue is ever
	   completed, delete this rule rather than leaving the data hidden - a
	   purity figure on every card is worth showing. It still renders on the
	   desktop grid and on the product page either way. */
	body ul.products li.product .pcard-spec { display: none; }
}

/* Above the mobile breakpoint the fuller wording is used. */
.upl-cta-short { display: none; }
.upl-cta-full { display: inline; }

/* --------------------------------------------------------------------------
   19. CARD ADDS: HOLD THE CART DRAWER BACK  (added 2026-09-17)

   Ralph: tapping a size on the size sheet should close the sheet, leave the
   cart drawer alone, and show the toast - nothing else.

   shop-quickview.js sets this class on <body> for about a second around a
   card add, then clicks Astra's own close button so the drawer's internal
   state and scroll lock are unwound properly rather than left half-open
   behind a mask. See suppressCartDrawer() there for why the underlying
   `added_to_cart` event is still fired (GA4 tracking rides on it).

   transition:none matters - without it the drawer animates out when the
   class is removed, which is the flash this is meant to avoid.
   -------------------------------------------------------------------------- */
body.upl-no-drawer #astra-mobile-cart-drawer,
body.upl-no-drawer .astra-cart-drawer,
body.upl-no-drawer .astra-mobile-cart-overlay,
body.upl-no-drawer .ast-mobile-popup-overlay {
	visibility: hidden !important;
	opacity: 0 !important;
	transition: none !important;
	pointer-events: none !important;
}

/* The toast is the one thing that should still appear. Guard it explicitly in
   case Astra ever parents it inside the drawer. */
body.upl-no-drawer .cart-toast {
	visibility: visible !important;
	opacity: 1 !important;
	pointer-events: auto !important;
}

/* --------------------------------------------------------------------------
   COA card: stop inherited capitalisation           (2026-09-18, TopOpti)
   --------------------------------------------------------------------------
   `.coa-doc-rows > div` inherits text-transform:capitalize from the legacy
   theme. That was harmless while every value was a single word ("Confirmed"),
   but the certificate data is sentences, and capitalize rendered them as
   "9.91 Mg Per Vial (10 Mg Labelled)" and "Certificate Of Analysis".
   Units in particular must not be capitalised: "Mg" is not milligrams.
   -------------------------------------------------------------------------- */
.coa-doc-rows > div,
.coa-doc-rows > div > span,
.coa-doc-rows > div > b,
.coa-tests .coa-test-k,
.coa-tests .coa-test-v,
.nlx-coa .coa-dl,
.coa-meta,
.coa-scope { text-transform: none; }

/* These two sit inside .coa-card, whose background is brand navy #002A8F, so
   they must NOT use --muted (#5a6478): that is the body-secondary colour for
   white backgrounds and lands at 2.0:1 on navy, against a 4.5:1 minimum. It was
   legible on the desktop mock only because that card was being read light-on-
   light. White at reduced alpha keeps the hierarchy and passes:
     .coa-meta   72% white on #002A8F = 6.9:1
     .coa-scope  62% white on #002A8F = 5.4:1
   The scope line carries its alpha in the colour rather than a separate
   opacity, so the two cannot compound into something unreadable again. */
.nlx-coa .coa-meta {
	margin: 18px 0 0;
	font-size: .82rem;
	color: rgba(255, 255, 255, .72);
	letter-spacing: .01em;
}

.nlx-coa .coa-scope {
	margin: 10px 0 0;
	font-size: .78rem;
	line-height: 1.55;
	color: rgba(255, 255, 255, .62);
}

/* --------------------------------------------------------------------------
   Sticky section rail: stop content landing underneath it
   --------------------------------------------------------------------------
   `aside.pdp-rail` is position:sticky, top:0, z-index:999, 58px tall. The
   sections it links to had no scroll-margin, so tapping "03 COA" scrolled the
   heading to y=0 and the rail then covered the first row of the card - on a
   phone that reads as a clipped, half-drawn line of text.
   -------------------------------------------------------------------------- */
.pdp-sec,
#sec-details,
#sec-coa {
	scroll-margin-top: 76px;
}

/* --------------------------------------------------------------------------
   Public COA index - [upl_coa_index] on /coas/   (2026-09-18, TopOpti)
   --------------------------------------------------------------------------
   Replaces six hand-built Elementor cards that all showed lot NLL-5525811,
   Northline's prefix, and linked to nothing. This grid is rendered from the
   products, so it cannot drift from what is actually certified.
   -------------------------------------------------------------------------- */
.upl-coa-index { max-width: var(--pdp-max); margin: 0 auto; }

/* ---- Page header + lede  (2026-09-22, TopOpti - Ralph) -------------------
   The page opened with no heading and a browser-default paragraph running the
   full width of the viewport, directly above a 1240px styled grid. Markup for
   the header comes from upl_coa_page_header() in functions.php.

   Type scale is the one already used for page titles on the cart and checkout
   (cart-checkout.css): clamp(30px, 3.4vw, 44px) / 600 / -.03em, with the same
   uppercase 12.5px eyebrow above it. Nothing new is invented here.

   The lede is the page's OWN first paragraph, which lives in post content, so
   it is reached positionally rather than by a class. `> p:first-of-type` is
   scoped to .upl-coa-page (added by upl_coa_body_class) and to the direct
   children of .entry-content, so it cannot reach a paragraph inside the
   certificate cards.
   -------------------------------------------------------------------------- */
/* Inter, to match /quality/ and the homepage.
   ------------------------------------------------------------------------
   The Inter rollout in home-refresh.css is scoped to `body.home` on purpose
   (section 2: "rolling this out site-wide ... wants its own review pass"),
   and /quality/ only gets Inter because Elementor's kit applies it there.
   /coas/ is neither, so every word on it was falling back to Arial - which is
   why the eyebrow still did not match after the sizes were right.

   Scoped to .entry-content, not to body: that keeps it off the header, the
   footer and the admin bar, so no icon font (dashicons, eicons, star) can be
   caught by the descendant selector. Inter is already declared on this page,
   so this costs no extra request. */
body.upl-coa-page .entry-content,
body.upl-coa-page .entry-content * {
	font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
}

/* The pale section wash, full bleed behind the header.
   #f0f5fa is --gr, the token this build already calls "section wash", and it
   is exactly the rgb(240, 245, 250) the banded sections on
   /shipping-protection/ use. Measured off that page rather than guessed -
   /quality/ looks like it should have one too (its header section carries a
   `bg-topo` class) but nothing anywhere defines .bg-topo, so that section
   renders transparent. Do not copy `bg-topo`; it does nothing. */
.upl-coa-head-band {
	background: var(--gr);
}

/* Two columns, matching the /quality/ page header (text left, image right,
   vertically centred, 60px above and 40px below). That 60px is also what was
   missing at the top of this page - the content began hard against the
   header. The image column is the 630x387 frame /quality/ uses, expressed as
   an aspect-ratio so it scales, with the same 20px radius. */
.upl-coa-head {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: center;
	gap: 40px;
	max-width: var(--pdp-max);
	margin: 0 auto;
	padding: 60px 0 40px;
}

.upl-coa-head--text-only { grid-template-columns: 1fr; }

.upl-coa-head__media img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 630 / 387;
	object-fit: cover;
	border-radius: 20px;
}

/* Stacks at Astra's mobile-header breakpoint, the same one the header phone
   number uses. Image first would push the heading below the fold, so the text
   column keeps its order and the image follows. */
@media (max-width: 921px) {
	.upl-coa-head {
		grid-template-columns: 1fr;
		gap: 28px;
		padding: 40px 0 32px;
	}
}

/* The card grid ended flush against the footer - zero gap, the last row of
   cards touching the black band. 64px is the rhythm unit this build already
   uses for section spacing (home-refresh.css section 3 sets 64px top and
   bottom on every .e-con-inner), so the page closes on the same beat as the
   rest of the site. Set on .entry-content rather than on the grid so it still
   holds if anything is ever added below the cards. */
body.upl-coa-page .entry-content {
	padding-bottom: 64px;
}

/* The eyebrow is the same outlined pill as "Testing & Verification" on
   /quality/ (Elementor icon-box 954b2b9), measured off that widget:
   1px navy hairline, 100px radius, 2px/10px padding, 12px uppercase 600 in
   --ink with NORMAL tracking - not the letter-spaced muted eyebrow used on
   the cart - and a 12px icon ahead of the text. inline-flex so the pill hugs
   its content; as a block <p> the border would run the full column.
   The 20px below it is the flex gap that separates that widget from the
   heading under it on /quality/. */
/* Selector carries .upl-coa-head__text deliberately. As a bare
   `.upl-coa-head__eyebrow` (0,1,0) its 20px bottom margin lost to a theme
   rule on `p` and computed 19.2px, putting the heading 0.8px too close.
   Qualifying it to (0,2,0) wins without needing !important. */
.upl-coa-head__text > .upl-coa-head__eyebrow {
	/* flex + fit-content, NOT inline-flex. As an inline-level box the pill
	   sits in a line box whose height comes from the parent's line-height,
	   so the space to the heading measured 19.2px rather than the 20px
	   margin - the gap was being taken from the line box, not the pill.
	   Block-level with a shrink-to-fit width keeps the pill hugging its
	   content and makes margin-bottom mean exactly what it says. */
	display: flex;
	width: fit-content;
	align-items: center;
	/* 4px, measured off the icon-box wrapper on /quality/ - not 6. */
	gap: 4px;
	margin: 0 0 20px;
	padding: 2px 10px;
	border: 1px solid var(--ink);
	border-radius: 100px;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.65;
	letter-spacing: normal;
	text-transform: uppercase;
	color: var(--ink);
}

.upl-coa-head__icon { flex: 0 0 auto; }

/* ---- Three callouts under the standfirst --------------------------------
   The same block /quality/ carries under its header copy (Elementor row
   d4789cb: "Purity on record / Verified Purity" and its two neighbours),
   measured off it: three equal columns, no gap between them, each with a
   1px #d1d5db rule on its TOP and LEFT edge only - which reads as a light
   grid rather than three boxes - 10px padding and 5px between the two lines.
   Label is 13px/400 in grey; value is 20px/600 in --ink.

   Every value here restates something the standfirst above already says, so
   the block adds no claim that is not already on the page. Deliberate on a
   certificates page: four of the twelve products currently have a COA, so
   anything phrased as a count or an "every product" promise would overstate
   it. These describe METHOD, not coverage.
   -------------------------------------------------------------------------- */
/* Qualified to (0,2,0) for the same reason the eyebrow is: as a bare
   `.upl-coa-callouts` the list reset lost to a theme rule on ul, which left
   padding-left:20px and margin:16px 0 in place. That pushed the whole strip
   20px right of the heading above it - the block was inset where the same
   block on /quality/ is flush. With this, the first cell's rule sits on the
   column edge and only the text inside it is inset by its own 10px padding
   plus the 1px border, which is exactly the 11px /quality/ shows. */
.upl-coa-head__text > .upl-coa-callouts {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	margin: 28px 0 0;
	padding: 0;
	list-style: none;
}

.upl-coa-callouts li {
	display: flex;
	flex-direction: column;
	gap: 5px;
	padding: 10px;
	border-top: 1px solid #d1d5db;
	border-left: 1px solid #d1d5db;
	margin: 0;
}

.upl-coa-callouts .upl-coa-callout__label {
	margin: 0;
	font-size: 13px;
	font-weight: 400;
	line-height: 1.65;
	color: #54595f;
}

.upl-coa-callouts .upl-coa-callout__value {
	margin: 0;
	font-size: 20px;
	font-weight: 600;
	line-height: 1.2;
	letter-spacing: -.01em;
	color: var(--ink);
}

/* Two up, then one up. The left rule is dropped on whichever cell starts a
   row so the grid never opens with a stray vertical hairline. */
/* These carry .upl-coa-head__text too. The base rule above is (0,2,0); a bare
   `.upl-coa-callouts` here would be (0,1,0) and lose to it regardless of the
   media query, leaving the strip three-up on a phone. */
@media (max-width: 921px) {
	.upl-coa-head__text > .upl-coa-callouts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.upl-coa-callouts li:nth-child(odd) { border-left: 0; }
}

@media (max-width: 480px) {
	.upl-coa-head__text > .upl-coa-callouts { grid-template-columns: 1fr; }
	.upl-coa-callouts li { border-left: 0; }
}

.upl-coa-head__title {
	margin: 0;
	font-size: clamp(30px, 3.4vw, 44px);
	line-height: 1.08;
	font-weight: 600;
	letter-spacing: -.03em;
	color: var(--ink);
}

/* The standfirst. upl_coa_page_header() normally lifts this paragraph into
   the header's text column, where the column already sets the measure and no
   width work is needed. The second selector is the fallback for when that
   lift does not happen (content not starting with a <p>): the paragraph then
   sits loose in .entry-content, which runs to ~1900px here, so it is capped
   to --pdp-max and pulled in on the right to a readable line.

   :not(.upl-coa-head__eyebrow) is load-bearing. The eyebrow is ALSO a <p> and
   ALSO a direct child of .upl-coa-head__text, and `.upl-coa-head__text > p`
   outweighs `.upl-coa-head__eyebrow` on specificity (0,2,1 against 0,1,0), so
   without it this rule repainted the pill at 16.32px in --muted and replaced
   its 20px bottom margin with an 18px top one. */
.upl-coa-head__text > p:not(.upl-coa-head__eyebrow) {
	margin: 18px 0 0;
	font-size: 1.02rem;
	line-height: 1.65;
	color: var(--muted);
}

body.upl-coa-page .entry-content > p:first-of-type {
	max-width: var(--pdp-max);
	margin: 0 auto 34px;
	font-size: 1.02rem;
	line-height: 1.65;
	color: var(--muted);
	padding-right: clamp(0px, 28vw, 36%);
}

@media (max-width: 921px) {
	body.upl-coa-page .entry-content > p:first-of-type { padding-right: 0; }
}

.upl-coa-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
	gap: 20px;
}

.upl-coa-card {
	border: 1px solid var(--line);
	border-radius: 14px;
	padding: 22px;
	background: #fff;
	display: flex;
	flex-direction: column;
	box-shadow: var(--shadow-nav);
}

.upl-coa-card-title { margin: 0 0 2px; font-size: 1.12rem; line-height: 1.25; }
.upl-coa-card-title a { color: var(--ink); text-decoration: none; }
.upl-coa-card-title a:hover { color: var(--ink-soft); text-decoration: underline; }

.upl-coa-card-compound {
	margin: 0 0 14px;
	font-size: .84rem;
	color: var(--muted);
	text-transform: none;
}

.upl-coa-card-rows { margin: 0 0 18px; display: grid; gap: 8px; }
.upl-coa-card-rows > div {
	display: flex;
	justify-content: space-between;
	gap: 12px;
	font-size: .86rem;
	border-bottom: 1px solid var(--line);
	padding-bottom: 7px;
}
.upl-coa-card-rows dt { color: var(--muted); margin: 0; }
.upl-coa-card-rows dd { margin: 0; font-weight: 600; text-align: right; text-transform: none; }
.upl-coa-card-rows dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .78rem; }
.upl-coa-card-rows dd small { font-weight: 400; color: var(--muted); }

.upl-coa-card-link {
	margin-top: auto;
	align-self: flex-start;
	font-weight: 600;
	font-size: .88rem;
	color: var(--ink);
	text-decoration: none;
	text-transform: none;
}
.upl-coa-card-link:hover { color: var(--ink-soft); text-decoration: underline; }

.upl-coa-coverage,
.upl-coa-empty {
	margin: 26px 0 0;
	font-size: .86rem;
	color: var(--muted);
	text-transform: none;
}

@media (max-width: 480px) {
	.upl-coa-grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   Short description — the snippet between price and swatches
   (2026-09-18, TopOpti)
   --------------------------------------------------------------------------
   This block already existed: WooCommerce prints the product's "Product short
   description" field as .woocommerce-product-details__short-description, and
   the theme already places it between .price and the variations form. It had
   simply never been styled, because only 3 of 12 products had the field filled
   and the longest was one fragment ("Without DAC").

   Sized for the intended 2-3 sentences: it has to read as supporting copy under
   the price without competing with the H1 or pushing the swatches below the
   fold on a phone.
   -------------------------------------------------------------------------- */
.summary .woocommerce-product-details__short-description {
	margin: 14px 0 22px;
	padding: 0 0 0 14px;
	border-left: 3px solid var(--line);
	color: var(--muted);
	font-size: 15px;
	line-height: 1.6;
	text-transform: none;
	max-width: 52ch;            /* keeps a 3-sentence snippet to ~3 lines */
}

.summary .woocommerce-product-details__short-description > *:first-child { margin-top: 0; }
.summary .woocommerce-product-details__short-description > *:last-child  { margin-bottom: 0; }

.summary .woocommerce-product-details__short-description p { margin: 0 0 8px; }

.summary .woocommerce-product-details__short-description strong,
.summary .woocommerce-product-details__short-description b { color: var(--ink); font-weight: 600; }

@media (max-width: 767px) {
	.summary .woocommerce-product-details__short-description {
		margin: 12px 0 18px;
		font-size: 14.5px;
		max-width: none;
	}
}

/* --------------------------------------------------------------------------
   Related products: separate it from the COA card   (2026-09-18, TopOpti)
   --------------------------------------------------------------------------
   Measured at 390px: the navy .coa-card ended at y=3383 and the "Related
   products" heading began at y=3383. Zero gap. Nothing in the chain carried any
   separation - the section had margin-top:0 and padding-top:0, and #sec-coa had
   margin-bottom:0 - so a full-bleed dark card butted straight into the next
   heading with only its own line-height as breathing room.

   Put the space on the related block rather than on #sec-coa: the COA section
   is not always the last one (it is gated on enable_cda_tab, so on the eight
   products without a certificate the Details section ends the stack instead).
   Spacing the thing that always follows covers both cases.
   -------------------------------------------------------------------------- */
.single-product .related.products,
.single-product section.related {
	margin-top: 64px;
}

.single-product .related.products > h2,
.single-product section.related > h2 {
	margin-bottom: 24px;
}

@media (max-width: 767px) {
	.single-product .related.products,
	.single-product section.related {
		margin-top: 44px;
	}
	.single-product .related.products > h2,
	.single-product section.related > h2 {
		margin-bottom: 18px;
	}
}

/* --------------------------------------------------------------------------
   Site title: present for assistive tech, invisible on screen
   (2026-09-18, TopOpti)
   --------------------------------------------------------------------------
   The WordPress site title and tagline were both EMPTY, which is why the
   homepage shipped with a completely empty <title> tag and every other page
   showed a bare "Products" with no brand name. Setting them fixed the document
   titles site-wide, but it also gave Astra a site title to render: "Ultra Pep
   Labs" appeared as 182x31px of text next to the logo, which is not the design.

   Hidden visually rather than with display:none, so screen readers and search
   engines still get the site name. Astra's own "Display Site Title" toggle in
   the Customizer does the same job if this ever needs doing there instead.
   -------------------------------------------------------------------------- */
.ast-site-identity .site-title,
.ast-site-identity .site-description {
	position: absolute !important;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------------------
   Search results and post listings: featured images (2026-09-29)

   Astra forces every listing thumbnail into a 16:9 box
   (`.ast-article-post .post-thumb-img-content img { aspect-ratio: 16/9 }`)
   with the default object-fit: fill, so the 4:5 product photos in search
   results were squashed to 1.78:1. Every product image is 4:5, and each has a
   full-bleed blue background, so the box follows the photos' own shape
   instead of letterboxing them. object-fit: cover means anything that is not
   4:5 gets cropped rather than distorted.
   -------------------------------------------------------------------------- */
body .ast-article-post .post-thumb-img-content img {
	aspect-ratio: 4 / 5;
	height: auto;
	object-fit: cover;
	object-position: center;
}
