/**
 * Bergwerk WooCommerce Product Table — frontend styles.
 *
 * Everything is namespaced under .bwpt. Quotes Manager loads its own stylesheet on every
 * page of the site, so no selector here may rely on, or leak into, a qm- class.
 *
 * The rows are a list laid out with CSS Grid rather than a <table>. Two reasons: a table
 * cannot reflow on a narrow screen without pretending to be something it is not, and a
 * theme's td/th rules would otherwise dictate padding, borders and input widths inside
 * this widget.
 *
 * Layout is mobile-first and driven by CONTAINER queries, so the rows respond to the width
 * they are actually given — a narrow Elementor column gets the compact layout even on a
 * wide screen. If a browser somehow lacks container query support it keeps the compact
 * layout everywhere, which is degraded but never broken.
 */

.bwpt {
	--bwpt-gap: 2rem;
	--bwpt-row-gap: 0.75rem;
	--bwpt-border: 1px solid rgba(0, 0, 0, 0.1);
	--bwpt-hero-transition: 250ms;
	--bwpt-row-hover-bg: rgba(0, 0, 0, 0.03);
	--bwpt-row-selected-bg: rgba(0, 0, 0, 0.05);
	--bwpt-accent: currentColor;
	--bwpt-muted: color-mix(in srgb, currentColor 60%, transparent);

	--bwpt-badge-bg: #d63638;
	--bwpt-badge-color: #fff;
	--bwpt-badge-size: 1.25rem;
	--bwpt-badge-offset-y: -0.5rem;
	--bwpt-badge-offset-x: -0.5rem;

	display: flex;
	flex-direction: column;
	gap: var(--bwpt-gap);

	/*
	 * Explicit, so the rows never inherit their width from the hero. Without it a
	 * shrink-to-fit parent sizes the whole widget to its widest child — the image — and
	 * the table ends up as narrow as the picture above it.
	 */
	width: 100%;
}

.bwpt__title {
	margin: 0;
}

/*
 * Claim the full width of the Elementor widget we are placed in.
 *
 * An Elementor container with align-items other than stretch — "center" is a common
 * choice — makes each widget shrink to its own content instead of filling the row. Our
 * rows sit in a CSS container and contribute no intrinsic width, so the hero image is
 * left deciding: a 400px photo produces a 400px widget inside a 1400px container, and
 * nothing inside this stylesheet can reach up far enough to correct it.
 *
 * Specificity is deliberately low. Elementor's own width control emits
 * `.elementor-element.elementor-element-xxxx`, which outranks this, so setting a width
 * on the widget still wins.
 */
.elementor-widget-bwpt_product_table {
	width: 100%;
}

/* ---------------------------------------------------------------------------
   Hero
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   Hero width
   ---------------------------------------------------------------------------
   Three modes, and in every one the hero is the ONLY thing constrained — the
   rows below keep the full width of the widget.
   --------------------------------------------------------------------------- */

.bwpt__hero {
	position: relative;
	overflow: hidden;
	width: 100%;
	max-width: 100%;
}

/* Up to a maximum. */
.bwpt--hero-w-custom .bwpt__hero {
	max-width: var(--bwpt-hero-max-width, 100%);
}

/*
 * The image's own size. fit-content lets the box shrink to the image instead of the
 * image stretching to the box, and max-width keeps it from overflowing a narrow screen.
 * Layer A drops its forced dimensions so its intrinsic size can win.
 */
.bwpt--hero-w-natural .bwpt__hero {
	width: fit-content;
}

/*
 * BOTH layers must be sized by the image, not by the box.
 *
 * The box is not always the image's size: in the two-column layout the hero is a flex
 * item that grows to its column share, so fit-content loses. Layer A was already
 * intrinsic, but the staging layer stretched to 100% of that wider box — so a swap
 * faded the new picture in at the column's width and then snapped back to the real
 * width the moment layer A took over.
 *
 * The staging layer therefore drops inset: 0, which is what was stretching it, and is
 * pinned by a single corner instead so it can take its own size.
 */
/*
 * The cap comes from a custom property on the wrapper rather than from the image's own
 * attributes, because an optimisation plugin may have rewritten those — see hero.php.
 * Without a value it falls back to 100%, which is the previous behaviour.
 */
.bwpt--hero-w-natural .bwpt__hero-img--a,
.bwpt--hero-w-natural .bwpt__hero-img--b {
	width: auto;
	height: auto;
	max-width: min(100%, var(--bwpt-hero-natural-w, 100%));
}

.bwpt--hero-w-natural .bwpt__hero-img--b {
	inset: auto;
	top: 0;
	left: 0;
}

/*
 * With the picture narrower than its box, alignment has to move the image rather than
 * the box. The staging layer is positioned to match, or the crossfade would visibly
 * jump sideways as the layers exchange.
 */
.bwpt--hero-w-natural.bwpt--hero-align-center .bwpt__hero-img--a {
	margin-inline: auto;
}

.bwpt--hero-w-natural.bwpt--hero-align-center .bwpt__hero-img--b {
	left: 50%;
	transform: translateX(-50%);
}

.bwpt--hero-w-natural.bwpt--hero-align-right .bwpt__hero-img--a {
	margin-inline-start: auto;
}

.bwpt--hero-w-natural.bwpt--hero-align-right .bwpt__hero-img--b {
	left: auto;
	right: 0;
}

/* Where a hero narrower than the widget sits. */
.bwpt--hero-align-center .bwpt__hero {
	margin-inline: auto;
}

.bwpt--hero-align-right .bwpt__hero {
	margin-inline-start: auto;
}

.bwpt--ratio-1-1 .bwpt__hero { aspect-ratio: 1 / 1; }
.bwpt--ratio-4-3 .bwpt__hero { aspect-ratio: 4 / 3; }
.bwpt--ratio-3-2 .bwpt__hero { aspect-ratio: 3 / 2; }
.bwpt--ratio-16-9 .bwpt__hero { aspect-ratio: 16 / 9; }

.bwpt__hero-img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
}

.bwpt--ratio-auto .bwpt__hero-img {
	height: auto;
}

/*
 * Layer A is the one in normal flow, so it — and only it — sizes the box. That matters for
 * the "auto" ratio, where a swap that merely toggled which layer was visible would leave the
 * container sized by the previous image.
 *
 * Layer B is the staging layer: it loads the next image, fades in on top, and then hands its
 * attributes to A and disappears instantly. A is therefore always the current image.
 */
.bwpt__hero-img--b {
	position: absolute;
	inset: 0;
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--bwpt-hero-transition) ease;
}

.bwpt__hero-img--b.is-visible {
	opacity: 1;
}

.bwpt__hero-caption {
	margin: 0.5rem 0 0;
	font-size: 0.875em;
	opacity: 0.75;
}

/* ---------------------------------------------------------------------------
   Rows
   --------------------------------------------------------------------------- */

/*
 * container-type: inline-size is what powers the layout switch below, but it has a
 * consequence worth knowing: an element with inline-size containment contributes NO
 * intrinsic width to its parent. In a layout that sizes itself to its content, the hero
 * image is then the only thing left with an opinion, and the rows inherit whatever width
 * the picture happened to have.
 *
 * width: 100% cannot fix that — it resolves against a parent that already sized itself
 * from the image. Setting an explicit table width can, which is what the custom mode
 * below is for.
 */
.bwpt__body {
	width: 100%;
	min-width: 0;

	container-type: inline-size;
	container-name: bwpt;
}

/* A width of its own, independent of the hero. */
.bwpt--table-w-custom .bwpt__body {
	width: var(--bwpt-table-max-width, 100%);
	max-width: 100%;
}

.bwpt--table-align-center .bwpt__body {
	margin-inline: auto;
}

.bwpt--table-align-right .bwpt__body {
	margin-inline-start: auto;
}

.bwpt__list {
	list-style: none;
	margin: 0;
	padding: 0;
}

/*
 * Compact layout — the default.
 *
 *   ┌─────────────────────────────┐
 *   │ Product name                │
 *   │ SKU              7112.0401  │
 *   │ Gewicht               12 kg │
 *   │ [ qty ]             [ Add ] │
 *   └─────────────────────────────┘
 *
 * Named areas cannot express a column count that changes per table, so every column takes the
 * full width and the data ones carry their own heading, read from data-label. That scales to
 * any number of them without the row ever needing a horizontal scrollbar.
 *
 * The product name is one of those columns and sits wherever it has been ordered, so it is
 * not addressed by position here — only by being the name.
 */
.bwpt__head-row,
.bwpt__row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	column-gap: 0.75rem;
	row-gap: 0.35rem;
}

.bwpt__head-row {
	/* No column headings until the layout is actually columnar. */
	display: none;
}

.bwpt__row {
	padding: var(--bwpt-row-gap) 0;
	border-bottom: var(--bwpt-border);
	transition: background-color 150ms ease;
}

.bwpt__list > .bwpt__row:first-child {
	border-top: var(--bwpt-border);
}

.bwpt__cell--name   { grid-column: 1 / -1; min-width: 0; }
.bwpt__cell--custom { grid-column: 1 / -1; min-width: 0; }
.bwpt__cell--qty    { grid-column: 1; justify-self: start; }
.bwpt__cell--action { grid-column: 2; justify-self: end; }

.bwpt__cell--name {
	font-weight: 600;
	overflow-wrap: anywhere;
}

/*
 * Data columns read as metadata beside the product name: a little smaller, a little quieter,
 * and with tabular figures so a weight or price column lines up down the page. Overridable
 * per table from the widget's style tab.
 *
 * Here the heading sits on the line with its value, because there is no heading row to carry
 * it. It comes from the attribute rather than a real element so a 200-row table does not gain
 * a span per cell, and the attribute is simply absent when the column has no label.
 */
.bwpt__cell--custom {
	display: flex;
	justify-content: space-between;
	gap: 0.75rem;
	font-variant-numeric: tabular-nums;
	font-size: 0.9em;
	color: var(--bwpt-muted);
}

.bwpt__cell--custom[data-label]::before {
	content: attr(data-label);
	flex: none;
}

/* Nothing to show and nothing to label — the whole line goes. */
.bwpt__cell--empty {
	display: none;
}

/* Only paint a hover state where hovering is possible — on touch the sticky
   selected state is the only meaningful feedback. */
@media (hover: hover) and (pointer: fine) {
	.bwpt__row:hover {
		background-color: var(--bwpt-row-hover-bg);
	}
}

.bwpt__row.is-selected {
	background-color: var(--bwpt-row-selected-bg);
}

/*
 * The accent bar marks the selected row only where there is no pointer to do that job.
 *
 * On touch the selection is sticky — it stays after the finger lifts, and without a mark
 * nothing says which row the picture belongs to. With hover, the background already says
 * it, and a second indicator that follows the cursor down the list is just noise.
 *
 * Set the accent colour to transparent in the widget to switch it off entirely.
 */
@media (hover: none) {
	.bwpt__row.is-selected {
		box-shadow: inset 3px 0 0 0 var(--bwpt-accent);
	}
}

/* ---------------------------------------------------------------------------
   Controls
   --------------------------------------------------------------------------- */

/*
 * Three classes deep on purpose. Themes routinely set input[type="number"] { width: 100% }
 * and a single class loses that fight, which is how the quantity field ended up spanning
 * the whole screen on mobile.
 */
.bwpt .bwpt__cell--qty .bwpt__qty {
	box-sizing: border-box;
	width: 5rem;
	min-width: 0;
	max-width: 100%;
	padding: 0.4em 0.5em;
	text-align: center;
}

.bwpt__form {
	margin: 0;
}

.bwpt__add {
	position: relative;
	cursor: pointer;
	white-space: nowrap;
	transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease, transform 100ms ease;
}

/* A press should feel like one. */
.bwpt__add:active:not([disabled]) {
	transform: scale(0.97);
}

/* Only introduced when there is an icon, so a text-only button keeps whatever
   the theme gives it. */
.bwpt__add--has-icon {
	display: inline-flex;
	align-items: center;
	gap: var(--bwpt-icon-gap, 0.5em);
}

.bwpt__add-icon {
	display: inline-flex;
	align-items: center;
	line-height: 1;
}

.bwpt__add-icon svg {
	width: var(--bwpt-icon-size, 1em);
	height: var(--bwpt-icon-size, 1em);
	fill: currentColor;
}

.bwpt__add-icon i {
	font-size: var(--bwpt-icon-size, 1em);
}

/* Icon-only: square it off so it does not keep the padding of a text button. */
.bwpt__add--icon-only {
	gap: 0;
}

.bwpt__add[disabled] {
	cursor: default;
	opacity: 0.65;
}

.bwpt__add:focus-visible,
.bwpt__qty:focus-visible,
.bwpt__link:focus-visible {
	outline: 2px solid var(--bwpt-accent);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Button states
   ---------------------------------------------------------------------------
   Three icons live in the markup at once and CSS decides which is visible, so
   the swap costs no layout work. JavaScript only toggles is-loading / is-added.
   --------------------------------------------------------------------------- */

.bwpt__add-icon:empty {
	display: none;
}

/* Resting state shows the default icon only. */
.bwpt__add-icon--loading,
.bwpt__add-icon--success {
	display: none;
}

/* Working: the processing icon replaces the default and spins. */
.bwpt__add.is-loading .bwpt__add-icon--default {
	display: none;
}

.bwpt__add.is-loading .bwpt__add-icon--loading:not(:empty) {
	display: inline-flex;
	animation: bwpt-spin 700ms linear infinite;
}

/* Done: the success icon takes over until JavaScript clears the class. */
.bwpt__add.is-added .bwpt__add-icon--default,
.bwpt__add.is-added .bwpt__add-icon--loading {
	display: none;
}

.bwpt__add.is-added .bwpt__add-icon--success:not(:empty) {
	display: inline-flex;
	animation: bwpt-pop 320ms ease-out;
}

/* The built-in spinner is the fallback for anyone who set no processing icon. */
.bwpt__add-spinner {
	display: none;
	width: 1em;
	height: 1em;
	vertical-align: -0.125em;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: bwpt-spin 700ms linear infinite;
}

.bwpt__add.is-loading:not(.bwpt__add--has-loading-icon) .bwpt__add-spinner {
	display: inline-block;
}

/* ---------------------------------------------------------------------------
   Cart count badge
   ---------------------------------------------------------------------------
   How many of this product are already in the cart, so a long list can be read
   without holding the cart in your head. .bwpt__add is already position:
   relative for the spinner, which is what this anchors to.
   --------------------------------------------------------------------------- */

.bwpt__badge {
	position: absolute;
	top: var(--bwpt-badge-offset-y);
	inset-inline-end: var(--bwpt-badge-offset-x);

	display: flex;
	align-items: center;
	justify-content: center;

	/*
	 * A circle at one digit that grows into a pill at three, rather than a circle that
	 * clips its own number. min-width holds the round shape; the padding only ever
	 * applies once the text is wider than that.
	 */
	box-sizing: border-box;
	min-width: var(--bwpt-badge-size);
	height: var(--bwpt-badge-size);
	padding: 0 0.35em;
	border-radius: 999px;

	background: var(--bwpt-badge-bg);
	color: var(--bwpt-badge-color);
	font-size: calc(var(--bwpt-badge-size) * 0.6);
	font-weight: 700;
	line-height: 1;
	font-variant-numeric: tabular-nums;

	/* Above the button's own background, and never a click target of its own. */
	pointer-events: none;
	z-index: 1;
}

/*
 * Hidden rather than absent at zero: the element stays in the markup so the script only
 * writes a number, and visibility keeps it from being announced or found by search.
 */
.bwpt__badge--empty {
	display: none;
}

/* The same pop the success icon uses, so a change reads as a change. */
.bwpt__badge.is-bumped {
	animation: bwpt-pop 320ms ease-out;
}

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

@keyframes bwpt-pop {
	0%   { transform: scale(0.6); opacity: 0; }
	60%  { transform: scale(1.15); opacity: 1; }
	100% { transform: scale(1); opacity: 1; }
}

.bwpt__unavailable {
	display: inline-block;
	font-size: 0.9em;
	color: var(--bwpt-muted);
	white-space: nowrap;
}

/* Spans the whole row rather than sitting in the narrow action column, where a
   sentence would wrap one or two words per line. */
.bwpt__row-error {
	grid-column: 1 / -1;
	display: block;
	margin-top: 0.35em;
	font-size: 0.85em;
	color: #b32d2e;
}

.bwpt__empty,
.bwpt-notice {
	margin: 0;
	color: var(--bwpt-muted);
}

/* ---------------------------------------------------------------------------
   Columnar layout — once the container is wide enough to hold it
   --------------------------------------------------------------------------- */

@container bwpt (min-width: 600px) {

	/*
	 * One grid for the headings AND every row, so the columns line up.
	 *
	 * Two separate grids cannot agree: each sizes its own auto tracks from its own
	 * content, and "SKU" is narrower than 7112.0401, so the headings drift away from
	 * the values beneath them. Here the tracks are declared once and the rows adopt
	 * them with subgrid.
	 *
	 * The grid sits on .bwpt__grid rather than .bwpt__body because the body is the
	 * query container, and a @container rule can style the container's descendants
	 * but never the container itself.
	 *
	 * display: contents on the list lifts the rows into that grid. The list keeps its
	 * explicit role="list" in the markup, which is what preserves the semantics that
	 * display: contents would otherwise drop.
	 */
	/*
	 * The tracks come from PHP, built from the same array the rows loop over, because the
	 * number of them is a per-table setting. The fallback is the shape of a table with no
	 * data columns at all: name, quantity, action.
	 */
	.bwpt__grid {
		display: grid;
		grid-template-columns: var(--bwpt-tracks, minmax(0, 1fr) auto auto);
		column-gap: 1.5rem;
	}

	.bwpt__list {
		display: contents;
	}

	.bwpt__head-row,
	.bwpt__row {
		display: grid;
		grid-column: 1 / -1;
		grid-template-columns: subgrid;
		grid-template-areas: none;
		row-gap: 0;
	}

	/*
	 * The explicit placement belongs to the compact layout; here source order decides, one
	 * child per track. Written against every child rather than a list of cell classes so it
	 * cannot fall out of step with a column count that changes per table.
	 */
	.bwpt__row > *,
	.bwpt__head-row > * {
		grid-area: auto;
		justify-self: stretch;
	}

	/*
	 * Back to a plain cell: the label belongs on the heading row up top, and an empty cell
	 * still has to occupy its track or every column after it shifts left.
	 */
	.bwpt__cell--custom {
		display: block;
	}

	.bwpt__cell--custom[data-label]::before {
		content: none;
	}

	.bwpt__cell--empty {
		display: block;
	}

	/* Per-column alignment, on the values and their heading alike. */
	.bwpt__cell--align-start,
	.bwpt__head--align-start  { text-align: start; }
	.bwpt__cell--align-center,
	.bwpt__head--align-center { text-align: center; }
	.bwpt__cell--align-end,
	.bwpt__head--align-end    { text-align: end; }

	.bwpt__head-row {
		padding-bottom: 0.5rem;
		font-weight: 600;
		font-size: 0.85em;
		letter-spacing: 0.04em;
		text-transform: uppercase;
		color: var(--bwpt-muted);
	}

	.bwpt__row {
		padding: calc(var(--bwpt-row-gap) * 1.15) 0;
	}

	.bwpt__list > .bwpt__row:first-child {
		border-top: 0;
	}
}

/* ---------------------------------------------------------------------------
   Two-column layout
   ---------------------------------------------------------------------------
   The image sits beside the list rather than above it, so someone scrolling the
   rows can still see which product they are pointing at.

   Deliberately flex-wrap rather than a query. The columns fold to one as soon as
   they cannot both meet their minimum widths, which makes the fold depend on the
   width the table actually has — a narrow column on a large screen folds too. A
   container query would have to sit on .bwpt itself, and containing that element
   is what previously let a 400px image dictate the width of the whole widget.
   --------------------------------------------------------------------------- */

.bwpt--layout-two {
	flex-direction: row;
	flex-wrap: wrap;
	align-items: flex-start;
}

/* The title spans the full width so the columns start beneath it. */
.bwpt--layout-two .bwpt__title {
	flex: 1 0 100%;
}

/*
 * Ratios come from flex-grow with a zero basis, so the gap between the columns is
 * taken out of the available space rather than pushing the total past 100%.
 *
 * The min-widths are the fold: once both cannot be honoured, the second column
 * wraps. min() keeps them from overflowing a container narrower than the floor.
 */
.bwpt--layout-two .bwpt__hero {
	flex: var(--bwpt-hero-grow, 1) 1 0;
	min-width: min(100%, 14rem);
}

.bwpt--layout-two .bwpt__body {
	flex: var(--bwpt-table-grow, 2) 1 0;
	min-width: min(100%, 22rem);
}

.bwpt--layout-two.bwpt--hero-right .bwpt__hero {
	order: 2;
}

/* Column split presets. */
.bwpt--split-1-2 { --bwpt-hero-grow: 1; --bwpt-table-grow: 2; }
.bwpt--split-1-1 { --bwpt-hero-grow: 1; --bwpt-table-grow: 1; }
.bwpt--split-2-1 { --bwpt-hero-grow: 2; --bwpt-table-grow: 1; }

/*
 * Sticky image.
 *
 * align-self keeps the column from stretching to the full height of the row —
 * a sticky element that already fills its container has nothing to scroll within.
 *
 * Restricted to wider viewports on purpose. Once the columns have folded, the
 * image would be pinned above the list and eat most of a phone screen.
 */
@media (min-width: 782px) {
	.bwpt--layout-two.bwpt--sticky .bwpt__hero {
		position: sticky;
		top: var(--bwpt-sticky-offset, 2rem);
		align-self: flex-start;
	}
}

/* ---------------------------------------------------------------------------
   Reduced motion
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	.bwpt__hero-img--b,
	.bwpt__row,
	.bwpt__add {
		transition: none;
	}

	.bwpt__add:active:not([disabled]) {
		transform: none;
	}

	/* The state still has to read as "working", so the spin slows rather than stops. */
	.bwpt__add-spinner,
	.bwpt__add.is-loading .bwpt__add-icon--loading:not(:empty) {
		animation-duration: 2s;
	}

	.bwpt__add.is-added .bwpt__add-icon--success:not(:empty) {
		animation: none;
	}

	/* The number changing is the information; the pop was only decoration. */
	.bwpt__badge.is-bumped {
		animation: none;
	}
}
