/**
 * BellsWellOrg Design System — components
 *
 * Component styles built on the tokens in design-system.css.
 * Figma reference: "Bellswellorg-v1", Desktop - HiFi (node 266:3607).
 *   Navbar  → node 266:3639
 *   Footer  → node 285:4057
 *
 * @package BellsWellOrg
 */

/*--------------------------------------------------------------
>>> TABLE OF CONTENTS:
----------------------------------------------------------------
# Grey surfaces
# Brand lockups
# Site header / Navbar
	- Brand
	- Primary navigation
	- Mobile toggle & panel
# Site footer
	- Company column
	- Contact blocks
	- Link columns
	- Accreditation badge
	- Socials bar
--------------------------------------------------------------*/

/*--------------------------------------------------------------
# Grey surfaces
--------------------------------------------------------------*/

/**
 * The two colours that carry text at close to the 4.5:1 floor —
 * --bw-accent and --bw-text-muted — are sized against white. A surface
 * that is itself grey eats the margin: red is 4.39:1 on #e5e5e5 and
 * 4.04:1 on #d0d0d0, and muted grey fails on both.
 *
 * Rather than special-case each label, the surfaces re-point the two
 * tokens. They inherit, so `.bw-section-label` and anything else added
 * inside one of these later is covered without knowing this rule exists.
 *
 * Only the image-less states qualify. With a photograph behind it the
 * band and the banner set their own white type, which these would
 * override for the worse.
 */
.bw-band:not(.bw-band--media),
.bw-pagehead:not(.bw-pagehead--media),
.bw-block__placeholder {
	--bw-accent: var(--bw-primary-on-grey);
	--bw-text-muted: var(--bw-grey-800);
}

/*--------------------------------------------------------------
# Brand lockups
--------------------------------------------------------------*/

/**
 * Both lockups are single SVGs (assets/images/logo-horizontal.svg and
 * logo-stacked.svg) — the mark's position against the wordmark is the
 * designer's, baked into the file, so there is nothing to reproduce in CSS.
 *
 * Each is sized on the axis its design fixes: the navbar's by height, because
 * the header was drawn around a 41px mark and the mark fills the file's full
 * height; the footer's by width, because the wordmark spans the file edge to
 * edge and the column was drawn to 340.794px of it. The other axis follows
 * from the viewBox, which is also what the width/height attributes on the img
 * carry — the box is the right shape before the SVG has loaded.
 */
.bw-logo {
	display: inline-flex;
	flex-shrink: 0;
	line-height: 0;
	text-decoration: none;
}

/* 41px tall at the file's 1036x225. */
.bw-logo__img {
	display: block;
	height: 41px;
	width: 188.782px;
}

.bw-logo-stacked {
	display: inline-block;
	line-height: 0;
	max-width: 100%;
	text-decoration: none;
	width: 340.794px;
}

/* Follows the anchor, so the max-width above can still shrink it. */
.bw-logo-stacked__img {
	display: block;
	height: auto;
	width: 100%;
}

@media (max-width: 782px) {

	.bw-logo__img {
		height: 32px;
		width: 147.342px;
	}

	.bw-logo-stacked {
		width: 240px;
	}
}

/*--------------------------------------------------------------
# Site header / Navbar
--------------------------------------------------------------*/

.site-header {
	background-color: var(--bw-surface);
	border-bottom: 1px solid var(--bw-rule);
	position: relative;
	z-index: 100;
}

.site-header__inner {
	align-items: center;
	display: flex;
	justify-content: space-between;
	min-height: var(--bw-header-height);
}

/* The 40px insets Figma puts inside the brand and nav blocks. */
.site-header .site-branding {
	padding-inline: var(--bw-block-inset);
}

.site-header .site-branding a {
	display: inline-flex;
}

/*--------------------------------------------------------------
## Primary navigation
--------------------------------------------------------------*/

/**
 * Underscores ships `.main-navigation { width: 100% }`, which would stop
 * space-between from pushing the menu to the right gutter. Reset it here.
 */
.main-navigation {
	flex: 0 1 auto;
	padding-inline: var(--bw-block-inset);
	width: auto;
}

.main-navigation .nav-menu,
.main-navigation ul {
	display: flex;
	gap: clamp(1.25rem, 2.083vw, 2.25rem); /* 36px at design width */
	list-style: none;
	margin: 0;
	padding: 0;
}

.main-navigation li {
	position: relative;
}

.main-navigation a {
	color: var(--bw-text);
	display: block;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-nav);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-nav);
	letter-spacing: var(--bw-ls-nav);
	text-decoration: none;
	white-space: nowrap;
}

.main-navigation a:hover,
.main-navigation a:focus,
.main-navigation .current-menu-item > a,
.main-navigation .current-menu-ancestor > a,
.main-navigation .current_page_item > a {
	color: var(--bw-primary);
}

/**
 * The disclosure that opens a submenu. Company, Operations and the rest go
 * nowhere themselves, so they are buttons, not links — see inc/nav-disclosure.php.
 * Everything here is undoing a button so it reads as the nav item beside it;
 * the type rules it shares with the links are in `.main-navigation a` above.
 */
.main-navigation .bw-nav-disclosure {
	background: none;
	border: 0;
	color: var(--bw-text);
	cursor: pointer;
	display: block;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-nav);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-nav);
	letter-spacing: var(--bw-ls-nav);
	padding: 0;
	text-align: start;
	white-space: nowrap;
}

.main-navigation .bw-nav-disclosure:hover,
.main-navigation .bw-nav-disclosure[aria-expanded="true"],
.main-navigation .current-menu-ancestor > .bw-nav-disclosure {
	color: var(--bw-primary);
}

/* Sub-menus — the Figma dropdown frames become disclosure panels. */
.main-navigation ul ul {
	background-color: var(--bw-surface);
	border: 1px solid var(--bw-rule);
	box-shadow: none; /* Underscores default. */
	display: block;
	flex-direction: column;
	float: none;      /* Underscores default. */
	gap: 0;
	left: 0;
	min-width: 260px;
	opacity: 0;
	padding: var(--bw-space-4) 0;
	pointer-events: none;
	position: absolute;
	top: 100%;
	transform: translateY(4px);
	transition:
		opacity var(--bw-duration-panel) var(--bw-ease-enter),
		transform var(--bw-duration-panel) var(--bw-ease-enter),
		visibility 0s linear var(--bw-duration-panel);
	visibility: hidden;
	z-index: 10;
}

/**
 * `visibility` alongside the fade, which is the same device the carousel
 * slides use. A panel at opacity 0 is still in the tab order and still in the
 * accessibility tree, so before this the submenu links were reachable by TAB
 * while the button reported itself collapsed — the state and the truth have to
 * agree. Hidden panels now leave both, and `.is-open` is the only way in.
 *
 * `:hover` stays for pointer users and works with JavaScript off; navigation.js
 * mirrors it onto `aria-expanded` so the two never disagree.
 */
.main-navigation li:hover > ul,
.main-navigation li.is-open > ul {
	opacity: 1;
	pointer-events: auto;
	transform: translateY(0);
	transition:
		opacity var(--bw-duration-panel) var(--bw-ease-enter),
		transform var(--bw-duration-panel) var(--bw-ease-enter),
		visibility 0s;
	visibility: visible;
}

.main-navigation ul ul a {
	font-weight: var(--bw-weight-light);
	letter-spacing: var(--bw-ls-card-label);
	padding: var(--bw-space-2) var(--bw-space-5);
	text-transform: uppercase;
	width: auto; /* Underscores sets 200px. */
}

/*--------------------------------------------------------------
## Mobile toggle & panel
--------------------------------------------------------------*/

.menu-toggle {
	background: none;
	border: 1px solid var(--bw-black);
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	cursor: pointer;
	display: none;
	height: 44px;
	padding: 0;
	position: relative;
	width: 44px;
}

.menu-toggle .menu-toggle__bar,
.menu-toggle .menu-toggle__bar::before,
.menu-toggle .menu-toggle__bar::after {
	background-color: currentColor;
	content: "";
	display: block;
	height: 2px;
	left: 50%;
	position: absolute;
	top: 50%;
	transform: translate(-50%, -50%);
	transition:
		transform var(--bw-duration-icon) var(--bw-ease-industrial),
		opacity var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 20px;
}

.menu-toggle .menu-toggle__bar::before {
	transform: translate(-50%, calc(-50% - 6px));
}

.menu-toggle .menu-toggle__bar::after {
	transform: translate(-50%, calc(-50% + 6px));
}

.main-navigation.toggled .menu-toggle .menu-toggle__bar {
	background-color: transparent;
}

.main-navigation.toggled .menu-toggle .menu-toggle__bar::before {
	transform: translate(-50%, -50%) rotate(45deg);
}

.main-navigation.toggled .menu-toggle .menu-toggle__bar::after {
	transform: translate(-50%, -50%) rotate(-45deg);
}

@media (max-width: 900px) {

	.menu-toggle {
		display: block;
	}

	.main-navigation {
		padding-inline: 0;
		position: static;
	}

	.main-navigation .nav-menu,
	.main-navigation > div > ul {
		background-color: var(--bw-surface);
		border-bottom: 1px solid var(--bw-rule);
		display: none;
		flex-direction: column;
		gap: 0;
		left: 0;
		padding: var(--bw-space-4) var(--bw-gutter) var(--bw-space-6);
		position: absolute;
		right: 0;
		top: 100%;
	}

	.main-navigation.toggled .nav-menu,
	.main-navigation.toggled > div > ul {
		display: flex;
	}

	.main-navigation .nav-menu a,
	.main-navigation .nav-menu .bw-nav-disclosure {
		font-size: var(--bw-size-body-md);
		padding-block: var(--bw-space-3);
	}

	/*
	 * Sub-menus flow inline instead of floating — and `display`, not
	 * `visibility`, because a static panel that is merely invisible still
	 * holds its height and leaves a hole in the stack. The disclosure works
	 * the same way it does on the desktop panel: closed until its button
	 * says otherwise. There is no hover here to open it by accident.
	 */
	.main-navigation ul ul {
		border: 0;
		opacity: 1;
		padding: 0 0 var(--bw-space-2) var(--bw-space-5);
		pointer-events: auto;
		position: static;
		transform: none;
		transition: none;
		visibility: visible;
	}

	/*
	 * `.toggled` repeated on both sides of the pair, which is otherwise
	 * redundant. Underscores' style.css ships
	 *
	 *   .menu-toggle, .main-navigation.toggled ul { display: block }
	 *
	 * — two classes and an element, which outranks a plain `.main-navigation
	 * ul ul` however late it is declared. The submenu stayed open under a
	 * button reporting itself collapsed until these matched it.
	 */
	.main-navigation ul ul,
	.main-navigation.toggled ul ul {
		display: none;
	}

	.main-navigation li.is-open > ul,
	.main-navigation.toggled li.is-open > ul {
		display: flex;
	}
}

/*--------------------------------------------------------------
# Site footer
--------------------------------------------------------------*/

.site-footer {
	background-color: var(--bw-surface);
	border-top: 1px solid var(--bw-rule);
	color: var(--bw-text);
}

/**
 * Top region. Figma: 603px company column + 949px links column with a
 * vertical rule between them, inside the 144px page gutter.
 *
 * The vertical padding is held here as custom properties but applied to the
 * columns, not to this grid: the rule is the links column's left border, so
 * padding on the container would inset it and leave it floating short of the
 * horizontal rules above (.site-footer) and below (.site-footer__socials).
 * With the columns carrying it instead, they stretch the full block height
 * and the rule meets both.
 */
.site-footer__top {
	--bw-footer-pad-top: clamp(3rem, 8.507vw, 9.1875rem);    /* 147px */
	--bw-footer-pad-bottom: clamp(3rem, 5.266vw, 5.6875rem); /*  91px */

	display: grid;
	gap: var(--bw-space-6);
	grid-template-columns: minmax(0, 460px) minmax(0, 949px);
}

@media (max-width: 1024px) {

	.site-footer__top {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*--------------------------------------------------------------
## Company column
--------------------------------------------------------------*/

/**
 * Figma insets the company content 40px inside the 144px page gutter.
 *
 * The logo opens the column on the site's own 180px section rhythm rather than
 * Figma's 147px: this block sits below the same rule every other section is
 * measured from, and the footer already arrives 180px after the last one.
 * (--bw-footer-pad-top still carries the 147 for the links column, which
 * measures its 436px offset from it.)
 */
.site-footer__company {
	--bw-footer-company-gap: clamp(1.5rem, 3.819vw, 4.125rem); /* 66px between contact blocks */

	display: flex;
	flex-direction: column;
	gap: var(--bw-footer-company-gap);
	padding-bottom: var(--bw-footer-pad-bottom);
	padding-left: var(--bw-block-inset);
	padding-top: var(--bw-band-gap);
}

/**
 * Whichever column is taller sets the block height, so the stretched company
 * column usually has slack to place. It goes into one gap only — above the
 * newsletter, which is the column's foot. The logo keeps its 180px offset from
 * the rule with the addresses gathered under it, and the auto margin drops the
 * sign-up onto the bottom padding line, level with the badge opposite.
 */
@media (min-width: 1025px) {

	.site-footer__company {
		justify-content: flex-start;
	}

	.site-footer__company .bw-newsletter {
		margin-top: auto;
	}
}

/**
 * Stacked, the rule between the columns becomes a horizontal seam, and it wants
 * the same clearance on both sides: the grid gap is dropped so the whole seam is
 * the two columns' own padding, 80px each way.
 *
 * The newsletter carries the same 80 above it. At the column's own gap it read
 * as a third address block rather than as the foot of the column — there is no
 * badge beside it here to say otherwise. The margin is the part of that 80 the
 * flex gap does not already provide, so the measurement holds as the gap
 * scales.
 */
@media (max-width: 1024px) {

	.site-footer__top {
		row-gap: 0;
	}

	.site-footer__company {
		padding-bottom: var(--bw-space-11);
	}

	.site-footer__company .bw-newsletter {
		margin-top: calc(var(--bw-space-11) - var(--bw-footer-company-gap));
	}
}

/* The logo sits 87px above the first contact block, not 66px — the minimum,
 * which is what the stacked layout gets. Stretched, the auto margin above the
 * contact blocks opens the gap further. */
.site-footer__company .bw-logo-stacked {
	margin-bottom: clamp(0rem, 1.215vw, 1.3125rem);
}

/*--------------------------------------------------------------
## Contact blocks
--------------------------------------------------------------*/

.bw-contact-block {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-5);
}

.bw-contact-block__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h6);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h6);
	letter-spacing: var(--bw-ls-h6);
	margin: 0;
}

.bw-contact-block__list {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-3);
	list-style: none;
	margin: 0;
	max-width: 385px;
	padding: 0;
}

/**
 * Icon column is 20px wide (the widest glyph) with a 27px gap, which
 * puts every label at the 47px offset the design uses.
 */
.bw-contact-block__item {
	align-items: start;
	column-gap: 27px;
	display: grid;
	grid-template-columns: 20px minmax(0, 1fr);
}

.bw-contact-block__icon {
	display: block;
	justify-self: center;
}

/* Exact leaf sizes from the exported assets. */
.bw-contact-block__icon--location {
	height: 22.4px;
	width: 16px;
}

.bw-contact-block__icon--phone {
	height: 19px;
	width: 19px;
}

.bw-contact-block__icon--email {
	height: 15.833px;
	margin-top: 4px;
	width: 20px;
}

.bw-contact-block__text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

.bw-contact-block__text a {
	text-decoration: none;
}

.bw-contact-block__text a:hover,
.bw-contact-block__text a:focus {
	text-decoration: underline;
}

/*--------------------------------------------------------------
## Newsletter
--------------------------------------------------------------*/

/**
 * Not in Figma. It was added to give the company column a foot: the links
 * column beside it is the taller of the two at every width, and the slack that
 * leaves had nowhere to go but into a gap. The sign-up takes the bottom of the
 * column, so the block reads as two columns of content rather than one column
 * and a hole.
 *
 * Held to the 385px address measure so it sets to the same width as the
 * contact rows above it.
 */
.bw-newsletter {
	max-width: 385px;
}

/*
 * Titled a size larger than the offices above it, not the same. This is the
 * one thing in the footer asking for something rather than stating it, and at
 * the office heading's h6 it read as a third address block.
 */
.bw-newsletter__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h3);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h3);
	letter-spacing: var(--bw-ls-h3);
	margin: 0;
}

.bw-newsletter__text {
	color: var(--bw-text-muted);
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: var(--bw-space-3) 0 0;
}

.bw-newsletter__form {
	margin-top: var(--bw-space-5);
}

/**
 * Every rule below carries `.fluentform` for the cascade, not for the match.
 * The plugin enqueues its stylesheet after the theme's, so a tie on
 * specificity goes to Fluent Forms — and the theme's own Fluent Forms section
 * further down this file would win a tie against these anyway. Three classes
 * clears both without reaching for !important.
 *
 * Fluent Forms' block margins assume a form is a section of a page. Here it is
 * the last thing in a column whose bottom edge lines up with the badge
 * opposite, so they come off and the spacing is set from the title instead.
 */
.bw-newsletter .fluentform {
	margin-block: 0;
}

/**
 * The bundled Subscription Form is a two-column container — field beside
 * button — and neither half has room to breathe at 385px. The cells stack
 * instead, without fighting the flex-basis Fluent Forms writes inline: a
 * full-width minimum takes precedence over a basis, so no !important is needed.
 */
.bw-newsletter .fluentform .ff-t-container {
	flex-wrap: wrap;
	margin: 0;
	row-gap: var(--bw-space-4);
}

.bw-newsletter .fluentform .ff-t-cell {
	min-width: 100%;
	padding: 0;
}

/* One knob for the space between field and button: the row gap above. */
.bw-newsletter .fluentform .ff-el-group {
	margin-bottom: 0;
}

/* Shorter than the page-level form: this one sits in a column, not a section. */
.bw-newsletter .fluentform .ff-el-form-control,
.bw-newsletter .fluentform .ff-btn-submit {
	min-height: 52px;
}

/*
 * The trailing margin Fluent Forms puts under its button would hold the whole
 * block 20px clear of the bottom padding line the badge opposite sits on.
 * Nothing follows the button, so it goes — and it takes the cell class to do
 * it, because the plugin sets that margin from
 * `.frm-fluent-form .ff-t-cell .ff_submit_btn_wrapper_custom button`.
 */
.bw-newsletter .fluentform .ff-t-cell .ff-btn-submit {
	margin-bottom: 0;
	width: 100%;
}

/*--------------------------------------------------------------
## Link columns
--------------------------------------------------------------*/

/**
 * Row 1 holds the three link columns, row 2 absorbs the remaining height so
 * the badge can sit at the bottom-right the way the Figma block does.
 * The top padding aligns the column headings with "Corporate Office".
 */
/**
 * Four columns: Company · Operations · Solutions · Quick Links.
 *
 * The Figma footer has three fixed 214px columns, which cannot hold a fourth
 * inside the 949px block. The columns are therefore fluid (1fr) and the left
 * inset is tightened from 121px to 80px, giving ~193px each at the design
 * width — near enough that the type still sets the same way.
 */
.site-footer__links {
	border-left: 1px solid var(--bw-rule);
	display: grid;
	gap: var(--bw-space-6);
	grid-template-columns: repeat(4, minmax(0, 1fr));
	padding-bottom: var(--bw-footer-pad-bottom);
	padding-left: clamp(1.5rem, 4.63vw, 5rem);
	position: relative;
}

@media (min-width: 1025px) {

	.site-footer__links {
		/* 289px below the top of the block, which now starts at the rule. */
		padding-top: calc(var(--bw-footer-pad-top) + clamp(0rem, 16.725vw, 18.0625rem));
	}
}

/**
 * Four abreast only holds at something near the design width. Beside the
 * company column the block is barely half the viewport, so by 1600px the
 * columns are down to ~168px — the 123px heading rule then runs almost the
 * full width of its column and two-word links wrap far enough to read as one
 * run of text with the column beside them. They pair up 2x2 below that, which
 * is the same arrangement the stacked layout falls back to at 900px.
 *
 * The third row is the badge's — see the badge rules below, which have to move
 * it out of column 4 as well. An explicitly placed item widens the implicit
 * grid the others auto-place into, so a badge left in column 4 keeps all four
 * navigations side by side in row 1 no matter what this template says.
 */
@media (min-width: 1025px) and (max-width: 1600px) {

	.site-footer__links {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		grid-template-rows: auto auto 1fr;
		row-gap: var(--bw-space-11);
	}
}

@media (min-width: 1601px) {

	.site-footer__links {
		grid-template-rows: auto 1fr;
	}
}

@media (max-width: 1024px) {

	.site-footer__links {
		border-left: 0;
		border-top: 1px solid var(--bw-rule);
		padding-left: 0;
		padding-top: var(--bw-space-11);
	}
}

@media (max-width: 900px) {

	.site-footer__links {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		row-gap: var(--bw-space-11);
	}
}

@media (max-width: 640px) {

	.site-footer__links {
		grid-template-columns: minmax(0, 1fr);
		row-gap: var(--bw-space-8);
	}
}

/* Set to the same type as the socials label on the bar below. */
.bw-footer-nav__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-subtitle);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-subtitle);
	letter-spacing: 0;
	margin: 0 0 var(--bw-space-5);
}

/* The 123px rule under each column heading. */
.bw-footer-nav__title::after {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-5);
	width: 123px;
}

/* 14px sits between --bw-space-3 and -4, so the gap is set off the scale. */
.bw-footer-nav ul {
	display: flex;
	flex-direction: column;
	gap: 0.875rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

.bw-footer-nav a {
	color: var(--bw-text);
	font-size: var(--bw-size-body-md);
	font-weight: var(--bw-weight-light);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	text-decoration: none;
	text-transform: uppercase;
}

.bw-footer-nav a:hover,
.bw-footer-nav a:focus {
	color: var(--bw-primary);
}

/*--------------------------------------------------------------
## Accreditation badge
--------------------------------------------------------------*/

/**
 * The bundled PNG carries a margin below the mark — 9px inside its 302px-wide
 * canvas, so 4.5px once drawn at 151px. That strip is real layout height, so
 * aligning the boxes would leave the *visible* mark floating clear of the line
 * it is meant to sit level with: the sign-up button opposite.
 *
 * The negative bottom margin takes the strip back out of the layout, and the
 * calc spells the measurement out instead of hiding it as a magic number.
 *
 * Tuned to the shipped asset, and it changed three times on 2026-08-11 alone —
 * a 591px square with 93px of transparent padding wanted -23.76px, a 600x511
 * crop -5.29px, the 912x750 original of this file -4.80px. Re-measure a
 * replacement rather than assuming this still holds, and set it to 0 for one
 * cropped to its ink.
 */
.site-footer__badge {
	align-self: end;
	grid-column: 4;
	justify-self: end;
	margin-bottom: calc(151px * -9 / 302); /* -4.50px */
	margin-top: var(--bw-space-11);
}

/* Four columns of links above it, so the badge sits in the second row. */
@media (min-width: 1601px) {

	.site-footer__badge {
		grid-row: 2;
	}
}

/* Two columns of two, so it drops to the third row, under the second column. */
@media (min-width: 1025px) and (max-width: 1600px) {

	.site-footer__badge {
		grid-column: 2;
		grid-row: 3;
	}
}

/**
 * Figma draws the badge 151px wide, and the bundled asset is cropped close
 * enough to its mark to take that width directly and keep its own ratio
 * (302x248, so 151x124 — near Figma's 151x115 box). It is sized at exactly
 * twice the drawn width, for the retina screen and no further: the 912px
 * original of the same artwork cost 232kB to draw 151 of them.
 *
 * The height cap and `contain` are for the Site Settings slot rather than for
 * this file: an upload of any proportion is then bounded without being
 * cropped, and cropping an accreditation mark could cut off the thing it
 * certifies. Do not set both dimensions without an object-fit — a source of a
 * different ratio then stretches to the box and the badge looks squashed.
 */
.site-footer__badge img {
	display: block;
	height: auto;
	max-height: 151px;
	object-fit: contain;
	width: 151px;
}

@media (max-width: 900px) {

	.site-footer__badge {
		grid-column: 2;
	}
}

@media (max-width: 640px) {

	.site-footer__badge {
		grid-column: 1;
		justify-self: start;
		margin-top: var(--bw-space-6);
	}
}

/*--------------------------------------------------------------
## Socials bar
--------------------------------------------------------------*/

.site-footer__socials {
	border-top: 1px solid var(--bw-rule);
}

/**
 * Sits inside a .bw-container, so it adds only the 40px block inset —
 * setting padding-inline here would clobber the page gutter.
 *
 * The 160px bar is Figma's, and one row of 48px tiles centres in it with room
 * to spare. Once the copyright wraps onto a second line the default
 * `align-content: stretch` spreads the two flex lines to the full height
 * instead, which puts the label against the rule above and the copyright
 * against the one below. Packing the lines centred and padding the block keeps
 * the bar breathing at any width; both are inert while the content is one row,
 * so the design width is unchanged.
 */
.site-footer__socials-inner {
	align-content: center;
	align-items: center;
	column-gap: var(--bw-space-7);
	display: flex;
	flex-wrap: wrap;
	min-height: 160px;
	padding-block: var(--bw-space-6);
	padding-left: var(--bw-block-inset);
	row-gap: var(--bw-space-4);
}

/* Set to the same type as the footer nav titles above. */
.site-footer__socials-label {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-subtitle);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-subtitle);
	letter-spacing: 0;
	margin: 0;
}

/**
 * The auto right margin — rather than an auto left margin on the copyright —
 * is what pushes the copyright to the far end of the bar. It belongs here so
 * that when the copyright wraps onto a line of its own it carries no auto
 * margin with it, and falls back to the flex start edge instead of staying
 * pinned right.
 */
.bw-social-list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--bw-space-5);
	list-style: none;
	margin: 0 auto 0 0;
	padding: 0;
}

/* Sits opposite the socials on the same bar, so the footer gains no height. */
.site-footer__copyright {
	color: var(--bw-text-muted);
	font-size: var(--bw-size-body-sm);
	line-height: var(--bw-lh-body-sm);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
	padding-right: var(--bw-block-inset);
	text-align: left;
}

.bw-social-list__link {
	align-items: center;
	border: 1px solid var(--bw-black);
	border-radius: var(--bw-radius-control);
	display: flex;
	height: 48px;
	justify-content: center;
	overflow: hidden;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial);
	width: 48px;
}

.bw-social-list__link:hover,
.bw-social-list__link:focus {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
}

/**
 * The glyphs are images, so they cannot inherit a colour. Flattening to white
 * keeps them legible once the tile fills red, the same trick the track cards
 * use on hover.
 */
.bw-social-list__link:hover .bw-social-list__icon,
.bw-social-list__link:focus .bw-social-list__icon {
	filter: brightness(0) invert(1);
}

/* Shared 32x32 leaf for Facebook, Instagram, X and LinkedIn. */
.bw-social-list__icon {
	display: block;
	height: 32px;
	transition: filter var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 32px;
}

/* WhatsApp ships a 26.533x26.667 glyph centred in the same 32px box. */
.bw-social-list__icon--whatsapp {
	height: 26.667px;
	width: 26.533px;
}

@media (max-width: 640px) {

	.site-footer__socials-inner {
		align-items: flex-start;
		flex-direction: column;
		justify-content: center;
		min-height: 0;
		padding-block: var(--bw-space-7);
	}

	.bw-social-list {
		gap: var(--bw-space-4);
	}

	.site-footer__copyright {
		padding-right: 0;
	}
}

/*--------------------------------------------------------------
# Buttons
--------------------------------------------------------------*/

/**
 * Button Medium from the Figma component set: 195x66, Kanit Medium 24/34,
 * tracking 2%. Square corners — the design uses no radius on buttons.
 *
 * The height is the one departure: **every button on this site is 56** (client
 * decision, 2026-08-11), not the component's 66. It started with the search
 * form, where a button standing beside a 56px field had to square up with it,
 * and was then taken as the site-wide figure so a form's submit and a section's
 * call to action cannot be two different sizes. `.fluentform .ff-btn-submit`
 * carries the same value.
 *
 * Two neighbours deliberately keep their own heights, because neither is this
 * component: the 74px link tile (`.bw-linkgrid__item`, Figma's 459x74 "Link
 * button" — a full-width row with an arrow, not a button) and the footer
 * sign-up, which pairs a 52px field with a 52px submit inside a column.
 */
.bw-btn {
	align-items: center;
	background-color: transparent;
	border: 1px solid currentColor;
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	display: inline-flex;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-button);
	font-weight: var(--bw-weight-medium);
	justify-content: center;
	line-height: var(--bw-lh-button);
	letter-spacing: var(--bw-ls-button);
	min-height: 56px;
	padding-inline: clamp(1.25rem, 1.968vw, 2.125rem);
	text-decoration: none;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial),
		transform var(--bw-duration-icon) var(--bw-ease-industrial);
}

.bw-btn:hover,
.bw-btn:focus {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-btn--primary {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-btn--primary:hover,
.bw-btn--primary:focus {
	background-color: var(--bw-primary-hover);
	border-color: var(--bw-primary-hover);
}

/* On a photographic hero the outline button needs to be light. */
.bw-hero--media .bw-btn {
	border-color: var(--bw-primary-contrast);
	color: var(--bw-primary-contrast);
}

/*--------------------------------------------------------------
# Block: Hero
--------------------------------------------------------------*/

.bw-block__placeholder {
	background-color: var(--bw-surface-alt);
	color: var(--bw-text-muted);
	padding: var(--bw-space-7);
	text-align: center;
}

.bw-hero {
	position: relative;
}

/*
 * `minmax(0, 1fr)`, here and on the page banner and the statement band, for
 * the reason spelled out on `.bw-projects__stack`: an `auto` column floors at
 * the widest item's min-content, so the display heading inside sized the frame
 * to 313px at a 320px viewport and pushed the document out with it. The three
 * full-bleed grids all stack one cell and all had the same hole.
 */
.bw-hero__frame {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	min-height: clamp(28rem, 55.15vw, 59.5rem); /* 953px at the design width */
	position: relative;
}

.bw-hero--full .bw-hero__frame {
	min-height: calc(100svh - var(--bw-header-height));
}

/* Out of flow, so a tall photograph cannot stretch the hero past its
   min-height — see the note on .bw-pagehead__media. */
.bw-hero__media {
	grid-area: 1 / 1;
	overflow: hidden;
	position: relative;
}

.bw-hero__media img {
	display: block;
	height: 100%;
	inset: 0;
	object-fit: cover;
	position: absolute;
	width: 100%;
}

/* Legibility scrim — the Figma image is dark enough on the left, but uploads
   will not always be. See --bw-scrim. */
.bw-hero--media .bw-hero__media::after {
	background: var(--bw-scrim);
	content: "";
	inset: 0;
	position: absolute;
}

.bw-hero__inner {
	align-content: end;
	display: grid;
	grid-area: 1 / 1;
	justify-items: start;
	padding-block: clamp(2rem, 4.63vw, 5rem);
	position: relative;
	row-gap: var(--bw-space-5);
	z-index: 1;
}

/**
 * Display size at Kanit Bold — this is the "Display / Uppercase" token used
 * without the uppercase transform, which is how the Figma hero is set.
 */
.bw-hero__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-display);
	font-weight: var(--bw-weight-bold);
	line-height: var(--bw-lh-display);
	letter-spacing: 0;
	margin: 0;
	max-width: 22ch;
}

.bw-hero__subtext {
	font-size: var(--bw-size-subtitle);
	line-height: var(--bw-lh-subtitle);
	margin: 0;
	max-width: 55ch;
}

.bw-hero--media .bw-hero__heading,
.bw-hero--media .bw-hero__subtext {
	color: var(--bw-primary-contrast);
}

.bw-hero__cta {
	margin-top: var(--bw-space-4);
}

/*
 * The 40px block inset inside the page gutter, matching the statement band,
 * the page banner and the section blocks. Figma puts the hero heading at
 * x=184 on a 1728 canvas: 144 gutter + 40.
 */
.bw-hero__heading,
.bw-hero__subtext,
.bw-hero__cta {
	margin-inline-start: var(--bw-block-inset);
}

/*--------------------------------------------------------------
## Hero marquee
--------------------------------------------------------------*/

.bw-hero__marquee {
	background-color: var(--bw-surface);
	overflow: hidden;
	position: relative;
	width: 100%;
}

.bw-hero__marquee-track {
	display: flex;
	gap: clamp(2rem, 5.79vw, 6.25rem);
	width: max-content;
	animation: bw-marquee 30s linear infinite;
}

.bw-hero__marquee-text {
	color: var(--bw-grey-300);
	font-family: var(--bw-font-display);
	font-size: clamp(4rem, 13.3vw, 14.375rem);
	font-weight: var(--bw-weight-bold);
	line-height: 1.1;
	letter-spacing: 0;
	text-transform: uppercase;
	white-space: nowrap;
}

@keyframes bw-marquee {

	from {
		transform: translateX(0);
	}

	to {
		/* Half the track = one copy of the text, so the loop is seamless. */
		transform: translateX(-50%);
	}
}

.bw-hero__marquee:hover .bw-hero__marquee-track,
.bw-hero__marquee:focus-within .bw-hero__marquee-track {
	animation-play-state: paused;
}

/*
 * The explicit stop, set by the toggle button — see js/marquee.js and the
 * `.bw-marquee-toggle` rules below. It has to beat the :hover rule above,
 * or a paused marquee would start again the moment the pointer left the
 * button that paused it.
 */
.bw-hero__marquee.is-paused .bw-hero__marquee-track,
.bw-hero__marquee.is-paused:hover .bw-hero__marquee-track {
	animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {

	.bw-hero__marquee-track {
		animation: none;
	}
}

/*--------------------------------------------------------------
## Marquee pause control
--------------------------------------------------------------*/

/**
 * SC 2.2.2: anything that moves by itself for more than five seconds has
 * to be stoppable, and hover alone does not give a keyboard user that.
 * Shared by the hero wordmark and the client logo strip; js/marquee.js
 * injects one into each region and flips its label and `data-state`.
 *
 * Injected rather than rendered in PHP so the control cannot exist while
 * being inert: without JavaScript the CSS animation still runs but the
 * button would do nothing, and a dead pause button is worse than none.
 *
 * The glyph is drawn here rather than shipped as an icon — two bars and a
 * triangle, the same approach the menu toggle takes with its own bars.
 */
.bw-marquee-toggle {
	align-items: center;
	background-color: var(--bw-surface);
	border: 1px solid var(--bw-rule);
	border-radius: var(--bw-radius-control);
	color: var(--bw-accent);
	cursor: pointer;
	display: inline-flex;
	height: 44px;
	inset-block-start: 50%;
	inset-inline-end: var(--bw-gutter);
	justify-content: center;
	padding: 0;
	position: absolute;
	transform: translateY(-50%);
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial);
	width: 44px;
	z-index: 2;
}

.bw-marquee-toggle:hover,
.bw-marquee-toggle:focus-visible {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-marquee-toggle__glyph {
	display: block;
	height: 14px;
	position: relative;
	width: 12px;
}

/* Pause — two bars, shown while the marquee is running. */
.bw-marquee-toggle[data-state="playing"] .bw-marquee-toggle__glyph::before,
.bw-marquee-toggle[data-state="playing"] .bw-marquee-toggle__glyph::after {
	background-color: currentColor;
	content: "";
	inset-block: 0;
	position: absolute;
	width: 4px;
}

.bw-marquee-toggle[data-state="playing"] .bw-marquee-toggle__glyph::before {
	inset-inline-start: 0;
}

.bw-marquee-toggle[data-state="playing"] .bw-marquee-toggle__glyph::after {
	inset-inline-end: 0;
}

/* Play — one triangle, drawn with borders, shown while it is stopped. */
.bw-marquee-toggle[data-state="paused"] .bw-marquee-toggle__glyph::before {
	border-block: 7px solid transparent;
	border-inline-start: 12px solid currentColor;
	content: "";
	inset-block-start: 0;
	inset-inline-start: 0;
	position: absolute;
}

/* The hero marquee is already positioned; the client strip is not. */
.bw-clients--scroll {
	position: relative;
}

/**
 * On the client strip the control comes out of the overlap and sits under the
 * logos, right-aligned to the page gutter.
 *
 * Over the hero it can sit on the moving content: that content is a watermark,
 * declared decoration, and covering a slice of it costs nothing. These are
 * client logos — somebody's mark, and the reason the strip exists — and an
 * opaque 44px button parked over the track hid whichever one was passing
 * beneath it.
 *
 * A column flex rather than a nudge, so the button takes its own row and the
 * heading, the strip and the control stay in source order.
 */
.bw-clients--scroll {
	display: flex;
	flex-direction: column;
}

.bw-clients--scroll .bw-marquee-toggle {
	align-self: flex-end;
	inset: auto;
	margin-block-start: var(--bw-space-4);
	margin-inline-end: var(--bw-gutter);
	position: static;
	transform: none;
}

/*
 * With the animation already off there is nothing to pause, so the control
 * goes — `display: none` rather than a visual hide, so it leaves the
 * accessibility tree and the tab order with it.
 */
@media (prefers-reduced-motion: reduce) {

	.bw-marquee-toggle {
		display: none;
	}
}

/*--------------------------------------------------------------
# Arrow link
--------------------------------------------------------------*/

/**
 * "View Project ↗" — Kanit Medium 16/24, 18px gap, 16x16 red arrow.
 * Figma: "Learn more link".
 */
.bw-link-arrow {
	align-items: center;
	color: var(--bw-text);
	display: inline-flex;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-nav);
	font-weight: var(--bw-weight-medium);
	gap: 18px;
	line-height: var(--bw-lh-nav);
	letter-spacing: var(--bw-ls-nav);
	text-decoration: none;
}

.bw-link-arrow__icon {
	display: block;
	height: 16px;
	transition: transform var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 16px;
}

.bw-link-arrow:hover .bw-link-arrow__icon,
.bw-link-arrow:focus .bw-link-arrow__icon {
	transform: translate(3px, -3px);
}

/*--------------------------------------------------------------
# Block: Section (image + text)
--------------------------------------------------------------*/

/**
 * Figma "Section left" (image right) and "Section right" (image left) — exact
 * mirrors, so one block with an image_side modifier.
 *
 * Canvas 1728x722. Image 971 wide (56.19%), 12px radius, full-bleed to its
 * edge. Title and subtitle share a white card outlined in brand red that
 * straddles the image's inner edge; the body copy and link sit below the card,
 * indented to its inner text edge and clear of the photo.
 */
/*
 * The 722px canvas is the band itself; the space around it is not part of it.
 * "Services - Home" stacks six of these at a pitch of 902 — 722 of band and
 * 180 of air — and that 180 is supplied by the section rhythm at the end of
 * this file, not here, because it is the same 180 every other section gets.
 */
.bw-split {
	min-height: clamp(26rem, 41.78vw, 45.125rem); /* 722px at design width */
	position: relative;
}

/*
 * The photo is full-bleed to the viewport on its outer side, so only its INNER
 * corners are rounded. A radius is a corner between two visible edges; the
 * outer edge is cut off by the screen, and rounding it there just carves two
 * notches out of the bleed. Same reason the banner and the statement band are
 * square where they run off the page.
 */
.bw-split__media {
	inset-block: 0;
	overflow: hidden;
	position: absolute;
	width: 56.19%;
}

.bw-split--image-right .bw-split__media {
	border-radius: var(--bw-radius-surface) 0 0 var(--bw-radius-surface);
	right: 0;
}

.bw-split--image-left .bw-split__media {
	border-radius: 0 var(--bw-radius-surface) var(--bw-radius-surface) 0;
	left: 0;
}

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

/*
 * Card first, body below it. A column rather than a fixed two-row grid,
 * because the card now sets its own height — a title that wraps to a third
 * line pushes the body down instead of colliding with it.
 */
.bw-split__content {
	display: flex;
	flex-direction: column;
	padding-block: clamp(2rem, 5.09vw, 5.5rem) clamp(2rem, 4.63vw, 5rem);
	position: relative;
	z-index: 1;
}

/**
 * The card. Opaque so the type stays legible over the photo, outlined in brand
 * red, and wider than the gap beside the image so it deliberately runs ACROSS
 * it — that overlap is the whole composition.
 *
 * The 11.3% outer margin plus the card's own 4.17% padding put the title's
 * left edge at 15.45% (267px), where the design starts the text column.
 */
.bw-split__card {
	background-color: var(--bw-primary-contrast);
	border: 1px solid var(--bw-primary);
	border-radius: var(--bw-radius-surface);
	padding: clamp(1.5rem, 2.78vw, 3rem) clamp(1.5rem, 4.17vw, 4.5rem);
	width: 59.7%; /* 1032px */
}

.bw-split--image-right .bw-split__card {
	margin-left: 11.3%;
}

/* Mirrored: the card hangs off the right edge and overlaps the photo's right. */
.bw-split--image-left .bw-split__card {
	margin-left: auto;
	margin-right: 11.3%;
}

/* This is the heading that forced the base `overflow-wrap` — see design-system.css. */
.bw-split__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-display);
	font-weight: var(--bw-weight-bold);
	line-height: var(--bw-lh-display);
	letter-spacing: 0;
	margin: 0;
	text-transform: uppercase;
}

.bw-split__subtitle {
	font-family: var(--bw-font-display);
	font-size: clamp(1.25rem, 1.505vw, 1.625rem); /* 26px */
	font-weight: 400;
	line-height: 1.538;
	letter-spacing: 0.02em;
	margin: var(--bw-space-5) 0 0;
}

/*
 * The body copy has no backing plate, so it must clear the photo. Indented to
 * the card's inner text edge (11.3% + 4.17%) so it lines up with the title.
 */
.bw-split__body {
	display: flex;
	flex-direction: column;
	gap: clamp(1.25rem, 2.43vw, 2.625rem); /* 42px */
	margin-top: clamp(1.5rem, 2.31vw, 2.5rem); /* 40px */
	width: 27%;
}

.bw-split--image-right .bw-split__body {
	margin-left: 15.47%;
}

/*
 * Right-anchored so its right edge still lands on the card's inner text edge
 * (84.53%). The column is narrowed rather than shifted, which puts its left
 * edge at 58.87% — 46px clear of the photo, which ends at 56.19%.
 */
.bw-split--image-left .bw-split__body {
	margin-left: auto;
	margin-right: 15.47%;
	width: 25.66%;
}

.bw-split__text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

/*--------------------------------------------------------------
## Section — tablet & mobile
--------------------------------------------------------------*/

/**
 * Below 1024 the overlap stops working: a card three-fifths of the viewport
 * wide leaves the photo no room, and the display type inside it has nowhere to
 * wrap. Stack instead — image first, then content — and let the text columns
 * run full width.
 */
@media (max-width: 1024px) {

	.bw-split {
		display: flex;
		flex-direction: column;
		gap: var(--bw-space-7);
		min-height: 0;
	}

	/*
	 * Stacked, the photo bleeds to BOTH edges, so both sides lose the radius by
	 * the same rule the desktop layout follows.
	 */
	.bw-split__media,
	.bw-split--image-right .bw-split__media,
	.bw-split--image-left .bw-split__media {
		aspect-ratio: 16 / 10;
		border-radius: 0;
		inset: auto;
		position: relative;
		width: 100%;
	}

	/*
	 * The percentage insets are gone, so the text needs the page gutter back —
	 * without it copy runs to the viewport edge and overflows.
	 */
	.bw-split__content,
	.bw-split--image-right .bw-split__content,
	.bw-split--image-left .bw-split__content {
		gap: var(--bw-space-6);
		padding-block: 0;
		padding-inline: var(--bw-gutter);
	}

	/*
	 * Nothing left to overlap, so the card drops its inset and runs the width
	 * of the text column. The outline stays — it is the block's signature.
	 */
	.bw-split__card,
	.bw-split--image-right .bw-split__card,
	.bw-split--image-left .bw-split__card {
		margin-inline: 0;
		padding: var(--bw-space-6) var(--bw-space-5);
		width: auto;
	}

	.bw-split__body,
	.bw-split--image-right .bw-split__body,
	.bw-split--image-left .bw-split__body {
		margin-inline: 0;
		margin-top: 0;
		max-width: 55ch;
		width: auto;
	}
}

/*--------------------------------------------------------------
# Block: Projects Carousel
--------------------------------------------------------------*/

/**
 * The Section band, cycling through Projects. Each slide IS a `.bw-split` —
 * it inherits every rule above, so the two blocks cannot drift apart.
 *
 * The slides are stacked in one grid cell rather than laid out in a row. That
 * buys two things: the band never moves (only its contents cross-fade), and
 * the stack is as tall as its tallest slide, so a longer title on project
 * three does not make the page jump when it arrives.
 */
.bw-projects {
	position: relative;
}

/*
 * `minmax(0, 1fr)` and the slide's `min-width: 0` are what let the track
 * be narrower than its contents. An `auto` column floors at the widest
 * slide's min-content, which the display title sets — so at 320px the
 * stack sized itself to 393px and took the document with it, however the
 * title wrapped. Both halves are needed: this lets the box shrink,
 * `overflow-wrap` on `.bw-split__title` keeps the text inside it.
 */
.bw-projects__stack {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

/*
 * `visibility` rather than `display` — a hidden slide keeps its size (it is
 * what holds the stack open) but leaves the tab order and the accessibility
 * tree. The 0s visibility transition is delayed on the way out so the fade
 * has time to finish, and instant on the way in.
 */
.bw-projects__slide {
	grid-area: 1 / 1;
	min-width: 0;
	opacity: 0;
	transition:
		opacity var(--bw-duration-panel) var(--bw-ease-exit),
		visibility 0s linear var(--bw-duration-panel);
	visibility: hidden;
}

.bw-projects__slide.is-active {
	opacity: 1;
	transition:
		opacity var(--bw-duration-panel) var(--bw-ease-enter),
		visibility 0s;
	visibility: visible;
}

/*
 * A small settle on the card and the copy, so the swap reads as new content
 * arriving rather than a photo dissolving. The image only fades — moving it
 * would break the illusion that the band is holding still.
 */
.bw-projects__slide .bw-split__card,
.bw-projects__slide .bw-split__body {
	transform: translateY(var(--bw-reveal-shift));
	transition: transform var(--bw-duration-panel) var(--bw-ease-enter);
}

.bw-projects__slide.is-active .bw-split__card,
.bw-projects__slide.is-active .bw-split__body {
	transform: none;
}

/* The controls sit above the card, aligned to its outer edge. */
.bw-projects__nav {
	display: flex;
	gap: var(--bw-space-3);
	position: absolute;
	top: 0;
	z-index: 2;
}

.bw-projects__nav--image-right {
	left: 11.3%;
}

.bw-projects__nav--image-left {
	right: 11.3%;
}

/*
 * Room for the controls above the card. The floor is raised over the Section
 * block's own padding so the tallest button can never reach the card.
 */
.bw-projects .bw-split__content {
	padding-block-start: clamp(4.5rem, 5.09vw, 5.5rem);
}

.bw-projects__arrow {
	align-items: center;
	background-color: transparent;
	border: 1px solid var(--bw-border);
	border-radius: var(--bw-radius-control);
	color: var(--bw-primary);
	cursor: pointer;
	display: inline-flex;
	height: clamp(2.25rem, 2.43vw, 2.625rem); /* 42px */
	justify-content: center;
	padding: 0;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial);
	width: clamp(3.5rem, 6.25vw, 6.75rem); /* 108px */
}

.bw-projects__arrow:hover,
.bw-projects__arrow:focus-visible {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-projects__arrow:focus-visible {
	outline: var(--bw-focus-ring);
	outline-offset: var(--bw-focus-offset);
}

.bw-projects__chevron {
	transition: transform var(--bw-duration-icon) var(--bw-ease-industrial);
}

.bw-projects__arrow[data-bw-carousel-prev]:hover .bw-projects__chevron {
	transform: translateX(-2px);
}

.bw-projects__arrow[data-bw-carousel-next]:hover .bw-projects__chevron {
	transform: translateX(2px);
}

/*--------------------------------------------------------------
## Projects Carousel — tablet & mobile
--------------------------------------------------------------*/

/*
 * The Section block stops overlapping below 1024, so there is no longer a
 * clear column for the controls to sit in. They join the flow above the
 * stack, in the page gutter.
 */
@media (max-width: 1024px) {

	.bw-projects {
		display: flex;
		flex-direction: column;
		gap: var(--bw-space-6);
	}

	.bw-projects__nav,
	.bw-projects__nav--image-right,
	.bw-projects__nav--image-left {
		inset: auto;
		padding-inline: var(--bw-gutter);
		position: static;
	}

	.bw-projects .bw-split__content {
		padding-block-start: 0;
	}
}

@media (prefers-reduced-motion: reduce) {

	/* Swap outright: no cross-fade, no settle. */
	.bw-projects__slide,
	.bw-projects__slide.is-active,
	.bw-projects__slide .bw-split__card,
	.bw-projects__slide .bw-split__body {
		transition: none;
	}

	.bw-projects__slide .bw-split__card,
	.bw-projects__slide .bw-split__body {
		transform: none;
	}
}

/*--------------------------------------------------------------
# Block: Statement Band
--------------------------------------------------------------*/

/**
 * Figma "Section QHSE" (266:3791) — 1728x500, full-bleed photo, red eyebrow,
 * white Section Title, white arrow link. Without an image it degrades to a
 * flat surface with dark text.
 */
.bw-band {
	background-color: var(--bw-picture-frame);
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	min-height: clamp(20rem, 28.94vw, 31.25rem); /* 500px */
	position: relative;
}

/**
 * Absolute, not a grid cell — the trap documented for the hero and the banner,
 * which `.bw-band` was left holding. As a grid item the row is sized by the
 * image, so the `height: 100%` below has no definite height to resolve against
 * and the image falls back to its natural aspect: `min-height` becomes a floor
 * nothing ever touches. The Home statement band drew 959px against its 500,
 * and the equipment band 1285 against its 782. Taking the media out of flow
 * lets `.bw-band__inner` size the grid and the min-height govern (fixed
 * 2026-08-10).
 */
.bw-band__media {
	inset: 0;
	overflow: hidden;
	position: absolute;
}

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

.bw-band__inner {
	align-content: center;
	display: grid;
	grid-area: 1 / 1;
	justify-items: start;
	padding-block: clamp(2.5rem, 5.09vw, 5.5rem);
	position: relative;
	row-gap: var(--bw-space-5);
	z-index: 1;
}

/* The 40px block inset the design puts inside the page gutter. */
.bw-band__label,
.bw-band__statement,
.bw-band__heading,
.bw-band__text,
.bw-band__link {
	margin: 0;
	margin-left: var(--bw-block-inset);
}

.bw-band__statement {
	max-width: 1400px;
}

/**
 * The label joins the rest of the band's type on a photograph, where Figma
 * draws it in brand red.
 *
 * Red is the one colour that cannot be guaranteed here. The other four went
 * white because an editor uploads whatever photograph they like; the label was
 * left behind, so it stayed red over an image the design never saw — and over
 * the `--bw-band--scrim` overlay that exists to make this exact type legible,
 * an accessible red is around 2.8:1. Nothing between "light enough for a dark
 * photo" and "dark enough for a light one" passes both, which is why the band
 * picked white for everything else. The eyebrow keeps its red on the flat
 * fallback, where the surface is known — see "Grey surfaces" (A11Y-02).
 */
.bw-band--media .bw-band__label,
.bw-band--media .bw-band__statement,
.bw-band--media .bw-band__heading,
.bw-band--media .bw-band__text,
.bw-band--media .bw-link-arrow {
	color: var(--bw-primary-contrast);
}

/**
 * The archive feature band — "Operations - Assets" `Image` (`235:1037`), a
 * heading over a paragraph rather than the statement band's single pull-quote.
 * Subtitle is tracking 0; `body` sets 0.02em, which would otherwise inherit.
 * 909px is the width the design gives the paragraph.
 */
.bw-band__text {
	font-size: var(--bw-size-subtitle);
	line-height: var(--bw-lh-subtitle);
	letter-spacing: 0;
	max-width: 909px;
}

/* 782 at 1728, against the statement band's 500. */
.bw-band--tall {
	min-height: clamp(24rem, 45.25vw, 48.875rem);
}

/**
 * Text high in the band rather than centred on it. "Operations - Assets" starts
 * the heading's box at y=126 of 782 (`236:1038`) and drops the paragraph 13px
 * under it (`236:1040`) — the copy sits in the top third and the photograph
 * carries the rest. `.bw-band__inner` centres by default, which put the pair
 * about 190px too low.
 *
 * 126/1728 is the 7.29vw term. Only the top is pinned; the inherited
 * `padding-block` still guards the bottom if the copy ever grows past the band.
 */
.bw-band--top .bw-band__inner {
	align-content: start;
	padding-block-start: clamp(2.5rem, 7.29vw, 7.875rem);
	row-gap: var(--bw-space-3);
}

/**
 * An opt-in scrim. The Home statement band is drawn against a dark photograph
 * the designer chose and needs none; an archive band takes whatever is
 * uploaded, so its white type gets the same overlay the hero and banner use.
 */
.bw-band--scrim .bw-band__media::after {
	background: var(--bw-scrim);
	content: "";
	inset: 0;
	position: absolute;
}

/* Top-aligned copy needs the weight at the top — see `--bw-scrim-top`. */
.bw-band--scrim.bw-band--top .bw-band__media::after {
	background: var(--bw-scrim-top);
}

/**
 * The third alignment: copy low in the band. QHSE's Frame 26 (`255:3413`) sits
 * at y=442 of 657 and ends 73 short of the floor, so the photograph carries the
 * top two thirds and the type sits on the ground the scrim already darkens —
 * `--bw-scrim` needs no mirroring here, being bottom-weighted by default.
 *
 * The trailing padding is the block's, not the modifier's: only the alignment
 * is shared, and a second caller may not stop 73 from the bottom.
 */
.bw-band--bottom .bw-band__inner {
	align-content: end;
}

/*--------------------------------------------------------------
# Block: Feature Band
--------------------------------------------------------------*/

/**
 * Figma "QHSE" Frame 87 (`255:3412`) — 1728x657 of photograph closing the
 * screen, with a heading, a rule and a paragraph in its lower left.
 *
 * Not the statement band (one pull-quote, centred) and not `.bw-band--tall`
 * (782, copy at the top): it is the same component with a third alignment and
 * its own type scale, which is why it earns a modifier rather than a field.
 *
 * 657/1728 is the 38.02vw term.
 */
.bw-featureband {
	min-height: clamp(22rem, 38.02vw, 41.0625rem); /* 657px at design width */
}

/**
 * 73px of floor under the copy — Frame 26 ends at 584 of 657.
 *
 * `row-gap: 0` because this band's three rows are measured from each other
 * (15px, twice) rather than set on the inner's default 24. The heading owns
 * both gaps, so the pair cannot drift apart.
 */
.bw-featureband .bw-band__inner {
	padding-block-end: clamp(2.5rem, 4.22vw, 4.5625rem);
	row-gap: 0;
}

/**
 * No block inset. Every other band nests a 1440 frame in the 144 gutter and
 * insets its text a further 40, landing at 184; this one hangs Frame 26
 * straight off the band at x=145. So the gutter alone, and the 40px rule
 * inherited from `.bw-band__*` has to be taken back off.
 */
/**
 * `width: 100%` is doing real work, not padding the declaration. `.bw-band__inner`
 * sets `justify-items: start`, so a grid item is sized to its content: a heading
 * shorter than 818 would shrink to its text and take the rule with it, because
 * the rule is `width: 100%` OF THE HEADING. Filling the 818 first is what lands
 * the rule at its drawn 694 regardless of how long the heading runs.
 *
 * The paragraph does not need it — its max-content already overflows 818 — but
 * it shares the declaration harmlessly.
 */
.bw-featureband .bw-band__heading,
.bw-featureband .bw-band__text {
	margin-left: 0;
	max-width: 818px;
	width: 100%;
}

/* 35/48 at 2% — `--bw-size-h4`, read off the 48px text box (`255:3414`). */
.bw-featureband__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h4);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
}

/**
 * The rule (`255:3415`), 694 wide and 15px under the heading box. `::after` on
 * the heading for the reason given on `.bw-titled__title::after`, and
 * `width: 100%` with a `max-width` for the min-content trap documented there.
 *
 * `currentColor` at half strength rather than `--bw-rule`: this line is drawn
 * over a photograph, where the grey the pattern uses on white would disappear.
 * The weight is a derivation — the LoFi carries no stroke colour.
 */
.bw-featureband__heading::after {
	background-color: currentColor;
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-4);
	max-width: 694px;
	opacity: 0.5;
	width: 100%;
}

/* 20/32 — `--bw-size-body-lg`, from the 64px two-line box (`255:3416`). */
.bw-featureband .bw-band__text {
	font-size: var(--bw-size-body-lg);
	line-height: var(--bw-lh-body-lg);
	letter-spacing: 0;
	margin-top: var(--bw-space-4);
}

/*--------------------------------------------------------------
# Pattern: Titled statement
--------------------------------------------------------------*/

/**
 * Vision, Mission and Our Values are drawn with the *same* sub-frame on the
 * About Us screen — all three are literally named "Frame 26" (`243:1321`,
 * `243:1325`, `243:1409`). Heading on a 56px box, a 482px rule 32px under it,
 * statement 32px under the rule. One class, three callers; if a fourth section
 * wants the treatment, reach for this rather than redrawing it.
 *
 * 56 is `--bw-size-h3` (44/56) and 34 is `--bw-size-subtitle` (27/34), read
 * off the text-node heights: the statements measure 68 for two lines and 136
 * for four.
 */
/*
 * Brand red, not the base heading colour (client, 2026-08-11) — so the three
 * headings this pattern draws read as a set across the page. The base h1–h6
 * rule sets an explicit colour, but a class beats a type selector, so this
 * stands without help.
 */
.bw-titled__title {
	color: var(--bw-accent);
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h3);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h3);
	letter-spacing: var(--bw-ls-h3);
	margin: 0;
}

/**
 * The rule is `::after` on the heading rather than an element of its own, so
 * it cannot be separated from the title it belongs to.
 *
 * `width: 100%` with a `max-width`, never a flat `482px` — the trap documented
 * on `.bw-track-card__title`. The rule is a block child of the heading, so a
 * fixed width would set the heading's min-content width and stop it shrinking:
 * the panel would narrow while the rule stayed put and overflowed.
 */
.bw-titled__title::after {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-6);
	max-width: 482px;
	width: 100%;
}

/* Subtitle is tracking 0; `body` sets 0.02em and it inherits as computed px. */
.bw-titled__text {
	font-size: var(--bw-size-subtitle);
	line-height: var(--bw-lh-subtitle);
	letter-spacing: 0;
	margin: var(--bw-space-6) 0 0;
}

/*--------------------------------------------------------------
# Block: Statement Pair
--------------------------------------------------------------*/

/**
 * Figma "About Us" `243:1297` / `243:1320` — two panels, 862 wide each, sitting
 * at x=0 and x=864 of 1728, and the band is ruled top and bottom as well as
 * down the middle (designer, 2026-08-11). All three lines are the same 2px
 * weight, so the pair reads as a banded strip rather than two floating columns.
 * Left and right stay open: the band runs to the edge of the viewport.
 *
 * **Strokes are not in the metadata dump** — it carries geometry only, so the
 * rules had to come from a designer read, exactly like the asset chips' fills.
 * The 2px void between the two frames is the only trace of them the dump holds.
 * Assume nothing about lines or fills from a dump; that is what it cannot tell
 * you.
 *
 * All three are **borders**, never a grid `gap` over a rule-coloured container.
 * The container trick is what the project meta strip uses and it is right
 * there — but that strip is a *closed* box, so its border hides its own edges.
 * This band is open left and right, and a painted container cannot be covered
 * pixel-exactly by its children: at a fractional layout offset the panels'
 * painted boxes snap just inside the container's and a sliver of `--bw-rule`
 * antialiases down the open edge. Borders draw only where they are asked to.
 */
.bw-pair {
	border-block: 2px solid var(--bw-rule);
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.bw-pair__panel + .bw-pair__panel {
	border-left: 2px solid var(--bw-rule);
}

/**
 * 578 tall, content inset 142 on both sides and pinned 140 from the top. The
 * design's two panels hold that same 578 while their statements run to two and
 * four lines, so the height is a floor with the copy top-aligned, not a box the
 * content is centred in. A longer statement deepens both panels together —
 * grid stretches them to match.
 */
.bw-pair__panel {
	background-color: var(--bw-surface);
	min-height: clamp(20rem, 33.45vw, 36.125rem);
	padding: clamp(2.5rem, 8.102vw, 8.75rem) clamp(1.5rem, 8.218vw, 8.875rem);
}

/**
 * The design butts this pair straight onto the statement band above it: both
 * live inside Frame 113 and the band's 500 ends exactly where Vision's y begins.
 * Every other boundary on the page is 180, so this has to opt out by name.
 * Matched at the same specificity as the rhythm rule and declared later, which
 * is what lets it win.
 */
.entry--blocks > .entry-content > .bw-pair--flush,
.site-main > .bw-pair--flush {
	margin-block-start: 0;
}

@media (max-width: 900px) {

	.bw-pair {
		grid-template-columns: minmax(0, 1fr);
	}

	/* Stacked, the join turns through 90 degrees with the layout. */
	.bw-pair__panel + .bw-pair__panel {
		border-left: 0;
		border-top: 2px solid var(--bw-rule);
	}

	/* Stacked, the 578 floor is just dead space under two lines of copy. */
	.bw-pair__panel {
		min-height: 0;
	}
}

/*--------------------------------------------------------------
# Block: Track Record
--------------------------------------------------------------*/

.bw-track__intro {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-5);
	margin-bottom: clamp(2rem, 4.05vw, 4.375rem);
	max-width: 908px;
	padding-left: var(--bw-block-inset);
}

.bw-track__intro-text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

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

/**
 * Card: 458x468 in the design, 40px padding, 22px gaps, centred, with a rule
 * between the title and the body. The first card is filled brand red.
 */
.bw-track-card {
	align-items: center;
	background-color: var(--bw-surface);
	display: flex;
	flex-direction: column;
	gap: 22px;
	padding: var(--bw-space-7);
	text-align: center;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial),
		transform var(--bw-duration-control) var(--bw-ease-heavy);
}

/**
 * Hover fills the card in the brand red with white content — the state the
 * Figma mock shows on the first card. It is a hover state, not a permanent
 * variant, so no card is red at rest.
 */
.bw-track-card:hover {
	background-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

/*
 * The base h1–h6 rule sets an explicit colour, which beats inheritance from
 * the card — so the title has to opt back in.
 */
.bw-track-card:hover .bw-track-card__title {
	color: inherit;
}

/**
 * Icons are uploaded images, so their colour cannot be inherited. Flattening
 * to white covers whatever single-colour glyph an editor supplies, which is
 * what the red fill wants anyway.
 */
.bw-track-card:hover .bw-track-card__icon {
	filter: brightness(0) invert(1);
}

.bw-track-card__icon {
	display: block;
	height: 80px;
	object-fit: contain;
	transition: filter var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 88px;
}

/**
 * `width: 100%` matters. The rule below is a block child of this heading, so
 * giving it a fixed width would set the heading's min-content width and stop
 * the heading shrinking — the card would narrow while the title and rule stayed
 * put and overflowed. Sizing both from the card instead keeps them in step.
 */
.bw-track-card__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h4);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
	margin: 0;
	max-width: 342px;
	width: 100%;
}

.bw-track-card__title::after {
	background-color: currentColor;
	content: "";
	display: block;
	height: 1px;
	margin: 22px auto 0;
	opacity: 0.35;
	width: 100%;
}

.bw-track-card__body {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
	max-width: 346px;
}

/*--------------------------------------------------------------
# Block: Value List
--------------------------------------------------------------*/

/**
 * Figma "About Us" `245:1530`. Not the Track Record grid — that component was
 * derived from Home (`264:4342`, three 458px icon cards) and reusing it here
 * put four values in a three-column grid with an orphan on the second row.
 * This is a two-column list of 704x128 rows, title left and arrow right, one of
 * them drawn open at 330 with a rule and body copy (`245:1413`). A disclosure.
 *
 * 949 is the intro's own frame width with the text inset 40 inside it, giving
 * the 909 column the design measures — the same figure `.bw-people__intro`
 * uses. (`.bw-track__intro` caps at 908 *including* its inset and is 41px
 * narrow; left alone here, since fixing it moves the Home page.)
 *
 * 92 below the intro: the block ends at 188 and the first row starts at 280.
 */
.bw-values__intro {
	margin-bottom: clamp(2rem, 5.324vw, 5.75rem);
	max-width: 949px;
	padding-left: var(--bw-block-inset);
}

/**
 * Two 704 columns with a 32px gutter, and 32 between the rows (280 to 440 less
 * the 128 row). `align-items: start` is what makes an opened row grow downward
 * on its own instead of stretching the closed one beside it — exactly as the
 * design draws Frame 41 open at 330 with Frame 44 still 128.
 */
.bw-values__list {
	align-items: start;
	display: grid;
	gap: var(--bw-grid-gutter);
	grid-template-columns: repeat(2, minmax(0, 1fr));
	list-style: none;
	margin: 0;
	padding: 0;
}

/**
 * Surface treatment is derived, not lifted: the LoFi rows carry no fill, so
 * they follow `.bw-linkgrid__item` — the site's other full-width clickable row
 * with an arrow — as a grey tile at `--bw-radius-control`, the tier for things
 * you click.
 */
.bw-values__item {
	background-color: var(--bw-surface-alt);
	border-radius: var(--bw-radius-control);
	overflow: hidden;
}

/**
 * 40 top and bottom around a 48px line box makes the 128 exactly; the
 * `min-height` only matters once a title wraps, which should deepen the row
 * rather than overflow it. `list-style: none` (plus the WebKit pseudo-element)
 * removes the default disclosure triangle — the arrow on the right replaces it.
 */
.bw-values__summary {
	align-items: center;
	cursor: pointer;
	display: flex;
	gap: var(--bw-space-5);
	justify-content: space-between;
	list-style: none;
	min-height: 128px;
	padding: var(--bw-space-7) clamp(1.5rem, 4.63vw, 5rem);
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial);
}

.bw-values__summary::-webkit-details-marker {
	display: none;
}

/**
 * Red on hover and focus only — never on `[open]`. The site's rule, set by the
 * Track Record card and the link grid, is that the brand fill is a pointer
 * state; an open row already announces itself by showing its copy, and filling
 * it as well would invent a third surface the design never draws.
 */
.bw-values__summary:hover,
.bw-values__summary:focus-visible {
	background-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-values__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h4);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
	margin: 0;
}

/*
 * The base h1–h6 rule sets an explicit colour, which beats inheritance from the
 * row — so the title has to opt back in once the row fills.
 */
.bw-values__summary:hover .bw-values__title,
.bw-values__summary:focus-visible .bw-values__title {
	color: inherit;
}

/* Figma names the open row's glyph "Arrow upward", so closed points down. */
.bw-values__icon {
	color: var(--bw-primary);
	flex-shrink: 0;
	height: 24px;
	transform: rotate(180deg);
	transition:
		color var(--bw-duration-control) var(--bw-ease-industrial),
		transform var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 24px;
}

.bw-values__item[open] .bw-values__icon {
	transform: rotate(0deg);
}

.bw-values__summary:hover .bw-values__icon,
.bw-values__summary:focus-visible .bw-values__icon {
	color: var(--bw-primary-contrast);
}

/* Rule and copy are inset 53, not the summary's 80 (`245:1415` / `245:1416`). */
.bw-values__panel {
	padding: 0 clamp(1rem, 3.067vw, 3.3125rem);
}

/**
 * The rule sits at y=129 — immediately under the 128 summary — so it is the
 * panel's top edge, carried on the paragraph rather than the panel so that it
 * picks up the 53px inset. 34 to the copy, as drawn.
 *
 * The design's frame ends flush with the last line of text, which is a
 * content-tight LoFi frame rather than a deliberate zero: the 40 below matches
 * the summary's own padding.
 */
.bw-values__body {
	border-top: 1px solid var(--bw-rule);
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
	padding-block: 34px var(--bw-space-7);
}

/**
 * Animating a disclosure open needs no script: `interpolate-size` lets the
 * block-size transition to `auto`, and `allow-discrete` carries
 * `content-visibility` along with it. `interpolate-size` is scoped to the item
 * rather than `:root` so it cannot change how anything else on the site sizes.
 *
 * Where it is unsupported the row simply snaps open, which is the plain
 * `<details>` behaviour and entirely fine.
 */
@supports (interpolate-size: allow-keywords) {

	.bw-values__item {
		interpolate-size: allow-keywords;
	}

	.bw-values__item::details-content {
		block-size: 0;
		overflow: hidden;
		transition:
			block-size var(--bw-duration-panel) var(--bw-ease-industrial),
			content-visibility var(--bw-duration-panel) var(--bw-ease-industrial);
		transition-behavior: allow-discrete;
	}

	.bw-values__item[open]::details-content {
		block-size: auto;
	}
}

@media (prefers-reduced-motion: reduce) {

	.bw-values__item::details-content {
		transition: none;
	}
}

@media (max-width: 900px) {

	.bw-values__list {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*--------------------------------------------------------------
# Block: Client Wall
--------------------------------------------------------------*/

.bw-clients {
	overflow: hidden;
	padding-block: clamp(2rem, 3.7vw, 4rem);
}

.bw-clients__viewport {
	overflow: hidden;
	width: 100%;
}

.bw-clients__track {
	align-items: center;
	display: flex;
	gap: clamp(2.5rem, 4.86vw, 5.25rem);
	list-style: none;
	margin: 0;
	padding: 0;
	width: max-content;
}

.bw-clients--scroll .bw-clients__track {
	animation: bw-marquee 40s linear infinite;
}

.bw-clients--scroll:hover .bw-clients__track,
.bw-clients--scroll:focus-within .bw-clients__track {
	animation-play-state: paused;
}

/* The toggle's stop, which outranks :hover — see the hero marquee. */
.bw-clients--scroll.is-paused .bw-clients__track,
.bw-clients--scroll.is-paused:hover .bw-clients__track {
	animation-play-state: paused;
}

/* Static strip: centre it and let it wrap rather than overflow. */
.bw-clients:not(.bw-clients--scroll) .bw-clients__track {
	flex-wrap: wrap;
	justify-content: center;
	margin-inline: auto;
	max-width: var(--bw-container);
	padding-inline: var(--bw-gutter);
	width: auto;
}

.bw-clients__item {
	flex: 0 0 auto;
}

/**
 * Logos arrive at wildly different aspect ratios, so constrain the box and let
 * each sit inside it rather than forcing one width.
 */
.bw-clients__logo {
	display: block;
	height: auto;
	max-height: 94px;
	object-fit: contain;
	width: auto;
	max-width: 171px;
}

@media (prefers-reduced-motion: reduce) {

	.bw-clients--scroll .bw-clients__track {
		animation: none;
	}
}

/**
 * Grid layout — the Clients page (`204:429`).
 *
 * Frame 60 puts a heading and a 909-wide paragraph at the 40px block inset,
 * then Frame 146 lays nine 480x436 cells across 1440 with no gutter: three
 * equal columns sharing their edges. LoFi carries no strokes, so the hairline
 * that separates them is derived — it is the one reading of edge-to-edge cells
 * that is not just an accident of the placeholder boxes, and `--bw-rule` is
 * what every other divider on the site uses.
 */
.bw-clients--grid {
	overflow: visible;
}

.bw-clients__intro {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-6);
	/* Frame 60's paragraph ends 140 above the first row of cells. */
	margin-bottom: clamp(2.5rem, 8.102vw, 8.75rem);
	max-width: 949px;
	padding-left: var(--bw-block-inset);
}

.bw-clients__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0;
}

.bw-clients__intro-text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

/* The rules live on two edges per cell, with the wrapper closing the box. */
.bw-clients__grid {
	border-inline-start: 1px solid var(--bw-rule);
	border-block-start: 1px solid var(--bw-rule);
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	list-style: none;
	margin: 0;
	padding: 0;
}

.bw-clients__cell {
	align-items: center;
	aspect-ratio: 480 / 436;
	border-inline-end: 1px solid var(--bw-rule);
	border-block-end: 1px solid var(--bw-rule);
	display: flex;
	justify-content: center;
	padding: var(--bw-space-7);
}

.bw-clients__cell-link {
	align-items: center;
	display: flex;
	height: 100%;
	justify-content: center;
	width: 100%;
}

.bw-clients__cell-logo {
	height: auto;
	/* 132px at the design viewport, inside a 436-tall cell. */
	max-height: clamp(4rem, 7.639vw, 8.25rem);
	max-width: min(60%, 240px);
	object-fit: contain;
	width: auto;
}

.bw-clients__cell-fallback {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-card-label);
	font-weight: var(--bw-weight-medium);
	letter-spacing: var(--bw-ls-card-label);
	line-height: var(--bw-lh-card-label);
	text-align: center;
	text-transform: uppercase;
}

/*
 * Two columns below the container width and no further. A one-up logo wall is
 * a very long page for very little content, and logos stay legible at half a
 * phone's width.
 *
 * The logo's max-width is a share of the cell's CONTENT box, so the padding is
 * already subtracted before the percentage applies. The narrower the cell, the
 * larger that share has to be for the mark to hold its size — hence a step per
 * tier rather than one figure throughout.
 */
@media (max-width: 1024px) {

	.bw-clients__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.bw-clients__cell-logo {
		max-width: 75%;
	}
}

@media (max-width: 640px) {

	.bw-clients__cell {
		aspect-ratio: 4 / 3;
		padding: var(--bw-space-5);
	}

	.bw-clients__cell-logo {
		max-width: 100%;
	}
}

/*--------------------------------------------------------------
# Block: News Grid
--------------------------------------------------------------*/

.bw-news__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0 0 clamp(2rem, 4.05vw, 4.375rem);
	max-width: 704px;
	padding-left: var(--bw-block-inset);
}

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

/**
 * Card - news (275:3953): 459x768, image filling the top 436px.
 *
 * The component is drawn on a Grey 300 surface with a 12px radius and a soft
 * offset shadow. **That surface was dropped** (client decision, 2026-08-09) —
 * below the image the card carries no fill, border or shadow, and the radius
 * and the clip move to the image, the only surface the card still draws. With
 * nothing behind the text there is nothing to inset it from either, so the body
 * runs flush to the image edges rather than the design's 34/40px.
 *
 * Nothing here is interactive but the CTA: the card is not a link, so there are
 * no whole-card hover states.
 */
.bw-card-news {
	display: flex;
	flex-direction: column;
}

.bw-card-news__media {
	aspect-ratio: 459 / 436;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

.bw-card-news__media img {
	display: block;
	height: 100%;
	object-fit: cover;
	width: 100%;
}

.bw-card-news__body {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	gap: var(--bw-space-5);
	padding: var(--bw-space-5) 0 0;
}

.bw-card-news__title {
	font-family: var(--bw-font-body);
	font-size: var(--bw-size-subtitle);
	font-weight: var(--bw-weight-light);
	line-height: var(--bw-lh-subtitle);
	letter-spacing: 0;
	margin: 0;
}

/* The 369px rule between the title and the excerpt. */
.bw-card-news__title::after {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-5);
	max-width: 100%;
	width: 369px;
}

/**
 * The design clamps the excerpt to a fixed 102px box. Four lines at the body
 * line-height is the same thing, and survives a change of font size.
 */
.bw-card-news__excerpt {
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 4;
	display: -webkit-box;
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
	overflow: hidden;
}

/**
 * The card component ends in a "Text link with arrow", and it is the card's
 * only link. `margin-top: auto` pins it to the foot so the links line up across
 * a row of uneven cards. The arrow lift comes from `.bw-link-arrow` itself.
 */
.bw-card-news__cta {
	margin-top: auto;
}

.bw-card-news__cta:hover,
.bw-card-news__cta:focus-visible {
	color: var(--bw-primary);
}

/**
 * The News index: "News - Home" (204:504) lays the same card out two-up at
 * 704x716, where the image is a wide letterbox rather than the near-square of
 * the three-up card. Only the crop differs — the body is shared.
 */
.bw-news-index {
	list-style: none;
	margin: 0;
	padding: 0;
	row-gap: var(--bw-grid-row-gap);
}

.bw-card-news--wide .bw-card-news__media {
	aspect-ratio: 704 / 296;
}


/* `.bw-grid--2` goes one-up at 640, where the letterbox crop is too shallow. */
@media (max-width: 640px) {

	.bw-card-news--wide .bw-card-news__media {
		aspect-ratio: 459 / 296;
	}
}

/*--------------------------------------------------------------
# Post meta (category + date)
--------------------------------------------------------------*/

/**
 * The line that opens both the news card and the news single. The design draws
 * only the category here; the date is an addition (see
 * `template-parts/post-meta.php` for why). Both take `.bw-card-label`, so the
 * pair reads as one annotation and the row keeps the 24px height the frames
 * give the category on its own.
 */
.bw-postmeta {
	align-items: baseline;
	column-gap: var(--bw-space-4);
	display: flex;
	flex-wrap: wrap;
	margin: 0;
	row-gap: var(--bw-space-2);
}

/* Red, like the section eyebrow — `--bw-accent` is the role, not `--bw-primary`. */
.bw-postmeta__category {
	color: var(--bw-accent);
}

.bw-postmeta__date {
	color: var(--bw-text-muted);
}

/**
 * A tick between the two, like the one the project meta strip sets between a
 * label and its value. Scoped to the sibling pair, so an uncategorised post
 * shows its date with no orphaned rule in front of it.
 */
.bw-postmeta__category + .bw-postmeta__date::before {
	background-color: var(--bw-rule);
	content: "";
	display: inline-block;
	height: 1em;
	margin-right: var(--bw-space-4);
	vertical-align: -0.15em;
	width: 1px;
}

/*--------------------------------------------------------------
# News single
--------------------------------------------------------------*/

/**
 * Figma "News - One" (204:529): a 1194px reading column centred in the canvas,
 * opening on a 1194x574 feature image, then the meta line, a rule, and the body.
 *
 * The column is narrower than `.bw-container`, so it centres inside one rather
 * than replacing it — that keeps the page gutter doing its job on small screens.
 */
.bw-article {
	margin-inline: auto;
	max-width: var(--bw-article);
}

.bw-article > * + * {
	margin-top: var(--bw-space-9);
}

/* The feature image runs the full column; only the text is inset. */
.bw-article__media {
	aspect-ratio: 1194 / 574;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

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

/* Spacing comes from `> * + *`, so a post with no feature image starts clean. */
.bw-article > .bw-postmeta {
	padding-inline: var(--bw-block-inset);
}

/* Body Large is tracking 0; `body` sets 0.02em, which would otherwise inherit. */
.bw-article__body {
	font-size: var(--bw-size-body-lg);
	line-height: var(--bw-lh-body-lg);
	letter-spacing: 0;
	margin-top: var(--bw-space-7);
}

/**
 * `Line 11` — the same divider the card draws under its title. The frame puts
 * it below the headline; with the headline in the banner it divides the meta
 * line from the body instead, which is where it is drawn either way.
 *
 * 1033 of the frame's 1113 is 93%, near enough to full that it reads as a rule
 * across the text column, so it simply follows the measure.
 */
.bw-article__body::before {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-bottom: var(--bw-space-6);
	max-width: var(--bw-measure);
}

.bw-article__body > * + * {
	margin-top: var(--bw-space-5);
}

/**
 * Running text takes the measure and the 40px inset the frame draws; anything
 * else — images, galleries, embeds — keeps the full column, which is what the
 * two 581x342 images need.
 */
.bw-article__body > :where(p, ul, ol, dl, blockquote, pre, table, h2, h3, h4, h5, h6) {
	max-width: var(--bw-measure);
	padding-inline: var(--bw-block-inset);
}

.bw-article__body > :where(h2, h3, h4, h5, h6) {
	margin-top: var(--bw-space-10);
}

/* Pictures need more air around them than one paragraph needs from the next. */
.bw-article__body > :where(figure, .wp-block-image, .wp-block-gallery, .wp-block-embed) + *,
.bw-article__body > * + :where(figure, .wp-block-image, .wp-block-gallery, .wp-block-embed) {
	margin-top: var(--bw-space-9);
}

/**
 * The frame's two in-article images are 581x342 side by side on the 32px
 * gutter — a two-column gallery block. Cropping them to the drawn ratio is what
 * keeps a row level whatever the editor uploads; a standalone image is left at
 * its own proportions, since nothing has to line up with it.
 *
 * The gap comes from core's `--wp--style--unstable-gallery-gap`, which defaults
 * to 0.5em and also feeds the `flex-basis` of each tile — setting `gap` alone
 * moves the tiles apart but leaves them sized for the old value.
 */
.bw-article__body .wp-block-gallery.has-nested-images {
	--wp--style--unstable-gallery-gap: var(--bw-grid-gutter);

	gap: var(--bw-grid-gutter);
}

.bw-article__body .wp-block-gallery img {
	aspect-ratio: 581 / 342;
	object-fit: cover;
}

.bw-article__body :where(.wp-block-image, .wp-block-gallery) img {
	border-radius: var(--bw-radius-surface);
	display: block;
	height: auto;
	width: 100%;
}

/* "Back to News Home" (264:3845), on the article column's own left edge. */
.bw-article__back {
	margin-block: var(--bw-space-13) 0;
	margin-inline: auto;
	max-width: var(--bw-article);
	padding-inline: var(--bw-block-inset);
}

/**
 * Below 640 both of the article's fixed crops stop working, for the same reason
 * the news card's letterbox does at this width — there is not enough height left
 * in them to carry a photograph. The feature relaxes to the card's mobile ratio,
 * and the two-up gallery goes one-up rather than shrinking to thumbnails.
 */
@media (max-width: 640px) {

	.bw-article__media {
		aspect-ratio: 459 / 296;
	}

	.bw-article__body .wp-block-gallery.has-nested-images > figure {
		flex-basis: 100%;
		width: 100%;
	}
}

/*--------------------------------------------------------------
# Block: Gallery
--------------------------------------------------------------*/

/**
 * Figma "Gallery" (226:1430): 1441x670 — six 459x319 tiles in three columns
 * with a 32px gutter both ways. The tile aspect is 459:319.
 */
.bw-gallery__list {
	display: grid;
	gap: var(--bw-grid-gutter);
	grid-template-columns: repeat(3, minmax(0, 1fr));
	list-style: none;
	margin: 0;
	padding: 0;
}

.bw-gallery--cols-2 .bw-gallery__list {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Figma "Projects - One" Frame 117: 704x464 tiles, so a wider tile than the block's. */
.bw-gallery--project .bw-gallery__item {
	aspect-ratio: 704 / 464;
}

.bw-gallery__heading {
	margin: 0 0 var(--bw-space-7);
	padding-left: var(--bw-block-inset);
}

.bw-gallery__item {
	aspect-ratio: 459 / 319;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

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

@media (max-width: 1024px) {

	.bw-gallery__list,
	.bw-gallery--cols-2 .bw-gallery__list {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 640px) {

	.bw-gallery__list,
	.bw-gallery--cols-2 .bw-gallery__list {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*--------------------------------------------------------------
# Block: Link Buttons
--------------------------------------------------------------*/

/**
 * Figma "Services links" (226:1428): 1441x180 — 459x74 buttons in three
 * columns, 32px gutter, rows 106 apart. Only the LoFi frames specify this
 * component, so the surface treatment below is derived from the design system
 * rather than lifted from a HiFi frame.
 */
.bw-linkgrid--divided {
	border-top: 1px solid var(--bw-rule);
}

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

.bw-linkgrid__item {
	align-items: center;
	background-color: var(--bw-grey-300);
	border: 1px solid transparent;
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	display: flex;
	gap: var(--bw-space-4);
	justify-content: space-between;
	min-height: 74px;
	padding: var(--bw-space-4) var(--bw-space-5);
	text-decoration: none;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial),
		transform var(--bw-duration-icon) var(--bw-ease-industrial);
}

.bw-linkgrid__item:hover,
.bw-linkgrid__item:focus-visible {
	background-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-linkgrid__label {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-body-md);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-card-label);
	text-transform: uppercase;
}

/**
 * The arrow is inlined (bellswellorg_arrow_icon) and filled with currentColor,
 * so it carries its own `color` rather than inheriting the label's: brand red on
 * the grey rest state, then flipping to the contrast colour so it stays visible
 * once the button fills with red.
 */
.bw-linkgrid__icon {
	color: var(--bw-primary);
	display: block;
	flex-shrink: 0;
	height: 16px;
	transition:
		color var(--bw-duration-control) var(--bw-ease-industrial),
		transform var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 16px;
}

.bw-linkgrid__item:hover .bw-linkgrid__icon,
.bw-linkgrid__item:focus-visible .bw-linkgrid__icon {
	color: var(--bw-primary-contrast);
	transform: translate(3px, -3px);
}

/*--------------------------------------------------------------
# Block: Personnel Grid
--------------------------------------------------------------*/

.bw-people__intro {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-5);
	margin-bottom: clamp(2rem, 4.05vw, 4.375rem);
	max-width: 949px;
	padding-left: var(--bw-block-inset);
}

.bw-people__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0;
	max-width: 700px;
}

.bw-people__intro-text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

/*
 * Figma sets the row gap to 32px, the same as the columns (cards at y=0 and
 * y=800, 768 tall). In practice the cards have ragged bottoms — the bio runs to
 * whatever length it runs to — so 32px let one row's text crowd the next row's
 * photograph. Rows get roughly double; columns stay on the design's 32px.
 */
.bw-people__list {
	column-gap: var(--bw-grid-gutter);
	list-style: none;
	margin: 0;
	padding: 0;
	row-gap: var(--bw-grid-row-gap);
}

/**
 * Card - personnel (249:1682): 459x768 — photo 459x400, then name, role,
 * a 391px rule, and the bio, all inset 34px.
 */
.bw-person {
	background-color: var(--bw-surface);
	display: flex;
	flex-direction: column;
}

.bw-person__media {
	aspect-ratio: 459 / 400;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

.bw-person__media img {
	display: block;
	height: 100%;
	object-fit: cover;
	object-position: top center;
	width: 100%;
}

.bw-person__body {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-2);
	padding: var(--bw-space-6) 34px 0;
}

/*
 * Name, with the LinkedIn link beside it when the person has one. Baseline-ish
 * alignment rather than centre, so the icon sits with the name's cap height on
 * a two-line name.
 */
/*
 * The LinkedIn link is pushed to the card's right edge rather than sitting
 * against the name, so it lands in the same place on every card whatever the
 * name's length — a ragged icon column reads as an accident.
 */
.bw-person__namerow {
	align-items: baseline;
	display: flex;
	gap: var(--bw-space-4);
	justify-content: space-between;
}

.bw-person__namerow .bw-person__name {
	flex: 1 1 auto;
}

.bw-person__name {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h5);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h5);
	letter-spacing: var(--bw-ls-h5);
	margin: 0;
}

.bw-person__linkedin {
	align-items: center;
	display: inline-flex;
	flex-shrink: 0;
	justify-content: center;
	opacity: 0.75;
	transition: opacity var(--bw-duration-icon) var(--bw-ease-industrial);
	/* Nudge onto the name's optical line — baseline alignment sits it low. */
	transform: translateY(0.15em);
}

.bw-person__linkedin:hover,
.bw-person__linkedin:focus-visible {
	opacity: 1;
}

.bw-person__linkedin img {
	display: block;
	height: 24px;
	width: 24px;
}

.bw-person__role {
	color: var(--bw-primary);
	margin: 0;
}

/* The rule sits between the role and the bio. */
.bw-person__bio {
	border-top: 1px solid var(--bw-rule);
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: var(--bw-space-4) 0 0;
	padding-top: var(--bw-space-5);
}

/*--------------------------------------------------------------
# Block: Contact Blocks
--------------------------------------------------------------*/

.bw-offices__grid {
	display: grid;
	gap: clamp(2rem, 4.63vw, 5rem);
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

.bw-offices .bw-contact-block__list {
	max-width: 385px;
}

/**
 * On the Contact page the office name is a Section Title carrying a rule, not
 * the h6 the footer uses — Figma draws it at 64px tall with `Line 10` beneath
 * (`255:3326` / `255:3327`), against a 28px title and no rule in the footer
 * component. Same helper, same markup; the difference is entirely here.
 *
 * The rule is 385 wide, matching the list under it rather than the title text.
 * Title box 0–64, rule at 96, list at 128: a 32px padding and a 32px gap.
 */
.bw-offices .bw-contact-block {
	gap: var(--bw-space-6);
}

.bw-offices .bw-contact-block__title {
	border-bottom: 1px solid var(--bw-rule);
	font-size: var(--bw-size-section-title);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-section-title);
	letter-spacing: 0;
	max-width: 385px;
	padding-bottom: var(--bw-space-6);
}

@media (max-width: 782px) {

	.bw-offices__grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*--------------------------------------------------------------
# Block: Contact Form
--------------------------------------------------------------*/

/**
 * Figma "Contact Us" Frame 150 (`445:3148`). The heading and paragraph are
 * Frame 60 (`255:3344`), identical to the Clients page's, so they are typed
 * the same — see `.bw-clients__intro`. The form beneath is Frame 80
 * (`255:3328`): a 704px column, rows on a 32px rhythm, submit centred.
 */
.bw-contact-form__intro {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-6);
	/* Frame 60's paragraph ends 88 above the first field. */
	margin-bottom: clamp(2rem, 5.093vw, 5.5rem);
	max-width: 949px;
	padding-left: var(--bw-block-inset);
}

.bw-contact-form__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0;
}

.bw-contact-form__intro-text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

/* 704 of form at the 40px block inset. */
.bw-contact-form__form {
	max-width: calc(704px + var(--bw-block-inset));
	padding-left: var(--bw-block-inset);
}

/*
 * The block owns the spacing on this page, so the plugin's own vertical
 * margin (see the Fluent Forms section) is cancelled here.
 */
.bw-contact-form .fluentform {
	margin-block: 0;
}

.bw-contact-form .ff-el-group {
	margin-bottom: var(--bw-space-6);
}

.bw-contact-form .ff-el-group:last-child {
	margin-bottom: 0;
}

/*
 * Submit spans the form. Figma draws it centred at 284 of the 704 column
 * (`255:3348`); full width is a client decision taken 2026-08-11, and it is
 * the one measure on this block that does not come from the frame.
 *
 * Laid out with flex rather than `text-align`: Fluent Forms puts an alignment
 * class on this wrapper (`ff-text-left` here) and wins the specificity race,
 * but its rule only sets `text-align`, which a flex container ignores.
 */
.bw-contact-form .ff_submit_btn_wrapper {
	display: flex;
	margin-top: var(--bw-space-7);
}

.bw-contact-form .ff-btn-submit {
	flex: 1;
	justify-content: center;
}

/*--------------------------------------------------------------
# Block: Map
--------------------------------------------------------------*/

/**
 * Rectangle 24 (`255:3352`) — 1440x612 at the page gutter, in the 12px radius
 * tier. The aspect ratio is the design's; `min-height` takes over on narrow
 * screens, where 2.35:1 would leave a letterbox too short to read a street in.
 *
 * `width` is set explicitly rather than left to `auto`: with only a ratio and
 * a `min-height` to go on, the box resolves its width *from* the height and
 * blows a 2.35:1 panel out past the viewport on a phone.
 */
.bw-map__frame {
	aspect-ratio: 1440 / 612;
	background-color: var(--bw-surface-alt);
	border-radius: var(--bw-radius-surface);
	min-height: 320px;
	overflow: hidden;
	width: 100%;
}

.bw-map__embed {
	border: 0;
	display: block;
	height: 100%;
	width: 100%;
}

/*--------------------------------------------------------------
# Page banner (CPT archives & singles)
--------------------------------------------------------------*/

/**
 * The Figma "Hero 2" component: a 655px band carrying the page title. Shorter
 * than the home hero and without the marquee.
 */
/**
 * One height whether or not there is a photograph — a page header should not
 * change rank because someone did or did not attach an image. See
 * --bw-pagehead-height for how the number was chosen.
 */
.bw-pagehead {
	background-color: var(--bw-surface-alt);
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	min-height: var(--bw-pagehead-height);
	position: relative;
}

/**
 * The image is taken out of flow so it cannot drive the band's height. Left in
 * flow, `height: 100%` has no definite row to resolve against, the image falls
 * back to its natural aspect, and a tall photograph stretches the banner far
 * past the 655px the design asks for.
 */
.bw-pagehead__media {
	grid-area: 1 / 1;
	overflow: hidden;
	position: relative;
}

.bw-pagehead__media img {
	display: block;
	height: 100%;
	inset: 0;
	object-fit: cover;
	position: absolute;
	width: 100%;
}

.bw-pagehead--media .bw-pagehead__media::after {
	background: var(--bw-scrim);
	content: "";
	inset: 0;
	position: absolute;
}

.bw-pagehead__inner {
	align-content: end;
	display: grid;
	grid-area: 1 / 1;
	justify-items: start;
	padding-block: clamp(2.5rem, 5.09vw, 5.5rem);
	position: relative;
	row-gap: var(--bw-space-4);
	z-index: 1;
}

.bw-pagehead__eyebrow,
.bw-pagehead__title,
.bw-pagehead__subtitle {
	margin: 0;
	margin-left: var(--bw-block-inset);
}

.bw-pagehead__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h1);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h1);
	letter-spacing: var(--bw-ls-h1);
	max-width: 20ch;
}

.bw-pagehead__subtitle {
	font-size: var(--bw-size-subtitle);
	line-height: var(--bw-lh-subtitle);
	max-width: 60ch;
}

/* The eyebrow joins them over a photograph — see the note on `.bw-band__label`. */
.bw-pagehead--media .bw-pagehead__eyebrow,
.bw-pagehead--media .bw-pagehead__title,
.bw-pagehead--media .bw-pagehead__subtitle {
	color: var(--bw-primary-contrast);
}

/*--------------------------------------------------------------
# Lead paragraph & prose
--------------------------------------------------------------*/

.bw-lead__text {
	font-size: var(--bw-size-subtitle);
	line-height: var(--bw-lh-subtitle);
	margin: 0;
	max-width: 70ch;
	padding-left: var(--bw-block-inset);
}

/* Editor-authored body copy on singles. */
.bw-prose {
	max-width: 75ch;
	padding-left: var(--bw-block-inset);
}

.bw-prose > * + * {
	margin-top: var(--bw-space-5);
}

.bw-prose h2 {
	font-size: var(--bw-size-h4);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
	margin-top: var(--bw-space-10);
}

.bw-prose h3 {
	font-size: var(--bw-size-h5);
	line-height: var(--bw-lh-h5);
	letter-spacing: var(--bw-ls-h5);
	margin-top: var(--bw-space-9);
}

.bw-prose ul,
.bw-prose ol {
	padding-left: var(--bw-space-5);
}

.bw-prose li + li {
	margin-top: var(--bw-space-2);
}

/**
 * Figma "Body text" (226:1429) on the service singles: 950 wide with 245 clear
 * either side of a 1441 frame — centred on the measure rather than pushed
 * against the gutter, which is what the block inset above is for. The two rules
 * fight, so the inset is taken back off explicitly.
 *
 * A flat cap, not the fluid clamp the type uses: this is a measure, and scaling
 * it by viewport pinned the copy to 352px inside a 592px container at 640 —
 * a column too narrow to read. Below 950 the container governs and it fills.
 */
.bw-prose--centred {
	margin-inline: auto;
	max-width: 59.375rem; /* 950px */
	padding-left: 0;
}

/**
 * The closing arrow link (226:1287) hangs off the frame edge at the usual 40px
 * inset, not off the centred measure above it, and clears the body copy by 80.
 */
.bw-service__link {
	margin: var(--bw-space-11) 0 0;
	padding-left: var(--bw-block-inset);
}

/*--------------------------------------------------------------
# Project tiles & meta
--------------------------------------------------------------*/

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

/* Figma: 704x464 tiles with the title banded across the foot. */
.bw-card-project {
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
	position: relative;
}

.bw-card-project__link {
	color: var(--bw-primary-contrast);
	display: block;
	text-decoration: none;
}

.bw-card-project__media {
	aspect-ratio: 704 / 464;
	background-color: var(--bw-picture-frame);
	overflow: hidden;
}

.bw-card-project__media img {
	display: block;
	height: 100%;
	object-fit: cover;
	transition: transform var(--bw-duration-reveal) var(--bw-ease-heavy);
	width: 100%;
}

.bw-card-project__link:hover .bw-card-project__media img,
.bw-card-project__link:focus-visible .bw-card-project__media img {
	transform: scale(1.03);
}

/**
 * Figma draws the band at 120px with the title centred, and — on the fourth
 * tile, which is the hover state — at 191px with a rule and an arrow link
 * beneath. The band is pinned to the bottom, so it grows upward.
 *
 * The 60px left inset is the design's; the band keeps the title centred at
 * rest and simply gets taller as the revealed content expands into it.
 */
.bw-card-project__band {
	background: linear-gradient(0deg, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.5) 60%, rgba(0, 0, 0, 0) 100%);
	bottom: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
	left: 0;
	min-height: 120px;
	padding-block: var(--bw-space-7);
	padding-inline: clamp(1.25rem, 3.472vw, 3.75rem) var(--bw-space-7);
	position: absolute;
	right: 0;
}

/*
 * 0fr -> 1fr is the one way to transition to an intrinsic height. The inner
 * element needs min-height:0 and overflow:hidden for the row to actually clip.
 */
.bw-card-project__reveal {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--bw-duration-panel) var(--bw-ease-enter);
}

.bw-card-project__reveal-inner {
	min-height: 0;
	overflow: hidden;
}

.bw-card-project__link:hover .bw-card-project__reveal,
.bw-card-project__link:focus-visible .bw-card-project__reveal {
	grid-template-rows: 1fr;
}

/* Design: 24px below the title, 515 of the 704 tile. */
.bw-card-project__rule {
	background-color: rgba(255, 255, 255, 0.5);
	display: block;
	height: 1px;
	margin-top: var(--bw-space-5);
	width: min(515px, 100%);
}

/* Design: 24px below the rule. A span, not a link — the tile is the link. */
.bw-card-project__cta {
	color: var(--bw-primary-contrast);
	margin-top: var(--bw-space-5);
}

/*
 * The arrow picks up the brand red against the white label. The glyph is an
 * inline SVG filled with currentColor, so setting colour on the icon alone is
 * enough — the label stays white.
 */
.bw-card-project__cta .bw-link-arrow__icon {
	color: var(--bw-primary);
}

.bw-card-project__title {
	color: var(--bw-primary-contrast);
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-card-label);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-card-label);
	letter-spacing: var(--bw-ls-card-label);
	margin: 0;
	text-transform: uppercase;
}

/**
 * Figma "Projects - One" feature band — a full-bleed 1728x1190 image with two
 * 581x384 cards inset over its right half (255:1778, 255:1779).
 *
 * The band and the cards are fixed-aspect containers with the image cropped to
 * fill, so an upload of any size or shape lands in the same footprint. The
 * cards are centred in the band: at the design viewport that lands them 171px
 * off the top and bottom with an 80px gap, exactly as drawn.
 */
.bw-feature {
	/* Figma: 100px between the intro and the band. */
	margin-top: clamp(2.5rem, 5.787vw, 6.25rem);
	position: relative;
}

.bw-feature__frame {
	aspect-ratio: 1728 / 1190;
	background-color: var(--bw-picture-frame);
	overflow: hidden;
}

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

.bw-feature__overlay {
	inset: 0;
	position: absolute;
}

.bw-feature__cards {
	align-items: flex-end;
	display: flex;
	flex-direction: column;
	gap: clamp(1.5rem, 4.63vw, 5rem);
	height: 100%;
	justify-content: center;
	list-style: none;
	margin: 0;
	padding: 0;
}

/* 581 of the 1440 container. */
.bw-feature__card {
	aspect-ratio: 581 / 384;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
	width: 40.35%;
}

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

/* Too narrow to overlay: the cards drop below the band and go full width. */
@media (max-width: 782px) {

	.bw-feature__overlay {
		position: static;
	}

	.bw-feature__cards {
		gap: var(--bw-grid-gutter);
		height: auto;
		padding-block: var(--bw-grid-gutter);
	}

	.bw-feature__card {
		width: 100%;
	}
}

/**
 * Figma "Projects - One" Frame 61 — 1440x200.
 *
 * Two 100px rows, each split into a wide left cell and a narrow right one.
 * Prose values (scope, client) take the wide cell; short ones (the date-or-
 * status, location) take the narrow cell, and the pairs alternate down.
 *
 * The design splits 948 / 492; the build splits 868 / 572 — the divide moved
 * 80px left to give the right-hand cells room for their label chip and the
 * certificate pill, which the design did not have to carry together.
 */
/*
 * A closed, rounded box with its cells demarcated. The internal rules are the
 * 1px grid gap showing the container's own colour through — one line per join,
 * never doubled against the border, and they stay square when a slot is blank.
 */
.bw-projectmeta {
	background-color: var(--bw-rule);
	border: 1px solid var(--bw-rule);
	border-radius: var(--bw-radius-surface);
	display: grid;
	gap: 1px;
	grid-template-columns: minmax(0, 868fr) minmax(0, 572fr);
	margin: 0;
	overflow: hidden;
}

.bw-projectmeta__item {
	align-items: center;
	background-color: var(--bw-surface);
	column-gap: var(--bw-space-6);
	display: grid;
	grid-column: 2;
	grid-template-columns: auto minmax(0, 1fr);
	min-height: 100px;
	padding-right: var(--bw-space-7);
}

/* Prose values sit in the wide column. */
.bw-projectmeta__item--wide {
	grid-column: 1;
}

/*
 * The label chip: 60px indent, 32px to its value — as the design measures.
 * A step down from .bw-card-label, so the labels read as annotation against
 * the values rather than competing with them.
 */
.bw-projectmeta__label {
	color: var(--bw-text-muted);
	font-size: var(--bw-size-label-sm);
	line-height: var(--bw-lh-label-sm);
	letter-spacing: var(--bw-ls-label-sm);
	padding-left: clamp(1rem, 3.472vw, 3.75rem);
	padding-right: var(--bw-space-6);
	position: relative;
	white-space: nowrap;
}

/**
 * The tick between a label and its value. Figma's chip (Frame 65) is 52 tall in
 * a 100 row, so this is a short centred rule rather than a full cell divider.
 */
.bw-projectmeta__label::after {
	background-color: var(--bw-rule);
	content: "";
	height: clamp(2rem, 3.009vw, 3.25rem);
	position: absolute;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	width: 1px;
}

/* A blank slot keeps its cell but draws nothing inside it. */
.bw-projectmeta__label:empty::after {
	content: none;
}

.bw-projectmeta__value {
	font-size: var(--bw-size-body-sm);
	line-height: var(--bw-lh-body-sm);
	margin: 0;
}

/*
 * Only the cell carrying the certificate pill lays its contents out in a row.
 * It wraps rather than overflowing, so a longer date or a narrower viewport
 * drops the pill under the value instead of pushing the strip wider.
 */
.bw-projectmeta__value--pilled {
	align-items: center;
	column-gap: var(--bw-space-5);
	display: flex;
	flex-wrap: wrap;
	row-gap: var(--bw-space-2);
}

/**
 * "View certificate" — a pill beside the completion date, opening Figma's
 * "Project Certificate" overlay. It rides inside a 100px row next to a label
 * chip, so it is a compact pill rather than the section-anchoring .bw-btn.
 */
.bw-projectmeta__cert {
	background-color: transparent;
	border: 1px solid var(--bw-rule);
	border-radius: 999px;
	color: var(--bw-text);
	cursor: pointer;
	display: inline-block;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-micro);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-micro);
	letter-spacing: var(--bw-ls-micro);
	padding: 0.25rem 0.75rem;
	text-transform: uppercase;
	transition:
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial);
	white-space: nowrap;
}

.bw-projectmeta__cert:hover,
.bw-projectmeta__cert:focus-visible {
	border-color: var(--bw-primary);
	color: var(--bw-primary);
}

/* A document filling the tall modal — Figma's certificate overlay is 873x988. */
.bw-document {
	aspect-ratio: 663 / 850;
	border: 0;
	display: block;
	height: auto;
	object-fit: contain;
	width: 100%;
}

.bw-document__fallback {
	padding: var(--bw-space-7);
	text-align: center;
}

/*
 * The two-column split needs ~1280px to hold. Below that the label chip — a
 * fixed, nowrap column — eats the cell, collapsing the value column until the
 * scope text crowds the divider and the certificate pill is clipped by it.
 * Neither can shrink, so the strip goes single-column instead and every cell
 * gets the full width back.
 */
@media (max-width: 1280px) {

	.bw-projectmeta {
		grid-template-columns: minmax(0, 1fr);
	}

	.bw-projectmeta__item,
	.bw-projectmeta__item--wide {
		grid-column: 1;
	}
}

@media (max-width: 640px) {

	.bw-projectmeta__item {
		align-items: start;
		grid-template-columns: minmax(0, 1fr);
		min-height: 0;
		padding-block: var(--bw-space-5);
		padding-inline: var(--bw-space-5);
		row-gap: var(--bw-space-2);
	}

	/* Stacked: the label sits above its value, so the tick has nothing to divide. */
	.bw-projectmeta__label {
		padding-left: 0;
		padding-right: 0;
	}

	.bw-projectmeta__label::after {
		content: none;
	}
}


/*--------------------------------------------------------------
# Equipment cards
--------------------------------------------------------------*/

/**
 * `--bw-grid-row-gap` (80px at 1728), not the 32px grid gutter — the same knob
 * `.bw-news-index` and `.bw-people__list` turn. Every two-up listing on the site
 * breathes alike, and one token moves all three.
 */
.bw-assets {
	list-style: none;
	margin: 0;
	padding: 0;
	row-gap: var(--bw-grid-row-gap);
}

.bw-asset {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-6);
}

.bw-asset__media {
	aspect-ratio: 848 / 578;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

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

.bw-asset__body {
	align-items: start;
	display: flex;
	flex-wrap: wrap;
	gap: var(--bw-space-6);
	justify-content: space-between;
	padding-left: var(--bw-block-inset);
}

/*
 * `min-width: 0` releases the automatic minimum a flex item carries, which
 * would otherwise hold this column at its 20rem basis however narrow the row
 * gets: at a 320px viewport the 40px `padding-left` above put its right edge
 * past the page. Same guard as the carousel slide.
 */
.bw-asset__text {
	flex: 1 1 20rem;
	min-width: 0;
}

.bw-asset__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h4);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
	margin: 0;
}

/* The rule the design draws under the asset name. */
.bw-asset__title::after {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-4);
	max-width: 100%;
	width: 274px;
}

/**
 * Body Large, like the chips beside it: the dump puts this on a 32px line box
 * (`236:1079`, 274x64 for two lines), not the 24px one it was built with.
 * Tracking 0 — see the note on `.bw-asset__chip`.
 */
.bw-asset__capacity {
	font-size: var(--bw-size-body-lg);
	line-height: var(--bw-lh-body-lg);
	letter-spacing: 0;
	margin: var(--bw-space-4) 0 0;
	max-width: 40ch;
}

.bw-asset__chips {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-4);
	list-style: none;
	margin: 0;
	padding: 0;
}

/**
 * "Operations - Assets" draws these at 173x52 (`236:1108`, `236:1113`) with the
 * text on a 32px line box — Body Large, not Body Medium, and tracking 0 rather
 * than the 2% the smaller body sizes carry.
 *
 * The height is a `min-height` with the text centred, the way `.bw-linkgrid__item`
 * holds its own 74px: padding alone cannot hit 52 from a 32px line box on the
 * 8px spacing scale, and a value that grows to two lines should deepen the chip
 * rather than overflow it. 173 becomes a floor for the same reason — it keeps
 * the pair aligned as a column across cards, as drawn, without clipping a status
 * longer than "Owned".
 *
 * The tiles carry `--bw-radius-control`, the tier every other small tile on the
 * site uses.
 *
 * **The two are not the same chip.** Year is an outline and Status is filled
 * black (designer, 2026-08-09) — a distinction the metadata dump cannot show,
 * since it carries geometry only and no fills. The pair reads as a fact and its
 * standing rather than as two of a kind, so the modifiers are meaningful, not
 * decorative: keep them attached to `year` and `status` specifically.
 *
 * Both carry a 1px border, transparent on the filled one, so the two boxes are
 * identical and their text sits on the same line whatever the fill.
 */
.bw-asset__chip {
	align-items: center;
	border: 1px solid transparent;
	border-radius: var(--bw-radius-control);
	display: flex;
	font-size: var(--bw-size-body-lg);
	line-height: var(--bw-lh-body-lg);
	letter-spacing: 0;
	min-height: 52px;
	min-width: 173px;
	padding: var(--bw-space-2) var(--bw-space-5);
}

.bw-asset__chip--year {
	border-color: var(--bw-rule);
	color: var(--bw-text);
}

.bw-asset__chip--status {
	background-color: var(--bw-surface-inverse);
	color: var(--bw-text-inverse);
}

/*--------------------------------------------------------------
# Person profile (single)
--------------------------------------------------------------*/

.bw-profile {
	display: grid;
	gap: clamp(2rem, 4.63vw, 5rem);
	grid-template-columns: minmax(0, 459px) minmax(0, 1fr);
}

.bw-profile__media {
	aspect-ratio: 459 / 400;
	background-color: var(--bw-picture-frame);
	border-radius: var(--bw-radius-surface);
	overflow: hidden;
}

.bw-profile__media img {
	display: block;
	height: 100%;
	object-fit: cover;
	object-position: top center;
	width: 100%;
}

.bw-profile__role {
	color: var(--bw-primary);
	margin: 0 0 var(--bw-space-6);
}

.bw-profile .bw-prose {
	padding-left: 0;
}

@media (max-width: 782px) {

	.bw-profile {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*--------------------------------------------------------------
# Sibling link grid heading
--------------------------------------------------------------*/

.bw-linkgrid__heading {
	margin: 0 0 var(--bw-space-7);
	padding-left: var(--bw-block-inset);
}

/*--------------------------------------------------------------
# Block: Partner Wall
--------------------------------------------------------------*/

.bw-partners__intro {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-6);
	/* Frame 60's paragraph ends 140 above the first row, as on the Clients wall. */
	margin-bottom: clamp(2.5rem, 8.102vw, 8.75rem);
	max-width: 949px;
	padding-left: var(--bw-block-inset);
}

.bw-partners__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0;
}

.bw-partners__intro-text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: 0;
}

/*
 * Frame 143 spaces its rows 180 apart — the same figure as every section
 * boundary on the site, so it takes the token rather than a local clamp.
 */
/*
 * A grid rather than a flex column, because the even rows are pushed to the
 * right-hand edge. An auto cross-axis margin would do that in flex, but it
 * also switches the item off stretch, so each row would then size to its own
 * copy — the wall came out ragged, every row a different width.
 */
.bw-partners__list {
	display: grid;
	gap: var(--bw-band-gap);
	grid-template-columns: minmax(0, 1fr);
	list-style: none;
	margin: 0;
	padding: 0;
}

/**
 * Figma "Partners": a 459-wide logo panel beside an 818-wide text column, 40
 * apart, with the logo alternating side down the page. The pair measures 1317
 * in a 1440 frame, so a row never fills its container — the odd rows sit left
 * and the even ones are pushed right, which is the 123 offset Frame 140 and
 * Frame 142 carry.
 */
.bw-partner {
	align-items: center;
	display: grid;
	gap: var(--bw-block-inset);
	grid-template-columns: minmax(0, 459fr) minmax(0, 818fr);
	max-width: 1317px;
	/*
	 * Explicit, not left to stretch: `justify-self: end` on the flipped rows
	 * opts the item out of stretching, and it would then size to its own copy.
	 * A stated width keeps the cap doing the sizing for every row alike.
	 */
	width: 100%;
}

.bw-partner--flip {
	grid-template-columns: minmax(0, 818fr) minmax(0, 459fr);
	justify-self: end;
}

.bw-partner--flip .bw-partner__logo {
	order: 2;
}

.bw-partner__logo {
	align-items: center;
	aspect-ratio: 459 / 417;
	background-color: var(--bw-grey-300);
	border-radius: var(--bw-radius-surface);
	display: flex;
	justify-content: center;
	padding: var(--bw-space-7);
}

.bw-partner__logo img {
	display: block;
	max-height: 100%;
	max-width: 100%;
	object-fit: contain;
	width: auto;
}

.bw-partner__logo-fallback {
	color: var(--bw-text-muted);
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-card-label);
	font-weight: var(--bw-weight-medium);
	letter-spacing: var(--bw-ls-card-label);
	text-align: center;
	text-transform: uppercase;
}

/*
 * The text column is Figma's "Frame 26" (`255:2810`), 818x142 — the *same*
 * component the QHSE feature band closes on (`255:3413`): h4 on a 48px box, a
 * 694 rule 15 under it, then a 64px two-line body at 20/32. Kept in step with
 * `.bw-featureband__heading` and `.bw-band__text`; if one moves, check the
 * other. It is not `.bw-titled`, which is h3 on a 482 rule with 32px gaps.
 */
.bw-partner__name {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h4);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h4);
	letter-spacing: var(--bw-ls-h4);
	margin: 0;
}

/*
 * The rule the design draws under the partner name — 694 inside the 818
 * column, so it stops short of the copy rather than underlining it.
 * `width: 100%` with a `max-width` cap, never a flat 694: a fixed width here
 * feeds the row's min-content size, the trap already documented on
 * `.bw-track-card__title`.
 */
.bw-partner__name::after {
	background-color: var(--bw-rule);
	content: "";
	display: block;
	height: 1px;
	margin-top: var(--bw-space-4);
	max-width: 694px;
	width: 100%;
}

/*
 * No trailing margin: the body is centred against the logo panel, so a bottom
 * margin on the last line shifts the whole block off the panel's axis. The
 * website link — which is not in the design and only appears when the field is
 * filled — carries its own leading space instead.
 */
/* 20/32 — `--bw-size-body-lg`, from the 64px two-line box (`255:2813`). */
.bw-partner__desc {
	font-size: var(--bw-size-body-lg);
	line-height: var(--bw-lh-body-lg);
	letter-spacing: 0;
	margin: var(--bw-space-4) 0 0;
	max-width: 818px;
}

.bw-partner__body .bw-link-arrow {
	margin-top: var(--bw-space-5);
}

@media (max-width: 782px) {

	.bw-partner,
	.bw-partner--flip {
		grid-template-columns: minmax(0, 1fr);
		/*
		 * Stacked, both sides run the full width and neither is pushed right.
		 * `justify-self` has to go back to stretch as well as the cap coming
		 * off — left at `end` the row would size to its copy instead.
		 */
		justify-self: stretch;
		max-width: none;
	}

	.bw-partner--flip .bw-partner__logo {
		order: 0;
	}
}

/*--------------------------------------------------------------
# Non-BellsWellOrg blocks on a block-composed page
--------------------------------------------------------------*/

/**
 * front-page.php and a block-composed page.php add no wrapper, because the
 * BellsWellOrg blocks are full-bleed and bring their own. Core blocks (a
 * shortcode, a paragraph) have no such container, so they would run to the
 * viewport edge. Give anything that is not one of ours the page gutter.
 */
.site-main--blocks > *:not(.bw-block):not(.bw-bleed):not([class*="align"]),
.entry--blocks .entry-content > *:not(.bw-block):not(.bw-bleed):not([class*="align"]) {
	margin-inline: auto;
	max-width: calc(var(--bw-container) + (var(--bw-gutter) * 2));
	padding-inline: var(--bw-gutter);
	width: 100%;
}

/**
 * `_s` opens the prose with `.entry-content { margin: 1.5em 0 0; }`, which is
 * right for running text — it needs air under the page title.
 *
 * When a block-composed page opens with a full-bleed banner, though, that banner
 * is meant to sit flush against the header, and the 1.5em (27px at the 18px
 * base) collapses up through the article and main to push the whole page down —
 * a white strip between the navbar and the hero.
 *
 * Scoped with :has() to pages that actually start with a full-bleed element, so
 * a block page that opens with a paragraph keeps its breathing room rather than
 * butting against the navbar. Same three-class test as the gutter rule above.
 */
.entry--blocks > .entry-content:has( > :first-child:is( .bw-block, .bw-bleed, [class*="align"] ) ) {
	margin-top: 0;
}

/*--------------------------------------------------------------
# Search form
--------------------------------------------------------------*/

/**
 * Nothing in Figma draws a search control, so this is assembled from parts the
 * design system already owns rather than designed fresh: the field repeats the
 * Fluent Forms input treatment below (surface fill, rule border, 8px control
 * radius, Body Medium, `--bw-space-3` / `--bw-space-4` padding) and the button
 * is `.bw-btn--primary`, which brings its own hover fill and lift.
 *
 * EVERY RULE BELOW IS SCOPED TO `.bw-search`, and that is load-bearing rather
 * than tidiness. `_s` styles form fields by type —
 *
 *   input[type="search"] { border: 1px solid #ccc; border-radius: 3px; padding: 3px; }
 *
 * — and a type-plus-attribute selector outranks a single class, so written
 * plainly as `.bw-search__field` none of this applied: the field rendered with
 * a 3px radius (invisible at that size), 3px of padding and a grey that is not
 * in the palette. The extra class takes the weight back. This is the `_s`
 * fighting-you trap the navbar section already documents, in a third place.
 */
.bw-search {
	display: flex;
	flex-wrap: wrap;
	gap: var(--bw-space-4);
}

.bw-search .bw-search__field {
	background-color: var(--bw-surface);
	border: 1px solid var(--bw-rule);
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	flex: 1 1 16rem;
	font-family: var(--bw-font-body);
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	min-height: 56px;
	min-width: 0;
	padding: var(--bw-space-3) var(--bw-space-4);
	transition: border-color var(--bw-duration-control) var(--bw-ease-industrial);
}

.bw-search .bw-search__field::placeholder {
	color: var(--bw-text-muted);
	opacity: 1;
}

.bw-search .bw-search__field:focus {
	border-color: var(--bw-primary);
	outline: var(--bw-focus-ring);
	outline-offset: 1px;
}

/*
 * The button holds its width where the field stretches, until the pair wraps —
 * at which point it takes the full row rather than sitting stranded at one end.
 *
 * Its height needs no rule here: this pairing is where the site's 56 came from,
 * and `.bw-btn` now carries it for every button.
 */
.bw-search .bw-search__submit {
	flex: 0 0 auto;
}

@media (max-width: 640px) {

	.bw-search .bw-search__submit {
		flex: 1 0 100%;
	}
}

/*
 * The form as a section of its own — the 404 uses this. Heading spaced like the
 * link grid's, and the control sits in the contact form's 704 column at the
 * block inset, so the two forms on the site line up on the same left edge.
 */
.bw-search-block__heading {
	margin: 0 0 var(--bw-space-7);
	padding-left: var(--bw-block-inset);
}

.bw-search-block .bw-search {
	max-width: calc(704px + var(--bw-block-inset));
	padding-left: var(--bw-block-inset);
}

/*--------------------------------------------------------------
# Fluent Forms
--------------------------------------------------------------*/

/**
 * Fluent Forms ships its own styling. These overrides only align it with the
 * design system — the plugin keeps ownership of layout and validation states.
 */
/*
 * The form container is in the 12px tier. Fluent Forms' own wrapper div is the
 * only element at that level, so the radius goes here. Note it has no fill or
 * border yet, so the rounding is not visible — see BELLSWELLORG.md.
 */
/*
 * Fluent Forms keys its focus and accent colours to --fluentform-primary, a
 * blue it defines on :root. Repointing that one property at the brand red on
 * the form wrapper recolours all of them at once, without chasing each rule:
 * a custom property set on a closer element wins over :root whatever the
 * stylesheet order — and the plugin's sheet is enqueued after this one.
 */
/*
 * `overflow: clip` with a clip margin rather than `overflow: hidden`. The
 * fields run flush to this wrapper's edges, so `hidden` cut the focus ring off
 * down both sides of every field on the Contact page and in the footer sign-up
 * — the ring paints 3px outside the field (a 2px outline at a 1px offset) and
 * there was nowhere for it to go. The clip margin gives it exactly that room
 * while the corners still clip, so the radius above keeps its meaning if the
 * design ever puts a fill on this container. Browsers without `clip` drop the
 * declaration and simply do not clip, which is also fine here — there is no
 * fill to spill.
 */
.fluentform {
	--fluentform-primary: var(--bw-primary);

	border-radius: var(--bw-radius-surface);
	margin-block: clamp(2rem, 4.63vw, 5rem);
	overflow: clip;
	overflow-clip-margin: 4px;
}

.fluentform .ff-el-input--label label {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-body-sm);
	font-weight: var(--bw-weight-medium);
	letter-spacing: var(--bw-ls-card-label);
	text-transform: uppercase;
}

.fluentform .ff-el-form-control {
	background-color: var(--bw-surface);
	border: 1px solid var(--bw-rule);
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	font-family: var(--bw-font-body);
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	min-height: 56px;
	padding: var(--bw-space-3) var(--bw-space-4);
}

.fluentform textarea.ff-el-form-control {
	min-height: 160px;
}

/*
 * The class is doubled to carry the weight. Fluent Forms removes the outline
 * from its own `.ff-default .ff-el-form-control:focus`, which ties with this
 * selector written plainly and then wins on source order, leaving a focused
 * field with nothing but a recoloured border. Doubling beats it without
 * naming a style template this rule should not care about.
 */
.fluentform .ff-el-form-control.ff-el-form-control:focus {
	border-color: var(--bw-primary);
	box-shadow: none;
	outline: var(--bw-focus-ring);
	outline-offset: 1px;
}

/*
 * 56, the height of the fields above it, rather than the 66 `.bw-btn` carries
 * from the Figma button component (client decision, 2026-08-11): a button that
 * belongs to a form is read as part of the form and squares up with it. The
 * search form applies the same rule. The footer sign-up sets its own 52 for
 * both halves and is unaffected by this.
 */
.fluentform .ff-btn-submit {
	background-color: var(--bw-primary);
	border: 1px solid var(--bw-primary);
	border-radius: var(--bw-radius-control);
	color: var(--bw-primary-contrast);
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-body-md);
	font-weight: var(--bw-weight-medium);
	letter-spacing: var(--bw-ls-body);
	min-height: 56px;
	padding-inline: var(--bw-space-8);
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial);
}

.fluentform .ff-btn-submit:hover,
.fluentform .ff-btn-submit:focus-visible {
	background-color: var(--bw-primary-hover);
	border-color: var(--bw-primary-hover);
}

/**
 * The colour above is not enough on its own. Each form's own styler settings
 * are saved in the database and printed into an inline `<style>` on the page:
 *
 *   form.fluent_form_3 .ff-btn-submit:not(.ff_btn_no_style) { … }
 *
 * — three classes and an element against the two classes above, so the stored
 * value wins wherever they disagree. It was saved as the raw brand red, which
 * is 4.02:1 under the off-white label and is what the audit caught on the
 * contact form's submit button (A11Y-02, 2026-08-12).
 *
 * The doubled class buys the specificity to settle it, the way the focus rule
 * further up does for the same reason. This is deliberately not left to the
 * form's settings: a stored colour is per-database, does not deploy, and every
 * new form would start from the same failing default. The theme already owns
 * this button's size and shape, so it owns its contrast too.
 */
.fluentform .ff-btn-submit.ff-btn-submit:not(.ff_btn_no_style) {
	background-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.fluentform .ff-btn-submit.ff-btn-submit:not(.ff_btn_no_style):hover,
.fluentform .ff-btn-submit.ff-btn-submit:not(.ff_btn_no_style):focus-visible {
	background-color: var(--bw-primary-hover);
}

/*--------------------------------------------------------------
# Motion
--------------------------------------------------------------*/

/**
 * Hover emphasis, applied here rather than beside each component so the
 * vocabulary stays in one place. Transitions live with their components; only
 * the moved state is declared below.
 *
 * Distances are deliberately small (--bw-lift-*). This is an EPC contractor,
 * not a consumer app — the motion should read as weight, not bounce.
 */

/* Buttons and button-like controls rise slightly. */
.bw-btn:hover,
.bw-btn:focus-visible,
.fluentform .ff-btn-submit:hover,
.fluentform .ff-btn-submit:focus-visible {
	transform: translateY(var(--bw-lift-control));
}

/*
 * Cards lift further, on the heavy easing so the mass reads. The news card is
 * not in this list: it is no longer a link surface, and lifting something that
 * cannot be clicked promises an interaction that isn't there.
 */
.bw-card-project,
.bw-person,
.bw-asset {
	transition: transform var(--bw-duration-control) var(--bw-ease-heavy);
}

.bw-card-project:hover,
.bw-card-project:focus-within {
	transform: translateY(var(--bw-lift-card));
}

.bw-track-card:hover {
	transform: translateY(var(--bw-lift-card));
}

/*--------------------------------------------------------------
## Navigation underline
--------------------------------------------------------------*/

/**
 * A rule wipes in from the left on hover and out to the right on leave, which
 * is why the transform-origin flips between the two states.
 *
 * Only on items WITHOUT a submenu. A parent item opens a panel at `top: 100%`,
 * which sits exactly where the underline is drawn and hides it — and the panel
 * is already that item's affordance. Leaf items get the underline instead.
 *
 * NOTE: not in the Figma, which changes only the link colour. Added from the
 * motion brief — drop this block to return to the drawn behaviour.
 */
.main-navigation .nav-menu > li:not(.menu-item-has-children) > a {
	position: relative;
}

.main-navigation .nav-menu > li:not(.menu-item-has-children) > a::after {
	background-color: var(--bw-primary);
	bottom: -4px;
	content: "";
	height: 2px;
	left: 0;
	position: absolute;
	right: 0;
	transform: scaleX(0);
	transform-origin: right;
	transition: transform var(--bw-duration-control) var(--bw-ease-mechanical);
}

.main-navigation .nav-menu > li:not(.menu-item-has-children) > a:hover::after,
.main-navigation .nav-menu > li:not(.menu-item-has-children) > a:focus-visible::after,
.main-navigation .nav-menu > li:not(.menu-item-has-children).current-menu-item > a::after {
	transform: scaleX(1);
	transform-origin: left;
}

/* The mobile panel stacks links; an underline there reads as clutter. */
@media (max-width: 900px) {

	.main-navigation .nav-menu > li > a::after {
		display: none;
	}
}

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

/**
 * The one place a longer duration is warranted. Content settles once on load;
 * it does not repeat and nothing waits on it.
 */
@media (prefers-reduced-motion: no-preference) {

	/*
	 * `backwards`, not `both`. A forwards fill keeps the final keyframe
	 * applied for ever, and an animated property outranks a declaration — so
	 * `transform: none` at 100% would silently kill the CTA's hover lift.
	 * Backwards fill covers the delay and then hands the element back.
	 */
	.bw-hero__inner > * {
		animation: bw-hero-settle var(--bw-duration-hero) var(--bw-ease-enter) backwards;
	}

	.bw-hero__inner > :nth-child(2) { animation-delay: 120ms; }
	.bw-hero__inner > :nth-child(3) { animation-delay: 240ms; }
	.bw-hero__inner > :nth-child(n+4) { animation-delay: 360ms; }
}

@keyframes bw-hero-settle {

	from {
		opacity: 0;
		transform: translateY(16px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/*--------------------------------------------------------------
# Modal
--------------------------------------------------------------*/

/**
 * Native <dialog>. Figma draws two sizes: 1100x671 for the table overlays
 * (QHSE Stats, Equipment List) and 873x988 for the document ones.
 */
.bw-modal {
	background-color: var(--bw-surface);
	border: 0;
	color: var(--bw-text);
	max-height: min(90vh, 671px);
	max-width: min(calc(100vw - 2rem), 1100px);
	padding: 0;
	width: 100%;
}

.bw-modal--tall {
	max-height: min(90vh, 988px);
	max-width: min(calc(100vw - 2rem), 873px);
}

.bw-modal::backdrop {
	background-color: rgba(0, 0, 0, 0.6);
}

/* Entry is opt-in so reduced-motion users get the dialog with no movement. */
@media (prefers-reduced-motion: no-preference) {

	.bw-modal[open] {
		animation: bw-modal-in var(--bw-duration-panel) var(--bw-ease-enter);
	}
}

@keyframes bw-modal-in {

	from {
		opacity: 0;
		transform: translateY(8px);
	}

	to {
		opacity: 1;
		transform: translateY(0);
	}
}

.bw-modal__inner {
	display: flex;
	flex-direction: column;
	max-height: inherit;
}

/* Close sits at 1032,43 in the 1100-wide frame — a 68px inset. */
.bw-modal__head {
	align-items: start;
	border-bottom: 1px solid var(--bw-rule);
	display: flex;
	gap: var(--bw-space-5);
	justify-content: space-between;
	padding: var(--bw-space-7) clamp(1.5rem, 6.18vw, 4.25rem) var(--bw-space-5);
}

.bw-modal__title {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h5);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h5);
	letter-spacing: var(--bw-ls-h5);
	margin: 0;
}

.bw-modal__close {
	background: none;
	border: 0;
	color: var(--bw-text);
	cursor: pointer;
	display: grid;
	flex-shrink: 0;
	height: 32px;
	padding: 0;
	place-items: center;
	transition: color var(--bw-duration-icon) var(--bw-ease-industrial);
	width: 33px;
}

.bw-modal__close:hover,
.bw-modal__close:focus-visible {
	color: var(--bw-primary);
}

.bw-modal__body {
	overflow-y: auto;
	padding: var(--bw-space-6) clamp(1.5rem, 6.18vw, 4.25rem) var(--bw-space-7);
}

/*--------------------------------------------------------------
# Data table (inside the table modals)
--------------------------------------------------------------*/

/**
 * Figma draws 283/170/171/134/134 with a 69px header and 50px body rows — but
 * that table contains no text at all, so its ratios describe nothing. These
 * widths are set for the real content instead: the two prose columns (name and
 * capacity) take roughly a third each, the three short ones share the rest.
 * The 69px header and 50px minimum row are kept; a long value grows its row.
 */
.bw-datatable__scroll {
	overflow-x: auto;
}

/*
 * Automatic layout, not fixed. The component serves more than one table now,
 * and per-position widths tuned for one of them are wrong for the next; the
 * browser weights columns by their content, which is what hand-tuning was
 * approximating anyway. The row header is capped so a long name cannot take
 * the whole table.
 */
.bw-datatable {
	border-collapse: collapse;
	min-width: 720px;
	table-layout: auto;
	width: 100%;
}

.bw-datatable tbody th,
.bw-datatable thead th:first-child {
	max-width: 22rem;
}

.bw-datatable th,
.bw-datatable td {
	padding: var(--bw-space-3) var(--bw-space-4);
	text-align: left;
	vertical-align: middle;
}

.bw-datatable thead th {
	background-color: var(--bw-grey-300);
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-card-label);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-card-label);
	letter-spacing: var(--bw-ls-card-label);
	height: 69px;
	text-transform: uppercase;
}

.bw-datatable tbody tr {
	border-bottom: 1px solid var(--bw-rule);
}

.bw-datatable tbody td {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	height: 50px;
}

.bw-datatable tbody th {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-body-md);
	font-weight: var(--bw-weight-medium);
	line-height: var(--bw-lh-body-md);
}

/* Action row below the equipment cards (opens the list overlay). */
.bw-assets__actions {
	margin: var(--bw-space-11) 0 0;
	padding-left: var(--bw-block-inset);
}

/* Data Table block wrapper. */
.bw-datatable-block__heading {
	margin: 0 0 var(--bw-space-5);
	padding-left: var(--bw-block-inset);
}

.bw-datatable-block__actions {
	margin: 0;
	padding-left: var(--bw-block-inset);
}

/* Caveat line above a table — a source, a period, or a provisional-data note. */
.bw-datatable__note {
	color: var(--bw-text-muted);
	font-size: var(--bw-size-body-sm);
	line-height: var(--bw-lh-body-sm);
	letter-spacing: var(--bw-ls-body);
	margin: 0 0 var(--bw-space-5);
	max-width: 70ch;
}

/*--------------------------------------------------------------
# Archive intro
--------------------------------------------------------------*/

/**
 * The lead section every listing opens with. Figma: heading ~988px wide over a
 * ~909px paragraph, both inset 40px inside the page gutter, sitting between the
 * banner and the grid.
 */
/*
 * No padding of its own — the section rhythm at the end of this file puts 180
 * above it and 180 below, the same as every other section gets.
 */
.bw-archive-intro {
	padding-block: 0;
}

.bw-archive-intro__heading {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-h2);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-h2);
	letter-spacing: var(--bw-ls-h2);
	margin: 0;
	max-width: calc(988px + var(--bw-block-inset));
	padding-left: var(--bw-block-inset);
}

.bw-archive-intro__text {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	margin: var(--bw-space-6) 0 0;
	max-width: calc(909px + var(--bw-block-inset));
	padding-left: var(--bw-block-inset);
}

/*--------------------------------------------------------------
# Organisation chart
--------------------------------------------------------------*/

/**
 * Figma "Personnel" Chart (249:1747) is an empty 1728x1190 frame, so the shape
 * is ours. Built from the `reports_to` field as nested <ul>s; the connectors
 * are drawn with pseudo-elements so there is no SVG or JS to keep in step.
 *
 * Each level is a flex row of nodes. A node draws a stub up to its parent's
 * horizontal rail, and a parent draws the rail across its children.
 */
.bw-orgchart__heading {
	margin: 0 0 var(--bw-space-7);
	padding-left: var(--bw-block-inset);
}

/* Wide trees scroll rather than crushing the cards. */
.bw-orgchart__scroll {
	overflow-x: auto;
	padding-block: var(--bw-space-4);
}

.bw-orgchart {
	/* The rails have to bridge this gap, so they need it as a number. */
	--bw-orgchart-gap: 2rem;
}

.bw-orgchart__level {
	display: flex;
	gap: var(--bw-orgchart-gap);
	justify-content: center;
	list-style: none;
	margin: 0;
	padding: 0;
}

.bw-orgchart__node {
	display: flex;
	flex-direction: column;
	align-items: center;
	position: relative;
}

/* Nested levels sit below their parent with room for the connectors. */
.bw-orgchart__node > .bw-orgchart__level {
	margin-top: var(--bw-space-8);
	position: relative;
}

/* The vertical drop from a parent down to its children's rail. */
.bw-orgchart__node > .bw-orgchart__level::before {
	background-color: var(--bw-rule);
	content: "";
	height: var(--bw-space-8);
	left: 50%;
	position: absolute;
	top: calc(var(--bw-space-8) * -1);
	width: 1px;
}

/*
 * The horizontal rail across a set of siblings. Each node draws its own
 * segment, so the segment has to reach half the flex gap either side to meet
 * its neighbour's — otherwise the rail comes out dashed.
 */
.bw-orgchart__level .bw-orgchart__node::before {
	background-color: var(--bw-rule);
	content: "";
	height: 1px;
	left: calc(var(--bw-orgchart-gap) / -2);
	position: absolute;
	top: calc(var(--bw-space-5) * -1);
	width: calc(100% + var(--bw-orgchart-gap));
}

/* The run stops at the outermost siblings, starting/ending at their centres. */
.bw-orgchart__level .bw-orgchart__node:first-child::before {
	left: 50%;
	width: calc(50% + (var(--bw-orgchart-gap) / 2));
}

.bw-orgchart__level .bw-orgchart__node:last-child::before {
	left: calc(var(--bw-orgchart-gap) / -2);
	width: calc(50% + (var(--bw-orgchart-gap) / 2));
}

.bw-orgchart__level .bw-orgchart__node:only-child::before {
	display: none;
}

/* The stub from a node up to the rail. */
.bw-orgchart__level .bw-orgchart__node::after {
	background-color: var(--bw-rule);
	content: "";
	height: var(--bw-space-5);
	left: 50%;
	position: absolute;
	top: calc(var(--bw-space-5) * -1);
	width: 1px;
}

/* The root has nothing above it. */
.bw-orgchart__scroll > .bw-orgchart__level > .bw-orgchart__node::before,
.bw-orgchart__scroll > .bw-orgchart__level > .bw-orgchart__node::after {
	display: none;
}

.bw-orgchart__card {
	align-items: center;
	background-color: var(--bw-surface);
	border: 1px solid var(--bw-rule);
	border-radius: var(--bw-radius-control);
	color: var(--bw-text);
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-1);
	min-width: 210px;
	padding: var(--bw-space-4) var(--bw-space-5);
	text-align: center;
	text-decoration: none;
	transition:
		background-color var(--bw-duration-control) var(--bw-ease-industrial),
		border-color var(--bw-duration-control) var(--bw-ease-industrial),
		color var(--bw-duration-control) var(--bw-ease-industrial);
}

.bw-orgchart__card:hover,
.bw-orgchart__card:focus-visible {
	background-color: var(--bw-primary);
	border-color: var(--bw-primary);
	color: var(--bw-primary-contrast);
}

.bw-orgchart__name {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-body-md);
	font-weight: var(--bw-weight-semibold);
	line-height: var(--bw-lh-body-md);
}

.bw-orgchart__role {
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-card-label);
	font-weight: var(--bw-weight-medium);
	letter-spacing: var(--bw-ls-card-label);
	line-height: var(--bw-lh-card-label);
	text-transform: uppercase;
}

@media (max-width: 782px) {

	.bw-orgchart__card {
		min-width: 160px;
		padding: var(--bw-space-3) var(--bw-space-4);
	}
}

/* Qualifications and Projects led on a profile. Each hides when empty. */
.bw-profile__panel {
	border-top: 1px solid var(--bw-rule);
	margin-top: var(--bw-space-7);
	padding-top: var(--bw-space-5);
}

.bw-profile__panel-title {
	color: var(--bw-text-muted);
	margin: 0 0 var(--bw-space-4);
}

.bw-profile__quals,
.bw-profile__projects {
	display: flex;
	flex-direction: column;
	gap: var(--bw-space-3);
	list-style: none;
	margin: 0;
	padding: 0;
}

.bw-profile__quals li {
	font-size: var(--bw-size-body-md);
	line-height: var(--bw-lh-body-md);
	letter-spacing: var(--bw-ls-body);
	padding-left: var(--bw-space-5);
	position: relative;
}

/* A red tick-sized marker rather than a browser bullet. */
.bw-profile__quals li::before {
	background-color: var(--bw-primary);
	content: "";
	height: 6px;
	left: 0;
	position: absolute;
	top: 0.55em;
	width: 6px;
}

.bw-profile__linkedin {
	margin-top: var(--bw-space-7);
}

/*--------------------------------------------------------------
# Listing pagination
--------------------------------------------------------------*/

/**
 * The design draws no pagination — every listing frame fits on one screen — so
 * this is derived. `the_posts_navigation()` is already on the News index and
 * every CPT archive, and until now inherited nothing but the _s defaults, which
 * left it in body type with no separation from the grid above.
 *
 * _s handles the layout in style.css (flex, 50/50, the next link end-aligned);
 * this only adds the rule above it, the spacing and the link treatment.
 */
.site-main .posts-navigation {
	border-top: 1px solid var(--bw-rule);
	margin: clamp(3rem, 5.787vw, 6.25rem) 0 0;
	padding-top: var(--bw-space-7);
}

.posts-navigation .nav-links a {
	align-items: center;
	color: var(--bw-text);
	display: inline-flex;
	font-family: var(--bw-font-heading);
	font-size: var(--bw-size-nav);
	font-weight: var(--bw-weight-medium);
	gap: var(--bw-space-4);
	letter-spacing: var(--bw-ls-nav);
	line-height: var(--bw-lh-nav);
	text-decoration: none;
	transition: color var(--bw-duration-control) var(--bw-ease-industrial);
}

.posts-navigation .nav-links a:hover,
.posts-navigation .nav-links a:focus-visible {
	color: var(--bw-primary);
}

/*--------------------------------------------------------------
# Vertical rhythm between sections
--------------------------------------------------------------*/

/**
 * 180px between one full-width section and the next. This is the single
 * measurement the whole Figma file agrees on, HiFi and LoFi alike: Home's
 * Frame 103 runs hero → QHSE → track record → section → clients → news →
 * footer at 180 apart, and all twenty-five inner screens open banner (ends
 * 775) → first section (starts 955) on the same 180. Two exceptions are real
 * and handled at the bottom of this section.
 *
 * The measurement is between what you SEE, not between padding boxes. Frames
 * in the file are content-tight — a frame's box is its content unless it
 * carries a fill, in which case the fill is the box. So a transparent section
 * must not add padding on top of the 180, and a filled one (the statement
 * band, the banner, a split's photograph, the footer) is spaced by its edge.
 *
 * Blocks used to fake the separation with ~88px of their own padding-block,
 * which is close to right where two padded sections met (88 + 88 = 176) and
 * wrong everywhere else, because the painted sections contribute nothing at
 * their edges. Measured across the site, no two boundaries matched: 0, 24, 87,
 * 90, 103, 106, 107, 111, 135, 143, 154, 202, 258. That padding is stripped at
 * the top level here and the gap is owned in one place instead.
 *
 * Leading margin only, never trailing. Adjacent siblings' vertical margins
 * collapse to the larger of the two, so the obvious "half a gap on each side"
 * model would yield 90 between two sections rather than 180. With nothing
 * carrying a bottom margin there is nothing to collapse against, and the
 * leading value always stands.
 *
 * Sections are matched by class rather than with a blanket `> *`, for two
 * reasons. The undesigned loop templates (search, index, archive) would
 * otherwise put 180 between every result. And not every full-width element is
 * a section: `.bw-feature` on a project single sits INSIDE Frame 120 with the
 * intro above it, 100px away, so it keeps its own margin and must not be
 * caught here. If you add a section, give it `bw-block` or `bw-container`.
 *
 * `.entry-content` is safe to match wholesale — a block page's children are
 * all sections, and the stray <script> a shortcode leaves behind is
 * display:none and generates no box.
 */
.site-main > .bw-block,
.site-main > .bw-bleed,
.site-main > .bw-band,
.site-main > .bw-container,
.entry--blocks > .entry-content > *,
.site-footer {
	margin-block-start: var(--bw-band-gap);
}

/*
 * The rhythm owns the space now, so the sections must not also pad themselves.
 * Only the outer box is reset: every painted section keeps its fill inset,
 * because that padding lives on an inner element, and a nested .bw-section is
 * left alone.
 */
.site-main > .bw-block,
.site-main > .bw-bleed,
.site-main > .bw-band,
.site-main > .bw-container,
.entry--blocks > .entry-content > * {
	padding-block: 0;
}

/* Whatever opens the page has nothing above it. */
.site-main > :first-child,
.entry--blocks > .entry-content > :first-child {
	margin-block-start: 0;
}

/*
 * ...unless the banner was rendered above `main` rather than inside it, which
 * is what every CPT single and most archives do. Then main's first section is
 * not the page's first section and the gap under the banner is real.
 */
.bw-pagehead + .site-main > :first-child {
	margin-block-start: var(--bw-band-gap);
}

/*
 * Exception 1 — the sibling-links strip gets a double gap. "Services links"
 * sits 360 below the section above it on all six service singles and all four
 * project singles, which reads as a deliberate break before the page stops
 * being about its subject. The link grid on Certifications is the same block
 * without the divider and keeps the standard 180.
 *
 * The 360 is two gaps with the rule ON the midpoint, not one long drop with
 * the rule at the bottom: Projects - One (`204:254`) ends its gallery at 4207,
 * draws the Divider (`255:1930`) at 4387 and starts the links (`255:1931`) at
 * 4567; the service singles agree (3644 / 3824 / 4004). The rule reads as a
 * separator between two things, so it needs air on both sides — with the
 * leading margin outside the border and the rhythm reset above zeroing the
 * section's own padding, all 360 landed above the rule and the heading sat
 * hard against it. Split it: margin above the border, padding below it.
 */
.site-main > .bw-linkgrid--divided {
	margin-block-start: var(--bw-band-gap);
	padding-block-start: var(--bw-band-gap);
}

/*
 * Exception 2 — a news article opens 228 below its banner, not 180, on all
 * four "News - " singles (frame ends 775, article starts 1003).
 */
.single-post .bw-pagehead + .site-main > :first-child {
	margin-block-start: calc(var(--bw-band-gap) + 3rem);
}

/*
 * Exception 3 — a feature band and the button under it are one frame in the
 * design, not two sections. QHSE's Frame 154 (`445:3152`) holds the band (ends
 * 657) and the Link button (starts 737) 80 apart, which is the same 80 the
 * Equipment archive puts between its cards and the overlay trigger. The 180
 * belongs above the band, not inside the pair.
 *
 * The button loses its 40px inset here too: Figma draws it at x=145, on the
 * gutter, squared up with the band's copy above it rather than with the block
 * inset every free-standing section uses.
 *
 * Matched at the rhythm rule's own specificity and declared after it, which is
 * what lets it win — the pattern `flush` on the statement pair already uses.
 */
.bw-featureband + .bw-datatable-block {
	margin-block-start: var(--bw-space-11);
}

.bw-featureband + .bw-datatable-block .bw-datatable-block__actions {
	padding-left: 0;
}
