/*
 * Lucky Top Products
 *
 * Breakpoints are container based, not viewport based: WordPress themes
 * constrain content width in wildly different ways, and the catalog has to
 * respond to the space it actually gets. `--ltp-mode` on .ltp__layout is the
 * single switch the script reads to know whether the filter is a list or a
 * dropdown.
 *
 * Palette follows the catalog design; override the custom properties below
 * (or from LT Products > Settings) to re-skin it.
 */

/*
 * Onest, served from the plugin rather than from Google.
 *
 * The URLs are relative to this stylesheet, so they follow the plugin wherever
 * it is installed — no PHP has to work out a path. LTP_Shortcode skips the
 * Google request entirely while Onest is the chosen family; the other families
 * on the settings screen still come from there.
 *
 * All nine weights are declared, because the weight is itself a setting and a
 * missing face would be synthesised into a smeared fake bold. Only the faces a
 * page actually uses are downloaded, so the list costs nothing on its own.
 */
@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Thin.woff2") format("woff2");
	font-weight: 100;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-ExtraLight.woff2") format("woff2");
	font-weight: 200;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Light.woff2") format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Regular.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Medium.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-SemiBold.woff2") format("woff2");
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Bold.woff2") format("woff2");
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-ExtraBold.woff2") format("woff2");
	font-weight: 800;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Onest";
	src: url("../fonts/Onest-Black.woff2") format("woff2");
	font-weight: 900;
	font-style: normal;
	font-display: swap;
}

.ltp {
	--ltp-bg: #FFFCF0;
	--ltp-ink: #0E0000;
	--ltp-accent: #FD0000;
	--ltp-star: #FFC967;
	--ltp-muted: #6B5F5B;
	--ltp-line: #E7E0CC;
	--ltp-radius: 10px;
	--ltp-gap: clamp(24px, 4vw, 56px);
	--ltp-pad-y: clamp(20px, 3vw, 44px);
	--ltp-pad-x: clamp(16px, 2.5vw, 32px);

	/*
	 * Widest the *content* grows to. No cap by default: the catalog and the
	 * slider are dropped into a page the theme has already sized. The detail
	 * page sets a real value below, because it replaces the whole post content
	 * and a full-bleed theme would otherwise stretch its two-column rows to the
	 * width of the monitor.
	 */
	--ltp-max-width: 100%;

	container-type: inline-size;
	container-name: ltp;

	/*
	 * Size containment zeroes this element's intrinsic width, so any theme that
	 * sizes children by their content — a flex row, a float, an inline-block —
	 * would otherwise collapse the whole catalog to its padding. An explicit
	 * width opts out of intrinsic sizing in all of those cases.
	 */
	width: 100%;

	/*
	 * The cap lives in the padding, not in a max-width, so the background keeps
	 * running edge to edge while the content stays inside `--ltp-max-width`. A
	 * max-width here would centre the whole block instead and leave a strip of
	 * the theme's own colour down each side.
	 *
	 * The two are added rather than taken whichever is larger: the padding is
	 * asked for, and a page wide enough to trigger the cap should not quietly
	 * swallow it. So the cap decides where the content starts, and the padding
	 * always adds its own room on top.
	 */
	padding: var(--ltp-pad-y) calc(max(0px, (100% - var(--ltp-max-width)) / 2) + var(--ltp-pad-x));
	background: var(--ltp-bg);
	color: var(--ltp-ink);
}

/*
 * Form controls never inherit their font, so they are named explicitly. The
 * chosen webfont itself is applied from an inline rule the plugin prints when
 * a font is selected — see LTP_Shortcode::inline_vars(). It has to target the
 * descendants rather than rely on inheritance, because a theme rule as ordinary
 * as `h1 { font-family: … }` matches the element itself and inheritance always
 * loses to that.
 */
.ltp button,
.ltp input,
.ltp select,
.ltp textarea {
	font-family: inherit;
}

/* The root is included too: it carries both `width: 100%` and padding. */
.ltp,
.ltp *,
.ltp *::before,
.ltp *::after {
	box-sizing: border-box;
}

.ltp-sr {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/*
 * Theme margins.
 *
 * Themes style bare `p`, `ul` and `h3` inside the content area through a
 * selector like `.entry-content p { margin-bottom: 1.5em }`. That is two
 * classes' worth of specificity, so it outranks a plain `.ltp-card__spec` and
 * quietly pads every card and every track — which is what pushed the slider
 * arrows a long way below the photos. The class is doubled here so the
 * plugin's own spacing wins without reaching for !important.
 *
 * Only the blocks whose margin is genuinely zero live in this list; the few
 * that carry a real margin are restated below at the same weight.
 */
.ltp.ltp .ltp-grid,
.ltp.ltp .ltp-slider__track,
.ltp.ltp .ltp-card,
.ltp.ltp .ltp-card__title,
.ltp.ltp .ltp-card__spec,
.ltp.ltp .ltp-filter__list,
.ltp.ltp .ltp-filter__row,
.ltp.ltp .ltp-empty,
.ltp.ltp .ltp__count {
	margin: 0;
}

/*
 * The same story for the list indent and the bullets. `.entry-content ul` also
 * carries `padding-left: 1.5em` in most themes, which would push the first
 * product a finger's width in from everything above it.
 */
.ltp.ltp .ltp-grid,
.ltp.ltp .ltp-slider__track,
.ltp.ltp .ltp-card,
.ltp.ltp .ltp-filter__row {
	padding: 0;
	list-style: none;
}

.ltp.ltp .ltp-card__code {
	margin: 4px 0 0;
}

/* ---------- Layout ---------- */

.ltp__layout {
	--ltp-mode: dropdown;

	display: grid;
	gap: var(--ltp-gap);
	align-items: start;
}

.ltp__main {
	container-type: inline-size;
	container-name: ltpmain;
	min-width: 0;
}

/*
 * The filter column and the space beside it are separate settings on purpose.
 * A wide column with a short gap reads as crowded even though the two never
 * touch, because the category names run right up to the products; a narrow
 * column with the same gap reads as roomy. `filter_width` and `filter_gap`
 * move them independently.
 */
@container ltp (min-width: 720px) {
	.ltp__layout {
		--ltp-mode: list;
	}

	.ltp--has-filter .ltp__layout {
		grid-template-columns: var(--ltp-filter-col, minmax(170px, 22%)) minmax(0, 1fr);
		column-gap: var(--ltp-filter-gap, clamp(28px, 6cqi, 88px));
	}
}

/* ---------- Category filter ---------- */

.ltp-filter {
	border: 1px solid var(--ltp-line);
	border-radius: var(--ltp-radius);
	padding: 0 16px;
}

/*
 * The size is the one the category names below it use, expression and all —
 * the toggle is the same list with everything but the current entry folded
 * away, and reading a size of its own left it visibly behind whenever
 * `filter_size` moved the names it stands in for.
 */
.ltp-filter__toggle {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	gap: 24px;
	width: 100%;
	padding: 15px 0;
	border: 0;
	background: none;
	color: var(--ltp-accent);
	font: inherit;
	font-weight: 700;
	font-size: var(--ltp-filter-font, clamp(0.9375rem, 0.5vw + 0.82rem, 1.25rem));
	line-height: 1.2;
	text-align: left;
	cursor: pointer;
}

.ltp-filter__caret {
	flex: 0 0 auto;
	width: 15px;
	height: 15px;
	fill: var(--ltp-accent);
	transition: transform 0.2s ease;
}

.ltp-filter.is-collapsed .ltp-filter__caret {
	transform: rotate(-90deg);
}

.ltp-filter.is-collapsed .ltp-filter__list {
	display: none;
}

.ltp.ltp .ltp-filter__list {
	list-style: none;
	margin: 0;
	padding: 0 0 10px;
	max-height: 55vh;
	overflow-y: auto;
	border-top: 1px solid var(--ltp-line);
}

.ltp-filter__row {
	margin: 0;
}

/*
 * The weight falls back to `inherit` rather than a number, so leaving the
 * attribute alone keeps whatever the theme gives a link here — which is what
 * happened before there was an attribute at all.
 */
.ltp-filter__item {
	display: block;
	padding: 11px 0;
	color: var(--ltp-ink);
	font-size: var(--ltp-filter-font, clamp(0.9375rem, 0.5vw + 0.82rem, 1.25rem));
	font-weight: var(--ltp-filter-weight, inherit);
	line-height: 1.25;
	text-decoration: none;
	transition: color 0.15s ease;
}

.ltp-filter__item:hover,
.ltp-filter__item:focus-visible,
.ltp-filter__item.is-active {
	color: var(--ltp-accent);
}

@container ltp (min-width: 720px) {
	.ltp-filter {
		border: 0;
		padding: 0;
	}

	.ltp-filter__toggle {
		padding: 0 0 30px;
	}

	/* Bumped in step with the base rule above, which is now one class heavier. */
	.ltp.ltp .ltp-filter__list {
		max-height: none;
		overflow: visible;
		border-top: 0;
		padding-bottom: 0;
	}

	.ltp-filter__item {
		padding: 12px 0;
	}
}

/* ---------- Search + count ---------- */

.ltp__bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 12px 20px;
	margin: 0 0 clamp(20px, 3vw, 36px);
}

.ltp-search {
	display: flex;
	align-items: center;
	gap: 8px;
	flex: 1 1 200px;
	max-width: 380px;
	padding: 6px 6px 6px 16px;
	border: 1px solid var(--ltp-line);
	border-radius: 999px;
	background: transparent;
}

.ltp-search:focus-within {
	border-color: var(--ltp-accent);
}

.ltp-search__input {
	flex: 1 1 auto;
	min-width: 0;
	padding: 4px 0;
	border: 0;
	background: transparent;
	color: var(--ltp-ink);
	font: inherit;
	font-size: 0.9375rem;
}

.ltp-search__input:focus {
	outline: none;
}

.ltp-search__submit {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: 34px;
	height: 34px;
	border: 0;
	border-radius: 50%;
	background: var(--ltp-accent);
	color: #fff;
	cursor: pointer;
}

.ltp-search__submit svg {
	width: 17px;
	height: 17px;
	fill: currentColor;
}

.ltp__count {
	margin: 0;
	color: var(--ltp-muted);
	font-size: 0.875rem;
}

/* ---------- Product grid ---------- */

/*
 * `products_width` caps the grid and the pagination under it and centres the
 * pair, leaving the search box and the count on the full width of the column.
 * A cap rather than a width: a narrow theme still gets everything it has.
 */
.ltp__results {
	max-width: var(--ltp-results-w, 100%);
	margin-inline: auto;
	transition: opacity 0.15s ease;
}

.ltp__results[aria-busy="true"] {
	opacity: 0.4;
	pointer-events: none;
}

.ltp-grid {
	--ltp-grid-gap-y: clamp(26px, 4vw, 58px);
	--ltp-grid-gap-x: clamp(14px, 2.5vw, 40px);

	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ltp-grid-gap-y) var(--ltp-grid-gap-x);
	list-style: none;
	margin: 0;
	padding: 0;
}

@container ltpmain (min-width: 420px) {
	.ltp-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@container ltpmain (min-width: 700px) {
	.ltp-grid {
		grid-template-columns: repeat(var(--ltp-cols, 3), minmax(0, 1fr));
	}
}

/*
 * fit_columns="yes".
 *
 * An ordinary column is an equal share of the row, so a photo narrower than
 * its share sits in the middle of it with the leftover split either side —
 * which is why `gap="0"` still looks airy: the space is inside the card, not
 * between cards. Sizing the column to its content instead makes `gap` the
 * distance between one picture and the next, the same as it is in the slider.
 *
 * Turning this on turns fit_image on too: without a photo of a definite size
 * for the column to measure, `max-content` would fall back to the file's own
 * full width.
 *
 * The trade is that a column is only as wide as its widest photo, so a long
 * product name wraps into more lines than it would in an even grid.
 */
.ltp--fit-columns .ltp-grid {
	grid-template-columns: repeat(2, max-content);
	justify-content: center;
}

@container ltpmain (min-width: 420px) {
	.ltp--fit-columns .ltp-grid {
		grid-template-columns: repeat(3, max-content);
	}
}

@container ltpmain (min-width: 700px) {
	.ltp--fit-columns .ltp-grid {
		grid-template-columns: repeat(var(--ltp-cols, 3), max-content);
	}
}

/* Same trick as the slider: the name must not decide the column's width. */
.ltp--fit-columns .ltp-grid > .ltp-card > .ltp-card__body {
	width: 0;
	min-width: var(--ltp-card-text-w, 100%);
}

/*
 * `fit-content` shrinks to the photo but clamps to the space available, and
 * under a `max-content` column that space is itself being worked out from this
 * box — so the two chase each other and the photo ends up stretched to the
 * column, losing the common height fit_image just gave it. `max-content` asks
 * only how wide the photo wants to be at its fixed height, and nothing else.
 */
.ltp--fit-columns .ltp-card__media {
	width: max-content;
}

/*
 * The gap here is the one between the photo and the name under it, which the
 * `image_gap` attribute writes. It is separate from `gap`, which is the space
 * between one card and the next.
 */
.ltp-card {
	display: flex;
	flex-direction: column;
	gap: var(--ltp-card-gap, clamp(10px, 1.5vw, 18px));
	margin: 0;
	text-align: center;
}

/*
 * The photo sits in a fixed square. The image is taken out of flow so a tall
 * product cannot stretch the box — that keeps every title in a row on the
 * same line, the way the printed catalog does.
 *
 * The shortcodes can override the box through --ltp-media-w / --ltp-media-h.
 * A set height wins over the aspect ratio, because a box with both dimensions
 * definite ignores aspect-ratio by definition.
 */
/*
 * `image_padding` writes the padding. It reaches the photo two different ways,
 * because the photo is laid out two different ways: in fit_image mode it is in
 * normal flow and ordinary padding pushes it in, but everywhere else it is
 * absolutely positioned, and an absolute box is placed against the padding box
 * — so padding alone would move nothing. There the room is taken out of the
 * photo's own maximum size instead, further down.
 */
.ltp-card__media {
	position: relative;
	width: 100%;
	max-width: var(--ltp-media-w, 100%);
	height: var(--ltp-media-h, auto);
	aspect-ratio: var(--ltp-media-ar, 1 / 1);
	margin-inline: auto;
	padding: var(--ltp-media-pad, 0px);
	border: 0;
	background: none;
}

.ltp-card__media--zoom {
	cursor: zoom-in;
}

.ltp-card__media--link {
	display: block;
	text-decoration: none;
}

.ltp-card__link {
	color: inherit;
	text-decoration: none;
}

.ltp-card__link:hover,
.ltp-card__link:focus-visible {
	color: var(--ltp-accent);
	text-decoration: underline;
	text-underline-offset: 3px;
}

.ltp-card__media--link:hover ~ .ltp-card__body .ltp-card__link {
	color: var(--ltp-accent);
}

/*
 * The photo keeps filling the whole box and staying centred in it; the padding
 * is taken off its ceiling instead. Same result as inset padding, and it works
 * where real padding cannot reach.
 */
.ltp-card__img {
	position: absolute;
	inset: 0;
	margin: auto;
	display: block;
	width: auto;
	height: auto;
	max-width: calc(100% - 2 * var(--ltp-media-pad, 0px));
	max-height: calc(100% - 2 * var(--ltp-media-pad, 0px));
	object-fit: contain;
	transition: transform 0.25s ease;
}

.ltp-card__media--zoom:hover .ltp-card__img,
.ltp-card__media--zoom:focus-visible .ltp-card__img {
	transform: scale(1.04);
}

.ltp-card__placeholder {
	position: absolute;
	inset: 12%;
	display: block;
	border: 1px dashed var(--ltp-line);
	border-radius: var(--ltp-radius);
}

/*
 * The badge hangs off the photo box, so where it lands depends on how big that
 * box is. With fit_image the box is the photo, and the badge sits on the
 * photo's own corner whatever shape the product is; without it the box is the
 * full square and a narrow product leaves the badge stranded out to the side.
 * `star_top` / `star_right` nudge it either way, and may be negative.
 */
.ltp-star {
	position: absolute;
	/* Follows the photo in when the box is padded, unless told otherwise. */
	top: var(--ltp-star-top, var(--ltp-media-pad, 0px));
	right: var(--ltp-star-right, var(--ltp-media-pad, 0px));
	/*
	 * `star_size` replaces the responsive default outright rather than capping
	 * it: a badge asked for at 60px is wanted at 60px, not at 18% of whatever
	 * the photo box happens to be on a phone.
	 */
	width: var(--ltp-star-size, clamp(24px, 18%, 46px));
	height: auto;
	fill: var(--ltp-star);
	pointer-events: none;
}

/*
 * The name and the size line run the full width of the card by default, which
 * on a narrow product leaves a one-line title stretching far past the photo
 * above it. `text_width` caps the text block and centres it, so the writing
 * can be brought back in line with the picture.
 */
.ltp-card__body {
	display: flex;
	flex-direction: column;
	gap: 2px;
	/*
	 * `100%` rather than `auto`. An auto margin in the cross axis switches a
	 * flex item out of stretching and into shrink-to-fit, so with `auto` here
	 * every card's text would collapse to the width of its longest word — a
	 * default nobody asked for. A stated width leaves the margins nothing to
	 * distribute until the setting narrows it.
	 */
	width: var(--ltp-card-text-w, 100%);
	max-width: 100%;
	margin-inline: auto;
}

/*
 * Two sizes, not one. `--ltp-card-font` is the shared fallback the `font_size`
 * attribute still writes, and `heading_size` / `subheading_size` override the
 * name and the size line on their own.
 */
.ltp-card__title {
	margin: 0;
	color: var(--ltp-ink);
	font-size: var(--ltp-heading-font, var(--ltp-card-font, clamp(0.8125rem, 0.3vw + 0.75rem, 1rem)));
	font-weight: 700;
	line-height: 1.3;
	letter-spacing: 0.01em;
	text-transform: uppercase;
}

.ltp-card__spec {
	margin: 0;
	color: var(--ltp-ink);
	font-size: var(--ltp-subheading-font, var(--ltp-card-font, clamp(0.8125rem, 0.3vw + 0.75rem, 1rem)));
	line-height: 1.35;
}

/*
 * Narrow containers get their own pair of sizes. A card is roughly a third of
 * the width here, and a name like "Garden Valves – Bent Nose Heavy Duty" runs
 * to five lines at the size that reads well on a desktop. The `_mobile`
 * attributes write these; without them nothing changes, because each falls
 * back to the value it would have had anyway.
 *
 * 760px is the same line the slider already draws: below it the track shows
 * three products instead of the number the shortcode asked for.
 */
@container ltp (max-width: 759px) {
	.ltp-card__title {
		font-size: var(--ltp-heading-font-m, var(--ltp-heading-font, var(--ltp-card-font, clamp(0.8125rem, 0.3vw + 0.75rem, 1rem))));
	}

	.ltp-card__spec {
		font-size: var(--ltp-subheading-font-m, var(--ltp-subheading-font, var(--ltp-card-font, clamp(0.8125rem, 0.3vw + 0.75rem, 1rem))));
	}
}

.ltp-card__code {
	margin: 4px 0 0;
	color: var(--ltp-muted);
	font-size: 0.75rem;
	letter-spacing: 0.04em;
}

.ltp-empty {
	margin: 0;
	padding: clamp(28px, 6vw, 64px) 0;
	color: var(--ltp-muted);
	text-align: center;
}

/* ---------- Slider ---------- */

.ltp.ltp .ltp-slider__heading {
	margin: 0 0 clamp(18px, 3cqi, 36px);
	color: var(--ltp-ink);
	font-size: var(--ltp-title-font, clamp(1.25rem, 2cqi + 0.9rem, 2rem));
	font-weight: 700;
	text-align: center;
}

/*
 * Wide enough: arrows flank the track, as in the printed design. On small
 * screens flanking arrows would eat almost a third of the row, so the track
 * takes the full width and the arrows wrap underneath — bigger cards, and a
 * tap target that sits where the thumb already is.
 *
 * One custom property drives both directions, because the same distance reads
 * as the space beside the photos on a desktop and the space beneath them on a
 * phone. The `arrow_gap` attribute writes it.
 */
.ltp-slider {
	--ltp-nav-gap-y: clamp(14px, 2cqi, 22px);
	--ltp-nav-gap-x: clamp(8px, 1.5cqi, 26px);

	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--ltp-nav-gap, var(--ltp-nav-gap-y)) var(--ltp-nav-gap, var(--ltp-nav-gap-x));
}

.ltp-slider__nav--prev {
	order: 1;
}

.ltp-slider__nav--next {
	order: 2;
}

@container ltp (min-width: 560px) {
	.ltp-slider {
		flex-wrap: nowrap;
	}

	.ltp-slider__nav--prev {
		order: 0;
	}
}

/*
 * `flex-start`, not the default `stretch`: a stretched card reports the height
 * of the tallest product in the whole track, and the script needs each card's
 * own height to work out how much empty space sits under the ones on screen.
 * Nothing moves visually — card content was top-aligned either way.
 */
.ltp-slider__track {
	display: flex;
	align-items: flex-start;
	flex: 1 1 100%;
	order: 0;
	min-width: 0;
	gap: var(--ltp-slide-gap);
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	-ms-overflow-style: none;

	/*
	 * Phones and tablets show three; wider containers use the shortcode value.
	 * `slides_mobile` replaces the three, and may be fractional — 1.5 leaves
	 * half of the next product showing at the edge.
	 */
	--ltp-slides: var(--ltp-slides-mobile, 3);
	--ltp-slide-gap: clamp(10px, 2.5cqi, 40px);
}

.ltp-slider__track::-webkit-scrollbar {
	display: none;
}

.ltp-slider__track:focus-visible {
	outline: 2px solid var(--ltp-accent);
	outline-offset: 4px;
}

.ltp-slider__track > .ltp-card {
	flex: 0 0 calc((100% - (var(--ltp-slides) - 1) * var(--ltp-slide-gap)) / var(--ltp-slides));
	/* Without this the flex item's automatic minimum size (its content width)
	   overrides the basis and fewer slides fit than asked for. */
	min-width: 0;
	scroll-snap-align: start;
}

@container ltp (min-width: 760px) {
	.ltp-slider__track {
		--ltp-slides: var(--ltp-slides-max, 4);
	}
}

/*
 * fit_image="yes".
 *
 * Every photo the same height, its width following its own proportions, and
 * the box shrunk to fit it exactly. Three things follow from that:
 *
 *  - photos line up at a common height instead of each one sitting differently
 *    inside a square;
 *  - the best-seller badge lands on the photo's corner rather than the corner
 *    of an empty box beside it;
 *  - in the slider the slide is as wide as its photo, so `gap` becomes the real
 *    distance between one picture and the next. `slides` stops applying there —
 *    how many fit now depends on how wide the photos are.
 *
 * The photo is put back in the normal flow so the box can shrink to it; a
 * fixed height (`image_height`, 160px otherwise) is what gives it a width to
 * shrink to.
 */
.ltp--fit-image .ltp-slider__track > .ltp-card {
	flex: 0 0 auto;
}

.ltp--fit-image .ltp-card__media {
	width: fit-content;
	max-width: none;
	height: var(--ltp-media-h, 160px);
	aspect-ratio: auto;
	margin-inline: auto;
}

.ltp--fit-image .ltp-card__img {
	position: static;
	width: auto;
	height: 100%;
	max-width: none;
	max-height: none;
}

/*
 * A product with no photo has nothing for the box to shrink to, so the dashed
 * placeholder is given the width the box would otherwise have had. Taking it
 * out of the absolute flow is what lets it carry that width.
 */
.ltp--fit-image .ltp-card__placeholder {
	position: static;
	inset: auto;
	width: var(--ltp-media-h, 160px);
	height: 100%;
}

/*
 * Zero contribution to the card's own width, then full width once the card has
 * one: a percentage minimum is ignored while the browser measures content, so
 * the longest product name no longer decides how wide the slide is. Only the
 * slider needs this — a grid column already has a width of its own.
 */
.ltp--fit-image .ltp-slider__track > .ltp-card > .ltp-card__body {
	width: 0;
	min-width: var(--ltp-card-text-w, 100%);
}

/*
 * The track is as tall as the longest product name in the whole set, even
 * while that product is scrolled out of sight — so a screen full of short
 * names leaves a band of empty space the arrows would otherwise sit below.
 *
 * The script measures the products actually on screen and writes the leftover
 * as `--ltp-nav-shift`; this negative margin takes it back. One declaration
 * covers both layouts, because a flex item's negative top margin moves it by
 * the full amount on its own line (phone, arrows underneath) and by half of it
 * when the line is centre-aligned (desktop, arrows beside the track) — which
 * is exactly the correction each one needs.
 *
 * `position: relative` keeps the buttons painting above the track once they
 * move up into its box.
 */
.ltp-slider__nav {
	position: relative;
	z-index: 1;
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: clamp(38px, 3.2cqi, 46px);
	height: clamp(38px, 3.2cqi, 46px);
	margin-top: calc(-1 * var(--ltp-nav-shift, 0px));
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: var(--ltp-accent);
	color: #fff;
	cursor: pointer;
	transition: opacity 0.15s ease, transform 0.15s ease, margin-top 0.2s ease;
}

.ltp-slider__nav svg {
	width: 58%;
	height: 58%;
	fill: none;
	stroke: currentColor;
	stroke-width: 2.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.ltp-slider__nav:hover:not(:disabled) {
	transform: scale(1.06);
}

.ltp-slider__nav:disabled {
	opacity: 0.3;
	cursor: default;
}

/*
 * Nothing to scroll, or no script yet: drop the controls rather than showing
 * dead buttons. `display: grid` above is an author rule and would otherwise
 * beat the user agent's `[hidden] { display: none }`, so the template's hidden
 * attribute has to be honoured explicitly.
 */
.ltp--slider.is-static .ltp-slider__nav,
.ltp-slider__nav[hidden] {
	display: none;
}

.ltp.ltp .ltp-slider__more {
	margin: clamp(24px, 4cqi, 46px) 0 0;
	text-align: center;
}

.ltp-btn {
	display: inline-block;
	padding: 14px 40px;
	border-radius: 5px;
	background: var(--ltp-accent);
	color: #fff;
	font-size: 0.9375rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	line-height: 1.2;
	text-decoration: none;
	text-transform: uppercase;
	transition: filter 0.15s ease;
}

.ltp-btn:hover,
.ltp-btn:focus-visible {
	color: #fff;
	filter: brightness(0.9);
}

/* ---------- Category banner ---------- */

/*
 * [lucky_top_banner]. The photograph above the catalog, swapped for another one
 * by the script as the category filter is used — see LTP_Banner.
 *
 * The wrapper is a `.ltp` like every other block, for the palette and the
 * border-box reset, but its padding starts closed: a banner is a picture
 * running the width of whatever holds it, and the room the catalog leaves
 * around itself would read as a mount around this one. The `padding` and
 * `max_width` attributes write these same two properties to open it back up.
 */
.ltp--banner {
	--ltp-pad-y: 0;
	--ltp-pad-x: 0;
}

/*
 * Doubled against the theme for the same reason as the cards: a rule as
 * ordinary as `.entry-content img { margin-bottom: 1.5em }` outranks a single
 * class, and the gap it leaves under the banner reads as a mistake.
 *
 * `object-fit` only does anything once there is a height to fit inside, so a
 * banner left at its own proportions ignores it — which is why `fit` is
 * documented as needing `height`.
 */
.ltp.ltp .ltp-banner__img {
	display: block;
	width: 100%;
	max-width: 100%;
	height: var(--ltp-banner-h, auto);
	margin: 0;
	border-radius: var(--ltp-banner-radius, 0);
	object-fit: var(--ltp-banner-fit, cover);
}

/*
 * A phone held upright gets the photograph at its own proportions, whatever
 * `height` says.
 *
 * The banners are wide — around 16:9 — and a fixed height that reads as a broad
 * strip on a desktop is nearly square by the time the page is 375px across.
 * `cover` then has to throw away most of the width to fill it, and a row of
 * fittings laid out left to right is exactly the picture that cannot afford to
 * lose its sides. Dropping the height lets it stay landscape, and it takes up
 * less of the screen doing so.
 *
 * A media query rather than a container query, unlike the rest of this
 * stylesheet: this is about the shape of the device the page is being read on,
 * and `orientation` is not something a container can be asked about. The width
 * bound keeps tablets out of it — 768px against a 360px height is already a
 * wider strip than the picture itself.
 */
@media (max-width: 600px) and (orientation: portrait) {
	.ltp.ltp .ltp-banner__img {
		height: auto;
	}
}

/*
 * Two or more pictures: a scroll-snap track, the same as the product slider.
 * The browser does the scrolling, the swiping and the keyboard; the script only
 * moves scrollLeft on a timer and lights the right dot. Without the script it
 * is still a row of banners that can be swiped through by hand.
 *
 * `relative` is what the dots are positioned against.
 */
.ltp-banner__slider {
	position: relative;
}

.ltp.ltp .ltp-banner__track {
	display: flex;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	-ms-overflow-style: none;
}

/* Hidden here, unlike the product slider's: a bar drawn across the bottom of a
   photograph is a mark on the photograph. The dots say the same thing. */
.ltp-banner__track::-webkit-scrollbar {
	display: none;
}

.ltp-banner__track:focus-visible {
	outline: 2px solid var(--ltp-accent);
	outline-offset: 4px;
}

.ltp.ltp .ltp-banner__slide {
	flex: 0 0 100%;
	/* Without this the flex item's automatic minimum size — the picture's own
	   width — beats the basis, and two banners share one screen. */
	min-width: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	scroll-snap-align: start;
}

.ltp.ltp .ltp-banner__dots {
	position: absolute;
	left: 50%;
	bottom: var(--ltp-banner-dots-inset, clamp(12px, 2cqi, 26px));
	z-index: 1;
	transform: translateX(-50%);
	display: flex;
	gap: var(--ltp-banner-dot-gap, 10px);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ltp.ltp .ltp-banner__dots li {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * White with a hairline round it rather than a flat colour: the dots sit on a
 * photograph that is light in some places and dark in others, and a plain white
 * dot disappears into the first pale corner it lands on.
 */
.ltp.ltp .ltp-banner__dot {
	display: block;
	width: var(--ltp-banner-dot, 10px);
	height: var(--ltp-banner-dot, 10px);
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: #fff;
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.22);
	opacity: 0.65;
	cursor: pointer;
	transition: opacity 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
}

.ltp.ltp .ltp-banner__dot:hover {
	opacity: 1;
}

.ltp.ltp .ltp-banner__dot.is-active {
	background: var(--ltp-accent);
	opacity: 1;
	transform: scale(1.2);
}

/* ---------- Pagination ---------- */

.ltp-pagination {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 8px;
	margin-top: clamp(30px, 4vw, 56px);
}

/*
 * `.page-numbers` is a core class, so it is one of the few pieces of markup a
 * theme is likely to have styled already — usually as `.pagination .page-numbers`
 * or similar, which outranks the catalog's own font rule. Naming the family
 * here, one class heavier, keeps the numbers in the configured webfont
 * alongside the rest of the catalog. `--ltp-font` is printed by the plugin
 * only when a font is chosen; without one the numbers inherit as before.
 *
 * The size and weight are their own properties so a site can dial the numbers
 * back down from Additional CSS without restating the rest of the button.
 */
.ltp.ltp .ltp-pagination .page-numbers {
	display: inline-grid;
	place-items: center;
	min-width: 46px;
	height: 46px;
	padding: 0 14px;
	border: 1px solid var(--ltp-line);
	border-radius: 8px;
	color: var(--ltp-ink);
	font-family: var(--ltp-font, inherit);
	font-size: var(--ltp-page-size, 1.375rem);
	font-weight: var(--ltp-page-weight, 700);
	line-height: 1;
	text-decoration: none;
}

.ltp.ltp .ltp-pagination a.page-numbers:hover,
.ltp.ltp .ltp-pagination a.page-numbers:focus-visible {
	border-color: var(--ltp-accent);
	color: var(--ltp-accent);
}

.ltp.ltp .ltp-pagination .page-numbers.current {
	border-color: var(--ltp-accent);
	background: var(--ltp-accent);
	color: #fff;
}

.ltp.ltp .ltp-pagination .page-numbers.dots {
	border-color: transparent;
}

/* ---------- Single product page ---------- */

.ltp--single {
	--ltp-panel: #EEEACD;

	/* Wider than this and the hero's two columns drift too far apart to read
	   as one unit, however much room the theme hands over. */
	--ltp-max-width: 1440px;

	/* Edge of the square the product photo is fitted into. */
	--ltp-hero-size: 200px;
}

.ltp-hero {
	display: grid;
	gap: clamp(16px, 3cqi, 48px);
	align-items: center;
	margin-bottom: var(--ltp-hero-gap, clamp(32px, 6cqi, 88px));
}

/*
 * The photo column is exactly as wide as the photo box, so the name and the
 * model list get every pixel that is left. A fractional column would leave a
 * 200px picture floating in the middle of an empty half-page.
 *
 * Centring the pair needs the opposite: a text column that stops at its own
 * content, or there is nothing left over for `justify-content` to centre. The
 * alignment setting swaps the track for `max-content` and sets the alignment
 * together, because either one alone does nothing.
 *
 * Both live in here, so the alignment only applies while there is a pair to
 * align. Stacked on a phone it would centre the one column that is left,
 * indenting the name and the model pills away from every other heading on the
 * page — which is not what anyone means by centring the top block.
 */
@container ltp (min-width: 640px) {
	.ltp-hero {
		grid-template-columns: var(--ltp-hero-size, 200px) var(--ltp-hero-body-col, minmax(0, 1fr));
		justify-content: var(--ltp-hero-justify, normal);
	}
}

.ltp-hero__media {
	display: grid;
	place-items: center;
	width: 100%;
	/* Also caps the photo in the stacked layout, where there is no column. */
	max-width: var(--ltp-hero-size, 200px);
	min-width: 0;
	margin-inline: auto;
}

/*
 * Height gets its own setting because product photos are rarely square. Left
 * empty it follows the column width, which is the square box it has always
 * been; set it and a tall valve can stand taller than the column is wide.
 */
.ltp-hero__img {
	display: block;
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: var(--ltp-hero-img-h, var(--ltp-hero-size, 200px));
	object-fit: contain;
}

.ltp-hero__body {
	min-width: 0;
}

/*
 * From here down the class is doubled for every rule that owns a margin, a
 * padding or a list style. All of these are bare `h1`, `p`, `ul` and `ol`
 * elements, and a theme rule as ordinary as `.entry-content p` outranks a
 * single class — see the "Theme margins" note at the top of this file.
 */
/*
 * Every size on this page is a custom property with the responsive default it
 * always had. Leave them alone and nothing changes; set one in Additional CSS
 * and that one piece of text is yours. See LT Products > Settings for the list.
 */
.ltp.ltp .ltp-hero__title {
	margin: 0;
	color: var(--ltp-accent);
	font-size: var(--ltp-hero-title-size, clamp(1.75rem, 5cqi, 4.375rem));
	font-weight: 700;
	line-height: 1.05;
	letter-spacing: -0.01em;
}

.ltp.ltp .ltp-hero__spec {
	margin: 0.6em 0 0;
	color: var(--ltp-ink);
	font-size: var(--ltp-hero-spec-size, clamp(0.9375rem, 1.2cqi, 1.25rem));
}

.ltp.ltp .ltp-hero__label {
	margin: var(--ltp-hero-label-gap, clamp(16px, 2.5cqi, 34px)) 0 0;
	font-size: var(--ltp-hero-label-size, clamp(1rem, 1.6cqi, 1.5625rem));
	font-weight: 700;
	letter-spacing: 0.01em;
}

.ltp.ltp .ltp-hero__code {
	margin: clamp(14px, 2cqi, 24px) 0 0;
	color: var(--ltp-muted);
	font-size: 0.875rem;
	letter-spacing: 0.04em;
}

/*
 * A grid rather than a wrapping flex row, so the number of pills on a line is
 * decided rather than left to whatever happens to fit: ten models used to run
 * off in one long line. The tracks are `max-content`, which lines the pills up
 * in columns down the page; `justify-items: start` keeps each pill at its own
 * width instead of stretching it to the widest one in its column.
 *
 * The count comes in from the template, already held down to however many
 * models the product has.
 */
.ltp.ltp .ltp-models {
	display: grid;
	grid-template-columns: repeat(var(--ltp-model-cols, 4), max-content);
	justify-items: start;
	gap: clamp(8px, 1.2cqi, 18px);
	margin: var(--ltp-model-gap, clamp(10px, 1.5cqi, 18px)) 0 0;
	padding: 0;
	list-style: none;
}

/*
 * The sides used to be about twice the top and bottom, which left the text
 * sitting in roughly half the pill. Closer to a third now, which is what the
 * printed design shows; the two are separate settings because the sides are
 * the half anyone ever wants to touch.
 */
.ltp.ltp .ltp-models__item {
	margin: 0;
	padding: var(--ltp-model-pad-y, clamp(6px, 0.8cqi, 12px)) var(--ltp-model-pad-x, clamp(12px, 1.1cqi, 18px));
	border-radius: 999px;
	background: var(--ltp-accent);
	color: #fff;
	font-size: var(--ltp-model-size, clamp(0.875rem, 1.4cqi, 1.5rem));
	font-weight: 500;
	line-height: 1.2;
	white-space: nowrap;
}

.ltp.ltp .ltp-section {
	margin: 0 0 var(--ltp-section-gap, clamp(36px, 6cqi, 96px));
}

/* Bumped alongside the rule above, or the base margin would outrank it. */
.ltp.ltp .ltp-section:last-child {
	margin-bottom: 0;
}

.ltp-section--center {
	text-align: center;
}

/*
 * Two sections can be pulled in from the sides on their own, without moving
 * anything else on the page: Warning & Notes, so its heading can sit further
 * in than the full-width rows around it, and the Maintenance panel, so the
 * panel itself narrows rather than the text inside it.
 *
 * Only from 760px up. On a phone the content is already about as narrow as it
 * can usefully be, and an inset meant for a monitor would leave two words a
 * line — so the setting is quietly ignored there rather than clamped to some
 * fraction of itself, which would be a third behaviour to keep in mind.
 */
@container ltp (min-width: 760px) {
	.ltp.ltp .ltp-section--warning {
		padding-inline: var(--ltp-warning-inset, 0px);
	}

	.ltp.ltp .ltp-section--panel {
		padding-inline: var(--ltp-panel-side, 0px);
	}
}

/*
 * `--ltp-scale` is the one dial a whole block of the page can be resized by.
 * It is 1 everywhere except inside the maintenance panel, which sets 0.8 and
 * pulls its heading, body text and padding down with it in one go.
 */
.ltp.ltp .ltp-section__title {
	margin: 0 0 calc(var(--ltp-section-title-gap, clamp(14px, 2cqi, 28px)) * var(--ltp-scale, 1));
	color: var(--ltp-accent);
	font-size: calc(var(--ltp-section-title-size, clamp(1.375rem, 2.6cqi, 2.25rem)) * var(--ltp-scale, 1));
	font-weight: 700;
	line-height: 1.2;
}

/*
 * Two headings get to break ranks with the rest. Both fall back to the general
 * setting above, so leaving them empty keeps every red heading on one number,
 * and filling one in moves only that block.
 */
.ltp.ltp .ltp-section--warning .ltp-section__title {
	margin-bottom: calc(var(--ltp-warning-title-gap, var(--ltp-section-title-gap, clamp(14px, 2cqi, 28px))) * var(--ltp-scale, 1));
}

.ltp.ltp .ltp-panel .ltp-section__title {
	margin-bottom: calc(var(--ltp-panel-title-gap, var(--ltp-section-title-gap, clamp(14px, 2cqi, 28px))) * var(--ltp-scale, 1));
}

.ltp.ltp .ltp-section__subtitle {
	margin: 0 0 calc(var(--ltp-section-subtitle-gap, clamp(12px, 1.6cqi, 22px)) * var(--ltp-scale, 1));
	color: var(--ltp-ink);
	font-size: calc(var(--ltp-section-subtitle-size, clamp(1.125rem, 2cqi, 1.875rem)) * var(--ltp-scale, 1));
	font-weight: 700;
	line-height: 1.2;
}

.ltp-prose {
	color: var(--ltp-ink);
	font-size: calc(var(--ltp-prose-size, clamp(0.9375rem, 1.1cqi, 1.0625rem)) * var(--ltp-scale, 1));
	line-height: 1.6;
}

.ltp.ltp .ltp-prose > :first-child {
	margin-top: 0;
}

.ltp.ltp .ltp-prose > :last-child {
	margin-bottom: 0;
}

/* Tables scroll sideways rather than squashing their columns on a phone. */
.ltp-tablewrap {
	max-width: 100%;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

/*
 * `--ltp-table-font` is the size the whole table works from, so the header can
 * be set relative to the body rather than restating the clamp. The modifiers
 * below redeclare it where they carry a size of their own; `--ltp-table-size`
 * stays the handle the settings screen writes to.
 */
.ltp-table {
	--ltp-table-font: var(--ltp-table-size, clamp(1.1875rem, 1.1cqi + 6px, 1.4375rem));

	width: 100%;
	border-collapse: collapse;
	background: transparent;
	font-size: var(--ltp-table-font);
	text-align: center;
}

/*
 * Margin only, on its own selector. Restating the whole rule one class heavier
 * would sink the `--dimension` and `--specs` modifiers below it.
 */
.ltp.ltp .ltp-table {
	margin: 0;
}

.ltp-table th,
.ltp-table td {
	padding: clamp(8px, 1.2cqi, 18px) clamp(10px, 1.5cqi, 22px);
	border: 1px solid var(--ltp-ink);
	font-weight: 400;
	white-space: nowrap;
}

/*
 * The column headings run a step above the measurements under them. Written as
 * an offset rather than a size of its own, so a table given a size from the
 * settings screen keeps the same gap between the two.
 */
.ltp-table thead th {
	font-weight: 700;
	font-size: calc(var(--ltp-table-font) + var(--ltp-table-head-step, 4px));
}

/*
 * Every column the same width. Left to itself the browser sizes each column to
 * its content, so the "Model" column ends up several times wider than the
 * measurements beside it — `table-layout: fixed` splits the width evenly
 * instead, because nothing here declares a column width.
 *
 * Both bounds are per column rather than per table, because the number of
 * columns is the only thing that says how much width this table can use. A
 * flat cap would leave a two-column table with two enormous cells and squeeze a
 * seven-column one; the count comes in from the template as `--ltp-table-cols`.
 *
 * The ceiling is what keeps a handful of three-digit measurements from being
 * spread across the whole page. The floor keeps the columns readable on a
 * phone; below it the wrapper scrolls sideways rather than crushing the
 * numbers. It stays under the ceiling at every count, so the two never fight.
 */
.ltp.ltp .ltp-table--dimension {
	table-layout: fixed;
	margin-inline: auto;
	width: 100%;
	min-width: calc(var(--ltp-table-cols, 4) * var(--ltp-dimension-col-min, 105px));
	max-width: calc(var(--ltp-table-cols, 4) * var(--ltp-dimension-col, 130px));
}

/* Headings may wrap now that the column width no longer follows the text. */
.ltp-table--dimension th,
.ltp-table--dimension td {
	white-space: normal;
}

/*
 * A cell holding a note that spans the rest of its row — see the table helper
 * in single-product.php. Centred over the columns it covers whichever table it
 * lands in, including the parts table, which is otherwise left-aligned, and
 * allowed to wrap because a note is a sentence rather than a measurement.
 */
.ltp-table__note {
	text-align: center;
	white-space: normal;
}

/*
 * "Dimension (mm)" sits over the table, not over the section, so it is given
 * the table's own width and then aligned inside it — a plain `text-align: left`
 * would take it out to the edge of the page, a long way left of the table it
 * belongs to.
 *
 * The same cap and the same centring as the table, so the two edges stay
 * together at every column count. On a narrow screen neither reaches its cap:
 * the heading fills the section and the table overflows it, and both start at
 * the same left edge anyway.
 */
.ltp.ltp .ltp-section--dimension .ltp-section__subtitle {
	max-width: calc(var(--ltp-table-cols, 4) * var(--ltp-dimension-col, 130px));
	margin-inline: auto;
	text-align: left;
}

/*
 * The parts table keeps its own size and its own flat heading — it is a dense
 * list beside a drawing, not the measurement table above, and the step the
 * dimension headings take would only cost it rows.
 */
.ltp-table--specs {
	--ltp-table-font: var(--ltp-specs-table-size, clamp(0.75rem, 0.9cqi, 0.9375rem));
	--ltp-table-head-step: 0px;

	text-align: left;
	font-size: var(--ltp-table-font);
}

.ltp-table--specs thead th {
	background: var(--ltp-ink);
	color: #fff;
	border-color: var(--ltp-ink);
	text-align: center;
}

.ltp-table--specs th,
.ltp-table--specs td {
	padding: 6px 10px;
	border-color: rgba(14, 0, 0, 0.55);
}

/*
 * The exploded diagram is a tall drawing and the parts table beside it is a
 * short block, so aligning both to the top left the table stranded against the
 * top third of the picture. Centred, the two read as one pair.
 *
 * Whichever is shorter is the one that moves; stacked below 760px each row
 * holds a single item, so this does nothing there.
 */
.ltp-specs {
	display: grid;
	gap: clamp(18px, 3cqi, 40px);
	align-items: center;
}

@container ltp (min-width: 760px) {
	.ltp-specs {
		grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
	}
}

/* `figure` carries a 1em/40px margin of its own in every user agent. */
.ltp.ltp .ltp-specs__diagram,
.ltp.ltp .ltp-specs__image {
	margin: 0;
	padding: clamp(10px, 1.5cqi, 20px);
	border-radius: var(--ltp-radius);
	background: #fff;
}

.ltp-specs__diagram img,
.ltp-specs__image img {
	display: block;
	width: 100%;
	height: auto;
}

.ltp-specs__body {
	min-width: 0;
}

/*
 * Installation steps.
 *
 * One row that scrolls sideways rather than wrapping onto a second line: the
 * steps are numbered, so reading order matters and a grid that reflows breaks
 * it. The native bar stays hidden here — the plugin draws its own underneath
 * instead, see `.ltp-scrollbar` below.
 */
.ltp.ltp .ltp-steps {
	--ltp-step-gap: clamp(14px, 2cqi, 28px);

	/*
	 * How many cards a screenful holds, counting the one hanging half off the
	 * right edge — three whole and a half of the fourth by default. That half
	 * card is the only thing telling anyone the row goes on, so the count is
	 * deliberately fractional. `--ltp-steps-shown` is the internal name; the
	 * settable one is only ever read, never declared, or a value from the
	 * settings screen could not reach past these rules.
	 */
	--ltp-steps-shown: var(--ltp-steps-visible, 1.5);

	display: flex;
	gap: var(--ltp-step-gap);

	/*
	 * `safe` is the whole point. A product with three steps leaves the half-card
	 * slot at the end of the row empty, and a row that stops two thirds of the
	 * way across reads as broken rather than as scrollable — so a set that fits
	 * is centred. A set that does not fit overflows, and centring an overflowing
	 * scroller would push step 01 off the left edge with no way to scroll back
	 * to it; `safe` drops the alignment back to the start in exactly that case.
	 *
	 * Browsers without `safe` discard the declaration and start the row at the
	 * left, which is where it was before.
	 */
	justify-content: safe center;

	margin: 0;
	padding: 0;
	list-style: none;
	text-align: left;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x proximity;
	scrollbar-width: none;
	-ms-overflow-style: none;
	-webkit-overflow-scrolling: touch;
}

.ltp-steps::-webkit-scrollbar {
	display: none;
}

/*
 * The scroll bar under the Installation steps, drawn by the plugin rather than
 * by the browser — see customScrollbar() in the script.
 *
 * A native bar cannot do this job on a phone. Both mobile platforms draw
 * scrollbars as an overlay that fades in while a finger is moving and fades
 * straight back out, and neither `::-webkit-scrollbar` nor `scrollbar-width`
 * can talk them out of it — iOS ignores custom scrollbar styling on these
 * scrollers altogether. So the row that most needs to say "there is more to the
 * right" is the one where the browser refuses to say it. Two elements and a
 * scroll handler is the only way to have a bar that is simply always there.
 *
 * Hidden until the script measures the row: without JavaScript there is nothing
 * to move the thumb, and a bar that never moves is worse than no bar. The row
 * itself still scrolls either way.
 */
.ltp-scrollbar {
	display: none;
	position: relative;
	height: var(--ltp-scrollbar-size, 10px);
	margin-top: var(--ltp-scrollbar-gap, clamp(12px, 1.6cqi, 22px));

	/*
	 * The thumb is dragged with pointer events, and on a touch screen the
	 * browser would otherwise claim the gesture and scroll the page with it
	 * before the first move event arrives.
	 */
	touch-action: none;
}

.ltp-scrollbar.is-active {
	display: block;
}

/*
 * No rail behind it. The steps sit on the page's own background and a groove
 * ruled under them reads as part of the layout rather than as a control — the
 * thumb alone is enough to say how far along the row is.
 */
.ltp-scrollbar__thumb {
	position: absolute;
	top: 0;
	left: 0;
	height: 100%;

	/* Long rows would otherwise end up with a thumb too small to take hold of. */
	min-width: 28px;

	border-radius: 999px;
	background: var(--ltp-accent);
	cursor: grab;
}

.ltp-scrollbar.is-dragging .ltp-scrollbar__thumb {
	cursor: grabbing;
}

/*
 * Drag to scroll, added by the script — see dragScroll().
 *
 * Both classes come from JavaScript, so a page without it shows the ordinary
 * cursor and nothing pretends to be draggable.
 *
 * Snapping is switched off for the length of the gesture. Left on, every pixel
 * of the drag would be fought by the browser pulling the row back to the
 * nearest card; it comes back on release, which is what settles the row.
 */
.ltp.ltp .ltp-steps.is-draggable,
.ltp.ltp .ltp-slider__track.is-draggable {
	cursor: grab;
}

/*
 * The class is doubled here for the same reason as everywhere else in this
 * file, but against the plugin's own rules rather than a theme's: the row that
 * declares `scroll-snap-type` is `.ltp.ltp .ltp-steps`, which outranks a bare
 * `.ltp-steps.is-dragging` and would leave the snapping on for the whole drag.
 */
.ltp.ltp .ltp-steps.is-dragging,
.ltp.ltp .ltp-slider__track.is-dragging {
	cursor: grabbing;
	scroll-snap-type: none;
	user-select: none;
	-webkit-user-select: none;
}

/* Nothing inside the row may swallow the pointer mid-drag. */
.ltp.ltp .ltp-steps.is-dragging *,
.ltp.ltp .ltp-slider__track.is-dragging * {
	pointer-events: none;
}

@container ltp (min-width: 560px) {
	.ltp.ltp .ltp-steps {
		--ltp-steps-shown: var(--ltp-steps-visible, 2.5);
	}
}

@container ltp (min-width: 900px) {
	.ltp.ltp .ltp-steps {
		--ltp-steps-shown: var(--ltp-steps-visible, 3.5);
	}
}

/*
 * Neither grow nor shrink. A card that grows would swallow the leftover and
 * sit flush with the right edge, and a row that ends flush looks finished —
 * the half card is what makes it read as continuing.
 *
 * Whole cards on screen is the count minus its half, so that is also how many
 * gaps sit between them. `--ltp-step-w` overrides the lot with a flat width.
 */
.ltp.ltp .ltp-steps__item {
	flex: 0 0 var(
		--ltp-step-w,
		calc(
			(100% - (var(--ltp-steps-shown) - 0.5) * var(--ltp-step-gap))
			/ var(--ltp-steps-shown)
		)
	);
	display: flex;
	flex-direction: column;
	gap: clamp(14px, 2cqi, 26px);
	min-width: 0;
	margin: 0;
	padding: clamp(18px, 2.4cqi, 30px);
	border-radius: 18px;
	background: var(--ltp-panel);
	scroll-snap-align: start;
}

/*
 * The circle and the number inside it are separate settings: growing the text
 * alone would push it out of a 44px disc, and growing the disc alone would
 * leave a small number rattling around in it.
 */
.ltp-steps__no {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: var(--ltp-step-no, 44px);
	height: var(--ltp-step-no, 44px);
	border-radius: 50%;
	background: var(--ltp-accent);
	color: #fff;
	font-size: var(--ltp-step-no-font, 0.9375rem);
	font-weight: 700;
	line-height: 1;
}

.ltp-steps__text {
	color: var(--ltp-ink);
	font-size: var(--ltp-step-size, clamp(0.875rem, 1cqi, 1rem));
	line-height: 1.5;
}

/*
 * Maintenance + tools.
 *
 * These two close the page off rather than carrying it, so the whole block is
 * set at 80% of the size it would have anywhere else. `--ltp-scale` inherits,
 * which is the point: the headings, the body copy, the tool list and the
 * padding all follow from this one number without being restated.
 */
.ltp.ltp .ltp-panel {
	/*
	 * `--ltp-scale` is read by rules that also run outside this panel, so it
	 * has to be declared here rather than up on the page. `--ltp-panel-scale`
	 * is the handle that can be set anywhere and still reach it.
	 */
	--ltp-scale: var(--ltp-panel-scale, 0.8);

	/*
	 * Worked out once because each is needed in three places. The two paddings
	 * split apart so the block can be given height without also being given
	 * width, which is what a stacked panel on a phone usually wants; both fall
	 * back to the single all-round setting.
	 */
	--ltp-panel-space: calc(var(--ltp-panel-gap, clamp(20px, 3cqi, 46px)) * var(--ltp-scale));
	--ltp-panel-inset: calc(var(--ltp-panel-pad-y, var(--ltp-panel-pad, clamp(22px, 3.5cqi, 56px))) * var(--ltp-scale));
	--ltp-panel-inset-x: calc(var(--ltp-panel-pad-x, var(--ltp-panel-pad, clamp(22px, 3.5cqi, 56px))) * var(--ltp-scale));

	position: relative;
	display: grid;
	gap: var(--ltp-panel-space);
	margin: 0;
	padding: var(--ltp-panel-inset) var(--ltp-panel-inset-x);
	border-radius: 22px;
	background: var(--ltp-panel);
}

/*
 * A product with only Tools Required — or only Maintenance — has no second
 * column to fill the row, and a full-width band holding two short lines is
 * mostly empty panel. Shrinking to the content turns it back into the box the
 * printed sheet shows, centred under the section above it.
 *
 * `fit-content` rather than `max-content`: it is the same shrink-to-content
 * width, but capped at the room available, so a long Maintenance paragraph
 * still fills the row and wraps as it always did instead of overflowing. The
 * split panel is left alone — its two columns are what set its width.
 */
.ltp.ltp .ltp-panel:not(.ltp-panel--split) {
	width: var(--ltp-panel-w, fit-content);
	max-width: 100%;
	margin-inline: auto;
}

/*
 * Maintenance runs the full width of its column, which on a wide split leaves
 * a long line to track back from. This pulls its right edge in without moving
 * the heading above it or the list beside it.
 *
 * From 760px only, like the other insets: an amount that trims a long desktop
 * line would take a third of the width off a phone, where the text is already
 * wrapping every few words.
 */
@container ltp (min-width: 760px) {
	.ltp.ltp .ltp-panel .ltp-prose {
		padding-right: var(--ltp-panel-text-pad-r, 0px);
	}

	.ltp.ltp .ltp-tools {
		padding-right: var(--ltp-panel-tools-pad-r, 0px);
	}
}

@container ltp (min-width: 760px) {
	/*
	 * The split is written as a percentage of the room left after the gap, so
	 * 60 really is six tenths of the two columns rather than six tenths of the
	 * panel with the gap eaten out of one side.
	 */
	.ltp-panel--split {
		grid-template-columns:
			calc((100% - var(--ltp-panel-space)) * var(--ltp-panel-split, 60) / 100)
			minmax(0, 1fr);
	}

	.ltp.ltp .ltp-panel--split .ltp-panel__col + .ltp-panel__col {
		position: relative;
		padding-left: var(--ltp-panel-space);
	}

	/*
	 * A border on the second column would stop where that column's text stops,
	 * short of the panel's own padding. This rule hangs off that column instead
	 * and reaches back out over the panel's padding, so it runs the full height
	 * — and because it is measured from the column rather than from the middle
	 * of the panel, it stays in the gap whatever the split is set to.
	 */
	.ltp-panel--split .ltp-panel__col + .ltp-panel__col::before {
		content: "";
		position: absolute;
		top: calc(-1 * var(--ltp-panel-inset));
		bottom: calc(-1 * var(--ltp-panel-inset));
		left: calc(-0.5 * var(--ltp-panel-space));
		width: var(--ltp-divider-w, 2px);
		transform: translateX(-50%);
		background: var(--ltp-divider, rgba(14, 0, 0, 0.18));
		pointer-events: none;
	}
}

.ltp-panel__col {
	min-width: 0;
}

/*
 * Maintenance keeps its own text size, separate from Warning & Notes. Both
 * used to share `--ltp-prose-size`, so making the panel's copy smaller shrank
 * the top of the page with it.
 */
.ltp.ltp .ltp-panel .ltp-prose {
	font-size: calc(var(--ltp-panel-prose-size, var(--ltp-prose-size, clamp(0.9375rem, 1.1cqi, 1.0625rem))) * var(--ltp-scale, 1));
}

.ltp.ltp .ltp-tools {
	margin: 0;
	padding-left: 1.4em;
	color: var(--ltp-ink);
	font-size: calc(var(--ltp-tools-size, var(--ltp-prose-size, clamp(0.9375rem, 1.1cqi, 1.0625rem))) * var(--ltp-scale, 1));
	line-height: 1.7;
}

/* ---------- Lightbox ---------- */

.ltp-lightbox {
	position: fixed;
	inset: 0;
	z-index: 99999;
	display: grid;
	place-items: center;
	padding: clamp(16px, 5vw, 48px);
	background: rgba(14, 0, 0, 0.85);
}

.ltp-lightbox__img {
	max-width: min(94vw, 1100px);
	max-height: 84vh;
	width: auto;
	height: auto;
	object-fit: contain;
}

.ltp-lightbox__caption {
	position: absolute;
	left: 0;
	right: 0;
	bottom: clamp(12px, 3vw, 28px);
	margin: 0;
	padding: 0 16px;
	color: #fff;
	font-size: 0.875rem;
	text-align: center;
}

.ltp-lightbox__close {
	position: absolute;
	top: clamp(10px, 2vw, 22px);
	right: clamp(10px, 2vw, 22px);
	display: grid;
	place-items: center;
	width: 44px;
	height: 44px;
	border: 0;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.16);
	color: #fff;
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
}

.ltp-lightbox__close:hover {
	background: rgba(255, 255, 255, 0.3);
}

/* ---------- Focus + motion ---------- */

.ltp a:focus-visible,
.ltp button:focus-visible,
.ltp input:focus-visible {
	outline: 2px solid var(--ltp-accent);
	outline-offset: 3px;
}

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

/* ---------- Viewport fallback for browsers without container queries ---------- */

@supports not (container-type: inline-size) {
	@media (min-width: 900px) {
		.ltp__layout {
			--ltp-mode: list;
		}

		.ltp--has-filter .ltp__layout {
			grid-template-columns: var(--ltp-filter-col, minmax(170px, 22%)) minmax(0, 1fr);
			column-gap: var(--ltp-filter-gap, clamp(28px, 5vw, 88px));
		}

		.ltp-filter {
			border: 0;
			padding: 0;
		}

		.ltp-filter__toggle {
			padding: 0 0 30px;
		}

		.ltp-filter__list {
			max-height: none;
			overflow: visible;
			border-top: 0;
			padding-bottom: 0;
		}

		.ltp-grid {
			grid-template-columns: repeat(var(--ltp-cols, 3), minmax(0, 1fr));
		}
	}

	@media (min-width: 600px) and (max-width: 899px) {
		.ltp-grid {
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}
	}
}
