/* ============================================================================
   PAGE SHELL

   Shared furniture for the newer pages: layout, masthead, the
   button system, media frames, cards, testimonials, and the footer.
   ============================================================================ */

@layer components {

/* Answered: the old "landing sections" file overlapped this one because it had
   outlived its name. Its portfolio wall and FAQ accordion moved to 09-pages,
   .landingSection moved to 03-base, and .blurbSection / .logoSection were
   deleted — neither had a single use left in any page. */

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

.page {
	background: var(--color-surface);
	color: var(--color-text-1);
	overflow-x: hidden;
}

.wrap, .wrapNarrow, .wrapWide {
	width: 90%;
	max-width: 1180px;
	margin: 0 auto;
}
.wrapWide { max-width: 1440px; }
.wrapNarrow { max-width: 720px; }

.section {
	padding: var(--space-4xl) 0;

	/* The space scale is already fluid — every --space-* token is a clamp of
	   its own — so a band's rhythm is one token, not a second clamp wrapped
	   around it. This renders identically to the clamp(--space-3xl, 6vw, 88px)
	   it replaces: that clamp's own minimum won at every width from 360px up,
	   so the 6vw/88px middle never once applied. */
	&.tight    { padding-block: var(--space-3xl); }
	&.flush    { padding-top: 0; }
	&.flushEnd { padding-bottom: 0; }
	&.alt      { background: var(--color-surface-2); }

	/* An always-dark band, for CTAs and "the turn". color-scheme flips every
	   token inside it, so there are no per-element colour overrides to keep in
	   sync with the type classes — which is what the old version needed. */
	&.inverse {
		color-scheme: dark;
		background: var(--page-surface-contrast);
		color: var(--color-text-1);
	}

	/* A full red band. Colours come from .onBrand in 02-tokens. */
	&.accent { /* see .onBrand */ }
}

.sectionHead, .sectionHeadRow {
	margin-bottom: var(--space-l);
}
/* A stacked head — the section mark, one heading, and an optional deck under
   it. Each of the three needs its own step off the next rather than one shared
   gap, which is what every page was otherwise re-declaring for itself. */
.sectionHead {
	.indexLabel { margin-bottom: var(--space-l); }
	& > h2 { margin: 0; }
	.lede { margin: var(--space-m) 0 0; max-width: 46ch; }
}
/* Heading on the left, a link on the right */
.sectionHeadRow {
	max-width: none;
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--space-l);

	& > div { max-width: 640px; }
}

.rule {
	border: 0;
	border-top: 1px solid var(--color-rule);
	margin: 0;
}

/* --- The section mark --------------------------------------------------------
   The top tier of the two-tier micro-cap system: full ink, widest tracking,
   exactly one per section, and a full-measure rule beneath it. .labelSmall is
   the other tier — tertiary ink, everything else. Written on the markup as
   `class="label indexLabel"`, so the voice still comes from the type set and
   this only says how the mark is placed and what it is bounded by.

   Two rule weights, two jobs, and the gap between them is an order of
   magnitude rather than a shade: this one is --space-2xs of ink and says "a
   section starts here"; a divider is a 1px --color-rule hairline and separates
   list rows. At one hairline apiece, nothing read as structure.

   The rule takes the colour of the type it bounds, so it needs no per-surface
   override: on the brand field --color-text-1 resolves to solid white through
   .onBrand, and inside .inverse it follows that band's scheme.

   Never numbered — see DESIGN-EXTENDING.md, "Deliberately removed".        */
.indexLabel {
	display: block;
	width: 100%;
	color: var(--color-text-1);
	letter-spacing: var(--letter-spacing-widest);
	padding-bottom: var(--space-s);
	border-bottom: var(--space-2xs) solid var(--color-text-1);
}

/* --- Page head ---------------------------------------------------------------
   The opening band of every page that is not the home poster: one section
   mark on the full measure, the page's single .display heading in the seven
   columns, and the deck dropped to its baseline in the five.

   It clears the fixed nav itself — nothing else on a page knows the nav
   height. The pre-redesign version was .pageHero in 09-pages.css, a centred
   900px stack under a red kicker; /field-notes was the last page on it and
   both it and .splitHero are deleted. The case studies do not use this
   component but do use its composition — see .csCoverHead in
   08-case-study.css, which carries a meta list and a hero under the same
   seven-and-five grid.

     <section class="pageHead">
       <div class="wrapWide pageHeadInner">
         <span class="label indexLabel">Work</span>
         <h1 class="display">…</h1>
         <p class="lede">…</p>
       </div>
     </section>
                                                                              */
.pageHead {
	padding-block: calc(var(--nav-height) + var(--space-2xl)) var(--space-2xl);
}
.pageHeadInner {
	display: grid;
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
	column-gap: var(--space-2xl);
	row-gap: var(--space-l);
	/* The deck sits on the headline's last baseline rather than at its cap
	   height — the two are different sizes, and topping them out together only
	   looks aligned in a wireframe. */
	align-items: end;

	.indexLabel {
		grid-column: 1 / -1;
		margin-bottom: var(--space-l);
	}
	& h1 { margin: 0; max-width: 14ch; }
	.lede { margin: 0; max-width: 40ch; }

	/* Actions are optional. When a head has them they hang under the deck in
	   the five rather than under the heading in the seven — the deck is what
	   they answer, and putting them below the h1 would push the two columns
	   back out of alignment. */
	.deckStack {
		display: flex;
		flex-direction: column;
		align-items: start;
		gap: var(--space-l);

		.lede, .btnRow { margin: 0; }
	}

	@media only screen and (max-width: 860px) {
		/* minmax(0, 1fr), not 1fr. A bare fr track is minmax(auto, 1fr), so its
		   floor is the content's min-content width — one word of --step-8 type
		   wider than the phone, and the track grows to fit it, taking the deck
		   under it out to the same width. The word still overflows (that is a
		   copy problem, and the fix is a shorter word), but it no longer drags
		   the rest of the column out with it. */
		grid-template-columns: minmax(0, 1fr);
		align-items: start;
	}
}

/* --- Statement band ----------------------------------------------------------
   A positioning moment: the deck in a 7-column well, with the closing line
   hanging in the 5 it leaves empty. Shared furniture — the home page opens on
   it and /about states the practice with it. Page-independent, so a page only
   decides what goes in it and how much room the band gets.

     <section class="section statementBand">
       <div class="wrapWide statementGrid">
         <p class="lede statement">…</p>
         <p class="statementClose">…</p>
       </div>
     </section>
                                                                              */
.statementBand {
	/* One of the airiest bands wherever it lands, but one step in from the
	   page's most open. A deck at running size stranded in --space-4xl reads
	   as a gap rather than as a pause. */
	padding-block: var(--space-3xl);

	/* The deck comes down to a 7-column well rather than running the full
	   1300px measure — running text at the site measure is the hardest
	   reading on any page — and the closing line hangs in the 5 columns it
	   leaves empty. */
	.statementGrid {
		display: grid;
		grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
		gap: var(--space-l) var(--space-2xl);
		align-items: end;

		@media only screen and (max-width: 860px) {
			grid-template-columns: 1fr;
			align-items: start;
		}
	}

	/* Layout only — the voice is .lede in the markup. The measure is a touch
	   wider than .lede's own 60ch so the paragraph fills the 7-column well it
	   was given instead of leaving a ragged third of it empty. */
	.statement {
		margin: 0;
		max-width: 68ch;
		color: var(--color-text-1);
	}

	/* The closing line breaks out of the paragraph rather than running on
	   inside it — the one piece of red ink on the band, hanging in the space
	   the deck doesn't use. Set by hand rather than by a type class: it is a
	   one-off voice between .displaySmall and .lede, and it exists only here. */
	.statementClose {
		font-family: var(--font);
		font-weight: 700;
		font-size: var(--step-4);
		line-height: 1;
		letter-spacing: var(--letter-spacing-tight);
		color: var(--color-action-text);
		margin: 0;
		max-width: 20ch;
	}
}

/* --- A claim and its pair ----------------------------------------------------
   A section mark and one heading, then two — or four — peer blocks. Neither
   band carries a deck of its own: the pair is the deck. The home page's
   position and its two refusals take this shape, and so does /about's full set
   of four.

   The head itself is the shared .sectionHead above. All .claimBand adds is how
   far the pair stands off it, which is the one thing .sectionHead can't know.

   Moved here from 09-pages.css (as .homeClaim / .homePair) the day a second
   page wanted it, which is what that file's note said should happen.       */

.claimBand {
	.sectionHead { margin-bottom: var(--space-2xl); }
}

/* Two tracks, declared rather than fitted. Two blocks give a pair and four give
   a 2×2, and that count is the decision: auto-fit was putting three of /about's
   four refusals on one line with the fourth orphaned underneath, which reads as
   a mistake rather than as a grid. This is the case CLAUDE.md describes right
   after "auto-fit beats a container query for same-width repeating tracks" —
   declare the count when somebody has an opinion about it.

   Layout only: every voice and every measure comes from the type class in the
   markup (.headingMedium, .body), which is why there is not one max-width in
   here. The track is the measure.

   Deliberately not .doorSplit: those halves are routes, and a whole panel is
   the link. These are prose, and the band offers nowhere to go.           */

.prosePair {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-xl) var(--space-2xl);
	align-items: start;

	& > div > :is(h2, h3) { margin: 0; }
	& > div > .body { margin: var(--space-s) 0 0; }

	@media only screen and (max-width: 767px) {
		grid-template-columns: 1fr;
	}
}

/* --- Ruled index -------------------------------------------------------------
   A single-column numbered index at display scale, with the numerals hanging
   in the page margin. Reusable for services, process steps, principles — any
   list where the rows are peers and the order is structural rather than
   sequential. The home page's capability index and /about's beliefs are both
   this; /for-agencies lists its four engagement shapes with it.

     <ol class="capList">
       <li class="capRow">
         <span class="capNum" aria-hidden="true">01</span>
         <h2 class="displaySmall">…</h2>
         <p>…</p>
         <span class="labelSmall capProof">…</span>   <!-- optional -->
       </li>
     </ol>
                                                                              */
.capList {
	list-style: none;
	margin: 0;
	padding: 0;
}
.capRow {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	column-gap: var(--space-l);
	align-items: baseline;
	padding-block: var(--space-l);
	border-bottom: 1px solid var(--color-rule); /* divider */

	&:last-child { border-bottom: 0; }

	/* The site's one display numeral, and the only one: the case study's
	   phases are this component now rather than a second counter with its own
	   hand-set type. Tabular and fixed-width so a column of them lines up.

	   --step-6, not --step-8. It is meant to sit at the page's display size,
	   which is what .display is — and .display moved from step-8 to step-6
	   when the type scale was retuned, leaving this numeral at 140px against a
	   79px h1. If a context wants a smaller one it sets --cap-num-size rather
	   than re-declaring the voice; the case study's phases do.

	   If a page is going to number things, the numbers have to do structural
	   work — at label size in a margin they were decoration with an argument
	   attached after the fact. */
	.capNum {
		font-family: var(--font);
		font-weight: 700;
		font-size: var(--cap-num-size, var(--step-6));
		letter-spacing: var(--letter-spacing-tight);
		line-height: 0.9;
		color: var(--color-rule-strong);
		font-variant-numeric: tabular-nums;
		width: 1.8ch;
	}

	/* h2 or h3 — the row's heading level is the document's decision, not the
	   component's. The case study's phases sit inside a chapter, so they are
	   h3 there and h2 on a page that owns its index outright. */
	& :is(h2, h3) { max-width: 20ch; }
	& p {
		grid-column: 2;
		margin: var(--space-s) 0 0;
		color: var(--color-text-2);
		/* Deliberately narrow against a display-size heading: the contrast in
		   measure is what makes the row read as a headline and a note rather
		   than as two columns of copy. */
		max-width: 52ch;
	}
	.capProof {
		grid-column: 2;
		display: block;
		margin-block-start: var(--space-xs);
		color: var(--color-text-3);
	}

	@media only screen and (max-width: 767px) {
		grid-template-columns: 1fr;
		row-gap: var(--space-2xs);

		.capNum { font-size: var(--cap-num-size-mobile, var(--step-4)); }
		& p, .capProof { grid-column: 1; }
	}
}

/* --- Tags & meta ----------------------------------------------------------- */

.tagRow {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-xs);
	margin: 0;
	padding: 0;
	list-style: none;
}
/* A tag carries the same weight in the hierarchy as a label, so it is styled
   as one: .tag is part of the label-family selector in 04-typography. What is
   left here is layout. */
.tag {
	white-space: nowrap;

	&.outline {
		background: transparent;
		border: 1px solid var(--color-rule);
		padding: var(--space-3xs) var(--space-2xs);
	}
}

/* A <dl> of small labelled facts — role, year, disciplines */
.metaGrid, .csCover .csMeta {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: var(--space-l) var(--space-xl);
	margin: 0;
	padding: 0;

	& dt {
		color: var(--color-text-3);
		margin: 0 0 var(--space-xs);
	}
	& dd {
		margin: 0;
		font-size: var(--step-1);
		font-weight: 400;
		line-height: 1.5;
		color: var(--color-text-1);
	}
}

/* --- Fact lists ---------------------------------------------------------------
   Hairline rows of labelled facts — an engagement list, a discipline list, a
   vendor-readiness list. A row is a term with its note under it, divided by a
   1px rule: a divider, not structure, so it never competes with the section
   mark above it. The columns it sits in are the page's decision.

     <ul class="factList">
       <li>West Music<span>Design Lead — design system &amp; site redesign</span></li>
     </ul>
                                                                              */
.factList {
	list-style: none;
	margin: 0;
	padding: 0;

	& li {
		color: var(--color-text-1);
		padding: var(--space-s) 0;
		border-top: 1px solid var(--color-rule);

		& span {
			display: block;
			font-size: var(--step-0);
			color: var(--color-text-2);
		}

		/* A note can carry a link out to the page that owns the fact — same
		   treatment as a link in .prose, so running text reads one way
		   wherever it sits. */
		& a {
			color: var(--color-text-1);
			text-decoration-line: underline;
		}
	}
}

/* --- The two-column fact band -------------------------------------------------
   The columns a pair of .factLists sits in when a band carries two of them.
   Asymmetric on purpose — the two lists are never peers, and 7/5 says so
   where two equal columns would not. The meta-tier head above each list is
   part of the component, because both consumers set it identically.

   /for-agencies' tooling and vendor readiness, and /process' money mechanics
   and what-you-own. It lived in 09-pages.css while only one page had it.
                                                                              */
.opsSplit {
	display: grid;
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
	gap: var(--space-l) var(--space-2xl);
	align-items: start;

	& h3 {
		color: var(--color-text-3);
		margin: 0 0 var(--space-s);
	}

	@media only screen and (max-width: 860px) {
		grid-template-columns: 1fr;
	}
}

/* --- Media frames -----------------------------------------------------------
   Every image and video on the newer pages sits in a .mediaFrame. Set the
   aspect ratio with a .ratioNxN class and swap the <img src> for the real
   asset — the frame handles the rest.

   .mediaFrame AND .artifactPlate — they are not alternatives, and the
   difference is worth stating once:

     .mediaFrame    a figure. Ratio, frame, and a caption under it. Reach for
                    it whenever the picture needs a sentence beside it — which
                    on a case study is every picture, because the captions are
                    the argument.
     .artifactPlate a surface. A designed empty state for a well whose
                    photograph does not exist yet: a modular substrate, the
                    client's name set large, and what the well is a picture of.
                    It is not a figure and has no caption of its own.

   They compose — `.mediaFrameInner.artifactPlate` is a plate inside a frame,
   and the :not() below is what keeps the frame from painting over it — but
   composing them is the exception, not the default. **A frame's empty state is
   the frame itself**: an empty .mediaFrameInner already draws a surface-2 well
   at the right ratio with a red data-slot badge on it, which is quiet enough
   to appear a dozen times down one page.

   The case studies were tried both ways. Eleven plates down /work/west-music
   read well in isolation and cost the page the thing it cannot afford to lose:
   .csTurn is the one dark band on a light page, and with eleven near-black
   wells above it, it stopped being a break in the rhythm and became one more
   dark rectangle. So the long-form pages use frames, the indexes use plates,
   and the combination is there for a well that needs both a caption and the
   weight — of which there are none today.

   Reach for a plate when the well is a card in an index and there are a few of
   them. Reach for a frame whenever the picture needs a sentence beside it.  */

.mediaFrame {
	margin: 0;
	position: relative;

	/* :not(.artifactPlate) because the plate paints its own ground and its own
	   rule grid, and `background:` here is a shorthand — it would win on
	   specificity (0,2,0 against 0,1,0) *and* reset the plate's
	   background-image, which is the trap documented in DESIGN-EXTENDING.md.
	   Excluding it explicitly is clearer than a specificity fight. */
	.mediaFrameInner:not(.artifactPlate) {
		background: var(--color-surface-2);
		border: 1px solid var(--color-rule);
	}
	.mediaFrameInner {
		position: relative;
		overflow: hidden;
		line-height: 0;
	}
	& img, & video {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}
	/* The frame owns the caption's layout and colour, and nothing else. It used
	   to declare the whole voice from @layer components, which out-specified
	   any type class put on the caption in the markup — a figcaption.labelSmall
	   silently rendered at step-0 in the narrow cut instead of as a label. */
	/* A caption is spaced off the picture in its own ems rather than in a space
	   token: it is text hanging off the bottom of an image, so the gap should
	   track the caption's size, not the page's rhythm. A 10px caption under a
	   --space-m gap reads as detached; the same caption at 1em does not. */
	& figcaption {
		margin-top: 1em;
		color: var(--color-text-3);
		max-width: min(100%, 620px);

		& b, & strong { color: var(--color-text-2); }
	}

	/* A caption with no type class of its own is running text about a picture,
	   not a label — the case studies are full of them. This mirrors .bodySmall
	   and stands aside the moment the markup names a class.

	   No font-family. It set --font-narrow, which put the condensed label cut
	   on text that is read as a sentence; the caption is running text one step
	   under the body and should be in the body's own cut. */
	& figcaption:not(.label, .labelLarge, .labelSmall, .body, .bodySmall) {
		font-size: var(--step-0);
		line-height: 1.55;
	}

	/* aspect ratios */
	&.ratio21x9 .mediaFrameInner { aspect-ratio: 21 / 9; }
	&.ratio16x9 .mediaFrameInner { aspect-ratio: 16 / 9; }
	&.ratio3x2  .mediaFrameInner { aspect-ratio: 3 / 2; }
	&.ratio4x3  .mediaFrameInner { aspect-ratio: 4 / 3; }
	&.ratio1x1  .mediaFrameInner { aspect-ratio: 1 / 1; }
	&.ratio4x5  .mediaFrameInner { aspect-ratio: 4 / 5; }
	&.ratio2x3  .mediaFrameInner { aspect-ratio: 2 / 3; }
	&.ratio5x2  .mediaFrameInner { aspect-ratio: 5 / 2; }
	&.ratio9x16 .mediaFrameInner { aspect-ratio: 9 / 16; }

	&.bare .mediaFrameInner {
		border: 0;
		border-radius: 0;
		background: transparent;
	}
	&.flat .mediaFrameInner { border-radius: 0; }

	/* A "what goes here" badge. Add data-slot="Hero — component sheet" to
	   .mediaFrameInner; delete the attribute once the real asset is in.

	   The selector used to read `&[data-slot] .mediaFrameInner::after`, which
	   wanted the attribute on the <figure>. All 22 uses on the site put it on
	   .mediaFrameInner, as the line above tells them to, so the badge had
	   never once rendered and every empty well on both case studies was an
	   anonymous grey rectangle. */
	.mediaFrameInner[data-slot]::after {
		content: attr(data-slot);
		position: absolute;
		left: var(--space-s);
		top: var(--space-s);
		font-family: var(--font-narrow);
		font-size: var(--step--1);
		font-weight: 500;
		letter-spacing: var(--letter-spacing-wide);
		text-transform: uppercase;
		line-height: 1;
		color: white;
		background: var(--color-surface-brand);
		padding: var(--space-2xs) var(--space-xs);
		border-radius: 100px;
		pointer-events: none;
	}
}

/* Media layout groups. Nested .mediaFrame elements deliberately get no margin
   of their own — the grid gap spaces them (see section 09). */
.mediaPair {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: clamp(var(--space-m), 2vw, var(--space-l));

	@media only screen and (max-width: 640px) {
		grid-template-columns: 1fr;
	}
}
.mediaTrio {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: clamp(var(--space-m), 2vw, var(--space-l));

	@media only screen and (max-width: 860px) {
		grid-template-columns: 1fr;
	}
}
.mediaScroller {
	display: flex;
	gap: clamp(var(--space-m), 2vw, var(--space-l));
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-x: contain;
	padding-bottom: var(--space-m);

	&::-webkit-scrollbar { height: 6px; }
	&::-webkit-scrollbar-thumb {
		background: var(--color-rule);
		border-radius: 100px;
	}

	& > * {
		flex: 0 0 min(78vw, 340px);
		scroll-snap-align: center;
	}
	&.wide > * { flex: 0 0 min(88vw, 620px); }
}

/* --- Pull quotes & testimonials -------------------------------------------- */

.pullQuote {
	margin: 0;
	max-width: 620px;

	& blockquote, & p.quoteText {
		font-family: var(--font-serif);
		font-size: var(--step-4);
		line-height: 1.3;
		font-weight: 400;
		font-style: italic;
		letter-spacing: var(--letter-spacing-tight);
		color: var(--color-text-1);
		margin: 0;
		text-wrap: pretty;
	}
	& figcaption, .quoteAttrib {
		margin-top: var(--space-l);
		font-family: var(--font-narrow);
		font-size: var(--step-0);
		font-weight: 500;
		text-transform: uppercase;
		letter-spacing: var(--letter-spacing-wide);
		line-height: 1.6;
		color: var(--color-text-3);

		& b { display: block; color: var(--color-text-2); }
	}
	&.wide { max-width: 820px; }
}

.testimonialGrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	gap: clamp(var(--space-l), 3vw, var(--space-3xl));
}
.testimonial {
	container-type: inline-size;
	container-name: testimonial;
	border-top: 1px solid var(--color-rule-strong);
	padding-top: var(--space-l);
	margin: 0;

	& blockquote {
		font-size: var(--step-1);
		line-height: 1.5;
		font-weight: 300;
		color: var(--color-text-2);
		margin-bottom: 2em;
		font-style: italic;
	}
	& figcaption {
		display: flex;
		align-items: center;
		gap: var(--space-s);
		font-size: var(--step--1);
		line-height: 1.5;
		text-transform: uppercase;
		letter-spacing: var(--letter-spacing-loose);

		& img {
			width: 40px;
			height: 40px;
			border-radius: 50%;
			object-fit: cover;
			flex-shrink: 0;
		}
	}
}

/* --- Proof row ------------------------------------------------------------- */

.proofRow {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap: clamp(var(--space-l), 3vw, var(--space-3xl));
}
.proofItem {
	border-top: 1px solid var(--color-rule);
	padding-top: var(--space-l);

	.proofNumber {
		font-family: var(--font);
		font-size: var(--step-5);
		font-weight: 700;
		letter-spacing: var(--letter-spacing-tight);
		line-height: 1;
		color: var(--color-text-1);
		margin: 0 0 var(--space-s);
		display: block;
	}
	& h3 {
		margin: 0 0 var(--space-xs);
		color: var(--color-text-1);
	}
	& p {
		font-size: var(--step-1);
		line-height: 1.6;
		color: var(--color-text-2);
		margin: 0;
		max-width: 42ch;
	}
}

/* --- CTA band & the two doors -----------------------------------------------
   The band every page ends on. It is the same composition everywhere: the ask
   in the seven columns at display scale, the reason and the actions hanging in
   the five it leaves, under one section mark on the full measure.

   It used to be a centred stack on a narrow measure. Every page is left-aligned
   for its whole length, so centring the last screen read as a default rather
   than as a climax — which is why the home page grew its own .ctaHome. That
   fork is gone; this is the one version, and .ctaHome with it.

     <section class="section inverse ctaBand">
       <div class="wrapWide ctaBandInner">
         <span class="label indexLabel">Next step</span>
         <h2 class="display">…</h2>
         <div class="ctaBandFoot">
           <p class="lede">…</p>
           <div class="btnRow">…</div>
         </div>
       </div>
     </section>
                                                                              */

.ctaBand {
	text-align: start;

	/* Always the last section on a page, so it closes tight rather than trailing
	   off into the footer's own dark field — the footer supplies the rest. */
	padding-block: var(--space-4xl) var(--space-2xl);

	.indexLabel { margin-bottom: var(--space-l); }
	& h2 { margin: 0; max-width: 16ch; }
	.lede { margin: 0; max-width: 38ch; }
	.btnRow { margin: 0; }
}

/* The headline and its actions share one row, so the display block is never a
   200px band with its right half void. */
.ctaBandInner {
	display: grid;
	grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
	gap: var(--space-2xl);
	align-items: end;

	.indexLabel { grid-column: 1 / -1; }

	@media only screen and (max-width: 860px) {
		/* minmax(0, 1fr) for the same reason .pageHeadInner takes it: this
		   column holds a .display heading too. */
		grid-template-columns: minmax(0, 1fr);
		align-items: start;
	}
}
.ctaBandFoot {
	display: flex;
	flex-direction: column;
	align-items: start;
	gap: var(--space-l);
}

/* --- The two doors -----------------------------------------------------------
   A hard split: two panels running to the edge of the viewport, divided by one
   rule, with either half optionally on the brand field. No boxes. Two
   identical bordered cards describe a choice; this one is felt.

   Shared furniture. The home page forks brands / agencies with it, /faq sends
   cost questions to the process page and partner terms to the agency page, and
   /contact closes on process / FAQ. It lived in the home page's section of
   09-pages.css until the second page wanted it; nothing in it was ever about
   the home page.

     <section class="doorSplit weightedStart">
       <a class="doorHalf" href="…">…</a>
       <a class="doorHalf isBrand onBrand" href="…">…</a>   <!-- optional -->
     </section>

   TWO SEPARATE DECISIONS, TWO SEPARATE CLASSES. The split owns its geometry
   and .isBrand owns nothing but colour — the same split the buttons already
   make (.btnPrimary / .btnSecondary / .btnGhost are recipes, .btnSmall is a
   size) and the same one the home hero makes with .homeHero + .homePoster.
   The weighting was on .isBrand until /contact used two neutral halves and
   inherited a page margin where a gutter belonged.

     .weightedStart   6/4 — the first door carries the page's main route
     .weightedEnd     4/6 — the second one does
     (neither)        1/1

   1/1 is the base, not the recommendation: two mirrored panels of identical
   width, type size and link position describe a choice without taking any
   position on it, which is the visual signature of a template. Every use on
   the site today is weighted. See rule 8 in DESIGN-EXTENDING.md.           */
.doorSplit {
	/* The two edge values every panel is built from. Outer edges take the page
	   margin, so content lands on the site measure even though the panel is
	   full-bleed; inner edges take one gutter either side of the rule, so the
	   two halves read as one grid rather than as two panels with their own
	   padding. Which edge is which is a question about position, which is why
	   it is answered by :first-child / :last-child below and not by a
	   modifier. */
	--door-margin: max(5vw, calc((100vw - 1440px) / 2));
	--door-gutter: var(--space-2xl);

	display: grid;
	grid-template-columns: 1fr 1fr;
	border-block: 1px solid var(--color-text-1); /* structural — bounds the band */

	&.weightedStart { grid-template-columns: minmax(0, 6fr) minmax(0, 4fr); }
	&.weightedEnd   { grid-template-columns: minmax(0, 4fr) minmax(0, 6fr); }

	/* 62/38 is an editorial weighting, and it only survives while the 38 is
	   still a comfortable column. Between the grid's collapse point and about
	   1200px it isn't: the narrow half's heading breaks to four or five lines
	   against the other half's two. Through that band the split goes even —
	   the asymmetry is worth having, but not at the cost of the panel it is
	   asymmetric against. */
	@media only screen and (max-width: 1200px) {
		&.weightedStart, &.weightedEnd { grid-template-columns: 1fr 1fr; }
	}

	@media only screen and (max-width: 860px) {
		&, &.weightedStart, &.weightedEnd { grid-template-columns: 1fr; }
	}
}
.doorHalf {
	display: flex;
	flex-direction: column;
	align-items: start;
	gap: var(--space-s);
	background: var(--color-surface);
	color: var(--color-text-1);
	padding-block: var(--space-2xl);
	padding-inline: var(--door-gutter);
	border-right: 1px solid var(--color-text-1);
	transition: background-color 0.35s ease;

	/* Outer edges to the page margin, inner edges already on the gutter. The
	   trailing edge of the band is the page margin too, not a divider. */
	&:first-child { padding-inline-start: var(--door-margin); }
	&:last-child  { padding-inline-end: var(--door-margin); border-right: 0; }

	& h2 { margin: 0; max-width: 13ch; }
	& p {
		margin: 0 0 var(--space-l);
		color: var(--color-text-2);
		max-width: 42ch;
		flex: 1;
	}
	.label { color: var(--color-text-3); }
	.textLink { margin-top: auto; }

	/* Colour only. .onBrand (02-tokens) re-points the text, rule, control and
	   button tokens for a saturated field, so there is not one ink override to
	   keep in sync here — the background is the exception, because .doorHalf
	   declares its own in @layer components and that beats the tokens layer.
	   Put onBrand in the class list; a modifier on its own gets no red. */
	&.isBrand {
		background: var(--color-surface-brand);

		&:hover { background: var(--c-red-700); }
	}

	&:hover { background: var(--color-surface-2); }

	@media only screen and (max-width: 860px) {
		/* Stacked, both edges are outer edges, and the divider between the
		   panels is horizontal. The band is still bounded by .doorSplit's own
		   rules, so the last panel doesn't draw one. */
		padding-inline: var(--door-margin);
		padding-block: var(--space-2xl);
		border-right: 0;
		border-bottom: 1px solid var(--color-rule-strong);

		&:last-child { border-bottom: 0; }
	}
}

/* --- Ruled rate table --------------------------------------------------------
   Published figures as a ruled table: rules between the columns rather than
   boxes around them, and the figures in full ink. /for-agencies publishes its
   engagement rates with it and /process its starting ranges — both pages spend
   their red on a field elsewhere, and four red numerals would drain it. The
   four bordered .priceCard boxes on /process are gone with it: an evenly
   weighted repeating unit takes no position on what is in it.

     <div class="engageGrid">
       <div>
         <h3 class="labelSmall">Hourly</h3>
         <span class="engageRate headingSmall">$[XXX]/hr</span>
         <p>…</p>
       </div>
     </div>
                                                                              */
.engageGrid {
	/* Explicit column counts, not auto-fit. auto-fit packs as many 240px
	   tracks as the row can hold, which between 768px and ~1066px put three
	   figures on one line and the fourth on its own — and a table whose
	   dividers assume a full row then draws them around the hole. The count
	   here is an editorial decision (four peer figures, read across), not
	   something to derive from the content's own minimum, which is exactly the
	   case CLAUDE.md says to declare rather than reach for auto-fit.

	   Three tiers, one rule each: four across, two across, stacked. The
	   dividers follow the tier — vertical between columns, horizontal under
	   every row — so there is no width at which a rule points at nothing. */
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	border-top: 1px solid var(--color-rule);

	& > div {
		/* A figure is the widest unbreakable thing in the row, so the track has
		   to be allowed to be as narrow as its share rather than as wide as its
		   content — without this the cells overrun each other and the last one
		   leaves the page. */
		min-width: 0;
		border-bottom: 1px solid var(--color-rule);
		padding-block: var(--space-xl);
		display: flex;
		flex-direction: column;
		gap: var(--space-s);

		* { margin: 0; }
	}

	/* Two across. The pair is divided by a rule, each row closed by one. */
	@media only screen and (min-width: 768px) and (max-width: 1024px) {
		grid-template-columns: repeat(2, minmax(0, 1fr));

		& > div {
			padding-inline: var(--space-l);

			&:nth-child(odd) {
				padding-inline-start: 0;
				border-right: 1px solid var(--color-rule);
			}
			&:nth-child(even) { padding-inline-end: 0; }
		}
	}

	/* Four across, in one row: the table this was drawn for. */
	@media only screen and (min-width: 1025px) {
		grid-template-columns: repeat(4, minmax(0, 1fr));

		& > div {
			padding-inline: var(--space-l);
			border-right: 1px solid var(--color-rule);

			&:first-child { padding-inline-start: 0; }
			&:last-child  { padding-inline-end: 0; border-right: 0; }
		}
	}

	& h3 {
		color: var(--color-text-3);
	}
	/* A price is a figure, not a heading, so it sits below the three heading
	   tiers rather than in them: "$[XX,XXX]/mo" is twelve unbreakable
	   characters, and anything larger than this breaks mid-token in a track
	   this narrow at every viewport. */
	.engageRate {
		color: var(--color-text-1);
		display: block;
		max-width: none;
	}
	& p {
		color: var(--color-text-2);
		margin: 0;
		max-width: 34ch;
	}
}

/* --- Work cards ------------------------------------------------------------ */

/* .workGrid — the two-up card grid, with .featured promoting the first card to
   full width — is gone with the last page that used it. Work is a .caseIndex
   of rows now (or the home page's asymmetric .caseRow), because a grid of
   evenly weighted cards takes no position on the work in it. Don't bring it
   back: add rows. */

.workCard {
	display: block;
	position: relative;

	/* A size container, so the card responds to the column it lands in rather
	   than to the viewport: a card in an archive tile and a card in a full
	   case-index row want different type at the same viewport width, and no
	   card has to carry a hand-set font-size to get it. */
	container-type: inline-size;
	container-name: workcard;

	/* :not(.artifactPlate) because a plate replaces this look wholesale — it is
	   a raw rectangle on the page's darkest ground, not a bordered thumbnail.
	   Excluding it here is clearer than having .artifactPlate try to out-weigh
	   a `background:` shorthand from a two-class selector. */
	.workCardMedia:not(.artifactPlate) {
		background: var(--color-surface-2);
		border: 1px solid var(--color-rule);
	}
	.workCardMedia {
		position: relative;
		overflow: hidden;
		aspect-ratio: 3 / 2;
		line-height: 0;

		& img {
			width: 100%;
			height: 100%;
			object-fit: cover;
			transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
		}
	}
	/* The body is one stack with one set of gaps, so a card reads the same
	   wherever it lands: meta, title, copy, tags. Type comes from the classes
	   named in the markup — .labelSmall for the meta line, a heading class for
	   the title — and this only says how far apart they sit. */
	.workCardBody {
		padding-top: var(--space-m);

		& > .labelSmall:first-child {
			display: block;
			margin-bottom: var(--space-s);
		}
		& > h3 { margin-bottom: var(--space-s); }
		& > p {
			color: var(--color-text-2);
			margin: 0 0 var(--space-m);
		}
		& > .textLink { margin-top: var(--space-m); }
	}

	& h3 {
		transition: color 0.25s ease;
	}
	/* Only a card that goes somewhere answers a hover. A card whose case study
	   isn't published is a <div>, and lighting it up would offer a click that
	   does nothing. */
	&:is(a):hover img { transform: scale(1.03); }
	&:is(a):hover h3  { color: var(--color-action-text); }
	/* A well with no photograph in it yet still has to answer a hover. The
	   substrate comes up rather than the whole plate changing colour — the
	   plate is the page's darkest ground and shifting it would break the band. */
	&:is(a):hover .artifactPlate {
		--plate-rule: color-mix(in srgb, var(--color-on-brand) 13%, transparent);
	}

	/* .isQuiet expresses editorial intent only — how much room the image gets.
	   Type sizing comes from the container query below. (.isFeatured went with
	   .workGrid: the lead treatments are .caseIndexRow.isLead and the home
	   page's .caseLead, both of which say more than one aspect ratio could.) */
	&.isQuiet .workCardMedia { aspect-ratio: 4 / 3; }
}

/* --- Artifact plates --------------------------------------------------------
   A media well that is *designed* while it waits for its photograph, rather
   than a light rectangle that reads as a void. Three things stack in one grid
   area, all behind the image, so a real photograph covers every one of them
   the day it lands and nothing has to be unwound:

     the substrate   a faint modular rule grid — the board a component sheet,
                     a token spec or a flow is set out on
     data-plate      the client's name, set large in the well's bottom corner
     data-artifact   what this well is a picture of, as a micro-cap at the top

   Used on the home page today; it is the component every other page will want
   for the same reason. See DESIGN-EXTENDING.md.

     <div class="workCardMedia artifactPlate ratio3x2"
          data-plate="West Music" data-artifact="Component sheet"></div>

   Drop an <img> inside and the plates disappear under it. Delete data-plate
   and data-artifact once the well has a real image and a real caption. */

.artifactPlate {
	display: grid;
	/* minmax(0, 1fr), not the implicit auto track. An auto row grows to the
	   image's natural height, so a 4:3 photo in a 21:9 well got a row taller
	   than the well: the img's height: 100% resolved against that row, cover
	   had nothing to crop, and overflow: hidden cut the bottom off instead of
	   cover trimming top and bottom evenly. A 0 floor pins the row to the
	   well's own height, so cover centres the crop as it should. */
	grid-template: "plate" minmax(0, 1fr) / minmax(0, 1fr);
	overflow: hidden;

	/* The plate sizes itself off the well, not the viewport, so a small well
	   never gets a name that overruns it. */
	container-type: inline-size;

	--plate-module: 56px;
	--plate-rule: color-mix(in srgb, var(--color-on-brand) 6%, transparent);
	background-color: var(--page-surface-contrast);
	background-image:
		repeating-linear-gradient(to right,  var(--plate-rule) 0 1px, transparent 1px var(--plate-module)),
		repeating-linear-gradient(to bottom, var(--plate-rule) 0 1px, transparent 1px var(--plate-module));

	/* A plate is not always a work-card well — the studio portrait is one on
	   its own — so it sizes its own image rather than relying on
	   .workCardMedia to do it. Same values, so a card's well is unaffected. */
	& img {
		position: relative;
		z-index: 1;
		grid-area: plate;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	&[data-plate]::after {
		content: attr(data-plate);
		grid-area: plate;
		align-self: end;
		z-index: 0;
		font-family: var(--font);
		font-weight: 700;
		font-size: 12cqi;
		line-height: 0.85;
		letter-spacing: -0.04em;
		text-transform: uppercase;
		color: color-mix(in srgb, var(--color-on-brand) 9%, transparent);
		padding: 0.3em;
		max-width: 7ch;
	}

	&[data-artifact]::before {
		content: attr(data-artifact);
		grid-area: plate;
		align-self: start;
		z-index: 0;
		font-family: var(--font-narrow);
		font-weight: 700;
		font-size: var(--step--1);
		letter-spacing: var(--letter-spacing-wider);
		text-transform: uppercase;
		color: var(--c-neutral-600);
		padding: var(--space-m);
	}
}

/* A card holding a plate must not be a query container. container-type applies
   containment, containment makes the card an independent formatting context,
   and an independent formatting context cannot be a subgrid — which silently
   defeats any attempt to line two cards' bodies up on one baseline. The
   container belongs on the well anyway, which is what the plate measures
   itself against. */
.workCard:has(.artifactPlate) { container-type: normal; }

/* A card wide enough to carry a bigger headline gets one, wherever it sits. */
@container workcard (min-width: 620px) {
	.workCard h3 { font-size: var(--step-4); }
	.workCard p  { max-width: 58ch; }
}

/* --- Case index --------------------------------------------------------------
   A ruled list of work, not a wall of cards: hairline-separated rows, a plate
   on the left and the copy on the right, with an optional lead row at twice
   the scale. /work is the full version (filterable, with a lead); a page that
   only needs to show three cases in role order uses the plain rows.

   Rows rather than a positioned grid for a functional reason as well: hiding
   one closes the list up, where a card in an explicit column leaves a hole.

     <div class="caseIndex">
       <a class="workCard caseIndexRow isLead" href="…">
         <div class="workCardMedia artifactPlate ratioCaseLead" …></div>
         <div class="workCardBody">…</div>
       </a>
     </div>
                                                                              */
.caseIndex {
	list-style: none;
	margin: 0;
	padding: 0;

	/* These two ratios belong to the component rather than to the page — the
	   difference between a lead well and a row well IS the index's structure,
	   not an editorial call a second page should get to re-make. (The home
	   page's wells are the other case: those ratios live with the page.)
	   Don't confuse either with the .mediaFrame ratio classes, which are a
	   different component. */
	.ratioCaseLead { aspect-ratio: 21 / 9; }
	.ratioCase     { aspect-ratio: 4 / 3; }

	/* Below the row breakpoint the lead well is the full column rather than
	   two fifths of it, and 21:9 across a phone is a letterbox slot, not a
	   lead. */
	@media only screen and (max-width: 767px) {
		.ratioCaseLead { aspect-ratio: 3 / 2; }
	}
}

.caseIndexRow {
	display: grid;
	grid-template-columns: repeat(12, minmax(0, 1fr));
	column-gap: var(--space-2xl);
	align-items: start;
	padding-block: var(--space-2xl);
	/* A divider, not structure: one hairline per row against the section
	   mark's --space-2xs rule. On the top edge rather than the bottom so the
	   list still reads correctly when a filter hides its last row. */
	border-top: 1px solid var(--color-rule);

	.workCardMedia { grid-column: 1 / 6; }
	.workCardBody  { grid-column: 6 / -1; padding-top: 0; }

	/* The lead case is not one of the equal rows. Its plate takes the full
	   measure and the copy sits under it in the seven columns, leaving the
	   five open — the same headline-before-evidence order the home page uses,
	   at the scale this page can afford to give it. */
	&.isLead {
		padding-block-start: var(--space-l);

		.workCardMedia { grid-column: 1 / -1; }
		.workCardBody  { grid-column: 1 / 8; padding-top: var(--space-l); }
	}

	/* The lead's spans have to be reset here too, and the selector has to match
	   theirs to do it. `.caseIndexRow.isLead .workCardBody` is (0,3,0) and beats
	   a plain `.caseIndexRow .workCardBody` whatever the source order, so the
	   lead kept `grid-column: 1 / 8` against a grid that now has one column —
	   and a span reaching line 8 of a one-column grid silently generates six
	   implicit `auto` tracks. The auto tracks take their content's width, the
	   single `1fr` track gets what's left, and the plate sitting in it collapsed
	   to ~130px inside a 450px measure. `&, &.isLead` puts both selectors at the
	   same specificity, and this one comes last. */
	@media only screen and (max-width: 767px) {
		grid-template-columns: 1fr;

		&, &.isLead {
			.workCardMedia { grid-column: 1; }
			.workCardBody  { grid-column: 1; padding-top: var(--space-m); }
		}
	}
}

/* Something whose page isn't written yet is not a link at all. The note takes
   the place a .textLink would have had, rather than sitting beside one — a row
   should never offer to open a page that does not exist.

   Named for the case index it was written for, and now also the marker on
   every unpublished row of /field-notes' reading list. Same rule, same voice;
   the name is older than its second consumer, like .capList and .engageGrid. */
.casePending {
	display: block;
	margin-top: var(--space-m);
	color: var(--color-text-3);
}

/* --- Portrait plate ----------------------------------------------------------
   A person, in the same well the work sits in. The plate carries the studio
   portrait on the home page and on /about, and the two are one element as far
   as the browser is concerned — both name it `studio-portrait`, so navigating
   between the pages morphs the small inset well into the page-head one rather
   than crossfading two pictures of the same face. See "Shared media morphs"
   in 11-animation.css.

     <figure class="portraitPlate">
       <div class="artifactPlate" data-plate="Tom Zeien" data-artifact="Portrait"
            style="view-transition-name: studio-portrait;
                   view-transition-class: media-morph"><img …></div>
       <figcaption class="labelSmall">…</figcaption>
     </figure>
                                                                              */
.portraitPlate {
	margin: 0;

	.artifactPlate { aspect-ratio: 4 / 5; }

	& figcaption {
		display: block;
		margin-block-start: var(--space-s);
		color: var(--color-text-3);
	}
}

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

.siteFooter {
	color-scheme: dark;
	background: var(--page-surface-contrast);
	color: var(--color-text-1);
	padding: var(--space-4xl) 0 0;

	/* Add .onDark when the page already ends on a dark section — otherwise the
	   two near-black blocks stack into one field of dead space. */
	&.onDark {
		padding-top: 0;

		.footerTop {
			padding-top: clamp(var(--space-3xl), 5vw, 80px);
			border-top: 1px solid var(--color-rule);
		}
	}

	& a {
		color: currentColor;
		fill: currentColor;

		&:hover { color: white; }
	}

	.footerTop {
		display: grid;
		grid-template-columns: 2fr 1fr 1fr 1fr;
		gap: var(--space-l);

		@media only screen and (max-width: 900px) { grid-template-columns: 1fr 1fr; }
		@media only screen and (max-width: 479px) { grid-template-columns: 1fr; }
	}
	.footerBrand {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		gap: var(--space-s);

		& p {
			margin: 0;
			max-width: 34ch;
		}
	}

	/* The mark, repeated at the foot of the page. Same lockup as the nav's —
	   the red square then the wordmark — but declared here rather than shared
	   with .navWordmark, because the nav's copy carries two behaviours this
	   one must not: it stretches the square to a full-width bar on the
	   two-row header, and it drops the square to white on the brand field.
	   The footer is always the dark band, so the square is always red.

	   The link inherits the footer's `& a { color: currentColor }` above, so
	   it takes the footer's ink and its hover to white for free. */
	.footerWordmark {
		display: flex;
		align-items: center;
		text-decoration-line: none;

		&::before {
			content: "";
			background: var(--color-brand);
			height: 0.8em;
			aspect-ratio: 1 / 1;
			display: block;
			margin-right: 0.2em;
			flex-shrink: 0;
		}
	}
	& h4 {
		color: var(--color-text-3);
		margin: 0 0 var(--space-m);
	}
	& ul {
		list-style: none;
		margin: 0;
		padding: 0;
        display: flex;
        flex-direction: column;
        gap: var(--space-s);

		& a {
			transition: color 0.2s ease;
		}
	}
	.footerBottom {
		margin-top: var(--space-3xl);
		padding: var(--space-l) 0;
		border-top: 1px solid var(--color-rule);
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-m);
		align-items: center;
		justify-content: space-between;

		& p, & a {
			margin: 0;
			color: var(--color-text-3);
		}
	}
	.footerSocial {
		display: flex;
		gap: var(--space-m);

		& a {
			display: inline-flex;
			width: 20px;
			fill: var(--color-text-2);
			transition: fill 0.2s ease, transform 0.2s ease;

			&:hover {
				fill: white;
				transform: translateY(-2px);
			}
		}
		& svg {
			width: 100%;
			height: auto;
		}
	}
}

/* --- Placeholder callout ----------------------------------------------------
   A visible note to whoever is filling the page in. Delete the whole
   <div class="todoNote"> once the section is written.                        */

.todoNote {
	border-left: 3px solid var(--color-brand);
	background: var(--color-brand-wash);
	padding: var(--space-m) var(--space-l);
	border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
	margin: var(--space-l) 0;
	max-width: 65ch;

	& p {
		font-family: var(--font-narrow);
		font-size: var(--step-0);
		line-height: 1.6;
		color: var(--color-text-2);
		margin: 0;
		max-width: none;
	}
	& b { color: var(--color-action-text); font-weight: 500; }
	& p + p { margin-top: var(--space-xs); }
}

/* A card that gets a wide column earns a larger quote; in a narrow one it
   stays compact. Driven by the card, not the viewport. */
@container testimonial (min-width: 460px) {
	.testimonial blockquote { font-size: var(--step-2); }
}

} /* @layer components */
