/**
 * Optic Bundle — storefront.
 *
 * Every value that a theme might reasonably want to change is a custom property on
 * `.optic-bundle`, so retuning to a brand means overriding six declarations rather
 * than fighting specificity across the whole file.
 *
 * Astra — the theme on this store — sets a fixed `height` on form controls through a
 * Customizer rule that matches by attribute. Anything here that is or contains a form
 * control therefore states `height: auto` explicitly. Declaring every property except
 * the one the theme sets is how the checkout's cut-off select happened.
 */

.optic-bundle {
	/* Inherited from the theme, with the measured value as the fallback.
	 *
	 * Astra publishes its palette as custom properties, and `--ast-global-color-0` on
	 * this store is #1569ff — the same blue this widget was drawn in. Reading the
	 * variable rather than repeating the hex means a rebrand in the Customizer moves
	 * the offer with it, instead of leaving one blue element behind.
	 *
	 * `--ob-accent` itself stays overridable, so a merchant who wants the offer OFF
	 * the brand colour can still set it. */
	--ob-accent: var( --ast-global-color-0, var( --e-global-color-primary, #1569ff ) );
	--ob-accent-soft: rgba( 21, 105, 255, 0.08 );
	--ob-accent-soft: color-mix( in srgb, var( --ob-accent ) 8%, transparent );
	--ob-border: #dfe3e8;
	--ob-border-strong: #11181c;
	--ob-text: #11181c;
	--ob-muted: #5c6b73;
	--ob-was: #9aa3ae;
	--ob-radius: 12px;
	--ob-radius-pill: 999px;
	--ob-gap: 10px;

	display: block;
	/* No margin of our own.
	 *
	 * This used to carry `margin: 0 0 24px`. The widget is placed by Elementor, by a
	 * shortcode in a theme template, or by the product-summary hook, and each of those
	 * already owns the spacing around what it places. Adding our own stacked on top of
	 * theirs, and it could not be removed from the page that placed it without
	 * outranking a plugin stylesheet.
	 *
	 * Spacing belongs to the container, not to the thing inside it. */
	margin: 0;
	color: var( --ob-text );

	/* Survive being a flex item.
	 *
	 * `display: block` fills its parent only when the parent lays out in flow.
	 * Elementor wraps a shortcode in `.elementor-shortcode`, which is `display: flex`,
	 * and a flex item defaults to `flex: 0 1 auto` — it shrinks to its CONTENT width
	 * instead of filling. Measured on a live page: 201px inside a 402px column, so
	 * the offer sat at half the width of everything around it.
	 *
	 * `min-width: 0` is the companion to that: a flex item's default `min-width: auto`
	 * refuses to shrink below its content, which is what makes a wide price row push
	 * a narrow phone layout sideways. */
	width: 100%;
	flex: 1 1 100%;
	min-width: 0;
}

/* ---------------------------------------------------------------- mode toggle */

/* Terms printed under the tabs by whoever owns the membership — the bundle knows
   the rate and nothing else. Kept at body-copy weight rather than fine print:
   it names a recurring fee, and a fee in fine print is a chargeback.

   THESE TWO RULES WERE PASTED INSIDE `.optic-bundle__modes { }` BELOW, and modern
   browsers do not treat that as a mistake — Chrome compiled them as CSS NESTING,
   read back from the live CSSOM as `& .optic-bundle__club-terms`. So they matched
   only a descendant of the tablist, which is a 1x1 clipped element containing
   exactly two buttons and nothing else. The styling was dead wherever the terms
   actually render, and silently so: no parse error, no console warning, and the
   surrounding block's own declarations still worked.

   Nested CSS is why "it must be fine, the block still works" is no longer a safe
   reading of a stylesheet. */
.optic-bundle__club-terms {
	margin: 8px 0 0;
	font-size: 12.5px;
	line-height: 1.5;
	color: #5A6472;
}

.optic-bundle__club-terms.is-member {
	color: #078A5B;
	font-weight: 600;
}

.optic-bundle__modes {
	display: flex;
	gap: 4px;
	padding: 4px;
	margin: 0 0 var( --ob-gap );
	background: #f1f3f5;
	border-radius: calc( var( --ob-radius ) - 2px );
}

.optic-bundle__mode {
	flex: 1 1 0;
	height: auto;
	min-height: 0;
	padding: 10px 12px;
	border: 0;
	border-radius: calc( var( --ob-radius ) - 6px );
	background: transparent;
	color: var( --ob-muted );
	font-size: 14px;
	font-weight: 600;
	line-height: 1.2;
	cursor: pointer;
	transition: background-color 0.15s ease, color 0.15s ease;
}

.optic-bundle__mode.is-active {
	background: #fff;
	color: var( --ob-text );
	box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.1 );
}

/* -------------------------------------------------------------------- heading */

.optic-bundle__heading {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 12px;
	margin: 0 0 8px;
	font-size: 13px;
	line-height: 1.4;
}

.optic-bundle__heading-text {
	color: var( --ob-muted );
	font-weight: 500;
}

.optic-bundle__heading-note {
	color: var( --ob-accent );
	font-weight: 600;
}

/* ----------------------------------------------------------------- deal bars */

.optic-bundle__tiers {
	display: flex;
	flex-direction: column;
	gap: var( --ob-gap );
}

/*
 * The whole card is the control, perks strip included.
 *
 * `cursor: pointer` belongs on the card and not on an inner element: a perk row is
 * a statement of what this bar includes, clicking it selects the bar like clicking
 * anywhere else, and on a three-row strip that is most of the card's height.
 */
.optic-tier {
	position: relative;
	display: block;
	margin: 0;
	padding: 14px 16px;
	border: 1px solid var( --ob-border );
	border-radius: var( --ob-radius );
	background: #fff;
	cursor: pointer;
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.optic-tier.is-selected {
	border-color: var( --ob-accent );
	box-shadow: inset 0 0 0 1px var( --ob-accent );
}

/* The radio stays in the accessibility tree and keeps arrow-key navigation and
   focus working. `display: none` would remove all three. */
.optic-tier__radio {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	padding: 0;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	clip-path: inset( 50% );
	white-space: nowrap;
	border: 0;
}

/* The badge sits ON the top border, to the right — a label attached to the card
   rather than a chip inside it. `translateY(-50%)` centres it on the edge, so the
   card's own padding does not have to leave room for it. */
.optic-tier__badge {
	position: absolute;
	top: 0;
	right: 12px;
	transform: translateY( -50% );
	padding: 3px 10px;
	border-radius: 999px;
	background: var( --ob-accent );
	color: #fff;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	line-height: 1.4;
	white-space: nowrap;
}

.optic-tier__badge[hidden] {
	display: none;
}

.optic-tier__body {
	display: flex;
	align-items: center;
	gap: 12px;
}

.optic-tier__mark {
	flex: 0 0 auto;
	width: 20px;
	height: 20px;
	border: 1px solid var( --ob-border );
	border-radius: 50%;
	background: #fff;
	transition: border-color 0.15s ease, border-width 0.15s ease;
}

.optic-tier.is-selected .optic-tier__mark {
	border-color: var( --ob-accent );
	border-width: 6px;
}

/* Two rows, three columns: title and saving on the left of row one with the price
   on its right; sub-label and compare-at the same way on row two. A grid rather than
   nested flexes so the price and the compare-at share one right edge. */
.optic-tier__grid {
	flex: 1 1 auto;
	min-width: 0;
	display: grid;
	grid-template-columns: auto auto 1fr;
	align-items: baseline;
	column-gap: 8px;
	row-gap: 2px;
}

.optic-tier__title {
	grid-column: 1;
	font-size: 15px;
	font-weight: 700;
	line-height: 1.3;
	white-space: nowrap;
}

.optic-tier__save {
	grid-column: 2;
	justify-self: start;
	padding: 2px 7px;
	border-radius: 999px;
	background: #e7f6ec;
	color: #157347;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.04em;
	line-height: 1.5;
	white-space: nowrap;
}

.optic-tier__save[hidden] {
	display: none;
}

.optic-tier__price {
	grid-column: 3;
	justify-self: end;
	font-size: 17px;
	font-weight: 700;
	line-height: 1.25;
	font-variant-numeric: tabular-nums;
}

.optic-tier__sub {
	grid-column: 1 / 3;
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var( --ob-muted );
	line-height: 1.3;
	min-width: 0;
}

.optic-tier__sub:empty {
	display: none;
}

/* The colour is set on the descendants too, not only the wrapper.
 *
 * `wc_price()` emits `<span class="woocommerce-Price-amount"><bdi>…</bdi></span>`
 * inside this element, and themes style that class directly — a colour on the
 * wrapper alone loses to it and the struck-through figure comes out the theme's
 * text colour instead of a muted one. */
.optic-tier__was,
.optic-tier__was * {
	color: var( --ob-was );
}

.optic-tier__was {
	grid-column: 3;
	justify-self: end;
	font-size: 13px;
	text-decoration: line-through;
	line-height: 1.3;
	font-variant-numeric: tabular-nums;
}

.optic-tier__was[hidden] {
	display: none;
}

.optic-tier__unit {
	display: block;
	margin: 4px 0 0 32px;
	font-size: 12px;
	color: var( --ob-muted );
	line-height: 1.3;
}

.optic-tier__unit[hidden] {
	display: none;
}

/* --------------------------------------------------------------------- perks */

/* Shown on the SELECTED card only. On every card they read as a list of things every
   option includes, which is the opposite of what they are — they are the reason to
   choose this one. */
.optic-tier__perks {
	display: none;
}

.optic-tier.is-selected .optic-tier__perks {
	display: flex;
	flex-direction: column;
	margin: 12px -16px -14px;
	border-top: 1px solid var( --ob-border );
	border-radius: 0 0 calc( var( --ob-radius ) - 1px ) calc( var( --ob-radius ) - 1px );
	background: var( --ob-accent-soft );
	overflow: hidden;
}

.optic-tier.is-selected .optic-tier__perks:empty {
	display: none;
}

/*
 * ONE WHITE TILE, 32px, FOR BOTH — a product photograph and the truck.
 *
 * EVERY SELECTOR IN THIS BLOCK IS SCOPED UNDER `.optic-bundle`, AND THAT IS LOAD
 * BEARING. `.optic-perk__img` alone is specificity (0,1,0) and Elementor ships
 * `.elementor img { border-radius: 0 }` at (0,1,1) — on this store the product page
 * IS an Elementor page, so the bare selector lost and the radius computed to 0px on
 * the live site while every test passed. tests/layout.mjs loads this stylesheet on
 * its own, where nothing competes; a fixture proves a rule is WRITTEN, never that it
 * WINS. Scoping is the honest fix. `!important` would win too and would take the
 * merchant's ability to override it away.
 */
.optic-bundle .optic-perk__img,
.optic-bundle .optic-perk__icon {
	flex: none;
	box-sizing: border-box;
	width: 32px;
	height: 32px;
	border-radius: calc( var( --ob-radius ) - 4px );
	background: #fff;
	box-shadow: inset 0 0 0 1px rgba( 17, 24, 28, 0.08 );
}

/* A product shot fills its tile: these are studio images on white, so the tile and
   the photograph share a background and the crop reads as the product's own edge. */
.optic-bundle .optic-perk__img {
	object-fit: cover;
}

/*
 * The truck is INSET in its tile rather than filling it. It is line art at a 24-unit
 * viewBox, and drawn edge to edge at 32px it out-weighs the photographs beside it —
 * strokes carry more visual mass than a pale product shot. The padding shrinks the
 * drawn glyph inside the same 32px box, so every row in the strip keeps one baseline
 * and one left edge.
 *
 * `box-sizing: border-box` above is what makes the padding shrink the artwork rather
 * than grow the tile: an SVG scales its viewBox into the CONTENT box.
 */
.optic-bundle .optic-perk__icon {
	padding: 6px;
	color: var( --ob-accent );
}

/* The zero, beside the price it replaces. Same green the cart uses for FREE, so a
   shopper who sees this on the product page recognises it in the drawer. */
.optic-bundle .optic-perk__now {
	margin-left: 4px;
	color: #157347;
	font-weight: 700;
	white-space: nowrap;
}

.optic-perk {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 10px 16px;
	font-size: 13px;
	line-height: 1.35;
}

.optic-perk + .optic-perk {
	border-top: 1px solid rgba( 255, 255, 255, 0.7 );
}

.optic-perk__plus {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	border-radius: 6px;
	background: #fff;
	color: var( --ob-accent );
	font-size: 14px;
	font-weight: 700;
}

.optic-perk__name {
	flex: 1 1 auto;
	font-weight: 600;
	min-width: 0;
}

.optic-perk__was,
.optic-perk__was * {
	color: var( --ob-was );
}

.optic-perk__was {
	flex: 0 0 auto;
	text-decoration: line-through;
	font-size: 12px;
	font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------------- button */

.optic-bundle__add {
	display: block;
	width: 100%;
	height: auto;
	min-height: 0;
	margin: 16px 0 0;
	padding: 16px 20px;
	border: 0;
	/*
	 * Its own token, not `--ob-radius`. That one is shared with the tier cards above
	 * it, and this button sits under them with margin, not flush against them — so
	 * nothing depends on the two corners matching, and the CTA takes the same fully
	 * rounded shape as the site's other primary actions (Checkout, Join Hero Club).
	 */
	border-radius: var( --ob-radius-pill );
	background: var( --ob-accent );
	color: #fff;
	font-size: 16px;
	font-weight: 700;
	line-height: 1.2;
	/* Explicit rather than relied-on: a UA default centres a raw text node the
	   same way, but the content is two spans now, and this file does not lean on
	   browser defaults for anything else either (see the height:auto notes above). */
	text-align: center;
	cursor: pointer;
	transition: filter 0.15s ease;
}

.optic-bundle__add:hover {
	filter: brightness( 0.94 );
}

/*
 * "Adding…" — CSS-driven, not a JS text swap.
 *
 * Both `.ob-label` and `.ob-adding` are always in the DOM (see the template);
 * `.is-busy` on the button is the only thing that decides which one is visible.
 * `submit()` in bundle.js therefore never touches the button's text at all — it
 * only toggles that one class — so there is nothing to snapshot before going busy
 * and nothing to restore afterward that could race `renderCta()` writing a new
 * price into `.ob-label` while a request from a moment ago is still in flight.
 *
 * Mirrors hr-square-gateway/assets/checkout/checkout.css's `.dots`: the three
 * dots are always PRESENT and only ever change opacity. Animating `content` is
 * discrete-only and not supported everywhere; a span that gains and loses child
 * dots is a span whose width jitters the button around it. The first dot never
 * animates, which is what makes the other two read as a countdown rather than a
 * flicker.
 */
.optic-bundle__add {
	position: relative;
}

.optic-bundle__add .ob-adding {
	display: none;
}

.optic-bundle__add.is-busy .ob-label {
	visibility: hidden;
}

.optic-bundle__add.is-busy .ob-adding {
	display: inline-flex;
	align-items: center;
	position: absolute;
	inset: 0;
	justify-content: center;
}

.ob-dots {
	display: inline-block;
	margin-left: 0.15em;
	letter-spacing: 0.06em;
}

.ob-dots i {
	font-style: normal;
}

.ob-dots i:nth-child( 2 ) {
	animation: ob-d2 1.2s infinite;
}

.ob-dots i:nth-child( 3 ) {
	animation: ob-d3 1.2s infinite;
}

@keyframes ob-d2 {
	0%, 49.9% { opacity: 1; }
	50%, 74.9% { opacity: 0; }
	75%, 100% { opacity: 1; }
}

@keyframes ob-d3 {
	0%, 24.9% { opacity: 1; }
	25%, 100% { opacity: 0; }
}

@media ( prefers-reduced-motion: reduce ) {
	.ob-dots i {
		animation: none !important;
		opacity: 1;
	}
}

.optic-bundle__add:disabled,
.optic-bundle__add.is-busy {
	opacity: 0.65;
	cursor: progress;
}

.optic-bundle__cta-note {
	margin: 10px 0 0;
	font-size: 12px;
	color: var( --ob-muted );
	text-align: center;
	line-height: 1.4;
}

.optic-bundle__notice {
	margin: 10px 0 0;
	font-size: 13px;
	color: #b42318;
}

.optic-bundle__notice[hidden] {
	display: none;
}

/* ------------------------------------------------ standing in for the theme's
   own quantity box and buy button, which this block replaces

   Keyed off the block being PRESENT, never off a bundle merely existing for the
   product. A store-wide bundle matches every product, including pages where nobody
   placed the shortcode — hiding the buy button there would make those products
   unbuyable with nothing in their place.

   `:has()` needs no JavaScript and so cannot flash. The `.optic-bundle-active`
   class is the fallback that bundle.js adds on boot for browsers without it. */

/* Two rules, not one list. A selector a browser does not understand invalidates the
   ENTIRE comma-separated list it appears in — so combining these would kill the
   fallback in precisely the browsers that need it. */

/* `single-product` is COMPOUNDED with body, not a descendant of it.
 *
 * WordPress puts `single-product` on `<body>` itself, via get_body_class(). Written
 * as `body.optic-bundle-active .single-product` it asks for an element carrying that
 * class INSIDE the body — which never exists, so both rules matched nothing and the
 * theme's quantity box and buy button stayed on the page beside the widget.
 *
 * It went unnoticed on this store because something else happens to hide them here.
 * Measured on the live page: both selectors matched 0 elements. */

body.optic-bundle-active.single-product div.product form.cart .quantity,
body.optic-bundle-active.single-product div.product form.cart .single_add_to_cart_button {
	display: none !important;
}

body.single-product:has( .optic-bundle ) div.product form.cart .quantity,
body.single-product:has( .optic-bundle ) div.product form.cart .single_add_to_cart_button {
	display: none !important;
}

/*
 * WooCommerce core's OWN "View cart" link, suppressed.
 *
 * `assets/js/frontend/add-to-cart.js` in WooCommerce itself — not this plugin, not
 * Elementor — binds `added_to_cart` on `document.body` and, given a real button
 * argument, adds `.added` to it and inserts `<a class="added_to_cart wc-forward">
 * View cart</a>` as its next sibling. `Bundle.prototype.added()` passes a real
 * button specifically so an UNRELATED Elementor Pro listener on the same event
 * does not throw and take Optic Cart's own listener down with it (see
 * dev-history.md #30) — WooCommerce core's handler was never the one that needed
 * guarding against, but it reads the same argument and does its own thing with it.
 *
 * The Optic Cart drawer already opens and shows the cart on the same event, so
 * this second, redundant "View cart" link is not wanted here. Scoped to `.optic-
 * bundle` rather than disabled at the source: WooCommerce core's script also
 * serves every OTHER add-to-cart button on the site (mini-cart, related products,
 * upsells), and this only needs to be gone from beside THIS widget's button.
 */
.optic-bundle a.added_to_cart {
	display: none !important;
}

/* ----------------------------------------------------------------------- cart */

.optic-gift-free {
	font-weight: 700;
	color: #157347;
	text-transform: uppercase;
	font-size: 0.9em;
	letter-spacing: 0.04em;
}

@media ( max-width: 480px ) {
	.optic-tier__main {
		flex-wrap: wrap;
	}

	.optic-tier__money {
		width: 100%;
		flex-direction: row;
		align-items: baseline;
		justify-content: flex-start;
		gap: 8px;
		text-align: left;
		padding-left: 32px;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.optic-bundle *,
	.optic-bundle *::before,
	.optic-bundle *::after {
		transition-duration: 0.01ms !important;
	}
}
