/* ============================================================
   Creativ Razor / Page templates
   (Work, Case, Services, Studio, Journal, Article, Contact, 404)
   Consumes the ground contract from tokens.css; no hardcoded colours.
   ============================================================ */

/* ---- Shared editorial opener ------------------------------------- */
.cr-open { padding-block: calc(var(--cr-nav-h) + clamp(2.5rem, 6vw, 5rem)) clamp(2.5rem, 5vw, 4rem); }
.cr-open__head { margin-top: clamp(0.75rem, 2vw, 1.25rem); max-width: 20ch; }
.cr-open__lead { margin-top: clamp(1.25rem, 3vw, 1.75rem); }

/* ---- Prose ------------------------------------------------------- */
.cr-prose { max-width: var(--cr-measure); }
.cr-prose p { margin-top: 1.2rem; color: var(--cr-fg-mute); }
.cr-prose p:first-child { margin-top: 0; }
.cr-prose a { color: var(--cr-accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px; }
.cr-prose__pull {
	font-size: clamp(1.4rem, 3.5vw, 2.35rem); font-weight: 600;
	line-height: 1.15; letter-spacing: -0.02em;
	color: var(--cr-fg) !important; margin-top: 2.25rem; text-wrap: balance;
}

/* ---- Work: filter pills ----------------------------------------- */
.cr-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: clamp(2rem, 4vw, 2.75rem); }
.cr-filter {
	min-height: 44px; padding: 0.6em 1.3em; border-radius: 100px;
	font-weight: 700; font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
	border: 1px solid var(--cr-hair); color: var(--cr-fg);
	transition: border-color var(--cr-dur-state) var(--cr-ease-quart),
	            color var(--cr-dur-state) var(--cr-ease-quart),
	            background var(--cr-dur-state) var(--cr-ease-quart);
}
.cr-filter:hover { border-color: var(--cr-amber); color: var(--cr-amber); }
.cr-filter[aria-pressed='true'] { background: var(--cr-amber); color: var(--cr-ink); border-color: var(--cr-amber); }
.cr-filter.is-current { background: var(--cr-amber); color: var(--cr-ink); border-color: var(--cr-amber); }
.cr-tile.is-hidden { display: none; }
.cr-work__empty { margin-top: 2rem; color: var(--cr-fg-mute); }
.cr-work__empty:not(.is-shown) { display: none; }

/* Featured tier: the first card takes the full row (doc: "large hero
   card on top, two-up below").

   3/2, not the 16/7 this started as. The frame contains rather than crops, so
   its ratio has to sit near the artwork's: the 19 featured images run 0.57 to
   2.55 with a median of 1.37, and only two are as wide as 16/7 (2.29). At
   1900px that left a 754px image floating in an 1150px frame, with roughly
   400px of bone down each side - the hero read as an empty cream slab. 3/2 sits
   at the top of the main 1.37-1.5 cluster, so the common case very nearly
   fills. Portrait outliers still letterbox, which is the right trade: empty
   margin beats slicing a logo. */
@media (min-width: 48rem) {
	/* `span 2`, not `1 / -1`. Once the shell went full width a whole-row hero
	   was 1761px across, and at 3/2 that is 1174px tall: taller than the
	   viewport, from a source image only 1248px wide. Two columns keeps the
	   featured tier obvious at roughly 870px while the rest of the row carries
	   two more cases, so the full width is used by work rather than by one
	   over-enlarged picture. On a tablet's two columns it still fills the row. */
	.cr-work--tiered .cr-tile:first-child { grid-column: span 2; }
	.cr-work--tiered .cr-tile:first-child .cr-tile__media { aspect-ratio: 3 / 2; }
}

/* ---- Services tiers --------------------------------------------- */
/* .cr-tier sits on .cr-container for tiers 1-2, so it must not set a
   max-width; `.cr-tier p` carries the measure. Tier 3 is nested, so
   .cr-tier--feature may constrain itself. */
.cr-tier__head { display: flex; align-items: baseline; gap: clamp(1rem, 2.5vw, 2rem); margin-bottom: clamp(1.25rem, 3vw, 1.75rem); }
.cr-tier__no {
	font-weight: 900; font-size: clamp(2.25rem, 6vw, 4rem);
	color: var(--cr-accent); line-height: 0.8; flex: none;
}
.cr-on-bone .cr-tier__no { color: var(--cr-ember); }
.cr-tier p { max-width: var(--cr-measure); color: var(--cr-fg-mute); }
/* A term being DEFINED, not a word being emphasised. Full strength against
   the muted body so SEO and GEO read as the two labels the paragraph is
   about.

   The `:not()` is required, not tidiness. The meta IS a direct child `p`, so
   a bare `.cr-tier > p strong` (0,1,2) outranks `.cr-tier__meta strong`
   (0,1,1) and would repaint every DELIVERABLES / PLATFORMS / INCLUDES label
   from amber to white. Excluding it means the two rules never compete. */
.cr-tier > p:not(.cr-tier__meta) strong { color: var(--cr-fg); font-weight: 800; }
.cr-tier__meta { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid var(--cr-hair); font-size: 0.88rem; color: var(--cr-fg-mute); }
.cr-tier__meta strong {
	display: block; margin-bottom: 0.5rem;
	font-weight: 700; font-size: 0.68rem; letter-spacing: 0.18em;
	text-transform: uppercase; color: var(--cr-accent-quiet);
}
/* Tier 3 is the featured one: it sits on its own inset ground. */
.cr-tier--feature {
	background: var(--cr-ink-2); border: 1px solid var(--cr-hair-ink);
	border-top: 2px solid var(--cr-amber);
	padding: clamp(1.5rem, 4vw, 3rem); border-radius: 2px;
}

/* ---- Services tiers, wide -----------------------------------------
   Three offers on one page are a COMPARISON, and a stack of 62ch
   ribbons against the left edge is the one layout that cannot be
   compared: each tier had to be read serially, and the other 60% of a
   1440px page was empty.

   So the headline still spans the full width, where it is loudest, and
   below it the prose and the deliverables sit side by side. Two things
   come out of that. The width is used, and the deliverables stop being
   a footnote under the copy and become a column a buyer can scan, at
   the SAME position in all three tiers, which is what lets the offers
   be told apart at a glance.

   Placement is explicit rather than auto-flowed because the tiers do
   not have the same number of paragraphs, two in 01 and 02, three in
   03. Auto-flow would put the meta column in a different row in the
   featured tier and break the alignment that is the whole point.
   ------------------------------------------------------------------ */
@media (min-width: 62rem) {
	.cr-tier {
		display: grid;
		/*
		 * Both tracks are sized to their CONTENT, not to a share of the
		 * row, and the leftover becomes the gutter via space-between. A
		 * `1fr` prose column looked like the obvious choice and was not:
		 * `.cr-tier p` caps the prose at 62ch for readability, so the
		 * track grew to 915px and held a 510px paragraph, parking 320px
		 * of nothing between the copy and the deliverables. Letting the
		 * slack fall BETWEEN two hugged columns puts the copy on the
		 * left edge and the deliverables on the right, and the page
		 * reads edge to edge with no dead band in the middle.
		 */
		grid-template-columns: minmax(0, var(--cr-measure)) minmax(0, 24rem);
		justify-content: space-between;
		column-gap: clamp(2rem, 5vw, 4rem);
		align-items: start;
	}
	.cr-tier__head { grid-column: 1 / -1; }
	.cr-tier > p { grid-column: 1; }
	/*
	 * The meta is pulled up beside the prose. Its top border was doing
	 * the work of a horizontal rule under the copy; in a column it is a
	 * left edge instead, which is what makes it read as a list rather
	 * than as a trailing sentence.
	 *
	 * Qualified `> p.` on purpose, NOT a bare `.cr-tier__meta`. The meta
	 * IS a `<p>`, so the class alone (0,1,0) loses to the `.cr-tier > p`
	 * rule above it (0,1,1) and the deliverables land in column one,
	 * stacked above the copy they are meant to sit beside. Matching the
	 * element as well takes it to (0,2,1) and it wins.
	 */
	.cr-tier > p.cr-tier__meta {
		grid-column: 2; grid-row: 2;
		margin-top: 0; padding-top: 0;
		border-top: 0;
		border-inline-start: 1px solid var(--cr-hair);
		padding-inline-start: clamp(1rem, 2vw, 1.5rem);
		line-height: 1.75;
	}
	/* The featured tier is the WIDEST, not the narrowest. It used to cap
	   itself at 64rem inside a full-bleed section, so the one tier meant
	   to stand out was visibly smaller than the two above it. */
	.cr-tier--feature { row-gap: 0.4rem; }
}

/* ---- FAQ --------------------------------------------------------- */
.cr-faq { margin-top: clamp(2rem, 4vw, 2.75rem); border-top: 2px solid var(--cr-accent-quiet); max-width: 64rem; }
.cr-faq__item { border-bottom: 1px solid var(--cr-hair); }
.cr-faq__item summary {
	list-style: none; cursor: pointer; min-height: 44px;
	padding-block: 1.4rem; gap: 1rem;
	display: flex; justify-content: space-between; align-items: center;
	font-weight: 800; font-size: 1.02rem; text-transform: uppercase; letter-spacing: -0.005em;
	transition: color var(--cr-dur-state) var(--cr-ease-quart);
}
.cr-faq__item summary:hover { color: var(--cr-accent-quiet); }
.cr-faq__item summary::-webkit-details-marker { display: none; }
.cr-faq__item summary::after {
	content: '+'; flex: none; color: var(--cr-accent-quiet);
	font-weight: 800; font-size: 1.5rem; line-height: 1;
	transition: rotate var(--cr-dur-state) var(--cr-ease-quart);
}
.cr-faq__item[open] summary::after { rotate: 45deg; }
.cr-faq__item p { padding-bottom: 1.4rem; max-width: 62ch; color: var(--cr-fg-mute); }

/* The heading holds the left of the bone field, the questions run down
   the right. Five one-line questions in a full-width field read as a
   list of loose ends with a third of the page empty beside them; paired
   with the heading they read as an exchange.

   `align-content: start` matters: without it the two columns centre
   against each other and the heading floats to the middle of a tall
   accordion, which is exactly the drifting look this is fixing. */
@media (min-width: 62rem) {
	.cr-ask {
		display: grid;
		grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
		column-gap: clamp(2.5rem, 6vw, 5rem);
		align-content: start;
	}
	.cr-ask__head { grid-column: 1; }
	.cr-ask .cr-faq {
		grid-column: 2; grid-row: 1 / span 2;
		margin-top: 0; max-width: none;
	}
}

/* ---- Studio: beliefs, team, process ----------------------------- */
.cr-beliefs {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
	gap: clamp(1.75rem, 4vw, 3.5rem); margin-top: clamp(2.25rem, 5vw, 3.5rem);
}
.cr-belief { padding-top: 1.5rem; position: relative; }
.cr-belief::before {
	content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
	background: var(--cr-accent-quiet);
}
.cr-belief h3 { margin-bottom: 0.8rem; }
.cr-belief p { color: var(--cr-fg-mute); }

.cr-team {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
	gap: clamp(0.75rem, 2vw, 1.25rem); margin-top: clamp(2.25rem, 5vw, 3.5rem);
}
.cr-team__card { background: var(--cr-surface); overflow: hidden; border-radius: 2px; }
/* Doc: "Black-and-white portraits, plain backgrounds. Quiet style." */
.cr-team__card img {
	width: 100%; aspect-ratio: 5 / 6; object-fit: cover;
	filter: grayscale(1) contrast(1.08);
	/* headroom for the scroll drift in motion.css */
	scale: 1.04;
	transition: filter 700ms var(--cr-ease-quart);
}
.cr-team__card:hover img { filter: grayscale(0.1) contrast(1); }
.cr-team__cap { padding: clamp(1.1rem, 2.5vw, 1.5rem); }
.cr-team__cap .cr-label { margin-top: 0.5rem; }

/* Process strip: amber dot per step (doc calls for "ember dot accents"). */
.cr-process {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
	gap: clamp(1.25rem, 2.5vw, 2rem); margin-top: clamp(2.25rem, 5vw, 3.5rem);
}
.cr-process__step { border-top: 1px solid var(--cr-hair); padding-top: 1.4rem; position: relative; }
.cr-process__step::before {
	content: ''; position: absolute; top: -5px; left: 0;
	width: 10px; height: 10px; border-radius: 50%; background: var(--cr-amber);
}
.cr-process__no {
	font-weight: 700; font-size: 0.68rem; letter-spacing: 0.18em; color: var(--cr-amber);
}
.cr-process__step h3 { margin-block: 0.5rem 0.6rem; font-size: 1rem; }
.cr-process__step p { font-size: 0.92rem; color: var(--cr-fg-mute); }

/* ---- Journal index ---------------------------------------------- */
.cr-articles { margin-top: clamp(2rem, 4vw, 2.75rem); border-top: 2px solid var(--cr-accent-quiet); }
.cr-article {
	display: grid; grid-template-columns: 1fr; row-gap: 0.6rem;
	padding-block: clamp(1.5rem, 3vw, 2.25rem);
	border-bottom: 1px solid var(--cr-hair);
	transition: background var(--cr-dur-state) var(--cr-ease-quart),
	            padding-inline var(--cr-dur-state) var(--cr-ease-quart);
}
.cr-article:hover { background: var(--cr-amber-wash); padding-inline: clamp(0.75rem, 2vw, 1.5rem); }
.cr-article h2 { margin-bottom: 0.6rem; transition: color var(--cr-dur-state) var(--cr-ease-quart); }
.cr-article:hover h2 { color: var(--cr-accent); }
.cr-article__ex { max-width: 62ch; color: var(--cr-fg-mute); margin-bottom: 0.9rem; }
@media (min-width: 44rem) {
	.cr-article { grid-template-columns: 9rem 1fr; column-gap: clamp(1.5rem, 4vw, 3rem); row-gap: 0; }
}

/* ---- Article / generic page reading ----------------------------- */

.cr-article-page__media { margin: clamp(2rem, 4vw, 3rem) 0; border-radius: 2px; overflow: hidden; }
.cr-article-page__body { max-width: 42rem; }
.cr-article-page__body p { margin-top: 1.35rem; line-height: 1.75; color: var(--cr-fg-mute); }
.cr-article-page__body h2, .cr-article-page__body h3 {
	font-weight: 800; text-transform: uppercase; letter-spacing: -0.01em;
	margin-top: 2.5rem; color: var(--cr-fg);
}
.cr-article-page__body ul, .cr-article-page__body ol { margin-top: 1.2rem; padding-left: 1.35rem; color: var(--cr-fg-mute); }
.cr-article-page__body li { margin-top: 0.5rem; }
.cr-article-page__body blockquote {
	margin: 2.25rem 0; padding-left: 1.5rem;
	border-left: 2px solid var(--cr-amber);
	font-size: 1.15rem; color: var(--cr-fg);
}
.cr-article-page__body img { border-radius: 2px; margin-block: 1.85rem; }

/* ==================================================================
   JOURNAL ARTICLE
   ==================================================================
   The posts are 6,500 to 8,400 characters of real argument, and they
   were being rendered as one undifferentiated grey column down the
   left of a very wide dark page. Nothing said where a section began,
   nothing carried the eye down, and two thirds of the screen was
   empty. Everything below is type and layout doing that work. No
   ornament: an editorial page earns its life from rhythm, contrast
   and hierarchy, and a dark page shows up decoration very badly.
   ================================================================== */
.cr-post__head { margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
.cr-post__ttl { margin-top: clamp(0.75rem, 2vw, 1.25rem); max-width: 34ch; }

.cr-post__media {
	margin: 0 0 clamp(1.75rem, 4vw, 2.5rem);
	border-radius: var(--cr-r); overflow: hidden;
	background: var(--cr-ink-2);
}
.cr-post__media img { display: block; width: 100%; height: auto; }

/* ---- The prose ---------------------------------------------------
   `--cr-measure` is 62ch, which is right for a paragraph and slightly
   wide for a long read on a dark ground, where light type on dark
   makes a line feel longer than it is. 40rem lands around 58
   characters at this size.                                          */
/* The MEASURE SITS ON THE CHILDREN, not on this wrapper.
   It used to be `max-width: 40rem` here, which left a wide block nowhere
   to go: the editor's "wide width" button existed but every image stayed
   capped at the text measure. Widening a child of a 40rem box instead
   needs negative margins, and those overflow the page the moment the
   viewport is narrow (LESSONS #185). Moving the measure down one level is
   also what DESIGN.md already says: the container keeps its own width,
   children carry the measure.

   Anything NOT in this list is deliberately unmeasured, which is how the
   align rules below get their room. */
.cr-post__body { max-width: none; }
.cr-post__body > :where(p, h2, h3, h4, ul, ol, blockquote, figure, img, pre, hr, .cr-table-wrap, .wp-block-table) {
	max-width: 40rem;
}
/* Ties with the rule above on specificity (both 0,1,0), so it wins on
   source order alone. Keep it BELOW that rule. See LESSONS #194.
   `alignfull` is capped at the container, not the viewport: this block
   lives inside `.cr-container`'s gutter, so true full bleed would need
   the markup moved out of it, and a rule that silently overflowed the
   gutter would be worse than one that stops at it. */
.cr-post__body > :where(.alignwide) { max-width: min(56rem, 100%); }
.cr-post__body > :where(.alignfull) { max-width: 100%; }

.cr-post__body p { margin-top: 1.4rem; line-height: 1.78; color: var(--cr-fg-mute); }

/* The lead. The first paragraph is the promise the rest of the piece
   keeps, so it is set larger and at full strength, and the step down
   to body size is what tells the eye the article proper has started.
   `:first-of-type` not `:first-child`: the content sometimes opens
   with a figure, and then `:first-child` styles nothing. */
.cr-post__body > p:first-of-type {
	font-size: clamp(1.12rem, 1rem + 0.5vw, 1.35rem);
	line-height: 1.55; color: var(--cr-fg);
	margin-top: 0;
	text-wrap: pretty;
}

/* Headings announce a section. The generous space ABOVE and the tight
   space below are what group a heading with its own text instead of
   letting it float between two blocks. */
.cr-post__body h2 {
	margin-top: clamp(2.5rem, 5vw, 3.5rem);
	font-size: clamp(1.25rem, 1rem + 0.9vw, 1.65rem);
	font-weight: 800; letter-spacing: -0.01em;
	text-transform: none; color: var(--cr-fg);
	max-width: 30ch; text-wrap: balance;
}
/* A short amber rule above each heading, drawn with a pseudo-element so
   it costs no markup and no extra element in the editor's content. It
   is the same device the eyebrow uses elsewhere, which is why it reads
   as part of this site rather than as a blog theme's flourish. */
.cr-post__body h2::before {
	content: ''; display: block;
	width: 2.5rem; height: 2px; margin-bottom: 1.1rem;
	background: var(--cr-accent);
}
.cr-post__body h3 {
	margin-top: 2.25rem; font-size: 1.05rem;
	font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
	color: var(--cr-fg);
}
.cr-post__body ul, .cr-post__body ol {
	margin-top: 1.2rem; padding-left: 1.35rem; color: var(--cr-fg-mute);
	line-height: 1.7;
}
.cr-post__body li { margin-top: 0.55rem; }
.cr-post__body li::marker { color: var(--cr-accent); }
.cr-post__body a { color: var(--cr-accent); text-decoration: underline; text-underline-offset: 3px; }

/* A quote is a change of voice, so it breaks the measure and loses the
   left rule that every template uses for the same job. */
.cr-post__body blockquote {
	margin: clamp(2rem, 4vw, 2.75rem) 0;
	padding: 0; border: 0;
	font-size: clamp(1.2rem, 1rem + 0.7vw, 1.5rem);
	line-height: 1.4; color: var(--cr-fg);
	font-style: italic;
	max-width: 32ch;
}
.cr-post__body blockquote p { color: inherit; font-size: inherit; line-height: inherit; }
.cr-post__body img { border-radius: var(--cr-r); margin-block: 2rem; }
/* Sources and footnotes: smaller, and allowed to wrap long URLs rather
   than pushing the page sideways. */
.cr-post__body ol.cr-sources { font-size: 0.9rem; line-height: 1.6; }
.cr-post__body ol.cr-sources a { overflow-wrap: anywhere; }

/* ---- The rail ----------------------------------------------------- */
.cr-post__facts { display: grid; gap: 0.15rem; margin: 0; }
.cr-post__facts dt {
	font-weight: 700; font-size: 0.66rem; letter-spacing: 0.18em;
	text-transform: uppercase; color: var(--cr-accent-quiet);
	margin-top: 1rem;
}
.cr-post__facts dt:first-child { margin-top: 0; }
.cr-post__facts dd { margin: 0; font-size: 0.95rem; color: var(--cr-fg); }
.cr-post__back { margin-top: 1.75rem; }

@media (min-width: 64rem) {
	/*
	 * Rail left, article right. The rail is narrow on purpose: it holds
	 * three facts and a link, and widening it would invite something
	 * else to be put there.
	 *
	 * `align-items: start` is what allows the sticky below to work at
	 * all. The default `stretch` makes the rail as tall as the article,
	 * and a sticky element cannot move inside a box it already fills,
	 * so the position silently does nothing.
	 */
	.cr-post__grid {
		display: grid;
		grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
		column-gap: clamp(2.5rem, 6vw, 5rem);
		align-items: start;
	}
	.cr-post__rail {
		position: sticky;
		/* Clear the fixed nav, plus a little air. */
		top: calc(var(--cr-nav-h) + 1.5rem);
	}
	/* The reading column is centred in the space left over, so a very
	   wide screen does not strand the text against the rail with a
	   third of the page empty to its right. */
	.cr-post__main { justify-self: center; width: 100%; max-width: 46rem; }
}

/* ---- Keep reading ------------------------------------------------- */
.cr-post__nexth { margin-bottom: clamp(1.25rem, 3vw, 1.75rem); }
.cr-post__next {
	display: grid; gap: clamp(1rem, 2.5vw, 1.75rem);
	grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}
/* These are photographs and illustrations rather than logos on white, so
   unlike the portfolio tiles they may crop. */
.cr-post__next .cr-tile__media { aspect-ratio: 16 / 9; }
.cr-post__next .cr-tile__media img { object-fit: cover; }

@media (prefers-reduced-motion: reduce) {
	.cr-post__rail { position: static; }
}

/* ---- Case page --------------------------------------------------- */
.cr-case__title { margin-top: clamp(0.75rem, 2vw, 1.25rem); max-width: 16ch; }
/* Case hero: a contained band, not a full-bleed crop.
   `cover` was slicing the work. On the Oromo case it cut the wordmark on the
   pouch clean in half, and these heroes are product and packaging shots, not
   editorial photography with room to lose. A fixed band height keeps the
   full-width impact while `contain` guarantees nothing is cut. The height is
   explicit rather than an aspect-ratio so the grid track stays definite. */
.cr-case__media {
	display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr);
	height: clamp(15rem, 42vw, 38rem);
	padding: clamp(0.9rem, 2vw, 1.75rem);
	background: var(--cr-bone);
	overflow: hidden;
}
.cr-case__media img { width: 100%; height: 100%; object-fit: contain; }

/* The reading column is centred on a wide screen. Left-aligned inside a
   full-width shell, 62ch of prose read as a ribbon stranded against the edge
   with a thousand empty pixels beside it. Set on the inner prose, never on
   `.cr-case__body` itself: that shares an element with `.cr-container`, and a
   narrower max-width there re-centres the whole block and breaks the section. */
.cr-case__body .cr-prose { margin-inline: auto; }
@media (min-width: 64rem) {
	.cr-case__body .cr-prose { max-width: 52rem; }
}

/* ---- Elementor-built case bodies ----------------------------------
   The 16 case posts are still built in Elementor, so single project
   pages load its CSS (see cr_needs_elementor()). Two things have to be
   clawed back from it: the typeface, which its kit sets to Poppins, and
   the width, since its containers do not know about .cr-container.
   ------------------------------------------------------------------ */
.cr-prose .elementor,
.cr-prose .elementor :where(p, li, h1, h2, h3, h4, h5, h6, span, div) {
	font-family: var(--cr-font), system-ui, sans-serif;
}
/* Elementor sections default to a boxed width and their own padding.
   Inside our prose column both are already handled by the container. */
.cr-prose .elementor :where(.e-con, .elementor-container, .elementor-widget-container) {
	max-width: 100%;
	padding-inline: 0;
}
/* Never let a builder image outgrow the measure. */
.cr-prose .elementor img { max-width: 100%; height: auto; }

/* ---- Taming Elementor's image carousel ----------------------------
   The carousel showed one small slide with arrows, at a different size on
   every case, and its slide widths are written inline by Swiper so they do
   not respond to anything here. A case page does not need a slideshow: it
   needs to show the work. So the carousel is flattened into the same framed
   grid the rest of the site uses, and the controls are dropped.

   The !important flags are deliberate and confined to this block: they exist
   to beat Swiper's inline styles, which is the only way to override them
   without running its JS API. Once flattened there is nothing to swipe, so
   nothing is lost by hiding the arrows.
   ------------------------------------------------------------------ */
.cr-prose .elementor-image-carousel-wrapper,
.cr-prose .elementor-image-carousel-wrapper .swiper-wrapper {
	display: grid !important;
	grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
	gap: clamp(0.6rem, 1.5vw, 1rem);
	transform: none !important;
	width: auto !important;
	height: auto !important;
}
.cr-prose .elementor-image-carousel-wrapper .swiper-slide {
	width: auto !important;
	margin: 0 !important;
	transform: none !important;
	/* Same contained bone frame as the work gallery: these are packaging and
	   artwork shots, so cropping them is not an option. */
	display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr);
	aspect-ratio: 4 / 3; overflow: hidden;
	background: var(--cr-bone); border-radius: var(--cr-r);
	padding: clamp(0.6rem, 1.5vw, 1rem);
}
.cr-prose .elementor-image-carousel-wrapper .swiper-slide img {
	width: 100%; height: 100%; object-fit: contain;
}
.cr-prose .elementor-swiper-button,
.cr-prose .elementor-swiper-button-prev,
.cr-prose .elementor-swiper-button-next,
.cr-prose .elementor-image-carousel-wrapper .swiper-pagination { display: none !important; }

/* ---- Case work gallery -------------------------------------------
   These are deliverables, not photography: logos, packaging mockups,
   publication spreads, app screens, most of them artwork on white.
   So each shot is CONTAINED in a framed bone box, never cropped.
   `cover` would slice a logo or crop a spread's margin off.
   ------------------------------------------------------------------ */
.cr-case__gallery {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
	gap: clamp(0.85rem, 2vw, 1.5rem);
	margin-top: clamp(2.25rem, 4vw, 3rem);
}
/* `minmax(0, 1fr)`, and the zero matters.
   The frame takes its height from `aspect-ratio`, but a grid track defaults to
   a min of `auto`, i.e. min-content, so the row grew to the image's intrinsic
   height (520px inside a 428px frame) and `overflow: hidden` cropped the
   bottom off every landscape shot. Allowing the track to shrink below
   min-content gives the item a definite area, so `height: 100%` resolves and
   `contain` letterboxes instead of overflowing. */
.cr-case__shot {
	display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr);
	aspect-ratio: 4 / 3; overflow: hidden;
	background: var(--cr-bone); border-radius: var(--cr-r);
	padding: clamp(0.85rem, 2vw, 1.5rem);
	margin: 0;
}
.cr-case__shot img { width: 100%; height: 100%; object-fit: contain; }

/* The LEAD shot goes wide, and only the lead.
   This was every third shot, which is what put holes in the grid: a
   double-width card every three cards does not divide into a column
   count that changes with the viewport, so at four columns the run
   came out as [wide][1][1] / [wide][1][gap] with a dead cell inside
   the grid. Widening only the first leaves the arithmetic alone, so
   the only short row is the last one, which is what a grid is meant
   to look like. */
@media (min-width: 52rem) {
	.cr-case__gallery > .cr-case__shot:first-child {
		grid-column: span 2; aspect-ratio: 16 / 9;
	}
}

/* Lead figure sits inside the prose, so it carries the body measure
   rather than the container's. */
.cr-case__lead { margin: clamp(1.85rem, 4vw, 2.5rem) 0; }
.cr-case__lead img { width: 100%; height: auto; border-radius: 3px; display: block; }
.cr-case__lead figcaption {
	margin-top: 0.7rem;
	font-weight: 600; font-size: 0.72rem; letter-spacing: 0.14em;
	text-transform: uppercase; color: var(--cr-fg-mute);
}

/* Stat trio: the case page's proof device. */
.cr-stats {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
	gap: clamp(1.25rem, 3vw, 2.5rem); margin-top: clamp(2.5rem, 5vw, 3.5rem);
}
.cr-stat { padding-top: 1.25rem; position: relative; }
.cr-stat::before {
	content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
	background: var(--cr-amber);
}
.cr-stat__fig {
	display: block;
	font-weight: 900; font-size: var(--cr-fs-figure);
	line-height: 0.86; letter-spacing: -0.035em; color: var(--cr-amber);
}
.cr-stat__cap {
	display: block; margin-top: 0.7rem;
	font-weight: 700; font-size: 0.68rem; letter-spacing: 0.16em;
	text-transform: uppercase; color: var(--cr-fg-mute);
}
/* "The work": 3-part breakdown */
.cr-steps { margin-top: clamp(2rem, 4vw, 2.75rem); border-top: 1px solid var(--cr-hair); }
.cr-step {
	display: grid; grid-template-columns: 1fr; row-gap: 0.7rem;
	padding-block: clamp(1.4rem, 3vw, 2rem); border-bottom: 1px solid var(--cr-hair);
}
.cr-step h3 { font-size: 0.95rem; letter-spacing: 0.1em; }
.cr-step p { color: var(--cr-fg-mute); max-width: 58ch; }
@media (min-width: 44rem) {
	.cr-step { grid-template-columns: 14rem 1fr; column-gap: clamp(1.5rem, 4vw, 3rem); row-gap: 0; }
}
.cr-embed { position: relative; padding-top: 56.25%; margin-inline: auto; max-width: 62rem; }
.cr-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; border-radius: 2px; }
.cr-upnext {
	display: grid; grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
	gap: clamp(1.25rem, 3vw, 2rem); margin-top: clamp(2.25rem, 4vw, 3rem);
}

/* ---- Contact ----------------------------------------------------- */
.cr-contact__grid { display: grid; grid-template-columns: 1fr; gap: clamp(2.5rem, 6vw, 5rem); align-items: start; }
@media (min-width: 56rem) {
	/* 45/55 rather than 50/50: the form is the point. */
	.cr-contact__grid { grid-template-columns: 45fr 55fr; }
}
.cr-brief {
	display: flex; flex-direction: column; gap: 1.2rem;
	background: var(--cr-ink-2); padding: clamp(1.4rem, 3.5vw, 2.5rem);
	border: 1px solid var(--cr-ink-3); border-top: 2px solid var(--cr-amber);
	border-radius: 2px;
}
.cr-field { display: flex; flex-direction: column; gap: 0.45rem; }
.cr-field > span {
	font-weight: 700; font-size: 0.68rem; letter-spacing: 0.16em;
	text-transform: uppercase; color: var(--cr-white);
}
.cr-field > span i { color: var(--cr-amber); font-style: normal; }
.cr-field input, .cr-field textarea {
	min-height: 44px;
	background: var(--cr-ink); border: 1px solid var(--cr-ink-3); border-radius: 2px;
	padding: 0.8em 1em; color: var(--cr-white);
	transition: border-color var(--cr-dur-state) var(--cr-ease-quart);
}
.cr-field textarea { min-height: 7rem; resize: vertical; }
.cr-field input::placeholder, .cr-field textarea::placeholder { color: var(--cr-mute-ink); }
.cr-field input:focus, .cr-field textarea:focus { outline: none; border-color: var(--cr-amber); }
.cr-field__err { display: none; color: var(--cr-amber); font-size: 0.82rem; font-style: normal; }
.cr-field.is-error .cr-field__err { display: block; }
.cr-field.is-error input, .cr-field.is-error textarea { border-color: var(--cr-amber); }
/* ---- Consent checkbox --------------------------------------------
   A real checkbox, not a styled span with a hidden input: the native
   control already carries the checked state to assistive tech and to
   autofill, and a consent control is the last place to reinvent that.
   `accent-color` tints it amber in every current browser for one line.

   Two columns so the sentence wraps against the text, not under the
   box. `align-items: start` because the label runs to two or three
   lines and a centred box drifts away from its first line. */
.cr-optin {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 0.7rem;
	align-items: start;
	cursor: pointer;
}
.cr-optin input[type='checkbox'] {
	inline-size: 1.15rem; block-size: 1.15rem;
	/* `min-block-size: 0` is REQUIRED, not tidiness. `.cr-field input` sets
	   `min-height: 44px` for text inputs, and a min always beats a smaller
	   height, so without this the box renders 18x44: correct width, stretched
	   into a tall rectangle. Setting `block-size` alone does not fix it. */
	min-block-size: 0;
	margin-block-start: 0.15rem;
	accent-color: var(--cr-amber);
	cursor: pointer;
	/* The 44px touch target comes from the LABEL, which is the whole row and is
	   taller than that already; growing the box itself would look clumsy. */
}
.cr-optin > span {
	font-size: 0.88rem; line-height: 1.55;
	color: var(--cr-fg-mute);
	/* Overrides `.cr-field span`, which is the bold uppercase field label
	   treatment and is wrong for a sentence. Placed after that rule in this
	   file, and it must stay after it: same specificity. See LESSONS #194. */
	font-weight: 400; letter-spacing: 0; text-transform: none;
}
.cr-optin a { color: var(--cr-amber); text-decoration: underline; text-underline-offset: 3px; }
.cr-optin i { color: var(--cr-amber); font-style: normal; }
.cr-optin .cr-field__err { grid-column: 2; }

/* The `.cr-budget*` radio-pill rules were deleted with the budget field itself.
   See template-contact.php: the USD bands were wrong for this market and a
   budget question on first contact costs leads. If a pill-style radio group is
   ever wanted again, note that `.cr-pill` already exists for buttons. */
.cr-brief__note { color: var(--cr-amber); font-size: 0.92rem; }
.cr-brief__note:not(.is-shown) { display: none; }
.cr-brief button { align-self: flex-start; }

/* ---- 404 --------------------------------------------------------- */
.cr-404 { min-height: 76svh; display: flex; align-items: center; }

/* ==================================================================
   CASE BREAKDOWN — the case page as a route walked
   ==================================================================
   One idea: a studio takes a client from a problem to a delivered
   thing, and the page should let you feel the distance covered. The
   brief is what was handed over, the approach is the walk itself, the
   deliverables are what came back.

   Guard rail: this is a portfolio, so the WORK stays the loudest thing
   on screen. Motion carries the eye between sections; it never
   competes with the artwork.
   ================================================================== */
.cr-case__breakdown { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
.cr-case__intro { max-width: var(--cr-measure); }

/* ---- The brief -------------------------------------------------
   Third attempt. The first was a bone card, which read as a pale slab
   dropped into a dark page. The second put a 2px amber rule down the
   left, which is the accent-bar-on-a-block pattern every template
   ships: it looked designed by default rather than on purpose.

   So: no bar, no box, no background. The brief earns its weight from
   type alone, and it is labelled with the site's OWN eyebrow device
   (label plus a hairline to the edge) which every other section here
   already uses, so it belongs to this page instead of decorating it.

   The `00` is not ornament. The approach waypoints below are numbered
   01 upward, and the brief is genuinely what comes before the first
   move, so numbering it zero states the real sequence.

   It is CONTENT: there on load, no reveal, nothing to wait for.
   ------------------------------------------------------------------ */
.cr-dossier { display: grid; gap: clamp(1rem, 2.5vw, 1.5rem); }
.cr-dossier__n {
	color: var(--cr-accent);
	font-variant-numeric: tabular-nums;
	margin-inline-end: 0.6em;
}
.cr-dossier__sheet p {
	font-size: clamp(1.2rem, 1rem + 0.85vw, 1.7rem);
	line-height: 1.38;
	letter-spacing: -0.01em;
	/* Full strength, not --cr-fg-mute: after the title this is the
	   loudest text on the page, because it is the commission. */
	color: var(--cr-fg);
	max-width: 44ch;
	text-wrap: pretty;
}

/* ---- The walk ----------------------------------------------------
   The trail is ONE walk down the whole route, not a run that restarts
   at each waypoint: a walk that resets every section reads as several
   short walks.

   On a wide screen the path runs down the MIDDLE of the page and the
   waypoints flank it, left, right, left. That is the difference between
   a map with a route drawn on it and a list with a decorated margin.
   The page is narrow on a phone, so there the path keeps its old lane
   at the left and the waypoints stay in one column beside it.

   And the path CURVES. Three numbers set it, all of them here:

     --cr-vine-s  the stride, one footfall to the next
     --cr-vine-a  how far the path swings either side of its centre
     --cr-vine-f  how far a foot lands off the path, since a walk is two

   Widen the swing and the prints tilt further into the bend on their
   own, because the tilt is computed FROM it (see `.cr-fp`). The 36deg
   of phase per print, one full swing every ten footfalls, is fixed in
   the template where the curve is sampled.

   The lane is DERIVED from those, never typed: it has to hold the swing,
   plus the step off the path, plus half a print, and a hand-written
   number there is a number that goes stale the first time the swing
   changes. Get it too small and `overflow: clip` starts shaving toes.

   That last term is 1rem, not the half-width of 0.65rem it looks like it
   should be, because a print is TILTED. Rotating a 1.3 by 2rem box 22deg
   gives it a half-extent of 0.65cos22 + 1sin22, which is 0.97rem, and
   measured at 390px the 0.65 version really did clip a pixel off the
   outermost toes.
   ------------------------------------------------------------------ */
.cr-walk__head, .cr-haul__head, .cr-evidence__head { margin-bottom: clamp(1.25rem, 3vw, 1.75rem); }
.cr-walk__body {
	position: relative;
	--cr-vine-s: 4.7rem;
	--cr-vine-a: 0.65rem;
	--cr-vine-f: 0.65rem;
	--cr-vine-w: calc(2 * (var(--cr-vine-a) + var(--cr-vine-f) + 1rem));
}
.cr-route {
	list-style: none; margin: 0;
	padding-inline-start: calc(var(--cr-vine-w) + 1.1rem);
	display: grid; gap: clamp(1.75rem, 4vw, 2.75rem);
	max-width: calc(var(--cr-measure) + var(--cr-vine-w) + 1.1rem);
}
.cr-route__step { position: relative; }
.cr-route__no {
	display: block; font-weight: 800; font-size: var(--cr-fs-label);
	letter-spacing: 0.06em; font-variant-numeric: tabular-nums;
	color: var(--cr-accent);
}
.cr-route__ttl {
	font-weight: 700; font-size: 0.85rem; letter-spacing: 0.13em;
	text-transform: uppercase; margin-top: 0.2rem;
}
.cr-route__txt { color: var(--cr-fg-mute); margin-top: 0.35rem; text-wrap: pretty; }

.cr-trail {
	position: absolute; top: 0.35rem; bottom: 0; left: 0;
	width: var(--cr-vine-w);
	/* `clip`, NOT `hidden`. Both cut the prints that run past the end of
	   the route, but overflow:hidden makes this a scroll container, and
	   then every print's view() timeline resolves against THIS box
	   instead of the page. It never scrolls, so the whole walk freezes
	   fully printed. Any ancestor with overflow:hidden does the same. */
	overflow: clip;
	pointer-events: none;
	color: var(--cr-accent);
}
/*
 * Safari only got `overflow: clip` in 16, and iOS 15 is still in the wild.
 * There the declaration above is dropped, the lane clips nothing, and the
 * prints that run past the end of the route walk straight over the copy.
 *
 * `hidden` is safe in exactly those browsers and nowhere else: the freeze it
 * causes is a scroll-driven-animation problem (a scroll container makes every
 * print's `view()` timeline measure a box that never scrolls), and any engine
 * without `overflow: clip` also has no `animation-timeline`, so it is already
 * running the JavaScript walk, which does not care.
 */
@supports not (overflow: clip) {
	.cr-trail { overflow: hidden; }
}

/* The stem: the path itself, drawn, so the prints are walking ON
   something instead of merely drifting in formation.

   It is stretched from a unitless viewBox to the real swing and stride,
   and those two are different numbers, so it is a NON-UNIFORM scale. A
   stroke would come out of it as if drawn with an elliptical nib, which
   is what `vector-effect="non-scaling-stroke"` on the path opts out of.

   `top: 1rem` is half a print's height: it drops the stem onto the line
   through the prints' CENTRES rather than their top edges. */
.cr-vine {
	position: absolute; left: 50%; top: 1rem;
	translate: -50% 0;
	width: calc(2 * var(--cr-vine-a));
	height: calc(var(--n) * var(--cr-vine-s));
	fill: none;
	stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;
	/* Faint on purpose. The prints are the walk and stay at full
	   strength; the stem only has to say the path was there first. */
	opacity: 0.28;
}

.cr-fp {
	position: absolute; left: 50%; width: 1.3rem; height: 2rem;
	/* fill on the <svg>, not on `.cr-fp path`: a CSS selector cannot
	   reach inside a <use> shadow tree, so a descendant rule silently
	   never matches and every print draws black. `fill` inherits, so
	   setting it on the host does cross the boundary. */
	fill: currentColor;
	/*
	 * Stride, and it is the whole reason the walk reads or does not.
	 *
	 * At a 42px stride there were 28 prints down a 628px route. One mouse
	 * wheel notch is roughly 100px, so a normal scroll skipped two or
	 * three footfalls at a time and the trail read as a static dotted
	 * line: mechanically animating, perceptually still. Fewer prints,
	 * further apart, means roughly ONE footfall per notch, which is what
	 * reads as walking.
	 */
	top: calc(var(--i) * var(--cr-vine-s));
	/*
	 * Placement lives in the `translate` PROPERTY, never inside
	 * `transform`. The individual properties apply in a fixed order,
	 * translate then rotate then scale then whatever `transform` says,
	 * so a translate written into `transform` would be spun by the tilt
	 * below and every print would slide off the path it is standing on.
	 *
	 * `--x` is this print's place along the swing, -1 to 1. `--sd` steps
	 * the foot off the stem, one either side, because a walk is two feet.
	 */
	translate: calc(var(--x) * var(--cr-vine-a) + var(--sd) * var(--cr-vine-f) - 50%) 0;
	/*
	 * Tilt into the bend. `--dx` is the curve's slope at THIS print, in
	 * swings per stride, so the angle is measured off the actual path
	 * rather than guessed: widen `--cr-vine-a` and every print leans
	 * further over on its own. Negated because CSS rotates clockwise
	 * while the page's y axis runs down.
	 *
	 * If atan2() is missing the declaration is dropped and the prints
	 * simply stand upright on the curve. Nothing else depends on it.
	 */
	rotate: calc(-1 * atan2(var(--dx) * var(--cr-vine-a), var(--cr-vine-s)));
	/*
	 * Toe-out, and the mirror that makes a left foot from a right one,
	 * because the two are reflections and not the same stamp shifted.
	 * Signs follow the glyph, whose ball leads at the bottom, so a
	 * positive angle swings the toes left; flip the glyph and these flip
	 * with it or every print toes inward.
	 *
	 * No vertical nudge on the second foot. Each print already has its
	 * own top, so adding half a stride to every other one did not
	 * stagger the gait, it bunched the prints into pairs: measured gaps
	 * ran 60, 20, 60, 20px instead of an even cadence.
	 */
	transform: rotate(calc(var(--sd) * -7deg)) scaleX(calc(var(--sd) * -1));
}

@keyframes cr-stamp { from { opacity: 0; scale: 0.62; } to { opacity: 1; scale: 1; } }

/* Hiding is gated behind `.cr-js`, added by main.js first thing, so a
   JS failure or a no-JS visit leaves the whole walk printed. */
.cr-js .cr-fp { opacity: 0; scale: 0.62; }
.cr-js .cr-fp.is-in { opacity: 1; scale: 1; transition: opacity 260ms var(--cr-ease-expo), scale 260ms var(--cr-ease-expo); }

@supports (animation-timeline: view()) {
	/*
	 * The prints are TIED TO SCROLL POSITION, not triggered by it: scroll
	 * down and the walk advances, scroll back up and it retreats, because
	 * the scroll offset IS the animation's clock.
	 *
	 * Longhands only. The `animation` shorthand resets animation-timeline
	 * to auto, so a shorthand written after it silently kills the link.
	 */
	.cr-js .cr-fp {
		animation-name: cr-stamp;
		animation-fill-mode: both;
		animation-timing-function: linear;
		/* The 26% inset lifts the footfall line off the bottom edge, which
		   is the least looked-at strip on screen. */
		animation-timeline: view(block auto 26%);
		/*
		 * How long ONE print takes to land, as a share of its full pass.
		 * This was 32%, about 230px of scroll, and that was the mistake:
		 * a print crossfading over a third of the viewport does not read
		 * as a footfall at all, it reads as nothing happening. A footfall
		 * has to be CRISP. The walk is made slow by the stride above, not
		 * by smearing each print out.
		 */
		animation-range: cover 0% cover var(--cr-walk-ease, 8%);
	}
	.cr-js .cr-fp.is-in { transition: none; }
}

/* ---- Wide screens: the page stops hugging its left edge -----------
   The breakdown sits in a full-bleed container, so every block in it
   was a ribbon of text against the left with the right two thirds of
   the screen empty. Two changes take that space back, and neither one
   invents any content to fill it with:

   1. The setup runs SIDE BY SIDE. The intro says who the client is and
      the brief says what they asked for; they are one thought, and the
      pair fills the width that either alone could not.
   2. The walk moves to the CENTRE and the waypoints flank it. The path
      is then down the middle of the page with 01 to the left of it, 02
      to the right, and so on down.

   Both text columns keep a real measure. The point is to use the width,
   not to run a 120-character line across it.
   ------------------------------------------------------------------ */
@media (min-width: 62rem) {
	.cr-case__breakdown {
		grid-template-columns: 1fr 1fr;
		column-gap: clamp(2rem, 5vw, 4.5rem);
		align-items: start;
	}
	/* Everything is full width by default; only the pair below opts out.
	   Anything added to the breakdown later inherits the safe case. */
	.cr-case__breakdown > * { grid-column: 1 / -1; }
	.cr-case__breakdown > .cr-case__intro { grid-column: 1; max-width: none; }
	/* Sibling selector, not `:nth-child(2)`: a case with no intro has to
	   get a full-width brief rather than a brief stranded in column two
	   with a hole beside it. */
	.cr-case__intro + .cr-dossier { grid-column: 2; }

	.cr-walk__body { --cr-vine-a: 3rem; --cr-vine-f: 0.9rem; }
	.cr-walk__head { text-align: center; }
	/* The lane is the page's centre line now, not a left margin. */
	.cr-trail { left: 50%; translate: -50% 0; }

	.cr-route { padding-inline-start: 0; max-width: none; }
	/* One waypoint per row, each filling the half not taken by the lane,
	   so the two sides meet the path from either side at every step.
	   The 1.4rem is a real gutter, not padding for looks: the prints
	   swing right out to the lane edge, so flush columns put a footprint
	   on top of the first letter of every other waypoint. */
	.cr-route__step { width: calc(50% - var(--cr-vine-w) / 2 - 1.4rem); }
	.cr-route__step:nth-child(odd)  { justify-self: start; text-align: right; }
	.cr-route__step:nth-child(even) { justify-self: end; }
	/* The measure lives on the CHILDREN, so on a very wide screen the
	   text stays readable AND stays against the path. Capping the step
	   itself would have parked it out at the page edge instead, with the
	   gap reopening beside the walk. */
	.cr-route__step > * { max-width: min(32rem, 100%); }
	.cr-route__step:nth-child(odd) > * { margin-inline-start: auto; }
}

/* ---- Deliverables, as the haul ----------------------------------- */
.cr-haul__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: clamp(0.7rem, 1.6vw, 1rem);
	grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}
.cr-haul__it {
	background: var(--cr-bone); color: var(--cr-ink);
	border: 1px solid var(--cr-hair-bone);
	border-radius: var(--cr-r);
	padding: clamp(0.9rem, 2vw, 1.2rem);
	display: grid; gap: 0.5rem; align-content: start;
	transition: transform var(--cr-dur-state) var(--cr-ease-quart),
	            box-shadow var(--cr-dur-state) var(--cr-ease-quart);
}
/* A restrained lift, mirroring `.cr-tile`. No zoom: these are labels. */
.cr-haul__it:hover { transform: translateY(-3px); box-shadow: 0 8px 20px -10px rgb(0 0 0 / 0.5); }
.cr-haul__n {
	font-weight: 800; font-size: 0.66rem; letter-spacing: 0.18em;
	font-variant-numeric: tabular-nums; color: var(--cr-ember);
}
.cr-haul__t { font-weight: 700; font-size: 0.94rem; line-height: 1.3; }

/* ---- The work, framed -------------------------------------------
   Same contained bone frames the rest of the site uses. A landscape
   spread reads better across the full measure than boxed into one
   cell, so every third shot spans two columns where there is room. */
.cr-evidence__grid {
	display: grid; gap: clamp(0.85rem, 2vw, 1.5rem);
	/*
	 * The cards size themselves off the DEVICE, not off a breakpoint
	 * list: `auto-fit` picks the column count the viewport can actually
	 * hold, and the `min(…, 100%)` is what makes a phone honest. Without
	 * it a 17rem floor is wider than a 375px screen minus its padding,
	 * so the track overflows its own container and the page scrolls
	 * sideways. That guard is the whole reason a single column happens
	 * on a phone and four happen on a desktop with nothing keyed to a
	 * particular handset.
	 */
	grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
	/* Backfill anything the wide lead card leaves beside it. */
	grid-auto-flow: dense;
}
@media (min-width: 52rem) {
	.cr-evidence__grid > .cr-case__shot:first-child {
		grid-column: span 2; aspect-ratio: 16 / 9;
	}
}

/* ---- Frosted hero band ------------------------------------------
   The artwork is CONTAINED, never cropped, so anything that is not the
   band's ratio leaves dead cream either side. That space is filled
   with a blurred enlargement of the same image, so the band reads as
   an extension of the artwork rather than a box it sits in.

   `isolation: isolate` is load bearing: without it the knockout below
   blends against the whole page instead of the frost.
   ----------------------------------------------------------------- */
/*
 * Shared by the hero band AND every work shot, because the problem is the
 * same in both: the artwork is contained rather than cropped, so anything
 * that is not the frame's ratio leaves dead cream either side.
 *
 * `position: relative` is required, not decorative. Without it the frost
 * resolves `inset: 0` against a different containing block entirely, escapes
 * the frame's `overflow: hidden`, and its scale() then put 40px of horizontal
 * page scroll onto every case page at 375px.
 *
 * `isolation` is also load bearing: without it the knockout further down
 * blends against the whole page instead of against the frost.
 */
.cr-case__media,
.cr-case__shot {
	position: relative;
	isolation: isolate;
}
.cr-case__media::before,
.cr-case__shot::before {
	content: ''; position: absolute; z-index: 0;
	/*
	 * The frost box is GROWN past the frame by three times the blur radius,
	 * and that number is the fix for a real bug rather than a margin for
	 * comfort. `filter: blur()` has nothing to sample beyond the element's
	 * own edge, so it fades that edge to transparent, and the fade reaches
	 * about three radii in. At `inset: 0` the fade sat inside the frame, the
	 * near-black page showed through it, and every card carried a dark
	 * vignette. On a `darken` card it was worse than cosmetic: the blend
	 * keeps the darker of the two layers, so the vignette got locked in and
	 * a black product on white paper came out as a silver flare in a black
	 * surround.
	 *
	 * This replaces `transform: scale(1.22)`, which was aiming at the same
	 * problem and could not reach it: 1.22 of a 320px card is 35px of cover
	 * against a 30px blur that bleeds nearer 90px. Deriving the growth from
	 * the blur means it stays correct at both sizes instead of at one.
	 */
	inset: calc(-3 * var(--cr-frost-blur, 44px));
	background: var(--cr-hero-bg) center / cover no-repeat;
	/* Blur scales with the frame: 44px across a full-bleed band is a wash,
	   the same 44px across a 400px shot is a smear. */
	filter: blur(var(--cr-frost-blur, 44px)) saturate(125%);
}
/*
 * On a work shot the frost is always LIFTED, not just on paper artwork.
 * Visibility of the work comes first: an unlifted frost takes its tone from
 * the image, so a mockup shot on mid-grey produced a mid-grey field with a
 * grey product sitting invisibly in it. Keeping the frost brighter than the
 * artwork guarantees the deliverable reads, and it still carries the image's
 * own hue, which is the whole point of drawing it from the image.
 */
.cr-case__shot::before {
	--cr-frost-blur: 30px;
	filter: blur(var(--cr-frost-blur)) saturate(80%) brightness(1.42);
}
.cr-case__media img,
.cr-case__shot img { position: relative; z-index: 1; }

/*
 * Knocking the white paper out, on artwork shot on white only.
 *
 * `darken` keeps the per-channel minimum, and the frost is lifted above
 * the artwork's own tones, so every pixel darker than the frost survives
 * untouched and only the near-white paper is replaced. That distinction
 * is the whole trick: `multiply` also removes the white, but it darkens
 * what it keeps, and it turned Nairobi City Stars' blue sleeves green.
 *
 * The lift is not a taste call, it is what makes `darken` safe. Measured
 * mean drift across that jersey against its true colour: 5.0/255 at
 * brightness 1.22, 1.2 at 1.6. Below is the 1.2, which is also why the
 * frost is pale here rather than a bold field.
 *
 * Which cases get it is measured, not assumed: see cr_hero_ground().
 */
/*
 * On paper the frost is a FLAT WHITE, not a blurred enlargement.
 *
 * Drawing the ground from the image is the whole idea everywhere else, and on
 * paper it is the one place the idea has nothing to give: the ground already
 * IS white, so a blur of the artwork only contributes the subject's own tone.
 * A lift was standing in for that, and a single lift cannot work, because how
 * far it gets depends on the artwork. Measured across one case: the white
 * polos sit at mean luminance 243, so `brightness(1.6)` clips them to white
 * and the card comes out clean; the black laptop bag sits at 139, the same
 * lift reaches about 130, and `darken` keeps the darker layer, so the
 * photograph's OWN white paper was dragged down to mid-grey and a black bag
 * ended up in grey mud. Same rule, same case page, opposite results.
 *
 * Flat white fixes it for every image at once, and it makes `darken` exact
 * rather than merely safe: min(artwork, 255) IS the artwork, so the drift the
 * lift was there to keep small becomes zero and the blue sleeves stay blue.
 * The blend still earns its place, it takes the seam between the photograph's
 * near-white paper and the frame out.
 */
.cr-case__media--paper::before,
.cr-case__shot--paper::before {
	background: #fff;
	filter: none;
}
.cr-case__media--paper img,
.cr-case__shot--paper img { mix-blend-mode: darken; }

/*
 * And the exact mirror, for artwork shot on black seamless.
 *
 * The training bibs on the Nairobi City Stars case were the case for it: shot
 * on black, so the paper test said no and the frost was LIFTED as usual, which
 * left a hard-edged black rectangle sitting in a pale field. The black was the
 * first thing the eye went to on a page about a football crest.
 *
 * `lighten` keeps the per-channel maximum, and the frost is pushed below every
 * tone in the artwork, so each pixel brighter than the frost survives untouched
 * and only the near-black ground is replaced. Same argument as `darken` above,
 * read the other way up. The frost lands near the page's own ink, so the frame
 * stops being a box and the work simply sits on the page.
 */
.cr-case__media--ink::before,
.cr-case__shot--ink::before { filter: blur(var(--cr-frost-blur, 44px)) saturate(55%) brightness(0.32); }
.cr-case__media--ink img,
.cr-case__shot--ink img { mix-blend-mode: lighten; }

@media (prefers-reduced-motion: reduce) {
	/* The finished composition at rest: every print already stamped, the
	   whole walk visible. Not a degraded page, just a still one. */
	.cr-js .cr-fp { opacity: 1 !important; scale: 1 !important; animation: none !important; transition: none !important; }
	.cr-haul__it { transition: none; }
}

/* ---- Team monogram ------------------------------------------------
   Stands in for a portrait that does not exist yet. Deliberately reads
   as a typographic placeholder rather than as an image: these are real
   named colleagues, and the previous stock photographs put strangers'
   faces against their names under a heading reading "Real people, not
   stock". An honest blank beats a convincing wrong.

   Matches the portrait aspect ratio the real photography will use, so
   dropping an <img> back in changes nothing about the layout.
   ------------------------------------------------------------------ */
.cr-team__mono {
	display: grid; place-items: center;
	aspect-ratio: 5 / 6; width: 100%;
	background: var(--cr-ink-2);
	border: 1px solid var(--cr-hair-ink);
	border-radius: var(--cr-r);
	font-weight: 900; line-height: 1;
	font-size: clamp(2.5rem, 7vw, 4.5rem);
	letter-spacing: 0.02em;
	color: var(--cr-amber);
	user-select: none;

	/* The SAME device and timing the portraits used: grayscale at rest,
	   full colour on hover, `filter 700ms var(--cr-ease-quart)`. Reusing it
	   means the card is not inert while it waits for a real headshot, and
	   when a photograph replaces this span the interaction is already right
	   rather than something to invent then. Grayscaling the amber instead of
	   hardcoding a grey keeps ONE source for the colour. */
	filter: grayscale(1);
	opacity: 0.42;
	transition: filter 700ms var(--cr-ease-quart),
	            opacity 700ms var(--cr-ease-quart);
}
.cr-team__card:hover .cr-team__mono,
.cr-team__card:focus-within .cr-team__mono {
	filter: grayscale(0);
	opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
	.cr-team__mono { transition: none; }
}

/* ==================================================================
   LEGAL / LONG-FORM DOCUMENT  (Privacy Notice and similar)
   ==================================================================
   A privacy notice has to be intelligible and easily accessible, which
   is a legal requirement rather than a design preference, so these
   rules are doing compliance work: readable tables, a navigable
   contents list, and a measure that does not strand 2,400 words of
   legal text in a ribbon down the left of a very wide dark page.
   ================================================================== */
.cr-doc__head { margin-bottom: clamp(1.75rem, 4vw, 2.5rem); }

/* ---- Width -------------------------------------------------------
   The document fills the column it is given. It was capped at 42rem,
   which left most of a 1440px page empty beside a legal notice that is
   mostly tables, and tables are exactly the content that benefits from
   width: three columns of retention periods in a 42rem box wrap every
   cell onto three lines.

   So: the container is full width, TABLES and lists take all of it,
   and only the running PROSE keeps a ceiling. That ceiling is not a
   stylistic preference. Past roughly 90 characters a line gets long
   enough that the eye loses its place on the return sweep and starts
   re-reading or skipping lines, which on a document a visitor is
   consulting for their legal rights is the one failure that matters.
   90ch at this size lands near 1000px, so on a 1440px screen the prose
   very nearly fills the column anyway and nothing looks stranded.
   ------------------------------------------------------------------ */
.cr-doc__body { max-width: none; }
.cr-doc__body :where(p, ul, ol, blockquote) { max-width: 90ch; }
/* Tables, figures and headings get the full measure of the column. */
.cr-doc__body :where(figure, table, h2, h3) { max-width: none; }

/* ---- Contents rail ----------------------------------------------- */
.cr-doc__toch { color: var(--cr-accent); margin-bottom: 0.9rem; }
.cr-doc__toc ol {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: 0.1rem;
	counter-reset: cr-toc;
	border-top: 1px solid var(--cr-hair);
}
.cr-doc__toc li { counter-increment: cr-toc; border-bottom: 1px solid var(--cr-hair); }
.cr-doc__toc a {
	display: flex; gap: 0.6rem; align-items: baseline;
	padding-block: 0.55rem;
	font-size: 0.88rem; line-height: 1.35;
	color: var(--cr-fg-mute);
	transition: color var(--cr-dur-state) var(--cr-ease-quart);
}
/* The number comes from the counter, not from the heading text, which is
   why the PHP strips the "1." prefix before printing: otherwise every
   entry would read "1. 1. Who we are". */
.cr-doc__toc a::before {
	content: counter(cr-toc);
	flex: none; min-width: 1.4em;
	font-variant-numeric: tabular-nums;
	font-weight: 800; font-size: 0.72rem;
	color: var(--cr-accent-quiet);
}
.cr-doc__toc a:hover { color: var(--cr-fg); }

@media (min-width: 64rem) {
	.cr-doc__grid {
		display: grid;
		grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
		column-gap: clamp(2.5rem, 6vw, 5rem);
		align-items: start;
	}
	/* Sticky so the contents stay reachable through a long read.
	   `align-items: start` above is what makes this work at all: the
	   default `stretch` gives the rail the full height of the document
	   and a sticky element cannot move inside a box it already fills. */
	.cr-doc__toc {
		position: sticky;
		top: calc(var(--cr-nav-h) + 1.5rem);
		max-height: calc(100svh - var(--cr-nav-h) - 3rem);
		overflow-y: auto;
	}
}

/* ---- Prose tables ------------------------------------------------
   The notice is mostly tables, and left unstyled the browser default
   draws a white 2px outset border around every cell, which on a
   near-black page reads as a grid of white boxes. Hairlines and real
   padding instead.

   The wrapper scrolls rather than the page: a three-column table of
   retention periods cannot always fit 375px, and the rule everywhere
   else on this site is that wide content scrolls inside its own box
   and the page never scrolls sideways.
   ------------------------------------------------------------------ */
.cr-prose :where(figure.wp-block-table, .cr-table-wrap) {
	margin: clamp(1.5rem, 3vw, 2rem) 0;
	overflow-x: auto;
	/* Momentum scrolling on iOS without trapping the vertical gesture. */
	-webkit-overflow-scrolling: touch;
}
.cr-prose table {
	width: 100%; border-collapse: collapse;
	font-size: 0.94rem; line-height: 1.55;
	/* min-width keeps columns legible; the wrapper above scrolls when the
	   viewport cannot hold it. */
	min-width: 28rem;
}
.cr-prose :where(th, td) {
	padding: 0.7rem 0.9rem;
	border: 0;
	border-bottom: 1px solid var(--cr-hair);
	text-align: left; vertical-align: top;
	color: var(--cr-fg-mute);
}
.cr-prose th {
	font-weight: 800; font-size: 0.7rem;
	letter-spacing: 0.14em; text-transform: uppercase;
	color: var(--cr-accent-quiet);
	border-bottom: 2px solid var(--cr-accent-quiet);
	white-space: nowrap;
}
/* First column carries the label, so it reads at full strength. */
.cr-prose tbody td:first-child { color: var(--cr-fg); font-weight: 600; }
.cr-prose tbody tr:last-child :where(td) { border-bottom: 0; }
.cr-prose table strong { color: var(--cr-fg); }

/* A deep link from the contents must not land the heading under the
   fixed nav. */
.cr-doc__body :where(h2, h3) { scroll-margin-top: calc(var(--cr-nav-h) + 1.5rem); }

/* ---- Brief form status -------------------------------------------
   Focusable (tabindex -1 in the markup) so the redirect can move a
   keyboard or screen-reader user straight to the outcome instead of
   dropping them at the top of the page to hunt for it.
   ------------------------------------------------------------------ */
/* A full hairline border and a leading marker, NOT a thick left stripe.
   A coloured `border-inline-start` over 1px on a callout is the
   accent-bar-on-a-block pattern every template ships; it was called AI slop on
   the case-page brief earlier in this project and it is the same thing here.
   The status reads by its own words plus a small amber or ember mark. */
.cr-brief__status {
	padding: 0.9rem 1.1rem; margin-bottom: 1.5rem;
	border-radius: var(--cr-r);
	border: 1px solid var(--cr-hair);
	background: var(--cr-ink-2);
	font-size: 0.95rem; line-height: 1.5;
	color: var(--cr-fg);
	display: flex; gap: 0.7rem; align-items: baseline;
}
.cr-brief__status::before {
	content: ''; flex: none;
	width: 0.5rem; height: 0.5rem; border-radius: 50%;
	translate: 0 -0.15em;
}
.cr-brief__status--ok::before  { background: var(--cr-amber); }
.cr-brief__status--err::before { background: var(--cr-ember); }
.cr-brief__status:focus-visible { outline: 2px solid var(--cr-amber); outline-offset: 3px; }
