/*
Theme Name: Bigger Steel
Theme URI: https://biggersteel.co.ke/
Author: Bigger Steel Limited
Author URI: https://biggersteel.co.ke/
Description: A fast, secure block theme for Bigger Steel Limited - steel manufacturing and fabrication in Kajiado County, Kenya. Built for a quote-driven catalogue: no page builder, no jQuery, no external requests.
Requires at least: 6.5
Tested up to: 7.0
Requires PHP: 7.4
Version: 2.1.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: biggersteel
Tags: block-patterns, full-site-editing, business, custom-colors, custom-logo, custom-menu, editor-style, featured-images, threaded-comments, translation-ready, accessibility-ready, wide-blocks
*/

/* -------------------------------------------------------------------------
 * This is a block theme. Nearly all design lives in theme.json — colours,
 * typography, spacing and block defaults. Only rules that theme.json cannot
 * express belong here.
 *
 * Before adding CSS, check whether theme.json can do it. Styles defined there
 * apply consistently in the editor and on the front end, which is the whole
 * point of a block theme.
 * ---------------------------------------------------------------------- */

/* --- Typefaces -------------------------------------------------------- */

/*
 * One typeface, site-wide: Archivo, SIL Open Font License, self-hosted.
 *
 * It was already setting every heading, so unifying on it keeps the design's
 * character rather than flattening it — and Archivo is a grotesque drawn for
 * text as well as display, so it holds up at body size. It is a variable
 * font, so the 800-weight headings and the 400-weight body come out of the
 * same file at no extra cost.
 *
 * Public Sans (body) and IBM Plex Mono (data) used to sit alongside it. Their
 * files are still in assets/fonts if that decision is ever reversed, but
 * nothing references them, so nothing downloads them. Dropping them takes the
 * page from three families to one and saves roughly 48KB.
 *
 * Where the mono face used to line up gauges in the specification table,
 * `font-variant-numeric: tabular-nums` now does that job — Archivo has
 * tabular figures, so "1.6mm" still sits squarely under "16g".
 *
 * Self-hosted, so there is no request to Google, nothing to consent to under
 * GDPR, and no third-party origin in the critical path. The face is split by
 * unicode-range: a visitor reading English downloads only the `latin` file,
 * and `latin-ext` is fetched solely if a character needs it.
 *
 * Technique and font files borrowed from the Glen Feeds theme (GPL), which
 * solved the same problem for the same kind of buyer.
 */

@font-face {
	font-family: "Archivo";
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url("assets/fonts/archivo-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Archivo";
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url("assets/fonts/archivo-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Accessibility ---------------------------------------------------- */

/* Visible keyboard focus everywhere. Never remove this without replacing it. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
	outline: 3px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
	border-radius: 2px;
}

/* Skip link — hidden until focused. */
.skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 999999;
	padding: 1rem 1.5rem;
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-weight: 700;
	text-decoration: none;
}

.skip-link:focus {
	left: 0;
}

/* Screen-reader-only text. */
.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* Respect a user's reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* --- Brand ------------------------------------------------------------ */

/*
 * The logo runs a gradient from deep blue through purple to red. Using it as
 * a text treatment on key headings ties the page to the mark without needing
 * an image. Falls back to a solid colour where background-clip is unsupported.
 */
.bs-gradient-text {
	color: var(--wp--preset--color--primary);
}

@supports (-webkit-background-clip: text) or (background-clip: text) {
	.bs-gradient-text {
		background: var(--wp--preset--gradient--brand);
		-webkit-background-clip: text;
		background-clip: text;
		color: transparent;
	}
}

/* Thin gradient rule used to separate major sections. */
.bs-gradient-rule {
	height: 4px;
	border: 0;
	background: var(--wp--preset--gradient--brand);
}

/*
 * Inline icons, as printed by biggersteel_icon(). Sized in em so an icon is
 * always the size of the text it sits beside, and left to inherit its colour.
 */
.bs-icon {
	flex: 0 0 auto;
	width: 1em;
	height: 1em;
}

/*
 * Section intro. Sits in a full-width band, so it needs its own measure —
 * a 1280px line of 22px text is unreadable however wide the band is.
 */
.bs-lede {
	max-width: 60ch;
}

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

/*
 * The hero photograph is a Gallery block, crossfaded by assets/js/hero-slides.js.
 *
 * Layers, bottom to top: the gallery, a gradient scrim, the text. The scrim is
 * the reason a headline stays legible over a photograph nobody has vetted -
 * a bright roll of galvanised wire behind white type is otherwise unreadable.
 */
.bs-hero {
	position: relative;
	display: flex;
	align-items: center;
	min-height: 78vh;
	overflow: hidden;
}

.bs-hero__slides {
	position: absolute;
	inset: 0;
	margin: 0;
	gap: 0;
}

.bs-hero__slides > figure {
	position: absolute;
	inset: 0;
	margin: 0;
	width: 100%;
	opacity: 0;
	transition: opacity 1.2s ease;
}

.bs-hero__slides > figure img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * Until the script marks the gallery ready the first slide is simply shown,
 * so the hero is a still photograph with JavaScript off rather than a hole.
 */
.bs-hero__slides:not(.is-ready) > figure:first-child,
.bs-hero__slides.is-ready > figure.is-active {
	opacity: 1;
}

/* Scrim. Sits between the photo and the words. */
.bs-hero::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--wp--preset--gradient--overlay);
	pointer-events: none;
}

.bs-hero__content {
	position: relative;
	z-index: 1;
	width: 100%;
}

/* --- Contact bar ------------------------------------------------------ */

/*
 * The red strip above the header, carried over from the previous site, where
 * buyers had learned that the phone number lives at the top of the page.
 *
 * It scrolls away: only .bs-header below it is sticky, so the number is
 * present on arrival without spending 40px of a phone screen forever.
 */
.bs-topbar {
	font-size: 0.8125rem;
}

.bs-topbar__list {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0.35rem clamp(1.25rem, 4vw, 3rem);
	margin: 0;
	padding: 0;
	list-style: none;
	font-family: var(--wp--preset--font-family--body);
	font-weight: 600;
	line-height: 1.5;
}

.bs-topbar__list li,
.bs-topbar__list a {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
}

.bs-topbar a {
	color: inherit;
	text-decoration: none;
}

.bs-topbar a:hover,
.bs-topbar a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

/* The theme's red focus ring is invisible on a red bar. */
.bs-topbar a:focus-visible {
	outline-color: var(--wp--preset--color--base);
}

/*
 * Narrow screens keep the two things a buyer can act on — the number and the
 * address to write to. The postal address and the hours are both on the
 * contact page and in the footer, and neither survives being wrapped onto
 * three lines here.
 */
@media (max-width: 1023px) {
	.bs-topbar__list .bs-topbar__address {
		display: none;
	}
}

@media (max-width: 719px) {
	.bs-topbar__list .bs-topbar__hours {
		display: none;
	}
}

/* --- Header ----------------------------------------------------------- */

/*
 * Sticky header. `position: sticky` needs no JavaScript, unlike the old
 * theme's scroll-listener implementation.
 */
.bs-header {
	position: sticky;
	top: 0;
	z-index: 100;
	backdrop-filter: blur(8px);
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
}

/*
 * Header row: brand, menu, quote button — in that reading order.
 *
 * The menu is given the space between the other two rather than being packed
 * against the button, so the three parts read as three parts. Only from the
 * width at which the Navigation block stops being a hamburger: below that the
 * toggle would be centred in a wide empty strip.
 */
/*
 * The row is `alignwide`, not content width. Left at the 760px content size
 * it bunched the brand and menu into the middle of a wide screen and pushed
 * the quote button onto a second line.
 */
.bs-header__inner {
	gap: var(--wp--preset--spacing--20) clamp(1.25rem, 3vw, 2.5rem);
}

/*
 * Logo and wordmark are one unit, but they are not the same object — set
 * against each other with no gap they read as a single smudged mark.
 */
.bs-header__brand {
	gap: 0.75rem;
}

.bs-header .wp-block-navigation {
	gap: clamp(1.25rem, 2vw, 2rem);
}

@media (min-width: 600px) {
	.bs-header__inner > .wp-block-navigation {
		flex: 1 1 auto;
	}
}

/* Navigation: underline that grows from the left on hover. */
.bs-header .wp-block-navigation-item__content {
	position: relative;
	padding-block: 0.35rem;
}

.bs-header .wp-block-navigation-item__content::after {
	content: "";
	position: absolute;
	left: 0;
	bottom: 0;
	width: 0;
	height: 2px;
	background: var(--wp--preset--gradient--brand);
	transition: width 0.25s ease;
}

.bs-header .wp-block-navigation-item__content:hover::after,
.bs-header .current-menu-item .wp-block-navigation-item__content::after {
	width: 100%;
}

/* --- Footer ----------------------------------------------------------- */

/*
 * The footer is the only place in the theme that needs a real grid: four
 * columns of very different content — a paragraph of prose, two link lists
 * and an address — that have to hold their proportions and then collapse
 * sensibly. Core's Columns block sizes columns from their own content, which
 * squeezed "All products" into three lines on a wide screen.
 *
 * Everything below is scoped to .bs-footer so nothing here can leak into page
 * content. Colours are set in absolute rgba rather than palette slugs: these
 * are transparency levels over the dark plate, not brand colours.
 */
.bs-footer {
	--bs-footer-muted: rgba(255, 255, 255, 0.68);
	--bs-footer-faint: rgba(255, 255, 255, 0.46);
	--bs-footer-rule: rgba(255, 255, 255, 0.14);
	/*
	 * Brand red, lightened for the dark ground. #e4121c itself measures about
	 * 3.4:1 against the indigo, which fails AA for text; this reads as the
	 * same red and clears 5:1.
	 */
	--bs-footer-hover: #ff6f66;
}

/* The wordmark is centred over the blurb it introduces. */
.bs-footer__brand .wp-block-site-title {
	text-align: center;
}

/*
 * Site-wide links are blue and underlined, which is unreadable on the plate.
 * One class of specificity is enough to beat theme.json's :root :where(a).
 */
.bs-footer a {
	color: inherit;
	text-decoration: none;
}

.bs-footer a:hover,
.bs-footer a:focus-visible {
	color: var(--bs-footer-hover);
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

.bs-footer .wp-block-button__link:hover {
	text-decoration: none;
}

.bs-footer__grid {
	display: grid;
	grid-template-columns: minmax(0, 2fr) repeat(2, minmax(0, 1fr)) minmax(0, 1.3fr);
	gap: clamp(2rem, 4vw, 3.5rem);
	align-items: start;
	padding-bottom: var(--wp--preset--spacing--50);
	border-bottom: 1px solid var(--bs-footer-rule);
}

/* The grid gap sets the rhythm; drop the stacking margins core adds. */
.bs-footer__grid > * {
	margin-block: 0;
}


/*
 * No measure cap, by request. It was 42ch, which is invisible at full desktop
 * width — the brand column is only about 350px there, so the column bound
 * first — but below 1024px that column spans the whole grid, and the cap left
 * the sentence stopping a third of the way across with dead space beside it.
 */
.bs-footer__blurb {
	color: var(--bs-footer-muted);
	text-wrap: pretty;
}

/*
 * Tighter than a button elsewhere on the site. Side by side the footer pair
 * needs about 366px at the site-wide padding, and the brand column is around
 * 350px — so they wrapped by roughly fifteen pixels. Trimming the padding and
 * the tracking here buys the room back without shrinking the text.
 */
.bs-footer__cta .wp-block-button__link {
	padding-inline: 1.35em;
	letter-spacing: 0.08em;
}

.bs-footer .bs-footer__heading {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.75rem;
	font-weight: 500;
	line-height: 1.4;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--bs-footer-faint);
	margin: 0 0 1rem;
}

.bs-footer__links,
.bs-footer__contact {
	list-style: none;
	margin: 0;
	padding: 0;
	font-size: var(--wp--preset--font-size--small);
}

.bs-footer__links {
	line-height: 1.9;
}

.bs-footer__contact {
	display: grid;
	gap: 0.65rem;
	line-height: 1.55;
}

/*
 * Icon and label share a line, so a wrapping address stays hung off the pin
 * rather than sliding back underneath it.
 */
.bs-footer__contact li,
.bs-footer__contact li > a {
	display: flex;
	align-items: flex-start;
	gap: 0.6rem;
}

/* Sits a shade back from its label, and lines up with the first line of it. */
.bs-footer .bs-icon {
	margin-top: 0.3em;
	color: var(--bs-footer-faint);
	transition: color 0.2s ease;
}

.bs-footer__contact a:hover .bs-icon,
.bs-footer__contact a:focus-visible .bs-icon {
	color: var(--wp--preset--color--accent);
}

/* A phone number broken across two lines is a phone number nobody dials. */
.bs-footer__nowrap {
	white-space: nowrap;
}

.bs-footer__bottom {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
	margin-top: var(--wp--preset--spacing--40);
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.75rem;
	letter-spacing: 0.02em;
	color: var(--bs-footer-faint);
}

.bs-footer__bottom p {
	margin: 0;
}

/* Provenance, not navigation — it steps back on any screen wide enough. */
.bs-footer__made {
	display: none;
}

.bs-footer__top {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
}

.bs-footer__top .bs-icon {
	margin-top: 0;
}

@media (min-width: 900px) {
	.bs-footer__made {
		display: block;
	}
}

/*
 * Below 1024px the brand column takes the full width — its paragraph and its
 * two buttons stop fitting beside three others — and the link columns keep an
 * even split under it. Four columns held any narrower would break the opening
 * hours mid-phrase and stack the quote buttons.
 */
@media (max-width: 1199px) {
	.bs-footer__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.bs-footer__brand {
		grid-column: 1 / -1;
	}
}

@media (max-width: 719px) {
	.bs-footer__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 479px) {
	.bs-footer__grid {
		grid-template-columns: minmax(0, 1fr);
	}

	.bs-footer__bottom {
		justify-content: flex-start;
	}
}

/* --- Placing an order ------------------------------------------------- */

/*
 * Closing strip: what we need from you on the left, three ways to send it on
 * the right. It replaces the full-bleed gradient call to action, which shouted
 * without answering the question a buyer has at the bottom of a page.
 */
.bs-order__inner {
	display: grid;
	grid-template-columns: minmax(0, 1.15fr) auto;
	gap: clamp(2rem, 5vw, 4.5rem);
	align-items: center;
}

/* Core's flow-layout stacking margin would push the buttons off centre. */
.bs-order__inner > * {
	margin-block: 0;
}

/*
 * The hairline runs from the end of the label to the edge of the column. It
 * is drawn on a flex line rather than as a border on the paragraph, so it
 * starts where the words stop however long the label is — a border would run
 * the full width and sit under the text.
 */
.bs-order__eyebrow {
	display: flex;
	align-items: center;
	gap: 1.25rem;
}

.bs-order__eyebrow::after {
	content: "";
	flex: 1;
	height: 1px;
	background: linear-gradient(
		to right,
		var(--wp--preset--color--steel-light),
		rgb(228 232 238 / 0%)
	);
}

.bs-order__lede {
	max-width: 46ch;
}

/* --- Button pairs ------------------------------------------------------ */

/*
 * A primary button beside an outline one — in the footer, and on every
 * product page beside the photo. Both pairs behave the same way.
 *
 * Left to themselves the buttons are sized to their own labels, so as soon as
 * the column narrows they wrap onto two lines at two different widths with a
 * ragged right edge, which reads as a mistake rather than as a pair.
 *
 * flex: 1 1 auto with wrapping does the whole job. When both fit on one line
 * they share it; when they do not, each takes a line of its own and grows to
 * fill it. Either way the edges line up, with no breakpoint guessing at how
 * wide two translated labels happen to be.
 *
 * The max-width stops them stretching the full span of whatever contains
 * them — the brand column runs edge to edge below 1200px, and the product
 * page column is around 590px.
 *
 * 32rem, not the 26rem this started at. At the product page's text size the
 * pair measures about 455px side by side, so a 416px cap forced them onto two
 * lines — the cap itself was doing the wrapping, inside a column with 590px
 * going spare. The footer pair is smaller (~358px) and never reaches either
 * figure, so it is governed by its column as before.
 */
.bs-button-pair {
	flex-wrap: wrap;
	max-width: 32rem;
}

.bs-button-pair .wp-block-button {
	flex: 1 1 auto;
}

.bs-button-pair .wp-block-button__link {
	display: flex;
	width: 100%;
	justify-content: center;
	white-space: nowrap;
}

/* --- Contact Form 7 submit --------------------------------------------- */

/*
 * Contact Form 7 renders its submit as a bare <input type="submit"> with its
 * own class, so it never picks up the button styling in theme.json — that
 * targets .wp-element-button. Without this the quote form ends a page of
 * 12px, wide-tracked buttons with a default browser control.
 *
 * Untested locally: Contact Form 7 is not installed on the preview, so the
 * contact page currently shows the fallback panel instead of the form.
 */
.wpcf7-submit {
	padding: 0.95em 1.85em;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	letter-spacing: 0.12em;
	line-height: 1.2;
	text-transform: uppercase;
	color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--accent);
	border: 0;
	border-radius: 12px;
	cursor: pointer;
	transition: background 0.2s ease;
}

.wpcf7-submit:hover,
.wpcf7-submit:focus-visible {
	background: var(--wp--preset--color--primary);
}

/* --- Contact action buttons -------------------------------------------- */

.bs-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 1rem;
}

/*
 * Generous padding, a soft 12px corner that sits with the 14px cards, and
 * wide tracking on the label. These are the last thing on the page and the
 * only thing on it a buyer is meant to act on.
 *
 * Padding in em, not rem, so it resolves against each button's own font size.
 * The same values in rem gave a 13px footer button and a 17px hero button an
 * identical box, which made every small button look oversized — the shape
 * should be consistent across the site, not the absolute size.
 */
.bs-action {
	display: inline-flex;
	align-items: center;
	gap: 0.75em;
	padding: 0.95em 1.85em;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	letter-spacing: 0.12em;
	line-height: 1.2;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
	border: 1px solid transparent;
	border-radius: 12px;
	transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.bs-action:hover,
.bs-action:focus-visible {
	transform: translateY(-2px);
	text-decoration: none;
}

.bs-action .bs-icon {
	width: 1.3em;
	height: 1.3em;
}

.bs-action__go {
	transition: transform 0.2s ease;
}

.bs-action:hover .bs-action__go {
	transform: translateX(3px);
}

/*
 * WhatsApp's own green is #25D366, which measures 1.98:1 against white text
 * and is unusable for a button. This is the same hue taken dark enough to
 * read — 6.53:1 — so it is still recognisably WhatsApp without being a
 * label nobody can make out.
 */
.bs-action--whatsapp {
	color: var(--wp--preset--color--base);
	background: #146c2e;
}

.bs-action--whatsapp:hover,
.bs-action--whatsapp:focus-visible {
	color: var(--wp--preset--color--base);
	background: #0f5623;
}

.bs-action--call {
	color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--accent);
}

.bs-action--call:hover,
.bs-action--call:focus-visible {
	color: var(--wp--preset--color--base);
	background: #b60e16;
}

/*
 * The outline button needs a border you can actually see. The theme's
 * steel-light rule measures 1.23:1 against white — fine as a divider inside a
 * card, invisible as the edge of a control sitting beside two solid buttons.
 * This grey is the lightest that still clears the 3:1 WCAG asks of a UI
 * boundary, so the button reads as a button and not as floating text.
 */
.bs-action--email {
	color: var(--wp--preset--color--contrast);
	background: transparent;
	border-color: #8a95a4;
}

.bs-action--email:hover,
.bs-action--email:focus-visible {
	color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--contrast);
}

/*
 * Below the split, the buttons move under the text and stretch to fill the
 * row. Three uppercase labels side by side stop fitting well before the
 * columns do, and a half-wrapped row of buttons reads as broken.
 */
@media (max-width: 899px) {
	.bs-order__inner {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--wp--preset--spacing--50);
	}

	.bs-order__lede {
		max-width: 60ch;
	}
}

@media (max-width: 479px) {
	.bs-actions {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
	}

	.bs-action {
		justify-content: center;
	}
}

/* --- Homepage stat strip ---------------------------------------------- */

/*
 * Four figures under the hero. Built as a grid rather than core columns
 * because the columns block only offers "stack on mobile" or "don't": at four
 * across on a 375px phone each label gets about 80px, and "product lines
 * manufactured in-house" in 80px is six ragged lines. Two-by-two is the only
 * arrangement that works at that width.
 */
.bs-stats {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--wp--preset--spacing--40);
}

/*
 * Zero the stacking margin core puts between sibling blocks.
 *
 * A group with the default flow layout gets
 * `:where(.is-layout-flow) > * { margin-block-start: <block-gap> }`, with the
 * first child exempted. Switching the container to grid does not switch that
 * off, so every item except the first was pushed down 24px — the first stat
 * sat a line higher than the other three, and the dividers were ragged.
 *
 * Core's selector is wrapped in :where(), so it has zero specificity and a
 * plain class overrides it. The same applies to .bs-line below, where the
 * effect was worse: it knocked the connecting rail out of alignment.
 */
.bs-stats > *,
.bs-line > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

/*
 * Hairline dividers, drawn as borders on the items rather than as separate
 * elements, so they reflow correctly when the grid changes shape.
 */
.bs-stats__item + .bs-stats__item {
	padding-left: var(--wp--preset--spacing--40);
	border-left: 1px solid var(--wp--preset--color--steel-light);
}

.bs-stats__figure {
	line-height: 1.05;
	letter-spacing: -0.02em;
}

/*
 * No measure cap here. The grid column is already about 270px at full width,
 * which is a short enough line on its own — a 22ch cap on top of that broke
 * "product lines manufactured in-house" onto a second line for no reason.
 */
.bs-stats__label {
	text-wrap: pretty;
}

@media (max-width: 899px) {
	.bs-stats {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
	}

	/* At two across, the divider belongs on the right-hand item only. */
	.bs-stats__item + .bs-stats__item {
		padding-left: 0;
		border-left: 0;
	}

	.bs-stats__item:nth-child(2n) {
		padding-left: var(--wp--preset--spacing--40);
		border-left: 1px solid var(--wp--preset--color--steel-light);
	}
}

@media (max-width: 479px) {
	.bs-stats {
		grid-template-columns: minmax(0, 1fr);
	}

	.bs-stats__item:nth-child(2n) {
		padding-left: 0;
		border-left: 0;
	}

	.bs-stats__item + .bs-stats__item {
		padding-top: var(--wp--preset--spacing--40);
		border-top: 1px solid var(--wp--preset--color--steel-light);
	}
}

/* --- Production line -------------------------------------------------- */

/*
 * The four manufacturing stages, drawn as a track with the stage numbers
 * sitting on it.
 *
 * The rail is drawn per step, running from that step's badge across the gap
 * to the next one, and suppressed on the last. Drawing one rail across the
 * whole container instead would overshoot past the final badge, because the
 * badges sit at the left edge of each column rather than in the centre.
 */
.bs-line {
	--bs-line-gap: var(--wp--preset--spacing--50);
	--bs-line-badge: 2.75rem;

	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--bs-line-gap);
}

.bs-line__step {
	position: relative;
	padding-top: calc(var(--bs-line-badge) + 1.25rem);
}

.bs-line__step::before {
	content: "";
	position: absolute;
	top: calc(var(--bs-line-badge) / 2 - 1px);
	left: calc(var(--bs-line-badge) + 0.75rem);
	width: calc(100% + var(--bs-line-gap) - var(--bs-line-badge) - 1.5rem);
	height: 2px;
	background: var(--wp--preset--color--steel-light);
}

/* Nothing comes after the last stage, so no rail runs off the edge. */
.bs-line__step:last-child::before {
	content: none;
}

.bs-line__num {
	position: absolute;
	top: 0;
	left: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--bs-line-badge);
	height: var(--bs-line-badge);
	margin: 0;
	font-size: 0.9375rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	line-height: 1;
	color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--contrast);
	border-radius: 3px;
}

/* The first stage is where the material enters, so it carries the brand mark. */
.bs-line__step:first-child .bs-line__num {
	background: var(--wp--preset--color--accent);
}

/*
 * Below four columns the track turns vertical and runs down the left, which
 * is the only way a connected sequence survives a narrow screen.
 */
@media (max-width: 899px) {
	.bs-line {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	/* Rails between columns would now point at the wrong step. */
	.bs-line__step::before {
		content: none;
	}
}

@media (max-width: 599px) {
	.bs-line {
		--bs-line-gap: var(--wp--preset--spacing--50);

		grid-template-columns: minmax(0, 1fr);
	}

	.bs-line__step {
		padding-top: 0;
		padding-left: calc(var(--bs-line-badge) + 1.25rem);
	}

	.bs-line__step::before {
		content: "";
		top: calc(var(--bs-line-badge) + 0.5rem);
		left: calc(var(--bs-line-badge) / 2 - 1px);
		width: 2px;
		height: calc(100% + var(--bs-line-gap) - var(--bs-line-badge) - 1rem);
	}

	.bs-line__step:last-child::before {
		content: none;
	}
}

/* --- Product gallery --------------------------------------------------- */

/*
 * A scroll-snapping strip with thumbnail links beneath it, and no JavaScript.
 *
 * Phones and trackpads already swipe a horizontally scrolling element, so the
 * interaction most people use is native. The thumbnails are plain anchors to
 * each frame — following one scrolls the strip, because the browser scrolls
 * the nearest scrollable ancestor to bring the target into view. That means
 * every photo has a real, linkable URL and the whole thing works before any
 * script would have loaded.
 */
.bs-gallery {
	margin: 0;
}

.bs-gallery__strip {
	display: flex;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	border-radius: 14px;

	/* Hide the scrollbar; the thumbnails and the snap already signal the axis. */
	scrollbar-width: none;
}

.bs-gallery__strip::-webkit-scrollbar {
	display: none;
}

.bs-gallery__slide {
	flex: 0 0 100%;
	scroll-snap-align: center;

	/*
	 * Anchor links scroll the strip horizontally, but the browser will also
	 * scroll the PAGE to bring the slide into view — landing with the photo
	 * jammed against the top of the window. A scroll margin keeps the header
	 * and a little breathing room above it.
	 */
	scroll-margin-top: 6rem;
}

.bs-gallery__image,
.bs-gallery__frame .bs-gallery__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.bs-gallery__frame {
	display: block;
	overflow: hidden;
	border-radius: 14px;
	background: var(--wp--preset--gradient--mill-finish, var(--wp--preset--color--base-2));
}

.bs-gallery__thumbs ul {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin: 0.75rem 0 0;
	padding: 0;
	list-style: none;
}

/*
 * The square crop is set on the link, not on the image inside it.
 *
 * An aspect-ratio on the <img> competes with the width and height attributes
 * WordPress prints on it, and loses whenever a size has not been generated at
 * exactly those dimensions — so thumbnails rendered at the photo's own
 * proportions, tall and uneven. Sizing the box and letting the image fill it
 * with object-fit gives the same crop for every image, whatever was uploaded.
 */
.bs-gallery__thumb {
	display: block;
	width: 3.25rem;
	aspect-ratio: 1;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--steel-light);
	border-radius: 6px;
	transition: border-color 0.2s ease, transform 0.2s ease;
}

.bs-gallery__thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.bs-gallery__thumb:hover,
.bs-gallery__thumb:focus-visible {
	border-color: var(--wp--preset--color--accent);
	transform: translateY(-2px);
}

.bs-gallery__count {
	margin-top: 0.6rem;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--steel);
}

/* --- Product cards ---------------------------------------------------- */

/*
 * Cards sit in a CSS grid, so they are all as tall as the tallest one in the
 * row. Laying the card out as a column lets the "View details" line be pushed
 * to the bottom with margin-top:auto, so that affordance lines up across the
 * row regardless of how long each product's excerpt runs. Without it the
 * arrow floats at a different height on every card and the row looks ragged.
 */
.bs-product-card {
	position: relative;
	display: flex;
	flex-direction: column;
	height: 100%;
	overflow: hidden;
	transition: transform 0.25s ease, box-shadow 0.25s ease;
}

/*
 * The whole card is the click target, not just the title.
 *
 * "View details" reads as a link and was not one — the photo and the title
 * were the only clickable parts, so the most obvious thing to click did
 * nothing. Rather than making it a third link, the title's link is stretched
 * across the card with a pseudo-element.
 *
 * That keeps ONE link per card, which matters: a card built from an image
 * link plus a title link plus a "View details" link is three separate stops
 * for one destination when tabbing through, and a screen reader announces the
 * same product three times. Stretching the title also means the accessible
 * name stays the product name, where a real "View details" link would give
 * ten identical, meaningless names down the page.
 */
.bs-product-card .wp-block-post-title a::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
}

/*
 * The category tag is the one thing on the card that goes somewhere else, so
 * it has to sit above the overlay or it becomes a link that looks clickable
 * and is not — which is the fault this pattern was introduced to fix, moved
 * two lines down the card.
 */
.bs-product-card .wp-block-post-terms a {
	position: relative;
	z-index: 2;
}

.bs-product-card:hover,
.bs-product-card:focus-within {
	transform: translateY(-4px);
	box-shadow: 0 12px 32px rgb(18 21 26 / 12%);
}

.bs-product-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
}

.bs-product-card__go {
	margin-top: auto;
	color: var(--wp--preset--color--primary);
}

.bs-product-card__go span {
	display: inline-block;
	transition: transform 0.2s ease;
}

.bs-product-card:hover .bs-product-card__go span,
.bs-product-card:focus-within .bs-product-card__go span {
	transform: translateX(4px);
}

/*
 * Keep catalogue images a consistent shape regardless of what was uploaded.
 * 16:10 rather than 4:3 - at four cards to a row the taller crop made each
 * card mostly photograph, and pushed the product name below the fold.
 */
.bs-product-card img {
	width: 100%;
	aspect-ratio: 16 / 10;
	object-fit: cover;
}

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

.bs-filter {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin-bottom: var(--wp--preset--spacing--50);
	padding-bottom: var(--wp--preset--spacing--30);
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
}

.bs-filter__item {
	display: inline-flex;
	align-items: center;
	gap: 0.45em;
	padding: 0.45rem 0.95rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.2;
	color: var(--wp--preset--color--steel);
	text-decoration: none;
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--steel-light);
	border-radius: 999px;
	transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.bs-filter__item:hover,
.bs-filter__item:focus-visible {
	color: var(--wp--preset--color--contrast);
	background: var(--wp--preset--color--base);
	border-color: var(--wp--preset--color--steel);
}

.bs-filter__item.is-current {
	color: var(--wp--preset--color--base);
	background: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--contrast);
}

/*
 * Opacity rather than a fixed colour, because this text sits on a light chip
 * normally and on the near-black chip when its category is selected — one
 * colour cannot serve both. 0.85 is the lowest value that still clears 4.5:1
 * in the harder of the two cases (steel on base-2, 4.94:1); 0.65, which looks
 * about right, measures 3.12:1 and fails at this size.
 */
.bs-filter__count {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.75em;
	font-variant-numeric: tabular-nums;
	opacity: 0.85;
}

/* --- At-a-glance specifications ---------------------------------------- */

/*
 * The first thing a buyer needs on a product page is a measurement, not a
 * paragraph — does this gauge fit my job? These sit beside the photo so that
 * question is answered without scrolling, with the full table further down.
 *
 * The left rule in the brand colours is the only decoration: it marks the
 * block as data rather than prose without adding a box inside a box.
 *
 * The rule is drawn as a background rather than with the --brand preset on a
 * border, because that preset runs at 135deg — sliced down a 3px edge it
 * would show as one flat colour, not a gradient. Same stops, turned to run
 * top-to-bottom so the transition is actually visible.
 */
.bs-keyspecs {
	margin-top: var(--wp--preset--spacing--30);
	padding-left: var(--wp--preset--spacing--40);
	background-image: linear-gradient(to bottom, #2b1cc7 0%, #8a16a0 55%, #e4121c 100%);
	background-repeat: no-repeat;
	background-size: 3px 100%;
	background-position: left top;
}

.bs-keyspecs__title {
	margin: 0 0 0.75rem;
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--steel);
}

.bs-keyspecs__list {
	margin: 0;
	display: grid;
	gap: 0.55rem;
}

.bs-keyspecs__row {
	display: grid;
	grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
	gap: 0.35rem 1rem;
	align-items: baseline;
}

.bs-keyspecs__row dt {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--steel);
}

.bs-keyspecs__row dd {
	margin: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.9375rem;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--contrast);
}

.bs-keyspecs__more {
	margin: 1rem 0 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

/* Fallback when a product has no measurable specification recorded yet. */
.bs-keyspecs--summary {
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--steel);
}

/*
 * Card variant: one fact, no label. A label/value pair at card size is noise,
 * and the product name above it already supplies the context.
 */
.bs-keyspecs--inline {
	margin: 0 0 0.75rem;
	padding: 0;
	background-image: none;
}

.bs-keyspecs__chip {
	display: inline-block;
	padding: 0.2rem 0.6rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.8125rem;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--steel-dark);
	background: var(--wp--preset--color--base-2);
	border: 1px solid var(--wp--preset--color--steel-light);
	border-radius: 3px;
}

@media (max-width: 479px) {
	.bs-keyspecs__row {
		grid-template-columns: minmax(0, 1fr);
		gap: 0.1rem;
	}
}

/* --- Related products -------------------------------------------------- */

.bs-related__title {
	margin: 0 0 var(--wp--preset--spacing--40);
	/*
	 * Smaller and centred over the cards. This is a footnote to the product
	 * you came to read, not a second page title - at 2rem and hard left it
	 * announced itself louder than the product's own headings.
	 */
	font-size: var(--wp--preset--font-size--large);
	text-align: center;
}

.bs-related__grid {
	display: grid;
	/*
	 * Cards keep a card's size instead of stretching to fill the row, and the
	 * set is centred under the heading. `auto-fit` rather than `auto-fill`:
	 * auto-fill would leave empty tracks holding space on the right, which
	 * pushes three cards off-centre on a wide screen.
	 */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 18rem));
	justify-content: center;
	gap: var(--wp--preset--spacing--30);
}

/*
 * The whole card is one link rather than a link on the image plus a link on
 * the title. Three sibling links to the same destination read as three
 * separate stops to a screen reader and to anyone tabbing through.
 */
.bs-related__card {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	text-decoration: none;
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--steel-light);
	border-radius: 14px;
	transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.bs-related__card:hover,
.bs-related__card:focus-visible {
	transform: translateY(-4px);
	border-color: var(--wp--preset--color--steel);
	box-shadow: 0 12px 32px rgb(18 21 26 / 12%);
}

/*
 * The frame holds the 4:3 crop, not the image.
 *
 * The card is a column flex container, so a bare <img> inside it would be a
 * flex item — and a flex item's height comes from its flex-basis, which
 * resolves to the image's own intrinsic height. That beats any aspect-ratio
 * set on the image, so photos rendered at their uploaded height: 600px tall
 * next to a 353px one, with the card text pushed far below the fold.
 *
 * Sizing the frame instead makes the image an ordinary in-flow child, and the
 * crop holds regardless of what was uploaded. A product with no photo yet
 * shows the mill-finish plate rather than a collapsed card.
 */
.bs-related__frame {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	background: var(--wp--preset--gradient--mill-finish, var(--wp--preset--color--base-2));
}

.bs-related__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.bs-related__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: 0.3rem;
	padding: var(--wp--preset--spacing--30);
}

.bs-related__name {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.3;
	color: var(--wp--preset--color--contrast);
}

.bs-related__spec {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.8125rem;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--steel);
}

.bs-related__go {
	margin-top: auto;
	padding-top: 0.75rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	color: var(--wp--preset--color--primary);
	transition: transform 0.2s ease;
}

.bs-related__card:hover .bs-related__go,
.bs-related__card:focus-visible .bs-related__go {
	transform: translateX(4px);
}

/* --- Industrial surface treatments ------------------------------------ */

/*
 * Cut-plate corner.
 *
 * A chamfered top-right corner, echoing a sheared steel plate. It is the one
 * decorative motif in the theme, so it stays subtle and is used only on
 * cards and image frames — enough to read as deliberate, not as a texture.
 */
.is-style-bs-plate {
	clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
	border-radius: 0;
}

/*
 * Blueprint grid.
 *
 * A faint engineering-drawing grid for tinted sections. Set in absolute
 * rgba rather than a palette colour so it stays at the same weight whatever
 * the section background is.
 */
/*
 * Blueprint grid, plus a wash of brand blue falling away down the band.
 *
 * The wash is the one atmospheric background in the theme, borrowed from Glen
 * Feeds, which spends its entire texture budget on a single 5%-alpha tint over
 * the hero and nothing else. At 4% it is barely nameable as a colour — it just
 * stops a large white band reading as blank paper, and ties the page head back
 * to the mark without another image to load.
 *
 * Layer order matters: the wash is listed last so the grid rules sit on top of
 * it, and background-size pairs positionally with the images above it.
 */
.is-style-bs-blueprint {
	background-image:
		linear-gradient(rgba(74, 84, 98, 0.07) 1px, transparent 1px),
		linear-gradient(90deg, rgba(74, 84, 98, 0.07) 1px, transparent 1px),
		linear-gradient(180deg, rgba(43, 28, 199, 0.04), transparent 60%);
	background-size: 32px 32px, 32px 32px, 100% 100%;
	background-position: center, center, top;
	background-repeat: repeat, repeat, no-repeat;
}

/*
 * Measured rule — a section divider with tick marks, like a steel rule.
 */
.bs-measure {
	position: relative;
	height: 12px;
	border: 0;
	margin: 0;
	background:
		repeating-linear-gradient(
			90deg,
			var(--wp--preset--color--steel-light) 0 1px,
			transparent 1px 16px
		);
}

.bs-measure::after {
	content: "";
	position: absolute;
	inset: auto 0 0 0;
	height: 2px;
	background: var(--wp--preset--gradient--brand);
}

/* --- Navigation dropdown ---------------------------------------------- */

/*
 * The Products dropdown.
 *
 * Worth having as a dropdown rather than a plain link: the catalogue is the
 * reason most people are on the site, and a buyer who already knows they want
 * barbed wire should not have to load the archive and then click again.
 *
 * Core's Navigation block handles the open/close behaviour and keyboard
 * support itself, so this is presentation only.
 */
/*
 * The panel is DARK on purpose.
 *
 * The Navigation block carries an overlay colour for the mobile menu, and
 * core applies those same colours to the desktop submenu — as
 * `has-contrast-background-color`, a theme.json preset class that WordPress
 * emits with `!important`. A light panel would have to fight that on every
 * rule.
 *
 * So the dropdown leans into it: a steel-plate panel below the white header,
 * matching the sectors and footer treatment. Text must therefore be LIGHT —
 * an earlier version set it to `contrast`, which produced dark text on a dark
 * panel and a dropdown that looked empty.
 */
.bs-header .wp-block-navigation__submenu-container {
	min-width: 16rem;
	padding: 0.4rem 0;
	border: 0;
	border-top: 3px solid var(--wp--preset--color--accent);
	border-radius: 0 0 4px 4px;
	box-shadow: var(--wp--preset--shadow--lifted);
}

.bs-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	width: 100%;
	padding: 0.6rem 1.15rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 500;
	/* Light, because the panel is dark. */
	color: var(--wp--preset--color--base);
}

/*
 * The growing-underline treatment belongs to the top-level items only —
 * inside the dropdown it would animate on every row and look busy.
 */
.bs-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content::after {
	display: none;
}

/* A red bar slides in on the left of the hovered row, echoing the top border. */
.bs-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover,
.bs-header .wp-block-navigation__submenu-container .wp-block-navigation-item__content:focus-visible {
	background: rgb(255 255 255 / 8%);
	color: var(--wp--preset--color--base);
	box-shadow: inset 3px 0 0 var(--wp--preset--color--accent);
}

/*
 * "All products" reads as the parent of the list, so it gets the mono label
 * treatment used for every other label on the site, and a rule beneath it.
 * Muted against the dark panel rather than against white.
 */
.bs-header .wp-block-navigation__submenu-container > li:first-child .wp-block-navigation-item__content {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.72rem;
	font-weight: 500;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: rgb(255 255 255 / 55%);
}

.bs-header .wp-block-navigation__submenu-container > li:first-child .wp-block-navigation-item__content:hover,
.bs-header .wp-block-navigation__submenu-container > li:first-child .wp-block-navigation-item__content:focus-visible {
	color: var(--wp--preset--color--base);
}

.bs-header .wp-block-navigation__submenu-container > li:first-child {
	border-bottom: 1px solid rgb(255 255 255 / 14%);
	margin-bottom: 0.4rem;
	padding-bottom: 0.4rem;
}

/*
 * On mobile the submenu renders inside the overlay, where a white panel on a
 * dark background would be jarring. Let it inherit instead.
 */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container {
	background: transparent;
	border: 0;
	box-shadow: none;
	padding-left: 1rem;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	color: inherit;
}

/* --- Facts strip ------------------------------------------------------ */

/*
 * Four founding facts under the company history. Figures in the display face
 * at a size that reads as a statement, with the explanation beneath in small
 * mono — the same label treatment as the footer, the index legend and the
 * contact card, so the whole site speaks with one voice.
 */
.bs-facts__grid {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: clamp(1.25rem, 3vw, 2.5rem);
	padding-top: var(--wp--preset--spacing--50);
	border-top: 2px solid var(--wp--preset--color--contrast);
}

.bs-facts__num {
	margin: 0 0 0.35rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: -0.025em;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--primary);
}

.bs-facts__label {
	margin: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.72rem;
	font-weight: 500;
	line-height: 1.5;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--steel);
}

@media (max-width: 899px) {
	.bs-facts__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 1.75rem;
	}
}

@media (max-width: 479px) {
	.bs-facts__grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* --- About page content ----------------------------------------------- */

/*
 * Whatever is typed into the About page renders in the same two-column
 * rhythm as the patterns above it: heading on the left, prose on the right,
 * one row per heading.
 *
 * Done in CSS rather than by rewriting the page, because that text lives in
 * the database and the theme cannot reach it. Grid auto-placement does the
 * work — a heading claims column one, everything after it stacks in column
 * two until the next heading starts a new row.
 */
@media (min-width: 900px) {
	.bs-about-content .wp-block-post-content {
		display: grid;
		grid-template-columns: minmax(0, 34%) minmax(0, 1fr);
		column-gap: clamp(2rem, 6vw, 6rem);
		row-gap: 1.25rem;
		align-items: start;
	}

	/* Row gap sets the rhythm, so drop the margins core stacks between blocks. */
	.bs-about-content .wp-block-post-content > * {
		grid-column: 2;
		margin-block: 0;
		max-width: 62ch;
	}

	.bs-about-content .wp-block-post-content > h2 {
		grid-column: 1;
		max-width: none;
	}
}

/* --- Location map ----------------------------------------------------- */

/*
 * Address panel and map side by side, the panel on a dark plate so the two
 * read as one object rather than a caption stuck above an iframe.
 *
 * The map is an OpenStreetMap embed: no tracking cookies, no API key, and
 * `loading="lazy"` so it is only fetched if a visitor scrolls to it.
 */
/*
 * The map is the point of the section, so it takes about two thirds of the
 * width and the address panel is sized to its text rather than to a share of
 * the grid.
 */
.bs-map {
	display: grid;
	grid-template-columns: minmax(280px, 0.62fr) minmax(0, 1.38fr);
	align-items: stretch;
	max-width: var(--wp--style--global--wide-size, 1280px);
	margin-inline: auto;
	box-shadow: var(--wp--preset--shadow--card);
	overflow: hidden;
}

.bs-map__panel {
	background: var(--wp--preset--gradient--plate);
	color: var(--wp--preset--color--base);
	padding: clamp(1.5rem, 2.5vw, 2.25rem);
	display: flex;
	flex-direction: column;
	/* Centred, so the panel's text sits against the middle of the map rather
	   than clinging to its top edge with the button adrift below it. */
	justify-content: center;
	gap: 0.75rem;
}

.bs-map__eyebrow {
	margin: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.7rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
}

.bs-map__title {
	margin: 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 800;
	letter-spacing: -0.022em;
	line-height: 1.12;
	color: var(--wp--preset--color--base);
}

.bs-map__address {
	margin: 0;
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.55;
}

.bs-map__pobox {
	color: rgb(255 255 255 / 68%);
	font-size: var(--wp--preset--font-size--small);
}

.bs-map__hours {
	display: flex;
	align-items: flex-start;
	gap: 0.6rem;
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	color: rgb(255 255 255 / 78%);
}

.bs-map__hours .bs-icon {
	width: 18px;
	height: 18px;
	margin-top: 0.15rem;
	color: var(--wp--preset--color--accent);
}

.bs-map__note {
	margin: 0;
	padding-top: 0.75rem;
	border-top: 1px solid rgb(255 255 255 / 14%);
	font-size: var(--wp--preset--font-size--small);
	color: rgb(255 255 255 / 68%);
}

.bs-map__cta {
	display: inline-flex;
	align-items: center;
	gap: 0.55rem;
	align-self: flex-start;
	margin-top: 0.5rem;
	padding: 0.85rem 1.5rem;
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	letter-spacing: 0.03em;
	text-decoration: none;
	border-radius: 4px;
	transition: background-color 0.2s ease;
}

.bs-map__cta:hover,
.bs-map__cta:focus-visible {
	background: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--base);
}

.bs-map__cta .bs-icon {
	width: 18px;
	height: 18px;
}

.bs-map__canvas {
	position: relative;
	min-height: 540px;
	background: var(--wp--preset--color--steel-light);
}

.bs-map__frame {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
	display: block;
}

@media (max-width: 899px) {
	.bs-map {
		grid-template-columns: minmax(0, 1fr);
	}

	.bs-map__canvas {
		min-height: 380px;
		/* Map above the text on a phone — it is the reason to scroll here. */
		order: -1;
	}

	.bs-map__cta {
		margin-top: 0.4rem;
	}
}

/* --- Contact details card --------------------------------------------- */

/*
 * The sidebar on the contact page. Rendered by the contact-details pattern
 * from the business config, so it cannot drift from the footer or the schema.
 */
.bs-contact-card {
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--steel-light);
	border-top: 3px solid var(--wp--preset--color--accent);
	padding: var(--wp--preset--spacing--50);
	position: sticky;
	top: 6rem;
}

.bs-contact-card__title {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 800;
	letter-spacing: -0.02em;
	margin: 0 0 var(--wp--preset--spacing--40);
}

.bs-contact-card__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--wp--preset--spacing--40);
}

.bs-contact-card__list > li {
	display: flex;
	align-items: flex-start;
	gap: 0.85rem;
}

.bs-contact-card__list .bs-icon {
	width: 20px;
	height: 20px;
	margin-top: 0.15rem;
	color: var(--wp--preset--color--accent);
}

.bs-contact-card__list > li > span {
	display: grid;
	gap: 0.2rem;
	min-width: 0;
}

/* Label above the value — mono, matching the footer and the index legend. */
.bs-contact-card__list em {
	font-family: var(--wp--preset--font-family--body);
	font-style: normal;
	font-size: 0.7rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--steel);
}

.bs-contact-card__list a {
	color: inherit;
	text-decoration: none;
	overflow-wrap: anywhere;
	font-weight: 600;
}

.bs-contact-card__list a:hover,
.bs-contact-card__list a:focus-visible {
	color: var(--wp--preset--color--accent);
	text-decoration: underline;
}

.bs-contact-card__address,
.bs-contact-card__hours {
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.5;
}

.bs-contact-card__map {
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--primary) !important;
	text-decoration: underline !important;
}

/* Sticky is only useful where there is room to scroll past it. */
@media (max-width: 781px) {
	.bs-contact-card {
		position: static;
	}
}

/* --- Contact fallback panel ------------------------------------------- */

/*
 * Shown in place of the quote form when Contact Form 7 is unavailable.
 *
 * Styled as a deliberate contact panel rather than an apology: this is the
 * page whose whole job is generating enquiries, so the degraded state still
 * has to convert. Each row is a large tap target with the channel and a line
 * saying what to expect from it.
 */
.bs-fallback__lead {
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--steel);
	margin-bottom: var(--wp--preset--spacing--40);
}

.bs-fallback__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0.75rem;
}

.bs-fallback__list a {
	display: flex;
	align-items: center;
	gap: 1rem;
	padding: 1.1rem 1.25rem;
	border: 1px solid var(--wp--preset--color--steel-light);
	border-radius: 4px;
	background: var(--wp--preset--color--base);
	color: inherit;
	text-decoration: none;
	transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.bs-fallback__list a:hover,
.bs-fallback__list a:focus-visible {
	border-color: var(--wp--preset--color--accent);
	transform: translateY(-2px);
	box-shadow: var(--wp--preset--shadow--card);
}

.bs-fallback__list .bs-icon {
	flex: 0 0 auto;
	width: 22px;
	height: 22px;
	color: var(--wp--preset--color--accent);
}

.bs-fallback__list span {
	display: grid;
	gap: 0.15rem;
	min-width: 0;
}

.bs-fallback__list strong {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 700;
	overflow-wrap: anywhere;
}

.bs-fallback__list em {
	font-style: normal;
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--steel);
}

/* Admin-only hint. Deliberately quiet — it is a note, not a warning. */
.bs-fallback__note {
	margin-top: var(--wp--preset--spacing--40);
	padding: 0.85rem 1rem;
	border-left: 3px solid var(--wp--preset--color--steel-light);
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--steel);
}

/* --- Product index board ---------------------------------------------- */

/*
 * A dense index of the range: line number, product, headline spec,
 * availability. For someone comparing ten products this beats a photo grid —
 * they are scanning for a gauge, not looking at pictures.
 *
 * Rows are anchors, not JavaScript accordions. Every line is a real URL that
 * can be shared, bookmarked and crawled, and the whole thing works with
 * scripting disabled.
 *
 * Column counts vary because the render drops the spec or availability
 * column when no product has that data — an empty column header over blank
 * space reads as broken.
 */
.bs-board {
	border-top: 2px solid var(--wp--preset--color--contrast);
}

.bs-board__legend,
.bs-board__row {
	display: grid;
	gap: clamp(0.75rem, 2vw, 2rem);
	align-items: center;
}

.bs-board--spec-lead .bs-board__legend,
.bs-board--spec-lead .bs-board__row {
	grid-template-columns: 2.5rem minmax(0, 1.4fr) minmax(0, 1fr) 10rem 1.5rem;
}

.bs-board--spec .bs-board__legend,
.bs-board--spec .bs-board__row {
	grid-template-columns: 2.5rem minmax(0, 1.5fr) minmax(0, 1fr) 1.5rem;
}

.bs-board--nospec-lead .bs-board__legend,
.bs-board--nospec-lead .bs-board__row {
	grid-template-columns: 2.5rem minmax(0, 1fr) 10rem 1.5rem;
}

.bs-board--nospec .bs-board__legend,
.bs-board--nospec .bs-board__row {
	grid-template-columns: 2.5rem minmax(0, 1fr) 1.5rem;
}

.bs-board__legend {
	padding: 0.75rem 0;
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.72rem;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--steel);
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
}

.bs-board__row {
	padding: clamp(1rem, 2vw, 1.4rem) 0;
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
	text-decoration: none;
	color: inherit;
	transition: padding-inline 0.25s ease, background-color 0.25s ease;
}

/* Row inset on hover — the whole line is the target, so it should feel like one. */
.bs-board__row:hover,
.bs-board__row:focus-visible {
	padding-inline: 0.9rem;
	background-color: var(--wp--preset--color--base);
}

.bs-board__num {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.78rem;
	font-weight: 500;
	color: var(--wp--preset--color--accent);
	font-variant-numeric: tabular-nums;
}

.bs-board__name {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	letter-spacing: -0.02em;
	line-height: 1.15;
}

.bs-board__spec,
.bs-board__lead {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.82rem;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--steel);
	line-height: 1.4;
}

.bs-board__lead {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	white-space: nowrap;
}

.bs-board__dot {
	width: 7px;
	height: 7px;
	flex: 0 0 auto;
	border-radius: 50%;
	background: var(--wp--preset--color--primary);
}

.bs-board__go {
	justify-self: end;
	color: var(--wp--preset--color--steel);
	transition: transform 0.25s ease, color 0.25s ease;
}

.bs-board__row:hover .bs-board__go,
.bs-board__row:focus-visible .bs-board__go {
	transform: translateX(4px);
	color: var(--wp--preset--color--accent);
}

/*
 * Below 780px the grid collapses to two rows per product: name and arrow on
 * top, then the technical columns beneath. Squeezing four columns onto a
 * phone makes every one of them unreadable.
 */
@media (max-width: 779px) {
	.bs-board__legend {
		display: none;
	}

	.bs-board__row,
	.bs-board--spec-lead .bs-board__row,
	.bs-board--spec .bs-board__row,
	.bs-board--nospec-lead .bs-board__row,
	.bs-board--nospec .bs-board__row {
		grid-template-columns: 2.5rem minmax(0, 1fr) 1.5rem;
		gap: 0.35rem 1rem;
	}

	.bs-board__name {
		grid-column: 2;
	}

	.bs-board__spec,
	.bs-board__lead {
		grid-column: 2;
	}

	.bs-board__go {
		grid-row: 1;
		grid-column: 3;
	}
}

/* --- Specification tables --------------------------------------------- */

/*
 * Steel buyers scan specs before anything else, so these must stay readable
 * on a phone. Below 600px each row becomes a stacked label/value pair using
 * the data-label attribute rather than a horizontal scroll.
 *
 * Values are set in a monospace face with tabular figures so that gauges and
 * dimensions align in a column — "12g – 14g" above "1.8m – 3.0m" should read
 * like a data sheet, not like prose.
 */
/*
 * Section heading inside the product body — "Specifications", "Details".
 *
 * At the theme's h2 size these were set at 2.75rem, the same size as the
 * product name at the top of the page, which made a two-column table look
 * like an afterthought under a banner. A subsection is not a page title.
 */
.bs-section-heading,
.bs-product-body h2 {
	font-size: var(--wp--preset--font-size--x-large);
}

/* A heading in the flow of the body opens its own block of space above. */
.bs-product-body h2 {
	margin-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--30);
}

/*
 * The specs heading does not: its section already carries that margin. Named
 * with the body class too, or `.bs-product-body h2` out-specifies it and the
 * table drops half a section below the details heading beside it.
 */
.bs-section-heading,
.bs-product-body .bs-section-heading {
	margin-block: 0 var(--wp--preset--spacing--30);
}

.bs-specs-section {
	margin-block: var(--wp--preset--spacing--50);
}

/*
 * Product body: description across the top, then the specification table on
 * the left with the written details beside it on the right.
 *
 * Buyers read these two together — the table gives the numbers, the list
 * qualifies them — and stacked they were a long scroll apart. The split is
 * driven by grid auto-placement rather than by columns typed into the page,
 * because the body is post content: it lives in the database, and a product
 * added next month gets the same layout without anyone laying it out.
 */
/*
 * Two classes, so this beats core's constrained-layout rule whichever order
 * the stylesheets land in. The body has to be wide for the split below to
 * have room, and a template edited in the Site Editor is a copy in the
 * database that no longer follows the template file.
 */
.wp-block-group.bs-product-body {
	max-width: var(--wp--style--global--wide-size, 1280px);
	margin-inline: auto;
}

/*
 * The group is wide, but that alone was not enough: it used the CONSTRAINED
 * layout, and core caps a constrained group's children at the content size
 * and centres them —
 *
 *   :where(.is-layout-constrained) > :not(.alignwide):not(.alignfull) {
 *       max-width: var(--wp--style--global--content-size);
 *       margin-inline: auto;
 *   }
 *
 * so the post content sat at 760px, centred, indented well to the right of
 * the photo and title above it. The float below then split that 760px in two,
 * leaving the table about 348px and wrapping "Outside diameter" onto two
 * lines. The template now uses the flow layout instead, so the content fills
 * the wide width and lines up with the rest of the page.
 *
 * The description runs the full width of that container. It was capped to a
 * 68-character measure, which left the intro ending halfway across the page
 * with dead space beside it — so the cap is gone by request.
 *
 * Note that no max-width may be reintroduced on anything AFTER the table.
 * Those elements are already narrowed by the float, and a max-width there
 * shrinks the box INTO the float rather than beside it, squeezing the text
 * into whatever slice is left over.
 */

@media (min-width: 1000px) {
	/*
	 * A float, not a grid. Grid can only place siblings cell by cell, so the
	 * details list landed a row below its own heading, level with the bottom
	 * of the table. A float lets the whole run of content after the table
	 * flow beside it as one block, and wrap under it when it outgrows it.
	 */
	.bs-product-body .wp-block-post-content > .bs-specs-section {
		float: left;
		width: calc(50% - 2rem);
		margin: 0 clamp(2rem, 4vw, 4rem) var(--wp--preset--spacing--50) 0;
	}

	/* The details heading starts level with the table, not below it. */
	.bs-product-body .wp-block-post-content > .bs-specs-section + h2 {
		margin-top: 0;
	}

	/* Nothing below the body may ride up alongside the table. */
	.bs-product-body .wp-block-post-content::after {
		content: "";
		display: block;
		clear: both;
	}
}

/* The table carries its own top rule, so it needs no gap above it. */
.bs-specs-section .bs-scroll-x {
	margin-top: 0;
}

/*
 * The table is a white panel on the tinted page field, not bare rows on it.
 *
 * Its zebra striping is base-2, which is now the colour of the page itself —
 * left transparent, every other row would have vanished into the background
 * and the striping would read as a rendering fault. Giving the table its own
 * white ground restores the stripe and makes it read as a data sheet.
 */
.bs-specs {
	width: 100%;
	border-collapse: collapse;
	background: var(--wp--preset--color--base);
	border-top: 2px solid var(--wp--preset--color--contrast);
}

.bs-specs thead th {
	padding: 0.6rem 1rem;
	font-size: 0.75rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.09em;
	color: var(--wp--preset--color--steel);
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
}

.bs-specs th,
.bs-specs td {
	padding: 0.85rem 1rem;
	text-align: left;
	border-bottom: 1px solid var(--wp--preset--color--steel-light);
}

.bs-specs tbody th {
	width: 42%;
	font-weight: 600;
	color: var(--wp--preset--color--steel);
}

.bs-specs tbody td {
	color: var(--wp--preset--color--contrast);
}

/*
 * Terse measurements get the data-sheet treatment: monospace with tabular
 * figures, so "16g" and "1.6mm" line up down the column.
 */
.bs-specs tbody td.bs-specs__data {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.9375rem;
	font-variant-numeric: tabular-nums;
}

/*
 * Explanatory values stay in the body face. A sentence set in monospace
 * reads as broken, and there is nothing to align.
 */
.bs-specs tbody td.bs-specs__prose {
	font-size: 0.9375rem;
	line-height: 1.6;
	color: var(--wp--preset--color--steel);
	max-width: 46ch;
}

.bs-specs tbody tr:nth-child(even) {
	background: var(--wp--preset--color--base-2);
}

/* Any figure that should align in a column, outside the spec table. */
.bs-figure {
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum";
}

@media (max-width: 599px) {
	.bs-specs thead {
		display: none;
	}

	.bs-specs tbody tr:nth-child(even) {
		background: none;
	}

	.bs-specs tr {
		display: block;
		margin-bottom: 1rem;
		border: 1px solid var(--wp--preset--color--steel-light);
	}

	.bs-specs th,
	.bs-specs td {
		display: block;
		width: auto;
		border-bottom: 0;
	}

	.bs-specs td::before {
		content: attr(data-label);
		display: block;
		font-size: 0.8rem;
		text-transform: uppercase;
		letter-spacing: 0.06em;
		color: var(--wp--preset--color--steel);
	}
}

/* --- Wide content ----------------------------------------------------- */

/* Any wide element scrolls inside itself rather than breaking the page. */
.wp-block-table,
.bs-scroll-x {
	overflow-x: auto;
	max-width: 100%;
}

/* --- Print ------------------------------------------------------------ */

/*
 * Buyers print product pages to take into procurement meetings, so make that
 * output usable: drop navigation and reveal link targets.
 */
@media print {
	.bs-header,
	.wp-block-navigation,
	.bs-no-print,
	footer {
		display: none !important;
	}

	body {
		color: #000;
		background: #fff;
		font-size: 11pt;
	}

	a[href^="http"]::after {
		content: " (" attr(href) ")";
		font-size: 9pt;
		word-break: break-all;
	}

	/*
	 * The at-a-glance figures are the reason the page is being printed, so
	 * they keep their emphasis but lose the gradient rule, which prints as a
	 * grey smear on most office printers.
	 */
	.bs-keyspecs {
		padding-left: 0;
		background-image: none;
	}

	/* Never break a specification away from its label across a page. */
	.bs-keyspecs__row,
	.bs-specs tr {
		break-inside: avoid;
	}

	.bs-specs tbody tr:nth-child(even) {
		background: none;
	}

	/* A card row that survives to paper wastes half a page on hover chrome. */
	.bs-related__go,
	.bs-product-card__go {
		display: none;
	}
}
