/* -----------------------------------------------------------------------
   home.css — the eleven homepage bands, in design order.
   Every measurement is taken from design-data.json at 1440px.
   ----------------------------------------------------------------------- */

/* ============================================================ 1. HERO ===
   1440x764. A vertical stack, gap 56, padding 48 top and bottom, with the
   content pinned to the bottom of the band.
   ======================================================================= */

.hero {
	position: relative;
	display: flex;
	min-height: 764px;
	overflow: hidden;
	isolation: isolate;
}

.hero__media,
.hero__scrim {
	position: absolute;
	inset: 0;
	z-index: -2;
}

/*
 * Fallback ground for when neither a video nor an image is set. The design's
 * own background is a video fill that no export could capture, so this is
 * deliberately neutral rather than a stand-in photograph.
 */
.hero__media {
	background-color: #2a2a2d;
	background-image:
		radial-gradient(120% 80% at 70% 20%, rgba(255, 255, 255, 0.10), transparent 60%),
		linear-gradient(180deg, #35343a 0%, #1d1c20 100%);
}

.hero__media.has-media {
	background: none;
}

.hero__image,
.hero__video {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The video sits over the image and only shows once it is actually playing. */
.hero__video {
	position: absolute;
	inset: 0;
	/* opacity: 0; */
	transition: opacity 0.6s ease;
}

.hero__video.is-playing {
	opacity: 1;
}

/* design fill: #000000 at 25%. */
.hero__scrim {
	z-index: -1;
	background-color: rgba(0, 0, 0, 0.25);
}

.hero__inner {
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	gap: 56px;
	width: 100%;
	max-width: 1440px;
	margin-inline: auto;
	padding-block: 48px;
}

/* the design: a 32px inset (Gap/Gap 4) at both sizes. */
.hero__headline {
	padding-inline: 32px;
}

.hero__title {
	color: var(--c-ink);
	font-family: var(--font-heading);
	font-size: var(--fs-h1);
	font-weight: 400;
	line-height: var(--lh-h1);
	letter-spacing: -0.02778em;
	text-transform: uppercase;
}

/*
 * The supporting column is right-aligned. Its inner width resolves to 439px,
 * ending 160px from the right edge of the 1440 frame.
 */
.hero__aside {
	display: flex;
	justify-content: flex-end;
	padding-inline: var(--gutter);
}

.hero__aside-inner {
	display: flex;
	flex-direction: column;
	gap: 16px;
	align-items: flex-start;
	width: 439px;
	max-width: 100%;
	margin-right: clamp(0px, 5.5556vw, 80px);
}

.hero__intro {
	color: var(--c-ink);
	font-size: var(--fs-small);
	font-weight: 300;
	line-height: var(--lh-body);
}

/*
 * the design: a 1280px rule at 10% white with a 400px full-white
 * segment sitting on top of it, inset 80px from each edge.
 */
.hero__rule {
	position: relative;
	height: 1px;
	margin-inline: var(--gutter);
	background-color: var(--c-line);
}

/* The design: a 400px bright segment at 1440, 200px on the mobile frame. */
.hero__rule-progress {
	position: absolute;
	inset: 0 auto 0 0;
	width: min(400px, 100%);
	background-color: var(--white);
}

@media (max-width: 1024px) {
	.hero {
		min-height: 620px;
	}

	.hero__inner {
		gap: 40px;
	}
}

@media (max-width: 900px) {
	.hero {
		min-height: 560px;
	}

	/*
	 * the design groups the headline, intro and CTA with a 16px gap and
	 * left-aligns them; the rule then sits the full 56px below that group.
	 */
	.hero__inner {
		gap: 16px;
		padding-top: 120px;
	}

	.hero__aside {
		justify-content: flex-start;
		padding-inline: 32px;
	}

	.hero__aside-inner {
		width: 100%;
		margin-right: 0;
	}

	.hero__rule {
		margin-top: 40px;
	}

	.hero__rule-progress {
		width: min(200px, 100%);
	}

	.hero__title {
		max-width: none;
	}
}

/* =========================================================== 2. ABOUT ===
   the design 1440x760. HORIZONTAL, gap 24, padding 80.
   Three columns: heading + collage, centre photo, copy + feature cards.
   ======================================================================= */

.about {
	position: relative;
	overflow: hidden;
	background-color: var(--c-bg);
}

/*
 * The three columns are bottom-aligned: in the design they start at
 * y=80/144/204 but every one ends at y=680.
 */
.about__inner {
	display: grid;
	grid-template-columns: 411px 410px 1fr;
	gap: 24px;
	align-items: end;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 80px var(--gutter);
}

.about__col {
	display: flex;
	flex-direction: column;
	gap: 24px;
	min-width: 0;
}

/*
 * The heading's 564px box is wider than its column, and the design render
 * shows it sitting ON TOP of the centre photograph (the "W" of "NEW" falls on
 * the sky). So the intro column is lifted above the photo rather than clipped.
 */
.about__col--intro {
	position: relative;
	z-index: 2;
	/* The design: 32 from the eyebrow to the heading, 31 from the heading to the
	   photograph — not the 24 + a further 24 of margin this used to stack. */
	gap: 32px;
}

.about__eyebrow {
	color: var(--c-ink);
	opacity: 0.6;
	font-family: var(--font-body);
	font-size: var(--fs-lead);
	font-weight: 300;
	line-height: var(--lh-body);
	letter-spacing: 0.01em;
}

/*
 * The design's text box is 564px wide and FIXED, wider than its own 411px column,
 * which is what wraps the heading to three lines rather than four. Reproduced
 * literally, capped so it never outgrows the viewport on narrow screens.
 */
.about__title {
	width: min(564px, 100%);
	font-size: var(--fs-h2);
	line-height: var(--lh-tight);
}

@media (min-width: 1201px) {
	.about__title {
		width: 564px;
	}
}

/* the design: 302x280 portrait beneath the heading. */
.about__collage {
	width: 302px;
	max-width: 100%;
}

.about__collage img {
	width: 100%;
	height: 280px;
	object-fit: cover;
}

/* the design: the 410x600 centre photo. */
.about__photo img {
	width: 100%;
	height: 600px;
	object-fit: cover;
}

/* the design: 16px light at 80%. */
.about__body {
	color: var(--c-ink);
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

.about__cards {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

/* the design: 6% white, 16px padding, 16px gap, no stroke. */
.about-card {
	display: flex;
	flex-direction: column;
	gap: 16px;
	padding: 16px;
	background-color: rgba(255, 255, 255, 0.06);
}

/* the design: a 20px icon 8px from its label, not 24px and 12. */
.about-card__head {
	display: flex;
	align-items: center;
	gap: 8px;
}

.about-card__icon {
	flex: 0 0 20px;
	width: 20px;
	height: 20px;
	color: var(--c-accent);
}

.about-card__icon svg {
	width: 100%;
	height: 100%;
}

/*
 * the design: H5 at 20px with a 1.2 line height — the build had it on
 * H4 (24px) at 1.1, which made the card headings a size too large and tight.
 */
.about-card__title {
	font-size: var(--fs-h5);
	line-height: var(--lh-heading);
}

/* the design: 187x44 — it hugs its label rather than filling the column. */
.about__cta {
	align-self: flex-start;
}

.about-card__text {
	font-size: var(--fs-small);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

@media (max-width: 1200px) {
	.about__inner {
		grid-template-columns: 1fr 1fr;
	}

	.about__col--photo {
		order: 3;
		grid-column: 1 / -1;
	}

	.about__photo img {
		height: 420px;
	}
}

/*
 * Mobile: one column at the 24px gutter — both photographs run
 * full width at 264 tall, the second sitting 8px under the first, and the copy
 * block 24px under that.
 */
@media (max-width: 768px) {
	.about__inner {
		grid-template-columns: 1fr;
		gap: 8px;
		padding-block: var(--section-py);
	}

	/*
	 * The 1200px rule pushes the photograph last so it can span two columns;
	 * the mobile frame stacks head+collage, photograph, copy in DOM order, so
	 * that reordering is undone here.
	 */
	.about__col--photo {
		order: 0;
		grid-column: auto;
	}

	.about__col--body {
		margin-top: 16px;
		/* 8 of grid gap + 16 = the design's 24 */
	}

	.about__collage {
		width: 100%;
	}

	.about__collage img,
	.about__photo img {
		height: 264px;
	}
}

/* Shared: the faint architectural line drawing that backs several bands.
   Composited from the "Group" layers into a single SVG per section. */
.about__blueprint,
.problem__blueprint,
.testimonials__blueprint,
.locations__blueprint,
.insights__blueprint {
	position: absolute;
	pointer-events: none;
	background-repeat: no-repeat;
}

.about__blueprint {
	inset: 0;
	opacity: 0.05;
	background-image: url("../icons/blueprint-about.svg");
	background-position: center;
	background-size: cover;
}

.problem__blueprint {
	top: 0;
	right: 0;
	width: min(769px, 60%);
	height: 739px;
	opacity: 0.1;
	background-image: url("../icons/blueprint-problem.svg");
	background-position: top right;
	background-size: contain;
}

.testimonials__blueprint {
	top: 0;
	right: 0;
	width: min(850px, 65%);
	height: 817px;
	opacity: 0.1;
	background-image: url("../icons/blueprint-testimonials.svg");
	background-position: top right;
	background-size: contain;
}

/*
 * Service Areas and Insights draw the SAME 36 vectors at two scales — the
 * locations copy is exactly 2.8355x the insights one (1440/507.849 and
 * 1383.73/488 both give 2.8355) — but they are NOT the same file, because the
 * two groups ink them differently. Both carry the group's own 10% opacity;
 * the locations vectors then each carry a further 0.2 (one 0.4) on top, so
 * they land near 2%, while the insights vectors carry none and land at the
 * full 10%. Sharing one file drew the Insights blueprint five times too faint
 * — a barely-there smudge where the design draws legible linework.
 *
 * Neither is a `cover` background: each is a fixed-size drawing pinned to a
 * measured offset, and the section's own overflow does the cropping.
 */

/*
 * the design: 1440 x 1383.73 at x=0, y=-401 on the 1440 desktop frame,
 * and at x=-525 on the 390 mobile frame — i.e. HORIZONTALLY
 * CENTRED at both sizes, since (390-1440)/2 = -525. The drawing keeps its
 * design size rather than scaling with the viewport, so it is centred with a
 * translate rather than stretched to fit.
 */
.locations__blueprint {
	top: -401px;
	left: 50%;
	width: 1440px;
	height: 1383.73px;
	background-image: url("../icons/blueprint-lines.svg");
	background-size: 100% 100%;
	transform: translateX(-50%);
}

/*
 * the design: 507.85 x 488 at x=-161, y=-7 —
 * identical on the desktop and mobile frames, so it is left-anchored and
 * overhangs the section's left edge by 161px.
 */
.insights__blueprint {
	top: -7px;
	left: -161px;
	width: 507.85px;
	height: 488px;
	background-image: url("../icons/blueprint-insights.svg");
	background-size: 100% 100%;
}

/* ========================================================= 3. PROBLEM ===
   the design 1440x899. VERTICAL, gap 64, padding 80.
   ======================================================================= */

.problem {
	position: relative;
	overflow: hidden;
}

.problem__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 64px;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 80px var(--gutter);
}

.problem__head {
	max-width: 1062px;
	margin-inline: auto;
}

.page-template-page-solutions .problem__inner .section-head__title {
	max-width: 846px;
}

/* the design: 6% white with an 8px background blur. */
.problem__panel {
	display: grid;
	grid-template-columns: 1fr 519px;
	gap: 24px;
	min-height: 512px;
	padding-left: 64px;
	background-color: rgba(255, 255, 255, 0.06);
	backdrop-filter: blur(4px);
}

.problem__content {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 24px;
	min-width: 0;
	padding: 64px 0 48px;
}

/*
 * See the note on .locations__detail — a Swiper in a grid track needs an
 * explicit min-width so its intrinsic slide width cannot inflate the column.
 *
 * The CTA's corner brackets overhang it by 4px, and the button sits flush with
 * this track's clipping edges, so the padding/negative-margin pair gives them
 * room to draw without moving the content or letting a neighbouring slide's
 * bracket peek in.
 */
.problem__swiper {
	/* The extra 16px is consumed by the padding, so the slide itself stays the
	   design's 673px while the clip region extends 8px past it. */
	width: calc(100% + 16px);
	min-width: 0;
	min-height: 0;
	padding: 8px;
	margin: -8px;
}

.problem__slide {
	display: flex;
	flex-direction: column;
	gap: 24px;
	align-items: flex-start;
	height: auto;
	max-width: 673px;
}

.problem__title {
	font-size: var(--fs-h3);
	line-height: var(--lh-tight);
}

.problem__text {
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

.problem__points {
	display: flex;
	flex-direction: column;
	gap: 8px;
	list-style: none;
}

.problem__point {
	display: flex;
	align-items: center;
	gap: 4px;
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

/* the design: an 8x8 primary-red square inside a 22x22 box. */
.problem__bullet {
	flex: 0 0 22px;
	position: relative;
	height: 22px;
}

.problem__bullet::before {
	content: "";
	position: absolute;
	top: 7px;
	left: 7px;
	width: 8px;
	height: 8px;
	background-color: var(--c-accent);
}

/* the design: rails and controls pinned to the bottom of the column. */
.problem__controls {
	display: flex;
	align-items: center;
	gap: 24px;
	max-width: 673px;
	margin-top: 24px;
}

.problem__controls .slider-rails {
	flex: 1 1 auto;
	gap: 8px;
}

/*
 * the design: a vector whose top edge runs from y=114.851 on the
 * left to y=0 on the right (114.851 / 512 = 22.43%). The exported asset
 * confirms it — its top-left corner is transparent.
 */
.problem__media {
	align-self: stretch;
	width: 519px;
	max-width: 100%;
}

.problem__figure {
	height: auto;
}

.problem__figure img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	clip-path: polygon(0 22.43%, 100% 0, 100% 100%, 0 100%);
}

/*
 * Below the desktop layout the panel becomes a single column. The design
 * the mobile design orders it copy -> image -> controls inside a
 * 24px-padded panel, so `display: contents` on the content column lets those
 * three interleave without duplicating any markup.
 */
@media (max-width: 1024px) {
	.problem__panel {
		display: flex;
		flex-direction: column;
		gap: 24px;
		min-height: 0;
		padding: 24px;
	}

	.problem__content {
		display: contents;
	}

	.problem__swiper {
		order: 1;
	}

	.problem__media {
		order: 2;
		width: 100%;
		height: 244px;
	}

	.problem__figure {
		height: 100%;
	}

	/*
	 * The diagonal is NOT desktop-only, which this override used to assume.
	 * Tracing the design's own renders column by column gives the same cut at
	 * both widths — 22.2% down the left edge falling to 0 at the right, within
	 * a fifth of a percent of each other:
	 *
	 *            0%      25%     50%     75%     99%
	 *   mobile   22.13   16.80   11.07   5.33    0.00
	 *   desktop  22.27   16.80   11.13   5.47    0.20
	 *
	 * Being a percentage polygon, the one rule already scales to either box, so
	 * the mobile override is simply removed rather than restated.
	 */

	.problem__controls {
		order: 3;
		max-width: none;
	}

	.problem__slide {
		max-width: none;
	}
}

/* The mobile frame left-aligns the heading block; the desktop one centres it. */
@media (max-width: 900px) {
	.problem__inner {
		gap: 64px;
		padding-block: 80px;
	}

	.problem__head {
		align-items: flex-start;
		text-align: left;
	}
}

@media (max-width: 600px) {
	.problem__controls {
		flex-wrap: wrap;
		gap: 16px;
	}
}

/* ======================================================= 4. SOLUTIONS ===
   Desktop: 1440x782, absolutely composed rather than stacked. Mobile is a
   different design altogether, a horizontal carousel of 316px cards.
   ======================================================================= */

.solutions {
	background-color: var(--c-bg);
}

/* --- desktop: collage at rest, four columns on hover --- */

.solutions__stage {
	position: relative;
	width: 100%;
	max-width: 1440px;
	margin-inline: auto;
	aspect-ratio: 1440 / 782;
	overflow: hidden;
}

.solutions__cards {
	position: absolute;
	inset: 0;
}

/*
 * One element per solution, carrying both states in custom properties:
 * --x/--y/--w/--h/--r is its place in the collage, --ex/--ey/--ew/--eh its
 * column, --gx/--gy where its title floats as a ghost, --ely where the title
 * lands under the column. Hovering the stage swaps which set applies.
 */
.solution-deck {
	position: absolute;
	inset: 0;
	z-index: var(--z, 1);
	pointer-events: none;
}

/*
 * The box stays the COLLAGE; the column is reached by transform alone.
 *
 * This used to animate left, top, width and height. All four are layout
 * properties, so every frame of the 1600ms re-ran layout and re-rasterised four
 * large photographs at a size that had just changed — which is what made the
 * move stutter rather than glide. Duration was never the problem; the property
 * list was. transform and opacity are the only two the compositor can run on
 * its own, and this now uses nothing else.
 *
 * Both states are written with the SAME three functions in the same order, so
 * the browser interpolates them one function at a time instead of decomposing
 * two mismatched matrices.
 */
.solution-deck__media {
	position: absolute;
	left: var(--x);
	top: var(--y);
	width: var(--w);
	height: var(--h);
	transform: translate(0, 0) scale(1, 1) rotate(var(--r));
	transform-origin: center;
	pointer-events: auto;
	/* Promoted up front: creating the layer on hover would hitch the first frame. */
	will-change: transform;
	transition: transform var(--solutions-ease);
}

/* Covers the photograph so the whole image is clickable, label included. */
.solution-deck__hit {
	position: absolute;
	inset: 0;
	z-index: 1;
}

.solution-deck__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * At rest the title is the "ghost": Inter 80px, uppercase, 5% white,
 * sitting behind the photographs. It is the same element that becomes the
 * column label, so the copy is never duplicated.
 */
.solution-deck__title {
	position: absolute;
	left: var(--gx);
	top: var(--gy);
	width: max-content;
	max-width: 100%;
	color: var(--white);
	opacity: 0.05;
	font-family: var(--font-heading);
	font-size: var(--fs-ghost);
	font-weight: 400;
	line-height: var(--lh-tight);
	text-transform: uppercase;
	white-space: nowrap;
	pointer-events: auto;
	transition:
		left var(--solutions-ease),
		top var(--solutions-ease),
		font-size var(--solutions-ease),
		opacity var(--solutions-ease);
}

/*
 * The label turns red for the whole card, not just for the words themselves.
 * The photograph and the title are separately positioned SIBLINGS rather than
 * one wrapper — the deck spans the entire stage, so it cannot carry the hover —
 * which is exactly what the sibling combinator is for. The media precedes the
 * title in the markup, so hovering the picture reaches it.
 */
.solution-deck__media:hover~.solution-deck__title,
.solution-deck__title:hover,
.solution-deck__title:focus-within {
	color: var(--c-accent);
}

/* The expanded state. :focus-within keeps it reachable from the keyboard. */
.solutions__stage:hover .solution-deck__media,
.solutions__stage:focus-within .solution-deck__media {
	transform: translate(var(--tx), var(--ty)) scale(var(--sx), var(--sy)) rotate(0deg);
}

.solutions__stage:hover .solution-deck__title,
.solutions__stage:focus-within .solution-deck__title {
	left: var(--ex);
	top: var(--ely);
	width: var(--ew);
	opacity: 1;
	font-size: var(--fs-h4);
	/* Long labels wrap inside their column, as they do on the mobile card. */
	white-space: normal;
}

/* The design: Inter 96px / 120px line-height / -3px tracking, centred, over the art. */
.solutions__title {
	position: absolute;
	top: 50%;
	left: 50%;
	z-index: 5;
	width: 100%;
	transform: translate(-50%, -50%);
	color: var(--white);
	font-size: var(--fs-display);
	line-height: var(--lh-heading);
	letter-spacing: -0.03125em;
	text-align: center;
	text-transform: uppercase;
	pointer-events: none;
	transition:
		top var(--solutions-ease),
		font-size var(--solutions-ease),
		opacity var(--solutions-ease);
}

/*
 * Expanded, the headline steps back exactly as the component's "Default"
 * variant draws it: 64px, 15% opacity, centred on y=120 so it sits above the
 * columns rather than across them.
 */
.solutions__stage:hover .solutions__title,
.solutions__stage:focus-within .solutions__title {
	top: 15.3453%;
	opacity: 0.15;
	font-size: var(--fs-solutions-open);
}

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

	.solution-deck__media,
	.solution-deck__title,
	.solutions__title {
		transition: none;
	}
}

/* --- mobile carousel (the design) --- */

/*
 * Hidden on desktop; below 900px it replaces the composition entirely, because
 * the two are separate designs rather than one layout reflowing.
 */
.solutions__mobile {
	display: none;
}

.solutions__mobile-title {
	font-size: var(--fs-h2);
	line-height: var(--lh-tight);
	text-transform: uppercase;
}

/* The design: 316px card, 24px gap, the next card peeking past the gutter. */
.solutions__swiper {
	width: 100%;
	min-width: 0;
	overflow: visible;
}

/*
 * The card is the "Knockdown Rebuild" component, which carries
 * two variants. Between them, three things change and the card height does
 * not — everything is a transform or an opacity:
 *
 *   image  scales 1.0618 about its centre (110.39% -> 117.21% wide, and
 *          100% -> 106.18% tall — the same ratio, and both stay centred)
 *   lines  the decorative graphic is opacity 0 at rest and grows from
 *          202x101 to 456x228 anchored to its bottom-left corner (0.443 -> 1)
 *   arrow  a trailing arrow appears after the label
 */
.solution-card {
	display: flex;
	flex-direction: column;
	gap: 24px;
	width: 316px;
	max-width: 84vw;
	height: auto;
	overflow: hidden;
}

.solution-card__media {
	position: relative;
	height: 340px;
	overflow: hidden;
}

.solution-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transform: scale(1);
	transition: transform var(--transition-slow);
}

/*
 * the design — the real exported artwork, a white polyline at
 * 50% fill. Sized to the active variant and scaled down at rest, because the
 * default variant sets the group to opacity 0 and 0.443 of this size.
 */
.solution-card__lines {
	position: absolute;
	bottom: 0;
	left: -121px;
	width: 456px;
	height: 228px;
	opacity: 0;
	background-image: url("../icons/solution-card-lines.svg");
	background-repeat: no-repeat;
	background-position: left bottom;
	background-size: 100% 100%;
	transform: scale(0.443);
	transform-origin: left bottom;
	pointer-events: none;
	transition: opacity var(--transition-slow), transform var(--transition-slow);
	display: none;
	/* This is hidden as per client request. */
}

/* Whole-card click target, same technique as the post card. */
.solution-card {
	position: relative;
}

.solution-card__label a::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
}

.solution-card__label {
	font-size: var(--fs-h4);
	line-height: var(--lh-tight);
	text-transform: uppercase;
}

/*
 * The homepage card carries no arrow. Its desktop band is the collage, which
 * has none by design, and the mobile carousel is a touch layout with no hover
 * to reveal one — so it is dropped here and kept for the inner pages, where the
 * cards are a plain grid and the arrow is the hover affordance.
 */
.solutions__mobile .solution-card__arrow {
	display: none;
}

/*
 * The arrow is decorative, so it is drawn rather than baked into the label.
 *
 * It is assets/icons/arrow-right — the same glyph the sliders page with — and
 * not the &rarr; character it used to be, which rendered as whatever the
 * reader's font supplied and matched nothing else on the site.
 *
 * Sized in em so it tracks the label rather than being pinned to the SVG's own
 * 20x20, and inline-flex so the box is exactly the glyph with no strut of
 * leading under it to push it off the text's centre.
 */
.solution-card__arrow {
	display: inline-flex;
	align-items: center;
	margin-left: 0.35em;
	vertical-align: middle;
	opacity: 0;
	transform: translateX(-0.25em);
	transition: opacity var(--transition), transform var(--transition);
}

.solution-card__arrow svg {
	display: block;
	/*
	 * flex: none is load-bearing. The solutions grid gives this span width: 0
	 * on purpose, so a decorative hover glyph cannot decide where a label
	 * wraps, and lets the glyph paint outside its box. The &rarr; character
	 * that used to be here overflowed happily; an SVG is a flex ITEM, and a
	 * flex item in a zero-width container shrinks to zero unless told not to —
	 * which is exactly what it did, rendering a 0x24 nothing.
	 */
	flex: none;
	/*
	 * And max-width: none, for the same reason. base.css gives every img/svg
	 * max-width: 100%, which in a zero-width box resolves to zero and caps the
	 * glyph away again — flex: none alone got it as far as still measuring 0.
	 */
	max-width: none;
	width: 1em;
	height: 1em;
}

/* The measured exception — see "the house card zoom" above. */
.solution-card {
	--card-zoom: 1.0618;
}

.solution-card:hover .solution-card__media img,
.solution-card:focus-within .solution-card__media img {
	transform: scale(var(--card-zoom));
}

.solution-card:hover .solution-card__lines,
.solution-card:focus-within .solution-card__lines {
	opacity: 1;
	transform: scale(1);
}

.solution-card:hover .solution-card__arrow,
.solution-card:focus-within .solution-card__arrow {
	opacity: 1;
	transform: translateX(0);
}

/* Same rule as the desktop deck: the whole card reddens the label, not just
   the words. Here there is a real wrapper to hang it on. */
.solution-card:hover .solution-card__label,
.solution-card:focus-within .solution-card__label {
	color: var(--c-accent);
}

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

	.solution-card__media img,
	.solution-card__lines,
	.solution-card__arrow {
		transition: none;
	}
}

@media (max-width: 900px) {
	.solutions__stage {
		display: none;
	}

	.solutions__mobile {
		display: flex;
		flex-direction: column;
		gap: 40px;
		padding: var(--section-py) 0 var(--section-py) var(--gutter);
		overflow: hidden;
	}

	.solutions__mobile-title {
		padding-right: var(--gutter);
	}
}

/* ====================================================== 5. IS THIS YOU ===
   Desktop: the design with the "Customer Cards" component
. Mobile: the design — the same parts as a carousel.

   The component's two variants are the SAME HEIGHT: the description and the
   photograph are always in the layout, and activating a row only changes the
   background and two opacities. Nothing expands.
   ======================================================================= */

.isthisyou {
	position: relative;
}

.isthisyou__inner {
	display: flex;
	flex-direction: column;
	gap: 80px;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 64px var(--gutter) 80px;
}

.isthisyou__head {
	align-items: center;
	max-width: 1062px;
	margin-inline: auto;
	text-align: center;
}

/*
 * The rows are 1320 wide — 20px wider than the container on each side — so the
 * rules extend past the content, exactly as the component draws them.
 */
/*
 * The component is 1320 wide with 20px of its own padding, so the rules sit
 * 20px outside the 1280 container on each side while the text inside still
 * lines up with it.
 */
.isthisyou__list {
	width: calc(100% + 40px);
	max-width: 1320px;
	margin-inline: -20px;
	/* The photograph overhangs its row, so the track must not clip. */
	overflow: visible;
}

.isthisyou__list .swiper-wrapper {
	flex-direction: column;
}

/*
 * the design: 40px vertical / 20px horizontal, 1px top rule.
 *
 * The rule is an inset SHADOW, not a border. The design draws the stroke on the
 * row's boundary without it taking layout space (the design is 124 tall
 * INCLUDING its stroke); a real border-top added a pixel to every row.
 */
.customer-row {
	position: relative;
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 24px;
	height: auto;
	padding: 40px 20px;
	box-shadow: inset 0 1px 0 0 var(--c-line-strong);
	background-image: linear-gradient(180deg, rgba(246, 40, 44, 0), rgba(246, 40, 44, 0));
	transition: background-image var(--isthisyou-ease);
}

.customer-row:last-child {
	border-bottom: 1px solid var(--c-line-strong);
}

.customer-row__head {
	display: flex;
	/* Shrinkable: 520 + 519 + gap exceeds the container below ~1360. */
	flex: 0 1 520px;
	gap: 48px;
	align-items: flex-start;
	min-width: 0;
}

/*
 * Trimmed to the cap height and the alphabetic baseline, as pages.css explains
 * for the inner-page headings. The design's own heading layers carry
 * [text-box-trim:trim-both], and so does the band's own H2.
 *
 * Untrimmed, this band measured 889 against the design's 835 on EVERY page
 * that draws it — the homepage and Customers as well as About Us — 20 of that
 * in the heading and the rest spread across the three rows.
 *
 * Here rather than in pages.css because the band is on the front page, which
 * does not load pages.css.
 */
.isthisyou .section-head__title,
.customer-row__number,
.customer-row__question,
.customer-row__label,
/*
 * Blogs & Insights is trimmed too — and the card titles at
 *. Untrimmed the band's heading measured 183 against the design's
 * 163 and each card 475 against 466, on the Resources page and the homepage
 * alike. In home.css for the same reason as the rest of this block: the front
 * page draws this band and does not load pages.css.
 */
.insights .section-head__title,
.post-card__title {
	text-box-trim: trim-both;
	text-box-edge: cap alphabetic;
}

.isthisyou .section-head__title {
	max-width: 900px;
}

.customer-row__number,
.customer-row__question {
	color: var(--c-ink);
	font-family: var(--font-heading);
	font-size: var(--fs-h4);
	font-weight: 400;
	line-height: var(--lh-tight);
	text-transform: uppercase;
}

.customer-row__number {
	flex: 0 0 85px;
	width: 85px;
}

.customer-row__question {
	flex: 1 1 auto;
	min-width: 0;
}

/*
 * The design: a 519-wide column holding the description and, 8px under it, the
 * link to that customer's page. The sizing lives on the COLUMN, which is all it
 * does now — it is a one-cell grid, and the 8px moved down to the wrapper that
 * actually holds those two.
 */
.customer-row__detail {
	display: grid;
	grid-template-areas: "detail";
	flex: 0 1 519px;
	align-items: start;
	justify-items: start;
	min-width: 0;
}

/*
 * The two states of this column share one cell rather than stacking, so the
 * column is as tall as the taller of them in BOTH states — which is what keeps
 * the row from changing height, the guarantee the whole component rests on. The
 * description is always the taller, so the row measures exactly what it did
 * before the title existed.
 *
 * Stacked with GRID, not `position: absolute`. Nothing between the link and
 * .customer-row may become a positioned ancestor: the anchor's stretched
 * ::after resolves `inset: 0` against the nearest one, and a positioned column
 * here would shrink the row-sized click target down to this column.
 */
.customer-row__label,
.customer-row__reveal {
	grid-area: detail;
	width: 100%;
}

/* The 8px between the description and its link, which the column used to give them. */
.customer-row__reveal {
	display: flex;
	flex-direction: column;
	gap: 8px;
	align-items: flex-start;
}

/*
 * Larger than the question it sits beside and muted against it, so the eye
 * takes the row in as "this is the Investors row" before it reads the question
 * — the whole point of putting a name here. The question stays white and stays
 * the H3; this is a label for the destination, and outranking it in SIZE while
 * yielding in WEIGHT of colour is what keeps the two from competing.
 *
 * No letter-spacing: the theme tracks only its two display styles (display at
 * 96 and h1 at 72). 40 is below both, and the 80px solutions ghost is untracked
 * for the same reason.
 *
 * Medium, and it is a REAL Inter Medium — fonts.css ships the 500 subsets
 * alongside the 400s for this. Inter is otherwise self-hosted at 400 only, so
 * asking for a heavier weight without that file would have had the browser
 * synthesise the bold, which at 40px smears the stems badly enough to see.
 * The Medium is 0.6%% wider than the Regular, which the column absorbs.
 *
 * Trimmed with the number and the question, so all three cap-align across the
 * gap between the columns despite the difference in size.
 *
 * Visible at rest and gone on hover: the only thing in the component that runs
 * in that direction, because it is the stand-in for what the hover reveals.
 */
.customer-row__label {
	color: var(--c-ink-muted);
	font-family: var(--font-heading);
	font-size: var(--fs-customer-label);
	font-weight: 500;
	line-height: var(--lh-tight);
	text-transform: uppercase;
	transition: opacity var(--isthisyou-ease);
}

/* The design: hidden until the row is active. */
.customer-row__answer {
	width: 100%;
	color: var(--c-ink);
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0;
	transition: opacity var(--isthisyou-ease);
}

/*
 * Paragraph 1/Medium — the same 16/1.4 as the description a step heavier, ruled
 * under the label only: the design leaves the arrow unruled.
 */
.customer-row__link-wrap {
	width: 100%;
	opacity: 0;
	/*
	 * Invisible has to mean unreachable. The description behind opacity 0 is
	 * only text, but this is a link — left live it would take a stray click
	 * from someone who never saw it. Pointer events come back with the reveal;
	 * keyboards are unaffected by the property, so tabbing still reaches it and
	 * :focus-within shows the row before it is activated.
	 */
	pointer-events: none;
	transition: opacity var(--isthisyou-ease);
}

.customer-row__link {
	color: var(--c-ink);
	font-size: var(--fs-body);
	font-weight: 500;
	line-height: var(--lh-body);
	text-decoration: none;
}

/*
 * The whole row is the target, not just these words.
 *
 * It is stretched from the anchor rather than wrapping the row in one, because
 * a second anchor around this one is invalid and a row-sized wrapper would pull
 * the number and the question into the link's accessible name — this way the
 * name stays "View <customer> in detail" and the row is still one link.
 *
 * `inset: 0` resolves against .customer-row, the nearest positioned ancestor:
 * nothing between here and there is positioned, and nothing should be.
 */
.customer-row__link::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
}

.customer-row__link-label {
	text-decoration: underline;
	text-underline-position: from-font;
}

/*
 * The theme's own arrow rather than a bare glyph, matching the solution cards:
 * sized in em so it tracks the label instead of being pinned to the file's
 * 20x20, and inline-flex so the box is the glyph with no leading under it. The
 * design leaves it unruled, which the anchor's own `text-decoration: none`
 * already gives it.
 */
.customer-row__link-arrow {
	display: inline-flex;
	align-items: center;
	margin-left: 0.35em;
	vertical-align: middle;
}

.customer-row__link-arrow svg {
	display: block;
	/* base.css caps every svg at max-width 100%; see .solution-card__arrow. */
	flex: none;
	max-width: none;
	width: 1em;
	height: 1em;
}

/*
 * The row's own reveal is all the design gives the link, and it fires on
 * hovering the ROW — which leaves the link itself with no feedback of its own.
 * Thickening the rule is the smallest thing that answers a pointer without
 * touching how the link sits at rest.
 */
.customer-row__link:hover .customer-row__link-label,
.customer-row__link:focus-visible .customer-row__link-label {
	text-decoration-thickness: 2px;
}

/*
 * the design: a 259x321 box at left 514, holding a 185x269
 * photograph rotated -15deg. It sits between the two columns and overhangs the
 * row top and bottom, so it is placed absolutely and does not affect height.
 */
.customer-row__media {
	position: absolute;
	top: 50%;
	/* 514 / 1320 — proportional so it stays between the columns as they shrink. */
	left: 38.9394%;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 259px;
	height: 321px;
	transform: translateY(-50%);
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--isthisyou-ease);
}

.customer-row__media img {
	width: 185px;
	height: 269px;
	object-fit: cover;
	transform: rotate(-15deg);
}

/*
 * --- the active state (the design's active variant) ---
 *
 * Behind `hover: hover` so a TOUCH device never gets it. On a phone there is
 * no hover to leave: the browser keeps :hover applied after a tap, so the red
 * wash stayed on the card the reader had just touched. It also dimmed the
 * answer from 1 to 0.8 — on mobile the answer is permanently visible
 *, so the tap was actually fading the copy out.
 *
 * :focus-within is inside the query too. It is here for keyboard users, who
 * are on a hovering device by definition; leaving it out would put the wash
 * back on a touch device the moment a tap moved focus into the card.
 */
@media (hover: hover) {

	.customer-row:hover,
	.customer-row:focus-within {
		background-image: linear-gradient(180deg, rgba(246, 40, 44, 0.25), rgba(246, 40, 44, 0));
	}

	.customer-row:hover .customer-row__answer,
	.customer-row:focus-within .customer-row__answer,
	.customer-row:hover .customer-row__link-wrap,
	.customer-row:focus-within .customer-row__link-wrap {
		opacity: 0.8;
	}

	.customer-row:hover .customer-row__link-wrap,
	.customer-row:focus-within .customer-row__link-wrap {
		pointer-events: auto;
	}

	.customer-row:hover .customer-row__media,
	.customer-row:focus-within .customer-row__media {
		opacity: 1;
	}

	/*
	 * The counterpart to the three reveals above. It is inside this query with
	 * them: on a touch device the browser leaves :hover applied after a tap, so
	 * outside it a tap would blank the title of the row just touched and leave
	 * the card with no heading at all.
	 */
	.customer-row:hover .customer-row__label,
	.customer-row:focus-within .customer-row__label {
		opacity: 0;
	}
}

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

	.customer-row,
	.customer-row__answer,
	.customer-row__label,
	.customer-row__link-wrap,
	.customer-row__media {
		transition: none;
	}
}

/* --- mobile: the same parts as a carousel --- */

@media (max-width: 900px) {
	.isthisyou__inner {
		gap: 48px;
		padding: 64px 0 80px var(--gutter);
	}

	.isthisyou .section-head__title {
		max-width: none;
	}

	/* The mobile frame left-aligns the heading block. */
	.isthisyou__head {
		align-items: flex-start;
		margin-inline: 0;
		padding-right: var(--gutter);
		text-align: left;
	}

	.isthisyou__list {
		width: 100%;
		max-width: none;
		margin-inline: 0;
		overflow: hidden;
	}

	.isthisyou__list .swiper-wrapper {
		flex-direction: row;
	}

	/* The design: 280px cards, 40px apart, everything permanently visible. */
	.customer-row {
		flex-direction: column;
		justify-content: flex-start;
		gap: 24px;
		width: 280px;
		max-width: 78vw;
		padding: 32px 0;
		background-image: none;
		margin-top: 40px;
	}

	.customer-row:last-child {
		border-bottom: 0;
	}

	/*
	 * And neutralised in the mobile LAYOUT as well, not only on touch. A
	 * narrow desktop window is a hovering device showing these cards, and the
	 * mobile card already has everything on show, so there is no closed state
	 * for the wash to reveal.
	 */
	.customer-row:hover,
	.customer-row:focus-within {
		background-image: none;
	}

	.customer-row:hover .customer-row__answer,
	.customer-row:focus-within .customer-row__answer,
	.customer-row:hover .customer-row__link-wrap,
	.customer-row:focus-within .customer-row__link-wrap,
	/*
	 * The title runs the other way to these two — it is the thing that LEAVES
	 * on the desktop reveal — but it is pinned here for the same reason, and it
	 * matters more: `hover: hover` is true of a narrow desktop window as well,
	 * so without this line pointing at a card would blank its title and leave
	 * the reader with no heading at all.
	 */
	.customer-row:hover .customer-row__label,
	.customer-row:focus-within .customer-row__label {
		opacity: 1;
	}

	/*
	 * Both wrappers dissolve, and the card's parts stand on their own.
	 *
	 * The title has to land between the number and the question, and those two
	 * are in __head while the title is in __detail — different boxes, so no
	 * amount of `order` on the card's own children could ever interleave them.
	 * `display: contents` removes the two wrappers from the layout and leaves
	 * their children as direct flex items of the card, where `order` can. The
	 * MARKUP is untouched, so the desktop row and its cross-fade are untouched
	 * too, and nothing is duplicated to achieve it.
	 *
	 * The other half of the same move: __detail stops being a one-cell grid.
	 * On the desktop row the title and the description SWAP, so they share a
	 * cell; here both are permanently on show and stacking them would print one
	 * over the other.
	 */
	.customer-row__head,
	.customer-row__detail {
		display: contents;
	}

	/*
	 * `flex: 0 0 85px` from the desktop row would set the number's HEIGHT once
	 * the card is a column, leaving a gap beneath it.
	 */
	.customer-row__number {
		order: 1;
		flex: 0 0 auto;
		width: 85px;
	}

	/*
	 * The card's title, dressed as one.
	 *
	 * On the desktop row this is the quiet stand-in for what the hover reveals,
	 * so it is muted and outsized. Here it is doing the opposite job — naming
	 * the card — so it takes the full ink back and steps a size ABOVE the
	 * question, which makes it the largest thing on the card and the first
	 * thing read after the number.
	 *
	 * fs-h3 rather than the desktop clamp: that floors at 28px, which runs the
	 * longest of them — "OWNER OCCUPIERS", 9.83em in Inter — to 275px inside a
	 * 280px card, flush to both edges. At 24 it measures 236 and sits clear.
	 *
	 * The negative margin eats half the card's 24px gap, binding the title to
	 * the number above it while leaving the full gap below — so the two read as
	 * one numbered title, rather than the title floating between the number and
	 * the question.
	 */
	.customer-row__label {
		order: 2;
		color: var(--c-ink-muted);
		font-size: var(--fs-h3);
		position: absolute;
		top: 0;
		color: var(--c-ink);
		font-size: var(--fs-h3);
		position: absolute;
		top: -38px;
		opacity: 0.6;
	}

	/*
	 * `flex: 1 1 auto` from the desktop row applies to the cross axis once the
	 * card is a column, so it is cleared with the number's basis.
	 */
	.customer-row__question {
		order: 3;
		flex: 0 0 auto;
		min-height: 82px;
	}

	.customer-row__reveal {
		order: 4;
	}

	.customer-row__answer,
	.customer-row__link-wrap {
		flex: 0 0 auto;
		width: 100%;
		opacity: 1;
	}

	.customer-row__answer {
		min-height: 90px;
	}

	.customer-row__link-wrap {
		pointer-events: auto;
	}

	.customer-row__media {
		order: 5;
		position: static;
		width: 100%;
		height: 269px;
		transform: none;
		opacity: 1;
		overflow: hidden;
	}

	.customer-row__media img {
		width: 100%;
		height: 100%;
		transform: none;
	}
}

/* ==================================================== 6. TESTIMONIALS ===
   the design 1440x797. VERTICAL, gap 64, padding 80 top and bottom.
   ======================================================================= */

.testimonials {
	position: relative;
	overflow: hidden;
}

.testimonials__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 64px;
	padding-block: 80px;
}

.testimonials__top {
	display: grid;
	grid-template-columns: 411px 1fr;
	gap: 24px;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	width: 100%;
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.testimonials__intro {
	display: flex;
	flex-direction: column;
	gap: 48px;
	align-items: flex-start;
}

.testimonials__slides {
	position: relative;
	min-width: 0;
}

/*
 * The quote glyph hangs 24px outside the tile's top-left corner, and the tile
 * sits at the slide's own edge — so the carousel's clip cropped it, and the
 * NEXT slide's glyph showed 24px inside the right edge (the second red mark).
 * The clip region is pushed out 24px and pulled back with an equal negative
 * margin, so the slide keeps its width, and `spaceBetween` on the slider keeps
 * the neighbour's glyph outside that bleed.
 */
.testimonials__swiper {
	width: calc(100% + 48px);
	min-width: 0;
	padding: 24px;
	margin: -24px;
}

.testimonial {
	display: flex;
	gap: 48px;
	height: auto;
}

/* the design: a 302x302 tile in --description with the quote glyph
   overhanging its top-left corner by 24px. */
.testimonial__tile {
	position: relative;
	flex: 0 0 302px;
	display: flex;
	flex-direction: column;
	gap: 4px;
	justify-content: center;
	height: 302px;
	padding: 212px 45px 32px 32px;
	background-color: var(--description);
	overflow: visible;
}

/*
 * The glyph hangs 24px off the tile's top-left corner. Once the band stacks the
 * tile sits AT the gutter, so that overhang is taken out of the gutter itself —
 * and the gutter clamps to 24 below 432px, which left the glyph starting on the
 * screen edge with nothing beside it.
 *
 * So the horizontal hang is whatever the gutter can spare while still keeping
 * 24px of clear, capped at the design's 24: the full -24 from 864px up (where
 * the tile is in the right-hand column anyway and has room to spare), tapering
 * to 0 by 432px. The vertical hang is never at risk and stays as drawn.
 */
.testimonial__quote-mark {
	position: absolute;
	top: -24px;
	left: calc(-1 * min(24px, max(0px, var(--gutter) - 24px)));
	width: 64px;
	height: 64px;
	color: var(--c-accent);
}

/* A featured image fills the tile beneath the glyph and the project name. */
.testimonial__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.testimonial__tile--image .testimonial__quote-mark {
	z-index: 1;
}

.testimonial__quote-mark svg {
	width: 100%;
	height: 100%;
}

.testimonial__project {
	position: relative;
	font-size: var(--fs-h3);
	line-height: var(--lh-tight);
}

.testimonial__body {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	gap: 24px;
	max-width: 495px;
}

/* the design: the heading sits 32px above the quote. */
.testimonial__head {
	display: flex;
	flex-direction: column;
	gap: 32px;
}

.testimonial__title {
	font-family: var(--font-heading);
	font-size: var(--fs-h3);
	font-weight: 400;
	line-height: var(--lh-tight);
	text-transform: uppercase;
}

.testimonial__quote {
	margin: 0;
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

/* As on the testimonials archive: the quote carries the editor's paragraphs
   rather than one hard-coded <p>. */
.testimonial__quote>* {
	margin: 0;
}

.testimonial__quote>*+* {
	margin-top: 0.85em;
}

.testimonial__author {
	font-size: var(--fs-body);
	font-weight: 500;
}

/*
 * The design puts the controls in the COPY column, 40px under the
 * author — not beneath the tile, where they were sitting. The nav stays
 * outside the carousel so it is rendered once, and is indented past the tile
 * and its 48px gap to line up with the copy.
 */
/*
 * The design puts the controls INSIDE the copy column — the copy, 40, then
 * the controls — not under the whole row — so they sit 40 below the QUOTE,
 * which lands them 15px above the tile's bottom edge rather than 40 below it.
 * In flow they made the band 386 tall against the design's 317. The copy
 * reserves the 84 they need and they float into it, so the row stays exactly as
 * tall as the design and still grows with a longer testimonial.
 */
.testimonial__body {
	padding-bottom: 84px;
}

.testimonials__nav {
	position: absolute;
	/*
	 * Above the slider, which carries Swiper's own `z-index: 1`. Without this
	 * the controls sat UNDER it: they were painted and looked right, but every
	 * click landed on the slide instead, so the arrows did nothing. The slider
	 * also reaches 24px past its content (it is padded and negatively
	 * margined to let the quote glyph bleed), so it covers them even where the
	 * two look well clear of each other.
	 */
	z-index: 2;
	bottom: 0;
	/* The tile's 302 plus the row's 48 gap: the copy column's own left edge. */
	left: 350px;
	margin: 0;
}

/*
 * the design: the two marquee rows are 80px tall and sit
 * 112px apart from the top of one to the top of the next — a 32px gap — and
 * they run the FULL width of the frame. Only the rule above them is inset to
 * the container, so that is drawn separately rather than as a border.
 */
/*
 * the design: the two rows sit 32px apart. The band takes the page
 * ground explicitly — the next section is a full-bleed photographic band, and
 * without an opaque ground its image reads through behind the logos.
 */
.testimonials__logos {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	gap: 32px;
	/* 64 above the marquees; nothing below — the section's own 80 closes it. */
	padding-top: 64px;
	background-color: var(--c-bg);
}

.testimonials__logos::before {
	content: "";
	position: absolute;
	top: 0;
	right: var(--gutter);
	left: var(--gutter);
	height: 1px;
	background-color: var(--c-line);
}

/*
 * Mobile: the tile runs the full column width at 302 tall with
 * the copy 40px beneath it, and the controls sit at the gutter rather than
 * indented past a tile that is no longer beside them.
 */
@media (max-width: 1024px) {
	.testimonials__top {
		grid-template-columns: 1fr;
		row-gap: 60px;
	}

	.testimonial {
		flex-direction: column;
		gap: 40px;
	}

	.testimonial__tile {
		flex-basis: auto;
		width: 100%;
		max-width: none;
	}

	/* Stacked here, so the controls go back into the flow beneath the copy. */
	.testimonial__body {
		padding-bottom: 0;
	}

	.testimonials__nav {
		position: static;
		margin-top: 32px;
		margin-left: 0;
	}
}

@media (max-width: 768px) {
	.testimonials__inner {
		gap: 48px;
		padding-block: var(--section-py);
	}

	.testimonials__intro {
		gap: 32px;
	}
}

/* ===================================================== 7. HOW IT WORKS ===
   Desktop the design, mobile, card component
   "How it Works Steps".
   ======================================================================= */

.howitworks {
	position: relative;
	overflow: hidden;
}

.howitworks__bg {
	position: absolute;
	inset: 0;
}

.howitworks__bg img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The design: #19181B opaque at the top fading to transparent at the bottom. */
.howitworks__bg::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, var(--title) 0%, rgba(25, 24, 27, 0) 100%);
}

/* The design: 140px top / 80px bottom padding, 64px gap. */
.howitworks__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 64px;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 140px var(--gutter) 80px;
}

.howitworks__title {
	font-size: var(--fs-h2);
	line-height: var(--lh-tight);
	text-align: center;
}

.howitworks__steps {
	display: flex;
	width: 100%;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow: hidden;
}

.howitworks__steps .swiper-wrapper {
	align-items: center;
	gap: 24px;
}

/* Swiper owns the spacing once the carousel takes over below 1024px. */
@media (max-width: 1024px) {
	.howitworks__steps .swiper-wrapper {
		gap: 0;
	}
}

@media (max-width: 767px) {
	.howitworks__inner {
		padding-right: 0;
	}

	.howitworks__inner .howitworks__title {
		padding-right: 24px;
	}
}

/*
 * the design: 411x480, 32px/24px padding, content pushed to
 * the two ends, and the card clips its own oversized photograph.
 */
.step-card {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	width: 411px;
	height: 480px;
	padding: 32px 24px;
	overflow: hidden;
}

/*
 * The photograph. At rest it exactly fills the card; on hover it grows from its
 * centre to 110.219% x 110.417% (the design 453x530 at -21,-25 inside 411x480).
 */
.step-card__media {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 0;
	width: 100%;
	height: 100%;
	transition:
		top var(--transition-slow),
		left var(--transition-slow),
		width var(--transition-slow),
		height var(--transition-slow);
}

.step-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * Two stacked overlays that cross-fade, because a background-image gradient
 * cannot itself be transitioned:
 *   ::after  the opaque default panel, fading out
 *   ::before the hover gradient, fading in
 */
/*
 * Both overlays must sit ABOVE the photograph. ::before is generated as the
 * card's first child, so without this z-index the hover gradient painted
 * beneath the image and the photo was revealed at full strength.
 */
.step-card::before,
.step-card::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	transition: opacity var(--transition-slow);
}

.step-card::after {
	background-color: var(--title);
	opacity: 1;
}

.step-card::before {
	background-image: linear-gradient(219.5936deg,
			rgba(25, 24, 27, 0.8) 1.4122%,
			rgb(25, 24, 27) 80.282%);
	opacity: 0;
}

.step-card:hover .step-card__media,
.step-card:focus-within .step-card__media {
	top: -5.2083%;
	left: -5.1095%;
	width: 110.219%;
	height: 110.4167%;
}

.step-card:hover::after,
.step-card:focus-within::after {
	opacity: 0;
}

.step-card:hover::before,
.step-card:focus-within::before {
	opacity: 1;
}

/* The design: "1" at 32px beside "/3" at 24px and 20% opacity, baseline aligned. */
.step-card__counter {
	position: relative;
	z-index: 2;
	display: flex;
	align-items: flex-end;
	font-size: var(--fs-h3);
	white-space: nowrap;
}

.step-card__counter .counter__total {
	font-size: var(--fs-h4);
}

.step-card__body {
	position: relative;
	z-index: 2;
	display: flex;
	flex-direction: column;
	gap: 24px;
	width: 100%;
}

.step-card__title {
	font-size: var(--fs-h4);
	line-height: var(--lh-tight);
}

.step-card__text {
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

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

	.step-card__media,
	.step-card::before,
	.step-card::after {
		transition: none;
	}
}

/* Mobile frame: 316x432 cards, 16px apart, and an 80px top padding. */
@media (max-width: 1024px) {
	.howitworks__inner {
		padding-top: 80px;
	}

	.step-card {
		width: 316px;
		max-width: 82vw;
		height: 432px;
	}
}

@media (max-width: 900px) {
	.howitworks__inner {
		gap: 48px;
	}
}

/* ======================================================= 8. LOCATIONS ===
   the design, mobile.
   ======================================================================= */

.locations {
	position: relative;
	overflow: hidden;
}

.locations__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 64px;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	/* 80 at 1440, 56 at 390 — both mobile frames
	   open at 56, which a hard-coded 80 was overshooting by 24 top and bottom. */
	padding: var(--section-py) var(--gutter);
}

/* The design: the heading block and its CTA sit 48px apart (40 on mobile). */
.locations__head {
	display: flex;
	flex-direction: column;
	gap: clamp(40px, 3.3333vw, 48px);
	align-items: flex-start;
}

/*
 * Gap 4 between eyebrow and heading — 32 desktop, 24 mobile (,
 *). The shared.section-head drops to 20 at 768 for the homepage's
 * other bands, which is 4px short of what this one draws.
 */
.locations .section-head {
	gap: var(--gap-4);
}

/*
 * Trimmed to the cap height and the alphabetic baseline, as pages.css explains
 * for the inner-page headings —, the design all
 * carry [text-box-trim:trim-both]. Untrimmed, the heading measured 123 against
 * the design's 103, the name 35 against 23 and the counter 26 against 18.
 *
 * These live HERE rather than in pages.css because this band is on the front
 * page too, and pages.css is only loaded on the inner-page templates.
 *
 * Not the "/8" beside the figure — the design leaves that one untrimmed, and
 * the row is bottom-aligned so the two still sit on a line together.
 */
.locations .section-head__title,
.location__name,
.location__counter .counter__current {
	text-box-trim: trim-both;
	text-box-edge: cap alphabetic;
}

.locations__body {
	display: flex;
	gap: 24px;
	align-items: flex-start;
}

/*
 * The design: a 302px column with 32px of vertical padding and 24px gaps.
 *
 * This column is a Swiper, so it clips at its own edge — which cut the CTA's
 * corner brackets (they overhang the button by 4px) and let the NEXT slide's
 * bracket peek in at the right edge. The clip region is pushed out 8px with
 * padding and pulled back with an equal negative margin, so the slide itself
 * is still the design's 302px and the 24px gap to the feature is unchanged.
 * `spaceBetween` on the slider then keeps the neighbour's bracket outside it.
 */
.locations__detail {
	flex: 0 0 318px;
	/* 302 + the 8px of clip room on each side */
	width: 318px;
	min-width: 0;
	padding: 8px;
	margin: -8px;
}

.location {
	display: flex;
	flex-direction: column;
	gap: 24px;
	align-items: flex-start;
	height: auto;
	padding-block: 32px;
}

.location__counter {
	display: flex;
	align-items: flex-end;
	font-size: var(--fs-h4);
}

/* The design: the total is Paragraph 1 sized (16px), not H4. */
.location__counter .counter__total {
	font-size: var(--fs-h6);
}

.location__name {
	font-size: var(--fs-h3);
	line-height: var(--lh-tight);
}

.location__text {
	font-size: var(--fs-body);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

/* The design: the feature image and the rail sit 80px apart. */
.locations__media {
	display: flex;
	flex: 1 1 auto;
	gap: 80px;
	align-items: flex-start;
	min-width: 0;
}

/* The design: 387px tall, taking the remaining width. */
.locations__feature {
	flex: 1 1 auto;
	width: 100%;
	min-width: 0;
	height: 387px;
}

.locations__figure {
	height: 100%;
}

.locations__figure img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* --- the thumbnail rail --- */

.locations__rail {
	position: relative;
	flex: 0 0 132px;
	width: 132px;
}

.locations__thumbs {
	width: 100%;
	max-height: 432px;
	/* five 80px thumbs with four 8px gaps */
}

.locations__thumbs .swiper-wrapper {
	flex-direction: column;
}

/*
 * The design: each thumb is a 132x80 BOX with 8px of padding. The border belongs to
 * that outer box, so it clears the photograph by 8px — it is not a border on
 * the image itself.
 */
.locations__thumb {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	width: 100%;
	height: 80px;
	padding: 8px;
	background: none;
	/*
	 * The active rule is an INSET SHADOW, not a border. The design draws it
	 * on the boundary of a fixed 132x80 box whose picture stays 116x64
	 * — a real border took a pixel off each edge of the padding
	 * box, so every thumbnail was rendering 114x62.
	 */
	box-shadow: inset 0 0 0 1px transparent;
	transition: box-shadow var(--transition);
}

.locations__thumb-media {
	position: relative;
	display: block;
	flex: 1 1 auto;
	width: 100%;
	min-height: 0;
	opacity: 0.2;
	transition: opacity var(--transition);
}

.locations__thumb-media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.locations__thumb:hover .locations__thumb-media {
	opacity: 0.6;
}

.locations__thumb.is-active,
.swiper-slide-thumb-active.locations__thumb {
	box-shadow: inset 0 0 0 1px var(--white);
}

.locations__thumb.is-active .locations__thumb-media,
.swiper-slide-thumb-active.locations__thumb .locations__thumb-media {
	opacity: 1;
}

/*
 * The design fades the last visible thumb out, signalling more below.
 *
 * In the file the gradient is a fill on that thumb's PICTURE — the 116x64 leaf
 * inside the 132x80 box, not the box — so it stops 8px short of the rail's
 * edges on three sides. Painted flush to the rail instead, it ran over the
 * thumb's padding and, with it, the active thumb's rule. It is placed here on
 * the rail rather than on one slide so it holds as the list scrolls.
 *
 * It is only painted while there IS more below — `has-more` comes off the rail
 * once it is scrolled to its end (or is short enough never to scroll). The
 * design only draws the scrollable state, where the bottom thumb is an
 * inactive one with more behind it; left on at the end of the rail it darkened
 * the last thumb, so the final slide read as clipped where the first did not.
 */
.locations__thumbs::after {
	content: "";
	position: absolute;
	right: 8px;
	bottom: 8px;
	left: 8px;
	z-index: 2;
	height: 64px;
	background: linear-gradient(180deg, rgba(25, 24, 27, 0) 0%, var(--title) 83.173%);
	opacity: 0;
	transition: opacity var(--transition);
	pointer-events: none;
}

.locations__thumbs.has-more::after {
	opacity: 1;
}

/* The design: 18x18 arrows, one 34px above the rail and one 34px below it. */
.locations__rail-btn {
	position: absolute;
	left: 50%;
	z-index: 3;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 18px;
	height: 18px;
	color: var(--c-ink);
	transform: translateX(-50%);
	transition: opacity var(--transition);
}

.locations__rail-btn svg {
	width: 18px;
	height: 18px;
}

.locations__rail-btn--prev {
	top: -34px;
}

/*
 * The design puts this one at -10px, overhanging the rail's foot so the glyph
 * starts level with the bottom of the last thumb's picture — its 18px box then
 * covers the rail's last 8px. That reads clean in the file, where the bottom
 * thumb is an inactive faded one, but on the site the bottom thumb takes the
 * active rule at the end of the rail and the arrow sat across it. Dropped by 8
 * so the box lands flush under the rail instead; everything else in the rail
 * is the design's own geometry.
 */
.locations__rail-btn--next {
	bottom: -32px;
}

.locations__rail-btn:hover {
	opacity: 0.6;
}

.locations__rail-btn.swiper-button-disabled {
	opacity: 0.2;
	cursor: default;
}

/* --- mobile: the same parts stacked --- */

@media (max-width: 1024px) {
	.locations__body {
		flex-direction: column;
		gap: 24px;
	}

	.locations__detail {
		flex: 0 0 auto;
		width: calc(100% + 16px);
		/* keeps the bracket clip room off the gutter */
	}

	.locations__media {
		flex-direction: column;
		gap: 32px;
		width: 100%;
	}

	.locations__feature {
		height: 264px;
	}

	/* The rail becomes a horizontal row of 100x80 boxes. */
	.locations__rail {
		flex: 0 0 auto;
		width: 100%;
	}

	.locations__thumbs {
		max-height: none;
	}

	.locations__thumbs .swiper-wrapper {
		flex-direction: row;
	}

	.locations__thumb {
		width: 100px;
	}

	.locations__thumbs::after,
	.locations__rail-btn {
		display: none;
	}

	/* The band opens 24 below the head here, not 64. */
	.locations__inner {
		gap: 24px;
	}

	/* And the detail column's own padding drops to 24. */
	.location {
		padding-block: 24px;
	}
}

/* ======================================================== 9. INSIGHTS ===
   the design (1440x945), mobile (390x834).

   Measured frames:
     section   padding 80/80, gap 64        (mobile 24/56, gap 48)
     head      1062 wide, centred, gap 32   (mobile full width, left, gap 24)
     body      cards -> CTA gap 48          (mobile 40)
     card      411 wide                      (mobile 316, carousel)
     image     248 tall at both sizes
   ======================================================================= */

.insights {
	position: relative;
	overflow: hidden;
	/* The design: the band's ground is a vertical white wash, 6% to nothing. */
	background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 100%);
}

.insights__inner {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 64px;
	align-items: center;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 80px var(--gutter);
}

.insights__head {
	max-width: 1062px;
}

/*
 * The cards and the CTA share a wrapper because the section's two gaps are
 * different: 64 from the heading to the cards, 48 from the cards to the CTA.
 */
.insights__body {
	display: flex;
	flex-direction: column;
	gap: 48px;
	align-items: center;
	width: 100%;
}

/*
 * Desktop lays the three cards out as a grid inside the Swiper wrapper; the
 * carousel only takes over below 1024px, where the wrapper reverts to Swiper's
 * own flex row. Overflow is visible here so a focus ring is never clipped.
 */
.insights__cards {
	width: 100%;
	min-width: 0;
	overflow: visible;
}

.insights__cards .swiper-wrapper {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 24px;
	width: 100%;
}

/* The generic grid still serves the blog index and archives. */
.card-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 24px;
	width: 100%;
}

/*
 * the design: a solid #19181B card — NOT a translucent white panel —
 * holding one padded text block above a full-bleed 248px photograph.
 */
/*
 * The whole card is the click target. The title's own link is stretched across
 * the card with a pseudo-element rather than wrapping everything in a second
 * anchor — one link in the DOM, the title as its accessible name, and the
 * photograph's hover still belongs to the card.
 */
.post-card {
	position: relative;
	display: flex;
	flex-direction: column;
	height: auto;
	background-color: var(--title);
	overflow: hidden;
}

.post-card__title a::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
}

/*
 * the design: padding 32/24/16 with 48px between the copy and the meta.
 *
 * flex: 1 takes up whatever height the row adds. The grid stretches every
 * card to the tallest in its row, and a card with a one-line title or a short
 * excerpt is shorter than that — so without this the slack collected UNDER
 * the photograph, lifting that card's date and image above its neighbours'.
 * Grown, the text block absorbs it, the meta's margin-top: auto drops the
 * date to its foot and the photograph sits on the card's bottom edge: dates
 * and photographs line up across the row whatever length the copy runs.
 */
.post-card__text {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	gap: 48px;
	padding: 32px 24px 16px;
}

/* the design: the title sits 19px above the excerpt. */
.post-card__body {
	display: flex;
	flex-direction: column;
	gap: 19px;
}

/*
 * Both text frames are fixed-height in the design, so each line box is
 * capped at two lines. That is what keeps the three cards equal height and
 * their photographs on one line.
 */
.post-card__title,
.post-card__excerpt {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
}

/*
 * `display: block`, NOT the -webkit-box line clamp this shared with the
 * excerpt. The two cannot coexist: inside a -webkit-box the trim leaves the
 * descender in, so a two-line title measured 49.3 against the design's 43
 * — and, oddly, a CLAMPED title measured 43.9, so the box was
 * only the right height when the text overflowed.
 *
 * max-height keeps the safety net the clamp provided. 2lh is taller than two
 * TRIMMED lines (52.8 against 43.9), so it never touches a title the design
 * draws; it only stops an over-long one from dragging the whole row taller.
 */
.post-card__title {
	display: block;
	max-height: 2lh;
	overflow: hidden;
	font-size: var(--fs-h4);
	line-height: var(--lh-tight);
	text-transform: uppercase;
}

.post-card__title a {
	transition: color var(--transition);
}

.post-card__title a:hover {
	color: var(--c-accent);
}

.post-card__excerpt {
	font-size: var(--fs-small);
	font-weight: 300;
	line-height: var(--lh-body);
	opacity: 0.8;
}

/* the design: date left, reading time right, both at full strength. */
.post-card__meta {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	margin-top: auto;
	font-size: var(--fs-small);
	font-weight: 300;
	line-height: var(--lh-body);
}

/* the design: 248px tall at both breakpoints, not a ratio. */
.post-card__media {
	height: 248px;
	overflow: hidden;
}

.post-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * ---- the house card zoom -------------------------------------------------
 *
 * Every card that shows a photograph lifts it slightly under the pointer. The
 * blog card set this behaviour and the project cards and the wide featured
 * tile now follow it, so a reader gets the same answer from every tile on the
 * site rather than only from the two that happened to have it.
 *
 * The CARD is the trigger, not the image, so the whole tile responds — its
 * copy and its button included — and :focus-within is there so a keyboard
 * reader tabbing onto the card's link sees the same thing a pointer does.
 *
 * The media boxes already clip; each declares its own `overflow: hidden`
 * alongside the height the design gives it, so that is left where it is.
 *
 * --card-zoom carries the amount. 1.04 is the house value. The solution card
 * overrides it below with 1.0618, which is not a house value but a measured
 * one: its the design component ships a hover variant whose photograph
 * is exactly 1.0618x the resting one.
 */
.post-card__media img,
.project-card__media img,
.project-feature__media img {
	transition: transform var(--transition-slow);
}

.post-card:hover .post-card__media img,
.post-card:focus-within .post-card__media img,
.project-card:hover .project-card__media img,
.project-card:focus-within .project-card__media img,
.project-feature:hover .project-feature__media img,
.project-feature:focus-within .project-feature__media img {
	transform: scale(var(--card-zoom, 1.04));
}

@media (max-width: 1024px) {
	.card-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	/*
	 * Mobile frame: the heading and CTA move to the left edge and
	 * the cards become a 316px carousel with the next card peeking.
	 */
	.insights__inner {
		gap: 48px;
		align-items: flex-start;
		padding-block: var(--section-py);
		padding-inline-end: 0;
	}

	.insights__head.section-head--center {
		align-items: flex-start;
		text-align: left;
	}

	/*
	 * The heading carries the desktop frame's hard break ("...Smarter |
	 * Building..."). The mobile frame wraps the same words naturally over
	 * three lines, so the authored break is dropped rather than adding a
	 * fourth short line.
	 */
	.insights__head .section-head__title br {
		display: none;
	}

	.insights__body {
		gap: 40px;
		align-items: flex-start;
	}

	.insights__cards {
		overflow: hidden;
	}

	.insights__cards .swiper-wrapper {
		display: flex;
		gap: 0;
	}

	.insights__cards .post-card {
		width: 316px;
		max-width: 82vw;
	}

	/* Mobile card: padding 24/20/12 — the mobile mode of the same tokens. */
	.post-card__text {
		padding: 24px 20px 12px;
	}
}

@media (max-width: 700px) {
	.card-grid {
		grid-template-columns: 1fr;
	}
}

/* ======================================================== 10. CONTACT ===
   the design (1440x705), mobile (390x1074).
   ======================================================================= */

.contact {
	position: relative;
	overflow: hidden;
	background-color: var(--c-bg);
}

.contact__map {
	position: absolute;
	inset: 0;
	overflow: hidden;
}

/*
 * The map is NOT a cover fill. The design places it as a fixed crop starting 22.95%
 * in from the left and running past the right edge, which is what puts the
 * pinned street on the right-hand side of the band; anything else moves the
 * pin off its location.
 */
.contact__map img {
	position: absolute;
	top: 0;
	left: 22.95%;
	width: 77.06%;
	max-width: none;
	height: 100.28%;
	/*
	 * cover, so the photograph fills that box at its own proportions instead
	 * of being stretched into them. The box is 1109.7x707 — 1.57 — where the
	 * map file is 2450x1468, or 1.67, and with the default `fill` the browser
	 * squeezed the difference out of its width. The design's own layer is a
	 * pre-cropped export, so it needs no fit; a live image does.
	 */
	object-fit: cover;
	object-position: center;
}

/*
 * The <picture> only exists to carry the mobile <source>; leaving it in the box
 * tree would give the absolutely-positioned <img> a second containing block to
 * be measured against.
 */
.contact__map picture {
	display: contents;
}

/*
 * The design's three fills, in the order they are stacked: #19181B across the left
 * to 40%, up from the bottom from 60%, and down from the top to 40%. The rule
 * this replaces had a fourth gradient on the right edge that the design does
 * not have, and nothing along the bottom.
 */
.contact__map::after {
	content: "";
	position: absolute;
	inset: 0;
	/* Must not swallow clicks — the live map underneath is interactive. */
	pointer-events: none;
	background:
		linear-gradient(90deg, var(--title) 15%, rgba(25, 24, 27, 0) 40%),
		linear-gradient(180deg, rgba(25, 24, 27, 0) 60%, var(--title) 100%),
		linear-gradient(180deg, var(--title) 0%, rgba(25, 24, 27, 0) 40%);
}

/*
 * Live map. Setting a Google Maps link in Site Settings replaces the still
 * image with an embed in the same box; everything over it — the three
 * gradients, the card — is unchanged, so the band keeps its composition.
 *
 * The embed fills the box rather than taking the still image's 22.95% crop:
 * Google centres the frame on the location itself, so an offset would push the
 * pin off-centre instead of onto the design's spot.
 */
.contact__map-frame {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
}

/*
 * Google's embed cannot be given a dark style without the Maps JavaScript API
 * and a billing key, so the band's dark treatment is applied in CSS. The
 * inversion is paired with a 180deg hue rotation, which is what carries the
 * hues back round — without it the red marker would come out cyan.
 */
.contact__map--live.is-dark .contact__map-frame {
	filter: invert(0.92) hue-rotate(180deg) saturate(0.6) brightness(0.95);
}

/*
 * Desktop's marker is part of the map bitmap, so the overlay is off here and
 * the mobile query below turns it on over the pin-free mobile crop.
 */
.contact__pin {
	display: none;
}

.contact__inner {
	position: relative;
	max-width: calc(var(--container-max) + var(--gutter) * 2);
	margin-inline: auto;
	padding: 80px var(--gutter);
	/* The column spans the whole band, so it must not shadow the live map. */
	pointer-events: none;
}

.contact__card {
	pointer-events: auto;
}

/* the design: 411 wide, 80% white over a 3px backdrop blur, padding 24. */
.contact__card {
	display: flex;
	flex-direction: column;
	gap: 40px;
	align-items: flex-start;
	width: 411px;
	max-width: 100%;
	padding: 24px;
	background-color: rgba(255, 255, 255, 0.8);
	backdrop-filter: blur(3px);
	-webkit-backdrop-filter: blur(3px);
	color: var(--c-ink-dark);
}

/*
 * the design — the heading block and the form are 24px apart, and it is
 * that PAIR the card's 40px gap separates from the phone row. Flattening the
 * three into one 40px flow spaced the heading off the form by 40 as well.
 */
.contact__top {
	display: flex;
	flex-direction: column;
	gap: 24px;
	align-items: flex-start;
	width: 100%;
}

.contact__head {
	display: flex;
	flex-direction: column;
	gap: 16px;
	align-items: flex-start;
	width: 100%;
}

/*
 * the design: 91x42. The design composites it with `multiply` purely to
 * knock out the white rectangle its placed bitmap sits on; our logo already
 * has a transparent ground, so the blend is not carried over — and it must
 * not be, because the card's backdrop-filter isolates the blend group, which
 * makes a multiplied child composite against an empty backdrop and vanish.
 */
.contact__logo {
	width: 91px;
	height: 42px;
	object-fit: contain;
	object-position: left center;
}

/* The design: a flat 24px at both breakpoints, so it does not track --fs-h4. */
.contact__title {
	color: var(--c-ink-dark);
	font-size: 24px;
	line-height: var(--lh-tight);
}

.bbqld-form {
	width: 100%;
}

.contact__notice {
	font-size: var(--fs-small);
	line-height: var(--lh-body);
	opacity: 0.7;
}

/* the design: the icon and its label sit 4px apart, 8px from the number. */
.contact__call {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	width: 100%;
	color: var(--c-ink-dark);
	font-size: var(--fs-small);
	font-weight: 300;
	line-height: var(--lh-body);
}

.contact__call-prompt {
	display: inline-flex;
	align-items: center;
	gap: 4px;
}

.contact__call-icon {
	display: inline-flex;
	flex: 0 0 18px;
	width: 18px;
	height: 18px;
	color: var(--c-accent);
}

.contact__call-icon svg {
	width: 100%;
	height: 100%;
}

/* The design: the number is the heading face at 16px, underlined — not body 500. */
.contact__call-number {
	font-family: var(--font-heading);
	font-size: var(--fs-h6);
	font-weight: 400;
	line-height: var(--lh-tight);
	text-transform: uppercase;
	text-decoration: underline;
	transition: color var(--transition);
}

.contact__call-number:hover {
	color: var(--c-accent);
}

/* --- mobile: a full-width card over a map scaled up and pushed left --- */

@media (max-width: 768px) {

	/*
	 * The mobile frame (390x1074) is a FIXED height, not hug: the map runs on
	 * below the card so the pin sits on open ground. Expressed against the
	 * viewport width — 1074/390 — so the map band scales with the device the
	 * same way the map crop itself does.
	 */
	.contact {
		min-height: 275.38vw;
	}

	.contact__inner {
		padding-block: var(--section-py);
	}

	/*
	 * The card's own gap is 32 here, not the 40 it takes on desktop — the
	 * design drops Gap 3 a step at this breakpoint, which is what pulls the
	 * phone row back up under the button.
	 */
	.contact__card {
		width: 100%;
		gap: 32px;
	}

	/*
	 * The design's mobile crop is a 654% blow-up hung off the left edge, but
	 * the slice that blow-up leaves on screen is only 374x1031 of the source —
	 * so it is served pre-cropped, through the <picture> source, rather than
	 * re-derived here from the full map. That leaves nothing for this rule to
	 * offset: the file already is the crop, at the band's own 0.363 aspect,
	 * and cover simply seats it.
	 */
	.contact__map img {
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: center;
	}

	/*
	 * the design (390x1074): a 32px marker at 139,760. Held in per-cent of the
	 * band rather than in pixels, because the band is itself sized from the
	 * viewport width — so the pin keeps its spot on the map at every width
	 * instead of drifting off it.
	 */
	.contact__pin {
		display: block;
		position: absolute;
		top: 70.7635%;
		left: 35.641%;
		width: 8.2051%;
		aspect-ratio: 1;
		/* The live map underneath stays clickable where it shows through. */
		pointer-events: none;
	}

	.contact__pin svg {
		display: block;
		width: 100%;
		height: 100%;
	}

	/* The mobile frame drops the horizontal fill, keeping only top and bottom. */
	.contact__map::after {
		background:
			linear-gradient(180deg, rgba(25, 24, 27, 0) 60%, var(--title) 100%),
			linear-gradient(180deg, var(--title) 0%, rgba(25, 24, 27, 0) 40%);
	}
}
