/* =============================================================================
 * Hillcrest Architects — component bundle.
 *
 * Ported from .design/reference/homepage.html, .design/reference/
 * case-study-cedar-cove.html and the nine component preview.html files. Class
 * names are kept identical to the reference so the two can be diffed; the
 * WordPress-specific work is confined to the first section.
 *
 * A monograph is mostly margin. When a band feels too empty it is probably
 * right — resist the urge to close the gaps.
 * ========================================================================== */

/* ── WordPress shell ──────────────────────────────────────────────────────
 * theme.json sets root padding to 0 and every band supplies its own gutter
 * through .wrap, exactly as the reference does. These rules stop the block
 * layout engine from adding a second one.
 * ------------------------------------------------------------------------ */

* { box-sizing: border-box; }

body {
	margin: 0;
	background: var(--paper-100);
	color: var(--ink-700);
	font-family: var(--font-text);
	font-size: 17px;
	line-height: 29px;
	-webkit-font-smoothing: antialiased;
	overflow-x: hidden; /* belt and braces: no horizontal scroll at 400px */
}

.wp-site-blocks > * { margin-block: 0; }
.wp-site-blocks main { display: block; }

/* Bands butt against each other; their own padding is the rhythm. WordPress's
 * flow layout otherwise inserts blockGap between every top-level child of the
 * post content, which puts a 24px stripe of paper-100 between each pair of
 * bands and breaks the alternating grounds the page depends on.
 *
 * `main` is load-bearing. WP's rule is `:root :where(.is-layout-flow) > *`,
 * which is (0,1,0) because :where() contributes nothing — the same weight as a
 * bare `.wp-block-post-content > *`, so that version would only win on source
 * order. The descendant makes it (0,1,1) and settles it. Platform gotcha 35. */
main .wp-block-post-content > *,
main.wp-block-group > * { margin-block: 0; }

/* A template part renders a wrapper element around its contents, and that
 * wrapper is exactly as tall as the header inside it — which would make
 * position:sticky pin against a box that scrolls away immediately. Taking the
 * wrapper out of the box tree lets the header stick against the viewport.
 * Platform gotcha 34. */
.wp-block-template-part { display: contents; }

html { scroll-behavior: smooth; }

img,
svg { max-width: 100%; }

:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

.sr {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

.skip-link:focus {
	position: fixed;
	top: var(--space-3);
	left: var(--space-3);
	z-index: 200;
	padding: var(--space-3) var(--space-5);
	background: var(--paper-000);
	color: var(--ink-900);
	box-shadow: var(--shadow-plate);
}

/* ── Platform demo banner ─────────────────────────────────────────────────
 * sfp-demo-banner is platform chrome, sticky at top:0 with z-index 99999 and
 * its own charcoal/amber defaults. Recolor it into this palette so the page
 * has no stray second accent, and give the site header something to stick
 * beneath. motion.js measures the banner and writes --banner-h; the 0px
 * fallback covers no-JS and the banner being switched off.
 * ------------------------------------------------------------------------ */

:root { --banner-h: 0px; }

.sfp-demo-banner {
	background: var(--ink-950) !important;
	color: var(--ink-inverse) !important;
	border-bottom: 1px solid var(--rule-hair) !important;
	box-shadow: none !important;
}

/* Recoloring the banner element is not enough. Its own stylesheet colors
 * every child with a hardcoded light literal — `__text` #cfd4e0, `__text
 * strong` and `__link` #fff — on the assumption that the banner is always
 * dark. It is dark in paper and light in plate, so in the plate theme those
 * children stay near-white on a near-white ground and the notice disappears.
 * The tokens below are the pair defined against ink-950, so they follow the
 * banner's own background in both themes.
 *
 * Color only. An earlier version also set the banner in Archivo, which cost
 * 0.37 of layout shift on every load: the banner's copy is long enough to wrap,
 * it sits above everything, and swapping a webfont into it at 100ms reflowed
 * it and moved the whole page down. Its own stylesheet asks for the system
 * stack, which never swaps. A typeface on platform chrome is not worth a CLS
 * failure on the page under it. */
.sfp-demo-banner__text { color: var(--ink-inverse-muted) !important; }
.sfp-demo-banner__text strong { color: var(--ink-inverse) !important; }

.sfp-demo-banner a,
.sfp-demo-banner__link {
	color: var(--oxide-inverse) !important;
	text-decoration-color: currentColor !important;
}

.sfp-demo-banner__badge {
	background: var(--oxide-inverse) !important;
	color: var(--ink-950) !important;
}

/* ── Layout primitives ─────────────────────────────────────────────────── */

.wrap {
	width: var(--col);
	margin-inline: auto;
	padding-inline: var(--space-7);
}

@media (max-width: 720px) {
	.wrap { padding-inline: var(--space-4); }
}

.band { padding-block: var(--space-9); }

@media (max-width: 720px) {
	.band { padding-block: var(--space-7); }
}

/* Exactly one ink-950 band per page — the studio band on the homepage, and
 * the footer everywhere, which shares the treatment and is allowed to. */
.band-ink {
	background: var(--ink-950);
	color: var(--ink-inverse);
}

.band-tone { background: var(--tone-200); }

/* Spacing between the blocks inside a band.
 *
 * A band's inner wrapper is this theme's own markup, not a WordPress layout
 * container, so neither WP's blockGap nor the reset that cancels it applies in
 * here — two blocks in a band sat flush against each other, which on the
 * studio page ran the lede straight into the first paragraph.
 *
 * The gap is a custom property rather than a value so that a component can ask
 * for a different one without a specificity fight it would lose: this selector
 * is (0,2,0) and every component class is (0,1,0), so a component's own
 * margin-top would be overridden here however the file is ordered. Setting
 * `--stack` on the component resolves on the element and is immune to it.
 *
 * The contact band is excluded because its wrapper is the grid itself, and a
 * top margin on a grid item is not a gap. */
.band > .wrap:not(.cgrid) > * + * { margin-top: var(--stack, var(--space-6)); }

/* ── Type ──────────────────────────────────────────────────────────────── */

h1, h2, h3 {
	font-family: var(--font-display);
	font-weight: 400;
	color: var(--ink-900);
	margin: 0;
	text-wrap: balance;
}

h1 { font-size: clamp(42px, 7vw, 88px); line-height: .94; letter-spacing: -.02em; }
h2 { font-size: clamp(34px, 5vw, 56px); line-height: 1; letter-spacing: -.015em; }
h3 { font-size: clamp(28px, 3.4vw, 36px); line-height: 1.1; letter-spacing: -.01em; }

.band-ink h1,
.band-ink h2,
.band-ink h3 { color: var(--ink-inverse); }

.lede {
	font-size: clamp(18px, 2.1vw, 21px);
	line-height: 1.62;
	font-weight: 300;
	color: var(--ink-700);
	max-width: 56ch;
	margin: 0;
}

.band-ink .lede { color: var(--ink-inverse); }

/* Body prose sits near a 68-character measure. A monograph is read, not
 * skimmed; do not narrow it to 45 characters to look designed. */
.prose { max-width: 68ch; }
.prose p { margin: 0 0 var(--space-5); }
.prose p:last-child { margin-bottom: 0; }

.small { font-size: 15px; line-height: 25px; }

/* `note` is the only italic in the system, and it means caption. */
.note {
	font-size: 13px;
	line-height: 21px;
	font-style: italic;
	color: var(--ink-700);
}

.label,
.num,
.spec-l {
	font-family: var(--font-label);
	text-transform: uppercase;
	font-weight: 600;
	margin: 0;
}

.label { font-size: 11px; line-height: 11px; letter-spacing: .18em; color: var(--ink-500); }
.num { font-size: 11px; line-height: 11px; letter-spacing: .1em; color: var(--ink-500); font-variant-numeric: tabular-nums; }
.spec-l { font-size: 11px; line-height: 16px; letter-spacing: .14em; color: var(--ink-500); }

.band-ink .label,
.band-ink .num,
.band-ink .spec-l { color: var(--ink-inverse-muted); }

.spec-v {
	font-family: var(--font-label);
	font-size: 15px;
	line-height: 24px;
	font-weight: 400;
	color: var(--ink-900);
	font-variant-numeric: tabular-nums;
}

/* ── SectionHeading ────────────────────────────────────────────────────── */

/* The oxide rule is the width of the eyebrow text and no wider — it reads as
 * an underscore of the label, not a divider. That is why the eyebrow and its
 * rule are wrapped in an inline-block rather than given a width. */
.eyebrow-wrap { display: inline-block; }

.orule {
	height: 1px;
	background: var(--oxide-600);
	margin: var(--space-2) 0 var(--space-6);
	transform-origin: left;
}

.band-ink .orule { background: var(--oxide-inverse); }

.js-motion .orule { transform: scaleX(0); }
.js-motion .in .orule,
.js-motion .orule.in {
	transform: scaleX(1);
	transition: transform var(--dur-2) var(--ease-out);
}

.mask { display: block; overflow: hidden; }
.mask > span { display: block; }
.js-motion .mask > span { transform: translateY(105%); }
.js-motion .go .mask > span {
	transform: none;
	transition: transform var(--dur-4) var(--ease-out);
}

/* ── Reveal ────────────────────────────────────────────────────────────── */

.js-motion [data-reveal] {
	opacity: var(--opacity-enter);
	transform: translateY(16px);
}

.js-motion [data-reveal].in {
	opacity: 1;
	transform: none;
	transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}

/* ── Header ────────────────────────────────────────────────────────────── */

.hdr {
	position: sticky;
	top: calc(var(--banner-h) + env(safe-area-inset-top, 0px));
	z-index: 50;
	background: color-mix(in srgb, var(--paper-100) 88%, transparent);
	backdrop-filter: blur(10px);
	border-bottom: 1px solid transparent;
	transition: background-color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out);
}

.hdr.stuck {
	background: var(--paper-100);
	border-color: var(--rule-hair);
}

.hdr-in {
	display: flex;
	align-items: center;
	gap: var(--space-7);
	height: 86px;
	transition: height var(--dur-2) var(--ease-out);
}

.hdr.stuck .hdr-in { height: 62px; }

/* There is no Hillcrest wordmark in the source and none has been drawn: the
 * name is set in display-s until a real mark exists. */
.mark {
	display: flex;
	align-items: baseline;
	gap: 8px;
	text-decoration: none;
	margin-right: auto;
}

.mark b {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 26px;
	color: var(--ink-900);
}

.mark span {
	font-family: var(--font-label);
	font-size: 11px;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--ink-500);
}

.nav {
	display: flex;
	gap: var(--space-6);
}

.nav a {
	font-family: var(--font-label);
	font-size: 13px;
	font-weight: 500;
	letter-spacing: .04em;
	color: var(--ink-700);
	text-decoration: none;
	position: relative;
	padding-bottom: 3px;
}

.nav a::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 1px;
	background: var(--oxide-600);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--dur-2) var(--ease-out);
}

.nav a:hover::after,
.nav a[aria-current]::after { transform: scaleX(1); }

@media (max-width: 620px) {
	.hdr-in { gap: var(--space-4); height: 64px; }
	.hdr.stuck .hdr-in { height: 56px; }
	.mark span { display: none; }
	.mark b { font-size: 20px; }
	.nav { gap: var(--space-4); }
	.nav a { font-size: 12px; letter-spacing: .03em; }
}

/* ── Link, in three ranks ──────────────────────────────────────────────── */

/* Text link: the default. No underline at rest — the rule drawing on hover is
 * the affordance, and the arrow marks it as a link before any hover. */
.tlink {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding-bottom: 4px;
	font-family: var(--font-label);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: .02em;
	color: var(--oxide-600);
	text-decoration: none;
}

.tlink::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 1px;
	background: var(--oxide-600);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--dur-2) var(--ease-out);
}

.tlink:hover::after,
a:hover .tlink::after,
a:focus-visible .tlink::after { transform: scaleX(1); }

.tlink .arw { transition: transform var(--dur-2) var(--ease-out); }
.tlink:hover .arw,
a:hover .tlink .arw,
a:focus-visible .tlink .arw { transform: translateX(4px); }

.band-ink .tlink { color: var(--oxide-inverse); }
.band-ink .tlink::after { background: var(--oxide-inverse); }

/* Button: the one rank with a border. Used for the contact email and nothing
 * else on the whole site. There is no filled button in this system. */
.btn {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding: var(--space-3) var(--space-6);
	font-family: var(--font-label);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: .02em;
	color: var(--ink-900);
	text-decoration: none;
	background: transparent;
	border: 1px solid var(--rule-strong);
	border-radius: var(--radius-sm);
	transition: border-color var(--dur-1) linear;
}

.btn:hover { border-color: var(--ink-900); }

.band-ink .btn {
	color: var(--ink-inverse);
	border-color: var(--ink-inverse-muted);
}

.band-ink .btn:hover { border-color: var(--ink-inverse); }

/* Quiet link: inside prose and in the footer. Never oxide — an accent inside
 * a paragraph of Newsreader is noise. */
.qlink {
	color: var(--ink-900);
	text-decoration: none;
	border-bottom: 1px solid var(--rule-hair);
	transition: border-color var(--dur-1) linear;
}

.qlink:hover { border-color: var(--rule-strong); }

.band-ink .qlink {
	color: var(--ink-inverse);
	border-color: var(--ink-inverse-muted);
}

/* ── Plate ─────────────────────────────────────────────────────────────── */

.plate { margin: 0; }

.box {
	position: relative;
	overflow: hidden;
	background: var(--tone-200);
	border-radius: var(--radius-none);
}

.box img,
.box picture {
	display: block;
	width: 100%;
	height: 100%;
}

.box img { object-fit: cover; }

.r-pan { aspect-ratio: var(--plate-panorama); }
.r-wide { aspect-ratio: var(--plate-wide); }
.r-land { aspect-ratio: var(--plate-landscape); }
.r-port { aspect-ratio: var(--plate-portrait); }
.r-sq { aspect-ratio: var(--plate-square); }

/* The unmask is the identity: a clip-path wipe from the bottom edge upward
 * while the image settles from scale(1.06). Opacity never changes, and the
 * mask is only ever applied under .js-motion so a no-JS page never ships an
 * image clipped to nothing. */
.js-motion .box { clip-path: inset(0 0 100% 0); }
.js-motion .box img { transform: scale(1.06); }

.js-motion .in .box,
.js-motion .box.in {
	clip-path: inset(0 0 0 0);
	transition: clip-path var(--dur-mask) var(--ease-mask);
}

.js-motion .in .box img,
.js-motion .box.in img {
	transform: scale(1);
	transition: transform var(--dur-mask) var(--ease-mask);
}

/* The picture lands first, then it is named. */
.pcap { margin-top: var(--space-2); }
.js-motion .pcap { opacity: 0; }
.js-motion .in .pcap {
	opacity: 1;
	transition: opacity var(--dur-2) var(--ease-out) var(--dur-mask);
}

.pcap .num { display: block; }
.pcap .note { display: block; margin-top: var(--space-1); }

/* The pending state. Where no photograph exists, say so: a tone-200 well at
 * the correct ratio with its plate number and the words. No icon, no camera
 * glyph, no gray mountain placeholder — the honesty is the design. */
.pending {
	display: grid;
	place-items: center;
	text-align: center;
	padding: var(--space-6);
	border: 1px solid var(--rule-hair);
	background: var(--tone-200);
}

.pending .note {
	color: var(--ink-500);
	margin-top: var(--space-2);
}

/* ── Full-bleed plate ──────────────────────────────────────────────────── */

/* The hero is a panorama plate wherever a panorama plate fits, and taller
 * than one wherever it does not.
 *
 * The reference page sets `aspect-ratio: 21/9` with `overflow: hidden` and
 * puts the firm name, the mission line and the plate number inside it. At
 * 400px that box is 171px tall and the text needs about 250px, so the
 * reference clips its own headline — at phone width the approved page reads
 * "not a brand exercise." over a letterboxed strip, with the first line gone.
 *
 * A breakpoint would only move the failure: at 768px the box is 329px and the
 * text still nearly fills it. So the ratio becomes a floor rather than a fixed
 * height. The section is at least as tall as a 21:9 plate and grows if the
 * type needs more, and the image — already `object-fit: cover` — simply crops
 * tighter. Nothing is ever letterboxed or distorted; on every viewport where
 * the panorama fits, which is every desktop and tablet, the box is exactly
 * 21:9 and identical to the reference. */
.bleed {
	position: relative;
	display: flex;
	align-items: flex-end;
	overflow: hidden;
	min-height: min(calc(100vw * 9 / 21), var(--bleed-max, 78vh));
	background: var(--photo-ground);
}

.bleed .drift { position: absolute; inset: 0; }

.bleed img,
.bleed picture {
	display: block;
	width: 100%;
	height: 100%;
}

.bleed img { object-fit: cover; }

/* Only the hero and a case study's opening plate drift: scale 1 → 1.04, once,
 * linear, not looping. No other image moves on its own. */
.js-motion .bleed .drift { animation: hc-drift var(--dur-drift) var(--ease-drift) both; }

@keyframes hc-drift {
	from { transform: scale(1); }
	to { transform: scale(1.04); }
}

/* The wash under text laid over a photograph.
 *
 * tokens.json sets opacity-wash at .46 and the reference page does not use it,
 * running a gradient that reaches .72 at the bottom edge and 0 at the top of
 * its own box. Measured against the actual composite — the photograph drawn
 * and the gradient painted over it, sampled under each line's own bounding
 * box — neither holds. The eyebrow sits near the top of that box, where the
 * gradient is nearly transparent, and came out at 1.5:1 on the homepage and
 * 1.6:1 on the case study. The case-study headline, which sits higher than
 * the homepage's because there is no lede under it, came out at 3.9:1.
 *
 * A gradient measured in percentages of a box whose height depends on how the
 * headline wrapped cannot be reasoned about, so the two jobs are separated.
 * The text box carries a flat .80 wash, which clears 4.5:1 for all three type
 * colors against a pure white frame and so cannot fail on any photograph
 * that might arrive later. The fade that keeps it from reading as a hard edge
 * lives in a pseudo-element above the box, in the region where no type sits. */
.bleed-in {
	position: relative;
	width: 100%;
	padding: var(--space-8) var(--space-7);
	background: rgba(20, 21, 26, .8);
	color: var(--on-photo);
}

.bleed-in::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 100%;
	height: var(--space-10);
	background: linear-gradient(to top, rgba(20, 21, 26, .8), rgba(20, 21, 26, 0));
	pointer-events: none;
}

.bleed-in h1,
.bleed-in h2 { color: var(--on-photo); }
.bleed-in .label,
.bleed-in .num { color: var(--on-photo-muted); }
.bleed-in .lede { color: var(--on-photo-lede); }

.bleed-num {
	position: absolute;
	right: var(--space-7);
	bottom: var(--space-7);
	color: var(--on-photo-muted);
}

@media (max-width: 720px) {
	.bleed-in { padding: var(--space-6) var(--space-4); }
	.bleed-num { display: none; }
}

/* ── ProjectIndex ──────────────────────────────────────────────────────── */

.idx {
	--stack: var(--space-8);
	display: grid;
	gap: var(--space-8);
	margin-top: var(--space-8);
}

/* A row is a plate and a text block side by side, the plate taking seven of
 * twelve columns. Rows alternate which side the plate sits on, so the eye
 * zig-zags down the page rather than running down a gutter. */
.row {
	display: grid;
	grid-template-columns: 7fr 5fr;
	gap: var(--space-7);
	align-items: center;
	text-decoration: none;
	color: inherit;
}

.row.flip .plate { order: 2; }

@media (max-width: 860px) {
	.row { grid-template-columns: 1fr; gap: var(--space-5); }
	.row.flip .plate { order: 0; }
}

.row .box img {
	opacity: var(--opacity-rest);
	transition: opacity var(--dur-2) var(--ease-out);
}

.row:hover .box img,
.row:focus-visible .box img { opacity: 1; }

.row .box {
	transition: box-shadow var(--dur-2) var(--ease-out), clip-path var(--dur-mask) var(--ease-mask);
}

/* The plate lifts and brightens. It does not scale — it already moved once,
 * and twice is fussy. */
.row:hover .box,
.row:focus-visible .box { box-shadow: var(--shadow-plate); }

.row .num { display: block; }

.row h3 {
	position: relative;
	display: inline-block;
	margin: var(--space-4) 0 var(--space-3);
	transition: transform var(--dur-2) var(--ease-out);
}

.row h3::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: -2px;
	height: 1px;
	background: var(--oxide-600);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--dur-2) var(--ease-out);
}

.row:hover h3,
.row:focus-visible h3 { transform: translateX(4px); }
.row:hover h3::after,
.row:focus-visible h3::after { transform: scaleX(1); }

.meta {
	display: flex;
	justify-content: space-between;
	gap: var(--space-5);
	padding-top: var(--space-3);
	border-top: 1px solid var(--rule-hair);
}

.soon { display: block; margin-top: var(--space-4); }

/* The row's "Case study" affordance is a text link in every way but the
 * element: the row is the anchor, so this is a span and takes its hover from
 * the row. It cannot also take `.soon`, which would turn the inline-flex that
 * positions the arrow into a block. */
.row .tlink { margin-top: var(--space-4); }

.row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 6px; }

/* The current project's plate number takes the accent — one of the six marks
 * the accent is rationed to. */
.row[aria-current] .num { color: var(--oxide-600); }

/* ── SpecTable ─────────────────────────────────────────────────────────── */

.spec {
	--stack: var(--space-8);
	margin: 0;
	background: var(--paper-000);
	padding: var(--space-6);
}

.spec .r {
	position: relative;
	display: flex;
	gap: var(--space-6);
	padding: var(--space-3) 0;
	align-items: baseline;
}

.spec .r::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	height: 1px;
	background: var(--rule-hair);
	transform-origin: left;
}

.js-motion .spec .r::before { transform: scaleX(0); }
.js-motion .spec.in .r::before {
	transform: scaleX(1);
	transition: transform var(--dur-2) var(--ease-out);
}

/* Long values wrap in the value column and never shrink the label column. */
.spec dt { flex: none; width: 180px; }
.spec dd { margin: 0; }

@media (max-width: 620px) {
	.spec .r { flex-direction: column; gap: var(--space-1); }
	.spec dt { width: auto; }
}

/* ── PullQuote ─────────────────────────────────────────────────────────── */

/* No quote marks — the size and the space say it is a quote. Never italic:
 * italic means caption in this system and nothing else. */
.pull {
	--stack: 0;
	margin: 0;
	padding-block: var(--space-9);
}

.pull p {
	margin: 0;
	max-width: 24ch;
	font-family: var(--font-display);
	font-size: clamp(28px, 3.4vw, 36px);
	line-height: 1.1;
	letter-spacing: -.01em;
	color: var(--ink-900);
}

.band-ink .pull p { color: var(--ink-inverse); }

/* ── Principals ────────────────────────────────────────────────────────── */

.prin {
	--stack: var(--space-8);
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-7);
	margin-top: var(--space-8);
}

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

/* The initials square, which the brand book describes as the state before real
 * portraits arrive — and says that when they do, they replace it at the same
 * ratio and size, as a Plate. Both states are here: `.sq` on its own is the
 * initials, `.sq.has-portrait` is the photograph, and they occupy exactly the
 * same box. */
.sq {
	width: 96px;
	aspect-ratio: var(--plate-square);
	display: grid;
	place-items: center;
	background: color-mix(in srgb, var(--ink-inverse) 9%, transparent);
	font-family: var(--font-display);
	font-size: 26px;
	color: var(--ink-inverse);
}

.sq.has-portrait {
	background: var(--tone-200);
	overflow: hidden;
}

.prin h3 {
	font-size: 26px;
	line-height: 1.22;
	margin: var(--space-5) 0 var(--space-1);
}

.prin .spec-l {
	display: block;
	margin-bottom: var(--space-4);
	color: var(--ink-inverse-muted);
}

.prin .small {
	max-width: 34ch;
	color: var(--ink-inverse);
	margin: 0;
}

/* ── ContactPanel ──────────────────────────────────────────────────────── */

.contact-band { padding-block: var(--space-10); }

@media (max-width: 720px) {
	.contact-band { padding-block: var(--space-8); }
}

.cgrid {
	display: grid;
	grid-template-columns: 4fr 8fr;
	gap: var(--space-7);
	align-items: start;
}

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

.panel {
	padding: var(--space-8);
	background: var(--paper-000);
	box-shadow: var(--shadow-plate);
}

.panel h3 {
	font-size: 26px;
	line-height: 1.22;
	margin-bottom: var(--space-6);
}

.panel dl { margin: 0; }

.panel .r {
	display: flex;
	gap: var(--space-6);
	align-items: baseline;
	padding: var(--space-3) 0;
	border-top: 1px solid var(--rule-hair);
}

.panel .r:last-of-type { border-bottom: 1px solid var(--rule-hair); }
.panel dt { flex: none; width: 140px; }
.panel dd { margin: 0; }
.panel .btn { margin-top: var(--space-6); }

@media (max-width: 620px) {
	.panel { padding: var(--space-6); }
	.panel .r { flex-direction: column; gap: var(--space-1); }
	.panel dt { width: auto; }
}

/* ── Case study ────────────────────────────────────────────────────────── */

/* A pair of plates is offset, never a symmetrical two-up: one landscape and
 * one portrait, the portrait dropped space-8 below the landscape's top edge.
 * The offset is a desktop idea; at phone width the pair simply stacks. */
.pair {
	display: grid;
	grid-template-columns: 7fr 5fr;
	gap: var(--space-7);
	align-items: start;
	margin-top: var(--space-8);
}

.pair .pair-b { margin-top: var(--space-8); }

@media (max-width: 860px) {
	.pair { grid-template-columns: 1fr; }
	.pair .pair-b { margin-top: 0; }
}

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

.decisions h3 {
	font-size: 26px;
	line-height: 1.22;
	margin: var(--space-3) 0;
}

.decisions p { margin: 0; }

/* ── Footer ────────────────────────────────────────────────────────────── */

.foot { padding-block: var(--space-8); }

.foot-in {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-7);
	justify-content: space-between;
	align-items: baseline;
}

.foot .mark b { color: var(--ink-inverse); }
.foot .mark span,
.foot .small { color: var(--ink-inverse-muted); }
.foot .small { margin: var(--space-4) 0 0; }

.foot-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-6);
}

.foot-links a {
	font-family: var(--font-label);
	font-size: 13px;
	font-weight: 500;
	letter-spacing: .04em;
	color: var(--ink-inverse);
	text-decoration: none;
}

.foot-links a:hover { color: var(--oxide-inverse); }
