/*
 * Homepage components.
 *
 * Loaded only on the front page. Matches the current site's visual language — 100px
 * pill buttons, light-weight centred section headings, greyscale photography with a
 * dark scrim and white text, alternating white and #f2f2f2 bands.
 */

.centred { text-align: center; max-width: none; margin-block-start: var(--space-10); }
.section--tight { padding-block: var(--space-10); }
.section--band { padding-block-end: 0; }
.section--band > .wrap { margin-block-end: clamp(var(--space-8), 4vw, var(--space-16)); }

/* Hero ------------------------------------------------------------------ */
/*
 * Exact values from reference/live/css/46_post-21.css, element d95bbde:
 *   --min-height: 71vh
 *   background: hero-image-home-1.jpg, center center / cover, no-repeat
 *   ::before overlay: linear-gradient(90deg, #000 0%, #00000000 64%)
 *   text column (93af421): --width: 55%
 *   h1 (741cd712): 4.5rem / 300 / 1em, text-align start
 *   intro (7568e259): max-width 662px, margin 20px 0
 *
 * The photograph is NOT greyscaled — the left-hand darkness is the horizontal
 * gradient, which is why an earlier pass wrongly read the whole image as mono.
 */

.hero {
	position: relative;
	display: flex;
	/*
	 * `safe` matters here. On a short window the 78vh ceiling and the height floor can leave
	 * less room than the copy needs, and plain centring resolves that by overflowing equally
	 * at both ends, which pushed the heading up underneath the floated navigation. `safe`
	 * gives up the centring in that case and aligns to the start instead, so the copy always
	 * begins below the chrome and any shortfall is taken off the bottom where the wash is.
	 */
	align-items: safe center;
	/*
	 * Live sets a fixed 639px, not a viewport fraction — 71vh happened to agree on a 900px
	 * window and drifted everywhere else.
	 *
	 * A fixed height is wrong once the photograph carries an argument. The box ratio changes
	 * with every window width while the image stays at 2.8:1, so `cover` crops whichever
	 * axis is spare. On a wide monitor the box became wider than the image and the crop took
	 * the top and bottom away, which on the current hero removes the upper floor and the
	 * racks and leaves the middle band. That is the whole point of the picture gone.
	 *
	 * So the ratio is pinned to the artwork, exactly as the solution heroes do it. The floor
	 * keeps it from collapsing on a narrow window and the ceiling stops it eating a large
	 * screen; between them the image is never cropped vertically.
	 */
	width: 100%;
	aspect-ratio: 14 / 5;
	min-height: 30rem;
	/*
	 * One ceiling, and it has to be the viewport-relative one.
	 *
	 * There were two. The absolute 57rem was meant to stop the band growing without limit on
	 * a wide monitor, but it is the thing that reintroduced the crop it was sitting next to:
	 * 912px of height stops matching the artwork's 2.817:1 as soon as the window passes
	 * about 2570 wide, the box turns wider than the picture, and `cover` answers that by
	 * taking the difference off the top and the bottom. On an ultrawide that removed the
	 * ceiling and the sky and left the wall running off the top edge.
	 *
	 * 78vh cannot do that, because it grows with the screen: a window wide enough to need a
	 * tall hero is normally tall enough to be given one, and the ratio pin holds. What is
	 * left is a 21:9 panel, where 78vh still bites and costs about 7% off the top and
	 * bottom. Worth knowing, and far short of the 19% the fixed cap was taking.
	 *
	 * 78 rather than 100 on purpose. A hero that fills the viewport gives a reader no reason
	 * to scroll, because nothing below it is visible. Leaving roughly a fifth of the screen
	 * for the next section to show through is the signal that there is more page.
	 */
	max-height: 78vh;
	overflow: hidden;
	background-color: #e9edf0;
	/*
	 * Three layers, front to back: the wash that carries the photograph into the ticker, the
	 * scrim that holds the copy's contrast, then the photograph. These were pseudo-elements
	 * until the heading needed to blend with the wall behind it. A pseudo-element paints in
	 * the positioned layer, so the copy had to be given a z-index to stay above it, and that
	 * z-index made the copy an isolated group with nothing behind it to blend against.
	 * As background layers they sit under every child, and the heading can see the wall.
	 */
	/*
	 * Only the photograph is a background layer now. The three gradients moved to ::after so
	 * the video can sit between them and the still, which a background layer can never do:
	 * backgrounds always paint below every child, video included.
	 */
	background-image: var(--hero-image);
	background-position: center center;
	background-repeat: no-repeat;
	background-size: cover;
}


.hero__body {
	position: relative;
	display: flex;
	/*
	 * The live site centres this row, which at wider windows walks the copy onto the
	 * subject's face. Pinned left instead: the photograph reads left-to-right, so the
	 * text belongs in the dark side of the gradient and the people stay clear of it.
	 */
	justify-content: flex-start;
	/*
	 * No width here: this element is also .wrap, and setting width:100% overrode it, so the
	 * heading ran to the viewport edge while the logo and phone number stayed in the
	 * content column. Live puts the hero copy on the same left edge as the chrome.
	 */
	/*
	 * Dark ink, not white. The photograph is a lit interior whose left third is a pale
	 * concrete wall, measured at 169 mean luma. White type on it runs at 2.3:1 and is
	 * unreadable; #282b2c runs between 5.6 and 10:1 across the same area.
	 */
	color: var(--colour-heading);
}

.hero__inner {
	/*
	 * 55% of the content column and nothing more: the column already caps at 1360, so the
	 * measure tops out at 748 on its own. An added max-width clipped it to 656 and pulled
	 * the copy in from where live sits.
	 */
	width: 55%;
}

.hero__heading {
	max-width: 100%;
	margin: 0;
	color: var(--colour-heading);
	font-size: var(--text-h1);
	font-weight: var(--weight-light);
	line-height: 1;
	text-align: start;
}

.hero__intro {
	max-width: 662px;
	margin: 20px 0;
	/*
	 * The heading's ink, not the body grey. #404040 over the shadowed near end of the wall
	 * measured 3.66:1, under the 4.5 body copy needs; #282b2c on the same pixels is 5.14:1.
	 * Buying that back by strengthening the veil instead would have cost the stone its
	 * colour across the whole left of the frame to fix one paragraph.
	 */
	color: var(--colour-heading);
	font-size: var(--text-body);
	line-height: 1.5;
}

@media (max-width: 60rem) {
	.hero__inner { width: 100%; }
	.hero__heading { font-size: 3.5rem; }   /* the live mobile override */
}

/* Article ticker ------------------------------------------------------- */
/*
 * Scrolls right to left, continuously. Two copies of the list sit in the track and the
 * animation shifts it by 50% — at which point copy two occupies copy one's starting
 * position, so the restart is invisible.
 *
 * Speed is set by distance rather than a fixed duration, so adding a fourth headline
 * makes the strip longer, not faster.
 */

.ticker {
	background: var(--colour-blue);
	color: var(--colour-on-dark);
	overflow: hidden;
}

.ticker__track {
	display: flex;
	width: max-content;
	animation: ticker-scroll 44s linear infinite;
}

.ticker__list {
	display: flex;
	align-items: center;
	gap: clamp(3rem, 6vw, 6rem);   /* even spacing between headlines */
	margin: 0;
	/*
	 * The band measured 67px, which is a lot of full-strength blue for six headlines. Halved
	 * to roughly 34 by taking it out of the padding, not the type: the 24px line box is
	 * untouched and the headlines are the size they always were.
	 *
	 * The padding is deliberately uneven, 7 above and 3 below, which seats the headlines
	 * slightly low in the band. Centred in a strip this shallow they read as floating, and the
	 * hero already dissolves into blue from above, so the space above the type is doing work
	 * the space below it is not.
	 */
	padding: 7px 0 3px;
	padding-inline-end: clamp(3rem, 6vw, 6rem);
	max-width: none;
	list-style: none;
	white-space: nowrap;
}

.ticker__list a {
	color: inherit;
	font-size: var(--text-accent);
}

@keyframes ticker-scroll {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(-50%, 0, 0); }
}

/* Let people stop it to read or click a headline. */
.ticker:hover .ticker__track,
.ticker:focus-within .ticker__track { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
	.ticker__track { animation: none; }
	.ticker__list:nth-child(2) { display: none; }
	.ticker { overflow-x: auto; }
}

/* Client / partner name strip ------------------------------------------- */

.clients {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-6) var(--space-12);
	align-items: center;
	justify-content: center;
	margin: 0;
	padding: 0;
	max-width: var(--wrap-max);
	list-style: none;
}

.clients__item {
	color: var(--colour-body);
	font-size: var(--text-card-h);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.04em;
	opacity: 0.55;
}

/* Three pillars --------------------------------------------------------- */
/*
 * One continuous photograph spans all three panels — not three separate images, which
 * is what the first pass assumed. Panels are transparent with hairline dividers, and
 * the photo stays in colour (only the industries and Telstra blocks are greyscaled).
 */

.pillars {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
	background-image: linear-gradient(rgb(0 0 0 / 0.34), rgb(0 0 0 / 0.34)), var(--band);
	background-size: cover;
	background-position: center;
	overflow: hidden;
}

@media (min-width: 55rem) {
	.pillars { grid-template-columns: repeat(3, 1fr); }
	.pillar + .pillar { border-inline-start: 1px solid rgb(255 255 255 / 0.4); }
}

.pillar {
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	min-height: 515px;
	padding: var(--space-10);
	color: var(--colour-on-dark);
}

.pillar__icon {
	position: absolute;
	width: 70px;    /* measured */
	height: 70px;
	color: var(--colour-on-dark);
}

.pillar { position: relative; }
.pillar__icon { inset-block-start: var(--space-10); inset-inline-start: var(--space-10); }

.pillar__heading { color: var(--colour-on-dark); font-size: var(--text-card-h); }
.pillar__body p { max-width: 34ch; margin-block-end: var(--space-12); }

.pillar__body a {
	color: var(--colour-on-dark);
	font-size: var(--text-accent);
	font-weight: var(--weight-semibold);
}

/* Dark service cards ---------------------------------------------------- */

.services {
	display: grid;
	gap: var(--space-10) var(--space-8);
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
}

@media (min-width: 40rem) { .services { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 65rem) { .services { grid-template-columns: repeat(4, 1fr); } }

.service__icon {
	width: 80px;    /* measured */
	height: 80px;
	margin-block-end: var(--space-5);
	color: var(--colour-on-dark);
}

.service {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
}

.service__heading { font-size: var(--text-card-h); }
.service p { flex: 1; }
.service .btn { margin-block-start: var(--space-5); }

/* Sector tabs ----------------------------------------------------------- */

.tabs__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	justify-content: center;
	margin-block-end: var(--space-12);
}

.tabs__tab {
	padding: var(--space-3) var(--space-6);
	border: 0;
	border-radius: var(--radius-pill);
	background: transparent;
	color: var(--colour-body);
	font-family: inherit;
	font-size: var(--text-accent);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	cursor: pointer;
}

.tabs__tab[aria-selected="true"] {
	background: var(--colour-blue);
	color: var(--colour-on-dark);
}

.sector {
	display: grid;
	gap: var(--space-10);
	align-items: center;
}

@media (min-width: 55rem) {
	.sector { grid-template-columns: 1fr 1.1fr; gap: var(--space-16); }
}

.sector__text h3 { font-size: var(--text-card-h); margin-block-start: var(--space-8); }
.sector__text h3:first-child { margin-block-start: 0; }

.sector__media { position: relative; }

.sector__media img {
	width: 90%;
	height: 430px;
	object-fit: cover;
	border-radius: var(--radius);
	filter: saturate(0);
	transition: filter 500ms;
}

/* Greyscale until hover, then full colour over 500ms — the site-wide treatment. */
.sector__media:hover img { filter: saturate(1); }

/*
 * The ring winds up from zero when it scrolls into view.
 *
 * A conic-gradient stop cannot normally be transitioned — the browser has no idea the
 * value is a percentage. Registering it with @property gives it a type, which makes it
 * animatable. Without this the arc would snap straight to its final angle.
 */
@property --pct-fill {
	syntax: "<percentage>";
	inherits: false;
	initial-value: 0%;
}

.stat {
	position: absolute;
	inset-inline-start: -2.5rem;
	inset-block-start: 50%;
	display: grid;
	place-content: center;
	width: 200px;
	height: 200px;
	margin: 0;
	max-width: none;
	translate: 0 -50%;
	border-radius: 50%;
	background: conic-gradient(
		var(--colour-blue-bright) 0 var(--pct-fill),
		#eef1f2 var(--pct-fill) 100%
	);
	transition: --pct-fill 1600ms cubic-bezier(0.22, 0.61, 0.36, 1);
	text-align: center;
}

/* Set by the counter script once the panel is on screen. */
.stat.is-counting { --pct-fill: var(--pct); }

.stat::before {
	content: "";
	position: absolute;
	inset: 34px;
	border-radius: 50%;
	background: var(--colour-surface);
}

.stat__value {
	position: relative;
	color: var(--colour-blue);
	font-size: 2.5rem;
	font-weight: var(--weight-light);
	line-height: 1;
	font-variant-numeric: tabular-nums;   /* stops the number jittering as it counts */
}

@media (prefers-reduced-motion: reduce) {
	.stat { transition: none; }
}

/* Testimonial ----------------------------------------------------------- */

.testimonial {
	display: grid;
	gap: 0;
	margin: 0;
	background: var(--colour-surface-grey);
	border-radius: 20px;
	overflow: hidden;
}

@media (min-width: 55rem) {
	.testimonial { grid-template-columns: 1fr 1.15fr; }
}

.testimonial__media { display: grid; }
.testimonial__image { width: 100%; height: 100%; object-fit: cover; }

.testimonial__body { padding: clamp(var(--space-8), 4vw, var(--space-16)); }

.testimonial blockquote {
	margin: 0 0 var(--space-6);
	font-size: var(--text-card-h);
	font-weight: var(--weight-light);
	line-height: 1.45;
}

.testimonial blockquote p { max-width: none; color: var(--colour-heading); }
.testimonial figcaption { margin-block-end: var(--space-6); font-size: var(--text-accent); }

/* Calculator cards ------------------------------------------------------ */

.calculators {
	display: grid;
	gap: 0;
	background-image: var(--band);
	background-size: cover;
	background-position: center;
	overflow: hidden;
	/*
	 * Full-bleed by structure: the list is a direct child of the section, outside the
	 * content wrapper. A 100vw approach overflows by the scrollbar width and shifts the
	 * band off-centre, which is exactly what it did.
	 */
	width: 100%;
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
}

@media (min-width: 55rem) { .calculators { grid-template-columns: repeat(3, 1fr); } }

.calculator {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	min-height: 450px;
	padding: var(--space-10) var(--space-12);
	background: rgb(0 0 0 / 0.6);
	color: var(--colour-on-dark-soft);
}

@media (min-width: 55rem) {
	.calculator + .calculator { border-inline-start: 1px solid rgb(255 255 255 / 0.3); }
}

.calculator__heading { color: var(--colour-on-dark); font-size: var(--text-card-h); }
.calculator__body a { color: var(--colour-on-dark); font-weight: var(--weight-semibold); }

/* How we make a difference --------------------------------------------- */

.difference {
	display: grid;
	gap: var(--space-12);
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
	text-align: center;
}

@media (min-width: 55rem) { .difference { grid-template-columns: repeat(3, 1fr); } }

.difference__item h3 { font-size: var(--text-card-h); }
.difference__item p { margin-inline: auto; max-width: 34ch; }

.difference__icon {
	width: 80px;    /* measured */
	height: 80px;
	margin: 0 auto var(--space-5);
	color: var(--colour-blue);
}

/* Industry cards ------------------------------------------------------- */

.industries {
	display: grid;
	gap: var(--space-6);
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
}

@media (min-width: 45rem) { .industries { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 65rem) { .industries { grid-template-columns: repeat(3, 1fr); } }

.industry {
	position: relative;
	display: grid;
	align-items: end;
	min-height: 400px;
	overflow: hidden;
	isolation: isolate;
	border-radius: var(--radius-card);
	background: var(--colour-surface-dark);
}

.industry__image {
	position: absolute;
	inset: 0;
	z-index: -2;
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: grayscale(100%);
	transition: filter 500ms;
}

.industry:hover .industry__image { filter: none; }

.industry::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(to top, rgb(0 0 0 / 0.88) 5%, rgb(0 0 0 / 0.3) 65%);
}

.industry__body { padding: var(--space-6); color: var(--colour-on-dark-soft); }
.industry__heading { color: var(--colour-on-dark); font-size: var(--text-card-h); }
.industry__body p { font-size: var(--text-accent); }

/* Telstra -------------------------------------------------------------- */
/*
 * Measured from the live DOM — see reference/live-homepage-spec.md. The mosaic is not a
 * uniform grid: four blocks of different sizes with 20px gaps, block B bottom-aligned in
 * the first row (90px below block A), and an absolutely-positioned badge overlapping
 * blocks B and D.
 *
 * The gaps are expressed as grid columns and rows in `fr`, which reproduces the exact
 * proportions at any width — the two rows have different column splits, so a single
 * two-column grid cannot describe it.
 */

.telstra {
	display: grid;
	gap: var(--space-12);
	align-items: center;
}

@media (min-width: 60rem) {
	/* The 53fr column is the gutter; a grid gap here would double-count it. */
	.telstra { grid-template-columns: 559fr 53fr 553fr; gap: 0; }
	.telstra__mosaic { grid-column: 1; }
	.telstra__body { grid-column: 3; }
}

.telstra__mosaic {
	position: relative;
	display: grid;
	/* A spans 1–3 (360), B is col 5 (179); C is col 1 (196), D spans 3–5 (343). */
	grid-template-columns: 196fr 20fr 144fr 20fr 179fr;
	grid-template-rows: 240fr 20fr 229fr;
	aspect-ratio: 559 / 489;
}

.telstra__block {
	margin: 0;
	overflow: hidden;
	border-radius: var(--radius);
}

.telstra__block img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: grayscale(100%);
	transition: filter 500ms;
}

.telstra__block:hover img { filter: none; }

.telstra__block--a { grid-column: 1 / 4; grid-row: 1; }

.telstra__block--b {
	grid-column: 5;
	grid-row: 1;
	align-self: end;
	aspect-ratio: 179 / 150;
}

.telstra__block--c {
	grid-column: 1;
	grid-row: 3;
	align-self: start;
	aspect-ratio: 196 / 150;
	background: var(--colour-surface-grey);
}

.telstra__block--d { grid-column: 3 / 6; grid-row: 3; }

/* The van and the Telstra logo keep their colour. */
.telstra__block--b img { filter: none; }

.telstra__block--c img {
	object-fit: contain;
	padding: var(--space-5);
	filter: none;
}

/*
 * The badge hangs from the top edge of the lower-right image like a bookmark. Measured
 * live: 80x110 anchored 6px above that block's top, 71% across it. Positioning it against
 * the block rather than against the whole mosaic keeps it there at every width — an
 * earlier version used percentages of the mosaic and drifted down as the layout narrowed.
 */
.telstra__block--d { overflow: visible; }

.telstra__badge {
	position: absolute;
	inset-block-start: -6px;
	inset-inline-start: 71%;
	width: 80px;
	margin: 0;
	padding: var(--space-3) var(--space-2) var(--space-6);
	max-width: none;
	background: var(--colour-blue);
	clip-path: polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%);
	color: var(--colour-on-dark);
	font-size: 0.72rem;
	font-weight: var(--weight-semibold);
	line-height: 1.25;
	text-align: center;
}

@media (max-width: 60rem) {
	/* The mosaic shrinks below the content column; keep the badge in proportion. */
	.telstra__badge { width: 64px; font-size: 0.62rem; padding-block: var(--space-2) var(--space-5); }
}

.telstra__heading {
	color: var(--colour-blue);
	font-size: var(--text-h2);
}

.telstra__heading em { font-weight: var(--weight-semibold); font-style: normal; }
.telstra__intro { margin-block-end: var(--space-8); }
.telstra__tier { margin-block-end: var(--space-4); }
.telstra__growth { font-size: var(--text-accent); }
.telstra__growth-label { display: block; }

/* Closing CTA ---------------------------------------------------------- */

.cta {
	display: grid;
	gap: var(--space-12);
	align-items: center;
}

@media (min-width: 60rem) { .cta { grid-template-columns: 1fr 1fr; } }

.cta__contacts {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	margin: var(--space-8) 0 0;
	padding: 0;
	max-width: none;
	list-style: none;
}

.cta__contacts li { padding-inline-start: var(--space-4); border-inline-start: 2px solid var(--colour-blue); }
.cta__label { display: block; font-size: var(--text-accent); font-weight: var(--weight-semibold); color: var(--colour-heading); }
.cta__contacts a { text-decoration: none; }

.cta__form {
	padding: var(--space-8);
	border-radius: var(--radius-card);
	background: var(--colour-surface);
	border: 1px solid var(--colour-rule);
}

.cta__form-note { color: var(--colour-body); font-size: var(--text-accent); }

/* FAQ ------------------------------------------------------------------ */
/*
 * Blue panels that open on hover. Hover alone is not an accessible control — it does not
 * exist on touch and cannot be reached by keyboard — so the underlying <details> keeps
 * working by click and by keyboard, and :focus-within opens it too. Hover is an
 * enhancement layered on a control that already works.
 */

.faq { display: grid; gap: var(--space-3); }

.faq__item {
	border-radius: var(--radius);
	background: var(--colour-blue);
	color: var(--colour-on-dark);
	overflow: hidden;
}

/*
 * Answers slide rather than snap. <details> cannot normally animate open because it goes
 * from nothing to auto height; interpolate-size lets that be interpolated, and
 * allow-discrete carries content-visibility along with it. Browsers without support get
 * the plain instant toggle, which is what this was before.
 */
.faq__item::details-content {
	block-size: 0;
	overflow: hidden;
	transition: block-size 260ms ease, content-visibility 260ms allow-discrete;
}

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

@media (prefers-reduced-motion: reduce) {
	.faq__item::details-content { transition: none; }
}

.faq__question:hover { background: var(--colour-blue-dark); }

/* Open: the question keeps its blue bar, the answer drops onto grey. */
.faq__item[open] { background: var(--colour-surface-grey); }
.faq__item[open] .faq__question { background: var(--colour-blue); }

.faq__question {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	padding: var(--space-5) var(--space-6);
	color: var(--colour-on-dark);
	font-weight: var(--weight-semibold);
	cursor: pointer;
	list-style: none;
	transition: background-color 200ms ease;
}

.faq__question::-webkit-details-marker { display: none; }

.faq__question::after {
	content: "⌄";
	flex: none;
	color: var(--colour-on-dark);
	font-size: 1.2em;
	line-height: 1;
	transition: transform 200ms ease;
}

.faq__item[open] .faq__question::after { transform: rotate(180deg); }

.faq__answer {
	padding: var(--space-6);
	background: var(--colour-surface-grey);
	color: var(--colour-body);
}

.faq__answer p { max-width: none; color: inherit; }

/* Article cards -------------------------------------------------------- */

.articles {
	display: grid;
	gap: var(--space-8);
	margin: 0;
	padding: 0;
	max-width: none;
	list-style: none;
}

@media (min-width: 45rem) { .articles { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 65rem) { .articles { grid-template-columns: repeat(3, 1fr); } }

.article {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	border-radius: var(--radius-card);
	background: var(--colour-surface);
}

.article__media { position: relative; }

.article__media img { aspect-ratio: 3 / 2; object-fit: cover; width: 100%; }

.article__read {
	position: absolute;
	inset-block-start: var(--space-4);
	inset-inline-end: var(--space-4);
	padding: var(--space-1) var(--space-3);
	border-radius: var(--radius-pill);
	background: var(--colour-blue);
	color: var(--colour-on-dark);
	font-size: 0.72rem;
	text-transform: uppercase;
	letter-spacing: 0.05em;
}

.article__body { padding: var(--space-6); }
.article__heading { font-size: var(--text-accent); font-weight: var(--weight-semibold); }
.article__heading a { color: var(--colour-heading); text-decoration: none; }
.article__heading a:hover { color: var(--colour-blue); }
.article__body p { font-size: var(--text-accent); }

.calculator__icon {
	position: absolute;
	inset-block-start: var(--space-10);
	inset-inline-start: var(--space-12);
	color: var(--colour-on-dark);
	font-size: 48px;
}


/*
 * Narrow windows: release the ratio so the floor behaves as a floor.
 *
 * With aspect-ratio resolving against a stated width the height is definite, so below the
 * governed range min-height becomes a ceiling too and the copy is clipped rather than the
 * band growing. This is the same fix the solution heroes needed.
 */
@media (max-width: 67rem) {

	.hero {
		aspect-ratio: auto;
		block-size: auto;
		padding-block: 4rem;
	}
}

/* =============================================================================
 * The top of the page as one field
 *
 * Four arrangements were measured before this one, and the answer in the end was to have less
 * chrome rather than to treat it better.
 *
 * The strip that carried the phone number, the rotating tagline and ApplyEasy has gone, and its
 * contents moved into the header row. That took the chrome from two rows and 138px to one row
 * and 100px, which is 38px less photograph underneath it and one fewer horizontal edge. Every
 * previous attempt here was a way of making white type or dark type survive a busy picture:
 * a wash over the chrome, an opaque band, a solid white zone. None of them were as effective as
 * simply putting less on the picture.
 */
@media (min-width: 62rem) {
	.home .site-header {
		/*
		 * Fixed rather than absolute, so it floats with the scroll.
		 *
		 * Not sticky: sticky stays in flow, so it would reserve 100px above the photograph and
		 * push the hero down, losing the overlay this whole arrangement depends on. Fixed keeps
		 * the overlay and pins it. Inner pages use sticky, where the header is in flow anyway.
		 *
		 * This lives here rather than in base.css because home.css loads after it and was
		 * quietly winning: the position was set to fixed there and read back as absolute.
		 */
		position: fixed;
		inset-inline: 0;
		top: 0;
		/* Above the hero and above any navigation panel it opens. */
		z-index: 50;
		background: transparent;
	}

	.home .hero {
		/*
		 * 100 for the header and 24 of air, down from 162. The 38px utility strip above it is
		 * gone, which is 38px of photograph the copy no longer has to clear and 38px less
		 * chrome sitting on the picture.
		 */
		padding-block-start: 124px;
		/*
		 * The ratio alone gives 509px at 1440 wide, and 162 of that is now spoken for, which
		 * left the heading sitting under the navigation. The floor has to clear the chrome
		 * and the copy together.
		 */
		min-height: 37rem;
	}
}

/*
 * A scrim, not a wash.
 *
 * Raking light across the concrete wall takes the middle of the frame down to 113 luma,
 * where the heading holds at 3:1 but the standfirst falls under the 4.5:1 it needs. A
 * white veil over the left half lifts the heading from 2.99:1 to 4.39:1 and the standfirst
 * from 3.81:1 to 5.93:1, while the already-pale left edge moves by fourteen luma and reads
 * unchanged. Feathering out by 70% keeps it clear of the comms room, which is the subject.
 */

/*
 * The photograph resolves into the ticker rather than being sliced off at the floor.
 *
 * The blue strip was the only hard horizontal edge left above the fold: polished stone
 * meeting full-strength #0081c9 with nothing in between. The floor is broad and even at that
 * point, so a rising wash of the ticker's own colour reads as light on the surface instead
 * of as a cut.
 *
 * Taken in twice. The first version started at 84% of the hero and was half strength by the
 * middle of that run, which is a lot of blue on a lot of floor; the second started at 91%.
 * This one starts at 95% and the stops follow an accelerating curve rather than a straight
 * line, so almost nothing happens for the first two thirds of it and the colour arrives in the
 * last few pixels. That is 30px at 1440 and 45px at 2560, against 95 and 145 originally.
 *
 * The copy carries z-index 1, so the standfirst and the button paint over the wash.
 */

/*
 * The hero button, against a light photograph.
 *
 * The site button is #0081c9, which was right when it sat on a black frame and is wrong
 * now: it is the ticker's colour, and the lower part of the hero dissolves into exactly
 * that. A blue pill was disappearing into a blue wash. Charcoal reads at 9.3:1 against the
 * pale wall it mostly sits on and keeps its own shape where the wash begins, and the white
 * label inside it is unambiguous either way.
 */
.hero .btn {
	background: var(--colour-heading);
	color: var(--colour-on-dark);
}

.hero .btn:hover { background: var(--colour-blue); }


/*
 * The heading reads as paint on the wall rather than type in front of it.
 *
 * Multiply lets the wall's own light through the letterforms: the raking bands of shadow
 * crossing the concrete cross the heading in exactly the same places and at the same
 * strength, which is the cue the eye actually reads as "printed on". Nothing else in the
 * effect matters as much, and it is one property.
 *
 * It also earns its keep on contrast. Multiplying #282b2c into a 148 luma backdrop lands
 * the strokes near 24, which is darker than the flat ink it replaces, so the heading holds
 * up better over the middle of the frame than it did before.
 *
 * Only the heading. Signage on a wall is the line, not the paragraph, and multiplying the
 * standfirst would drag body copy around with the shadows for no gain in meaning.
 */
.hero__heading {
	mix-blend-mode: multiply;
	/*
	 * A shadow the wall could actually cast. Multiply alone tells you the letters take the
	 * room's light; this tells you they are on a surface rather than hanging in front of it.
	 * Short offset, wide blur, low alpha: the wall is matt concrete lit from a long way off,
	 * so anything crisper would read as a drop shadow on a slide.
	 */
	text-shadow: 0 2px 10px rgba(40, 43, 44, 0.16);
}



/*
 * The dissolve into the ticker.
 *
 * This carried two white layers as well until Bryce asked for them out: a wash over the chrome
 * and a scrim across the left of the frame, both there to lift dark type off the limestone. What
 * is left is the blue, which is not a scrim but the join between the photograph and the strip
 * below it.
 *
 * These sat in the hero's own background stack until a video was tried behind them. A
 * background layer always paints below every child, so carrying them there put them under the
 * video and the whole top-of-page blend disappeared. They stayed on ::after when the video
 * came back out: it is the same result either way, and it means a second attempt at the video
 * is a markup change rather than an unpick of this.
 *
 * The copy needs its explicit z-index because ::after counts as the last child and would
 * otherwise paint over the heading.
 */

.hero::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background-image:
		/*
		 * The chrome wash, reinstated. It came out when the artwork changed and the header went
		 * to one row, and the row still sits on limestone: the wordmark reads 1.93:1 bare and the
		 * icon links are a mid blue with the same problem. 0.84 holding through the header's own
		 * 100px and released by 215px, so it never reaches the headline.
		 */
		linear-gradient(to bottom,
			rgba(255, 255, 255, 0.84) 0,
			rgba(255, 255, 255, 0.80) 100px,
			rgba(255, 255, 255, 0.36) 160px,
			rgba(255, 255, 255, 0) 215px);
}

.hero__body { z-index: 2; }

/*
 * Kept below the unconditional .hero rules on purpose.
 *
 * This block used to sit above them. Same specificity, so the later rule won at every
 * width and both overrides in here were dead: the heading stayed on multiply and ::after
 * kept the desktop chrome wash, which fades out by 215px and leaves nothing over the copy.
 * On a phone that put near-black type on the dark half of the photograph, unreadable.
 * A media query does not raise specificity, so source order is the whole mechanism.
 */
/*
 * Narrow viewports: the photograph becomes a backdrop.
 *
 * The artwork is 2.8:1 and the hero at 960 is nearer 2:1, so `cover` throws away 383px of
 * width and the pale wall that the copy relies on is mostly gone. Measured, only 212px of
 * the box still sits over a light enough region, which is not a headline. The copy also
 * goes full width down here, so it crosses the glass and the racks where the darkest
 * slices read 105 luma.
 *
 * Rather than fight the crop, the veil goes to just over half strength across the whole
 * frame. That puts the worst case at 7.6:1 and turns the picture into a wash, which is the
 * honest thing for it to be at a size where none of its detail survives anyway.
 */
@media (max-width: 61.99rem) {
	/*
	 * The one place a veil survives, and it is not a design preference.
	 *
	 * On the desktop hero the copy sits on a wall, so it can be left alone. Here `cover` has
	 * thrown away 383px of a 2.8:1 photograph to fit a box nearer 2:1, the pale wall the copy
	 * relied on is mostly gone, and the copy runs full width across the glass and the racks
	 * where the darkest slices read 105 luma. There is no clean ground left to put type on, so
	 * the choice is a veil or unreadable type rather than a veil or a nicer picture.
	 *
	 * It goes on ::after rather than through a variable, because the variable the rest of this
	 * used was removed with the desktop scrim and left this silently doing nothing.
	 */
	.hero::after {
		background-image:
			linear-gradient(to right,
				rgba(255, 255, 255, 0.60) 0%,
				rgba(255, 255, 255, 0.52) 70%,
				rgba(255, 255, 255, 0.46) 100%);
	}

	/* Nothing left of the wall to print on at this crop, so the heading goes back to ink. */
	.hero__heading { mix-blend-mode: normal; }
}


/*
 * The hero video.
 *
 * Paint order is stated rather than inherited: the still is the section's background, the video
 * covers it at z-index 0, ::after carries the chrome wash and the dissolve into the ticker at 1,
 * and the copy sits at 2. Without those the pseudo-element would paint over the heading, because
 * ::after counts as the last child.
 *
 * object-fit: cover with a centred position is the same transform as background-size: cover, and
 * the video and the still agree on their ratio to a tenth of a percent, so nothing shifts when
 * playback starts.
 */
.hero__video {
	position: absolute;
	inset: 0;
	z-index: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center center;
	pointer-events: none;
}

/*
 * Reduced motion removes the video rather than pausing it, leaving the still the section was
 * designed around.
 */
@media (prefers-reduced-motion: reduce) {
	.hero__video { display: none; }
}

/*
 * The copy is narrower on this artwork than it was on the last.
 *
 * Measured across the frame, the clean wall runs from 4% to 39% and carries charcoal at 5.2 to
 * 6.1:1 with no wash at all. At 40% the timber panel starts and by 44% it is the display screen,
 * where charcoal drops to 1.4:1. So the constraint is not brightness, it is a hard edge: the copy
 * has to stop before the wall does.
 */
@media (min-width: 62rem) {

	/*
	 * The third term is the one that holds on a large display.
	 *
	 * 55% and 37rem both size the copy against its own column, and the column is centred, so
	 * its left edge moves right as the window widens: (100vw - var(--wrap-max)) / 2. Past
	 * roughly 3200px that walks a 37rem measure off the limestone even though the measure
	 * itself never changed, and at 3840 the standfirst was landing at 42.5% against a wall
	 * that stops taking charcoal at 41%.
	 *
	 * Subtracting the column offset from a 38vw budget keeps the right edge on the wall at
	 * any width, without moving the left edge, so the copy still lines up with every section
	 * below it. Below about 2400px this term is wider than 37rem and does nothing at all.
	 */
	.home .hero__inner {
		width: min(
			55%,
			37rem,
			calc(38vw - (100vw - var(--wrap-max)) / 2)
		);
	}
}
