/**
 * Pattern styles — only what theme.json cannot express.
 *
 * NOTE ON SLATE. This file deliberately uses no `slate`. 02 §2 restricts it to
 * body size and never below 16px, and 02 §3 records it only against white
 * (4.83:1). On `surface` it is 4.47:1 and FAILS — found by axe on the
 * all-patterns page, in the price table. `ink-soft` is 10.31:1 on white and
 * 9.54:1 on surface, and is the token added for exactly this job.
 *
 * Every colour, size and space is a theme.json token. No literal hex, no
 * literal font sizes. 02 §14: borders are 1px --line or they do not exist,
 * nothing has both a border and a shadow, and nothing moves on hover but
 * colour.
 */

/* ── border-box, everywhere ───────────────────────────────
   Added 2026-09-06. The theme had no box-sizing reset at all: body, every
   heading and every custom .emcs-* class measured content-box, and only
   WordPress core's own block wrappers set border-box for themselves.

   HONESTY NOTE. This was added while chasing the 320px horizontal scroll
   below and it did NOT fix it — with border-box applied the overflowing card
   measured exactly the same. The real causes were the two things documented
   underneath. It is kept because a theme whose own classes measure
   content-box will grow this bug again, not because it solved this one. */
*, *::before, *::after{ box-sizing:border-box }

/* ── long words must be allowed to break ─────────────────
   1.4.10 Reflow asks that content reflow to 320 CSS px without horizontal
   scrolling, and a single long word at a display size cannot. "Three
   configurations." at `xxxl` measures 307px inside a 272px column at 320px
   wide, pushing the whole document 11px sideways on /pricing/ and
   /homebuilders/.

   break-word rather than break-all: it only breaks a word that would otherwise
   overflow, so no line that fits today changes. Applied to headings, where
   the type is largest and the words are longest — body copy at `base` has
   never been the one to overflow. */
h1, h2, h3, h4, h5, h6{ overflow-wrap:break-word }


/* ── shared ───────────────────────────────────────────── */
.emcs-eyebrow{
	font-weight:600; letter-spacing:.1em; text-transform:uppercase;
	color:var(--wp--preset--color--navy-mid); font-stretch:100%;
	margin:0 0 var(--wp--preset--spacing--50);
}
.has-navy-background-color .emcs-eyebrow,
.has-navy-deep-background-color .emcs-eyebrow{ color:var(--wp--preset--color--periwinkle) }
/* A 60ch measure keeps the lead readable, but WordPress's constrained layout
   auto-centers any child narrower than the container — so the lead floated to
   the middle while the h2 and the button beside it sat at the container's left
   edge. Cap the measure, keep the left edge. The :where() in core's rule gives
   Core emits `margin-left:auto !important` for constrained layouts, so this
   is one of the few places !important is the correct tool rather than a smell.
   Plain `margin-inline-start:0` overshoots: siblings sit at the container's
   own centering offset, not at the padding edge, so the lead has to take that
   same offset to share their left edge. It collapses to 0 below wide-size. */
.emcs-lead{ max-width:47ch }
.is-layout-constrained > .emcs-lead{
	margin-inline-start:max(0px, calc((100% - var(--wp--style--global--wide-size,1200px)) / 2)) !important;
}
/* EVERY capped paragraph in a page section, not only the lead (2026-10-07).
   Once the 52ch body measure landed, every plain paragraph and list in a
   1200-column section did exactly what the lead used to: centered itself.
   Headings started at x=120 and the copy under them at x=452 — "These are
   reference configurations…" under the cards on /homebuilders/ and
   /pricing/, "Or read how we price first →" 360px right of the button
   above it. Found in the design review.

   The wide-size offset is only right in a 1200 column, and nothing in the
   markup says which columns those are: a 720 reading column (/privacy-policy/)
   carries the same classes and the same template. So a reading section opts
   out with `emcs-prose`, and tests/align.mjs fails if any section's direct
   children stop sharing one left edge — which is how the privacy page was
   caught, the first time this rule was written without the opt-out.
   Centered text keeps core's centering. */
.wp-block-post-content > section.is-layout-constrained:not(.emcs-prose) > :is(p, ul, ol):not(.has-text-align-center){
	margin-inline-start:max(0px, calc((100% - var(--wp--style--global--wide-size,1200px)) / 2)) !important;
}
/* …and a reading column hangs its copy from the 720 edge its headings use.
   /privacy-policy/ had three left edges before this: headings at 360, the
   lead at 120 (the rule above, which assumes 1200), body copy at 452. */
.wp-block-post-content > section.emcs-prose > :is(p, ul, ol):not(.has-text-align-center){
	margin-inline-start:max(0px, calc((100% - var(--wp--style--global--content-size,720px)) / 2)) !important;
}
/* A case study has no sections: its copy sits directly in a 720 post-content
   column, so neither rule above reaches it and every capped paragraph centered
   itself 92px right of the heading above it (2026-10-08, the first four
   studies, and the two older drafts had it too). Same edge as a reading section. */
.single-case_study .wp-block-post-content > :is(p, ul, ol):not(.has-text-align-center){
	margin-inline-start:max(0px, calc((100% - var(--wp--style--global--content-size,720px)) / 2)) !important;
}

/* ── page sections meet edge to edge ─────────────────────
   Core's root blockGap puts margin-block-start:24px on every top-level
   block: `main` after the header, the footer after `main`, and every section
   inside post content. Between two white sections it is invisible; at every
   navy, surface or photograph boundary it was a 24px white stripe — under
   the header, under the hero, above the CTA band, above the footer, on every
   page. Found 2026-10-07 in the design review. Each section carries its own
   padding, so the gap was never the spacing; the padding is.

   Scoped to `section` on purpose: a page whose content is plain paragraphs
   (/privacy-policy/) still needs core's gap between them. The footer cancels
   the same gap inside itself, further down. */
.wp-site-blocks > :is(main, footer),
.wp-block-post-content > section{ margin-block-start:0 }

/* ── hero: the photograph is the hero ─────────────────── */
/* The first build put a letterboxed image strip above the headline, so the
   first screen was a banner and the promise sat below it on white. The
   photograph carries the argument — clusters receding down a model-home
   street is the navigation system explaining itself — so it now fills the
   hero and the headline sits on it.

   Every colour here is the navy-deep token via color-mix(); the scrim needs
   alpha and theme.json cannot express one, but hardcoding a hex would fork
   the palette. color-mix keeps the token as the single source of truth.

   The scrim stops are set from measurement, not taste. axe reports text over
   an image as "incomplete" rather than failing it, so the first tuning passed
   axe with the lead at 2.43:1 against a required 4.5:1. The opacity is held
   above ~68% across the full width of the text column so the worst background
   pixel behind the lead still clears AA, then falls away fast so the right of
   the frame — the clusters receding down the street — stays legible.
   plugins/emcs-core/tests/hero-contrast.mjs re-measures it. */
.emcs-hero{
	position:relative; isolation:isolate; overflow:hidden;
	display:grid; align-items:end;
	min-height:clamp(480px, 70vh, 700px);
	background:var(--wp--preset--color--navy-deep);
}
.emcs-hero__media{ position:absolute; inset:0; z-index:0 }
.emcs-hero__media img{ width:100%; height:100%; object-fit:cover; object-position:50% 58%; display:block }
.emcs-hero::after{
	content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
	background:
		linear-gradient(97deg,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 90%, transparent) 0%,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 84%, transparent) 38%,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 70%, transparent) 55%,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 30%, transparent) 73%,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 4%, transparent) 100%),
		linear-gradient(180deg,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 45%, transparent) 0%,
			color-mix(in srgb, var(--wp--preset--color--navy-deep) 0%, transparent) 38%);
}
.emcs-hero__inner{
	position:relative; z-index:2;
	width:100%; max-width:1200px; margin-inline:auto;
	padding:var(--wp--preset--spacing--100) var(--wp--preset--spacing--60);
}
.emcs-hero h1{
	color:var(--wp--preset--color--white);
	max-width:19ch; margin:0 0 var(--wp--preset--spacing--60);
	text-wrap:balance;
}
.emcs-hero__lead{
	color:var(--wp--preset--color--on-navy);
	max-width:40ch; margin:0 0 var(--wp--preset--spacing--80);
}
.emcs-hero__note{
	color:var(--wp--preset--color--periwinkle);
	margin:var(--wp--preset--spacing--80) 0 0; max-width:47ch;
}
/* Between the mobile breakpoint and wide-size the text column occupies a
   larger share of the frame, so the falloff has to start further right or the
   lead loses its ground. Measured at 900/1024/1100/1280. */
@media (min-width:861px) and (max-width:1280px){
	.emcs-hero::after{
		background:
			linear-gradient(97deg,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 92%, transparent) 0%,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 87%, transparent) 42%,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 78%, transparent) 66%,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 34%, transparent) 82%,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 5%, transparent) 100%),
			linear-gradient(180deg,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 45%, transparent) 0%,
				color-mix(in srgb, var(--wp--preset--color--navy-deep) 0%, transparent) 38%);
	}
}

/* On a phone the crop is too tight to hold both the photograph and four lines
   of type. Overlaying needs a scrim heavy enough to erase the image, which
   wastes the one asset doing the arguing — so below 860px the hero stops
   overlaying and stacks: photograph, then text on solid navy. Contrast stops
   being a gradient calculation, and the picture is actually visible. */
@media (max-width:860px){
	.emcs-hero{ display:block; min-height:0 }
	.emcs-hero__media{ position:relative; inset:auto }
	.emcs-hero__media img{ height:auto; aspect-ratio:16/10; object-position:50% 62% }
	.emcs-hero::after{ display:none }
	.emcs-hero__inner{ padding-block:var(--wp--preset--spacing--100) }
	.emcs-hero h1{ max-width:none }
}

/* ── evidence band ────────────────────────────────────── */
.emcs-evidence__grid{ display:grid; grid-template-columns:minmax(0,1fr); }
@media (min-width:1000px){ .emcs-evidence__grid{ grid-template-columns:minmax(0,1.75fr) minmax(0,1fr) } }
.emcs-evidence figure{ position:relative; margin:0 }
.emcs-evidence img{ width:100%; height:100%; object-fit:cover; aspect-ratio:3/2; display:block }
.emcs-evidence figcaption{
	position:absolute; inset:auto 0 0 0; padding:var(--wp--preset--spacing--60);
	background:linear-gradient(to top,color-mix(in srgb, var(--wp--preset--color--navy-deep) 90%, transparent),color-mix(in srgb, var(--wp--preset--color--navy-deep) 0%, transparent));
	color:var(--wp--preset--color--white); font-size:var(--wp--preset--font-size--sm);
}
.emcs-evidence figcaption b{ display:block; font-stretch:125%; margin-bottom:var(--wp--preset--spacing--20) }

/* ── proof bar: quiet, no icons ───────────────────────── */
.emcs-proofbar__list{ margin:0; display:grid; gap:var(--wp--preset--spacing--70) }
@media (min-width:760px){ .emcs-proofbar__list{ grid-template-columns:repeat(4,minmax(0,1fr)) } }
.emcs-proofbar__list dt{
	font-stretch:125%; font-weight:700; color:var(--wp--preset--color--navy);
	margin-bottom:var(--wp--preset--spacing--20);
}
.emcs-proofbar__list dd{
	margin:0; color:var(--wp--preset--color--ink-soft);
	font-size:var(--wp--preset--font-size--sm);
}

/* ── vertical selector: the one justified 3-up ────────── */
/* FOUR, IN A 2x2 — not four in a row. Updated 2026-09-10 when /events/ finally
   joined the selector it had been missing from since B5 resolved.

   Four across a 1200px container gives each card ~270px, and with the 48px
   padding these carry that leaves 174px of content for a heading, two lines
   and a link. The 2x2 gives each card ~570px and reads as two pairs rather
   than a row of boxes — which also keeps it clear of §7's "three equal cards
   in a row as the answer to every section", since the answer here is a block,
   not a row. */
.emcs-verticals{ display:grid; gap:var(--wp--preset--spacing--70) }
@media (min-width:700px){ .emcs-verticals{ grid-template-columns:repeat(2,minmax(0,1fr)) } }
.emcs-vertical{
	display:block; text-decoration:none; color:inherit;
	border:1px solid var(--wp--preset--color--line); border-radius:8px;
	padding:var(--wp--preset--spacing--80); transition:border-color 150ms,background-color 150ms;
}
.emcs-vertical:hover{ border-color:var(--wp--preset--color--navy); background:var(--wp--preset--color--surface) }
.emcs-vertical h3{ margin:0 0 var(--wp--preset--spacing--50); font-size:var(--wp--preset--font-size--xl) }
.emcs-vertical p{ margin:0 0 var(--wp--preset--spacing--60); color:var(--wp--preset--color--ink-soft) }
.emcs-vertical__go{ font-size:var(--wp--preset--font-size--sm); font-weight:600; color:var(--wp--preset--color--navy) }

/* ── VNS reveal ───────────────────────────────────────── */
.emcs-vns__aside{ position:static }
@media (min-width:900px){ .emcs-vns__aside{ position:sticky; top:var(--wp--preset--spacing--80) } }
.emcs-vns__photo{ margin:0 }
.emcs-vns__photo img{ width:100%; height:auto; display:block; border-radius:8px }
.emcs-vns__scalenote{
	margin-top:var(--wp--preset--spacing--60); padding-top:var(--wp--preset--spacing--50);
	border-top:1px solid color-mix(in srgb, var(--wp--preset--color--periwinkle) 28%, transparent);
	color:var(--wp--preset--color--on-navy-muted); max-width:32ch;
}
.emcs-vns__bands{ list-style:none; margin:0; padding:0 }
.emcs-vns__band{
	border-left:3px solid transparent;
	padding:0 0 var(--wp--preset--spacing--100) var(--wp--preset--spacing--60);
	transition:border-color 150ms;
}
.emcs-vns__band:last-child{ padding-bottom:0 }
/* Active state is BORDER ONLY. Text is never dimmed — 02 §6. */
.emcs-vns[data-js="on"] .emcs-vns__band[data-on="true"]{ border-left-color:var(--wp--preset--color--periwinkle) }
.emcs-vns:not([data-js="on"]) .emcs-vns__band{ border-left-color:color-mix(in srgb, var(--wp--preset--color--periwinkle) 35%, transparent) }
.emcs-vns__alt{
	display:block; font-stretch:125%; font-weight:700;
	font-size:var(--wp--preset--font-size--xxl); line-height:1.05;
	letter-spacing:-.025em; color:var(--wp--preset--color--white);
	font-variant-numeric:tabular-nums; margin-bottom:var(--wp--preset--spacing--30);
}
.emcs-vns__h{
	font-size:var(--wp--preset--font-size--xl); color:var(--wp--preset--color--white);
	margin:0 0 var(--wp--preset--spacing--50);
}
.emcs-vns__band p{ color:var(--wp--preset--color--on-navy); margin:0 }
.emcs-vns__part{
	margin-top:var(--wp--preset--spacing--50)!important;
	padding-top:var(--wp--preset--spacing--40);
	border-top:1px solid color-mix(in srgb, var(--wp--preset--color--periwinkle) 28%, transparent);
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--on-navy-muted)!important;
}

/* ── specification block ──────────────────────────────── */
.emcs-spec__list{ margin:0 }
.emcs-spec__row{
	display:grid; gap:var(--wp--preset--spacing--30);
	padding:var(--wp--preset--spacing--60) 0;
	border-top:1px solid color-mix(in srgb, var(--wp--preset--color--periwinkle) 28%, transparent);
}
@media (min-width:700px){ .emcs-spec__row{ grid-template-columns:minmax(0,.3fr) minmax(0,.7fr); gap:var(--wp--preset--spacing--70) } }
.emcs-spec__row:last-child{ border-bottom:1px solid color-mix(in srgb, var(--wp--preset--color--periwinkle) 28%, transparent) }
.emcs-spec__row dt{ font-stretch:125%; font-weight:600; color:var(--wp--preset--color--white) }
.emcs-spec__row dd{ margin:0; color:var(--wp--preset--color--on-navy) }
.emcs-spec__row dd b{ color:var(--wp--preset--color--white); font-variant-numeric:tabular-nums }

/* ── execution timeline: numbered, not cards ──────────── */
.emcs-timeline{ list-style:none; margin:0; padding:0; counter-reset:step }
.emcs-timeline li{
	counter-increment:step; position:relative;
	padding:0 0 var(--wp--preset--spacing--90) var(--wp--preset--spacing--100);
	border-left:1px solid var(--wp--preset--color--line);
	margin-left:var(--wp--preset--spacing--40);
}
.emcs-timeline li:last-child{ border-left-color:transparent; padding-bottom:0 }
.emcs-timeline li::before{
	content:counter(step,decimal-leading-zero);
	position:absolute; left:calc(var(--wp--preset--spacing--40) * -1 - 10px); top:0;
	background:var(--wp--preset--color--navy); color:var(--wp--preset--color--white);
	font-size:var(--wp--preset--font-size--xs); font-weight:700; font-stretch:125%;
	width:34px; height:34px; border-radius:50%; display:grid; place-items:center;
	font-variant-numeric:tabular-nums;
}
.emcs-timeline h3{ margin:0 0 var(--wp--preset--spacing--40); font-size:var(--wp--preset--font-size--xl) }
.emcs-timeline p{ margin:0; color:var(--wp--preset--color--ink-soft) }
.emcs-addon{
	margin-top:var(--wp--preset--spacing--80); padding:var(--wp--preset--spacing--70);
	background:var(--wp--preset--color--surface); border-radius:8px;
}
.emcs-addon p{ margin:0; color:var(--wp--preset--color--ink-soft) }

/* ── package cards ────────────────────────────────────── */
.emcs-packages{ display:grid; gap:var(--wp--preset--spacing--70) }
@media (min-width:860px){ .emcs-packages{ grid-template-columns:repeat(3,minmax(0,1fr)) } }
.emcs-package{
	border:1px solid var(--wp--preset--color--line); border-radius:8px;
	padding:var(--wp--preset--spacing--80);
}
.emcs-package h3{ margin:0 0 var(--wp--preset--spacing--50); font-size:var(--wp--preset--font-size--xl) }
.emcs-package__price{
	/* xxxl, not stat. `stat` is a full-width display size (up to 88px); inside
	   a card that is one third of a 1200px container it overflows its track at
	   every width from 860px to ~1400px and pushes a horizontal scrollbar onto
	   the whole document. It only appeared to fit at 1440 because that is the
	   one width where the container reaches full size. Found 2026-09-04 in a
	   breakpoint sweep — a single-width check does not catch this. */
	font-stretch:125%; font-weight:700; font-size:var(--wp--preset--font-size--xxxl);
	line-height:1; letter-spacing:-.03em; color:var(--wp--preset--color--navy);
	font-variant-numeric:tabular-nums; margin:0 0 var(--wp--preset--spacing--50);
}
.emcs-package__from{
	display:block; font-stretch:100%; font-weight:600; letter-spacing:.1em;
	text-transform:uppercase; font-size:var(--wp--preset--font-size--xs);
	color:var(--wp--preset--color--navy-mid); margin-bottom:var(--wp--preset--spacing--20);
}
.emcs-package p:last-child{ margin:0; color:var(--wp--preset--color--ink-soft) }
.emcs-packages__note{
	margin-top:var(--wp--preset--spacing--80); color:var(--wp--preset--color--ink-soft);
	max-width:52ch;
}

/* ── price table: a real table ────────────────────────── */
/* A figure per package card, added 2026-09-12. The frame sits above the name
   and BLEEDS to the card's edges. The card carries a 1px line border and
   spacing-80 of padding (02 §7: one elevation, no shadows), and an inset
   photograph inside that padding reads as a picture in a box rather than as
   the card's own subject — so the figure is pulled out by the padding on
   three sides and picks up the card's radius at the top. */
.emcs-package{ overflow:hidden }        /* so the bled figure is clipped to the radius */
.emcs-package__media{
	margin:calc(-1 * var(--wp--preset--spacing--80))
	       calc(-1 * var(--wp--preset--spacing--80))
	       var(--wp--preset--spacing--70);
}
.emcs-package__media img{
	width:100%; height:auto; display:block;
	aspect-ratio:3/2; object-fit:cover;
}

/* ── service region map ──────────────────────────────── */
/* Inline SVG, real county boundaries, no map service — see the pattern's
   docblock for why. Every colour is a theme.json token: §5 rules out
   hardcoded hex, and it means the map follows the palette if the palette
   moves.

   The separators are painted in `surface` rather than white so the five
   counties read as five against a surface ground; on white grounds they
   still separate because periwinkle against surface is a visible edge. */
.emcs-map{ margin:0 }
.emcs-map__svg{ width:100%; height:auto; display:block }
.emcs-map__context path{
	fill:var(--wp--preset--color--periwinkle-tint);
	stroke:var(--wp--preset--color--surface);
	stroke-width:3;
}
.emcs-map__served path{
	fill:var(--wp--preset--color--periwinkle);
	stroke:var(--wp--preset--color--surface);
	stroke-width:6;
}
.emcs-map__pin{
	fill:var(--wp--preset--color--navy);
	stroke:var(--wp--preset--color--surface);
	stroke-width:5;
}
/* paint-order puts the halo behind the glyph so a label stays legible where
   it crosses a county border. */
.emcs-map__label{
	fill:var(--wp--preset--color--navy);
	/* Was --font-family--display, a slug that does not exist, so it always fell
	   back to `inherit` and rendered the text face; and 21px written by hand.
	   Named for what renders, and the lg token, which is 21px at full width.
	   The text face is right here: the 125% display face would not fit
	   "San Bernardino" inside its county. Design review, 2026-10-07. */
	font-family:var(--wp--preset--font-family--primary-text);
	font-size:var(--wp--preset--font-size--lg); font-weight:600; text-anchor:middle;
	paint-order:stroke;
	stroke:var(--wp--preset--color--surface); stroke-width:5;
}
/* Under 640px the desktop labels collide, and they used to simply come off —
   which left a phone reader five unnamed shapes (Eric, 2026-10-08). There is
   a second label set for phones instead (see the pattern), at 40 user units
   so it reads at ~14px in a 342px map. SVG font sizes are user units, not
   type-scale tokens: a rem token would scale with the viewBox to nothing. */
.emcs-map__labels-sm{ display:none }
.emcs-map__leader{ stroke:var(--wp--preset--color--navy); stroke-width:3; fill:none }
@media (max-width:639px){
	.emcs-map__labels{ display:none }
	.emcs-map__labels-sm{ display:inline }
	.emcs-map__labels-sm .emcs-map__label{ font-size:40px; stroke-width:9 }
	.emcs-map__pin{ r:16 }
}

/* ── work quilt: a body of work, not a filmstrip ──────── */
/* /work/ showed three photographs under a heading that said "Work". Sixteen
   4:3 tiles, edge to edge, four up at desktop and two up on a phone. No gaps
   at the viewport edge: the grid IS the section, so it reads as one surface
   rather than as cards floating on white.

   No captions and no hover lift. 02 §7 rules out card lift, and a caption per
   tile would put back the copy this page exists to take out — the alt text
   carries the description for anyone who needs it. */
.emcs-quilt{
	list-style:none; margin:0; padding:0;
	display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:2px;
}
@media (min-width:600px){ .emcs-quilt{ grid-template-columns:repeat(3,minmax(0,1fr)) } }
@media (min-width:1000px){ .emcs-quilt{ grid-template-columns:repeat(4,minmax(0,1fr)) } }
.emcs-quilt__tile{ margin:0; overflow:hidden; background:var(--wp--preset--color--navy-deep) }
.emcs-quilt__tile img{
	width:100%; height:auto; display:block;
	aspect-ratio:4/3; object-fit:cover;
}

/* The tiles are the only thing in their section, so the section carries the
   ground rather than the tiles carrying a border. */
.emcs-quilt-section{ background:var(--wp--preset--color--surface) }

.emcs-tablewrap{ overflow-x:auto }
.emcs-table{ width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums }
.emcs-table caption{
	text-align:left; color:var(--wp--preset--color--ink-soft);
	font-size:var(--wp--preset--font-size--sm); padding-bottom:var(--wp--preset--spacing--60);
}
.emcs-table th,.emcs-table td{
	text-align:left; vertical-align:top;
	padding:var(--wp--preset--spacing--50) var(--wp--preset--spacing--60) var(--wp--preset--spacing--50) 0;
	border-top:1px solid var(--wp--preset--color--line);
}
.emcs-table thead th{
	border-top:0; border-bottom:1px solid var(--wp--preset--color--navy);
	color:var(--wp--preset--color--navy); font-size:var(--wp--preset--font-size--xs);
	letter-spacing:.08em; text-transform:uppercase; font-stretch:100%;
}
.emcs-table tbody th{ color:var(--wp--preset--color--navy); font-stretch:125%; font-weight:600 }
.emcs-table td:last-child{ color:var(--wp--preset--color--ink-soft); font-size:var(--wp--preset--font-size--sm) }
.emcs-table__note{ margin-top:var(--wp--preset--spacing--70); color:var(--wp--preset--color--ink-soft) }

/* Under 700px three columns leaves the last one about 190px wide, and the
   longest cell on the site — the outdoor and lot scope on /dealerships/ —
   became a thirty-line ribbon two words wide. The wrapper scrolls, so
   `responsive.mjs` passed it; nobody could read it.

   Stacking is the fix, not horizontal scroll: a reader should not have to
   swipe sideways through a sentence. The column heads are hidden rather
   than removed, and each row becomes title / terms / description in the
   order you would say it aloud.

   `display:block` DROPS THE NATIVE TABLE ROLES in every engine, which is
   why the markup carries explicit `role="table|row|columnheader|rowheader|
   cell"`. They are redundant at desktop width and load-bearing here — a
   screen reader on a phone still gets a table with a labelled region, not
   three stacked paragraphs. Verified with an accessibility snapshot at
   390px, not assumed. */
@media (max-width:699px){
	.emcs-table,
	.emcs-table tbody,
	.emcs-table tr,
	.emcs-table th,
	.emcs-table td{ display:block; width:auto }

	/* A `table-caption` with no table left to caption collapses to the width
	   of its longest word. It has to be told it is a block now. */
	.emcs-table caption{ display:block }

	/* Visually hidden, still announced. */
	.emcs-table thead{
		position:absolute; width:1px; height:1px; overflow:hidden;
		clip-path:inset(50%); white-space:nowrap;
	}

	.emcs-table tr{
		border-top:1px solid var(--wp--preset--color--line);
		padding:var(--wp--preset--spacing--60) 0;
	}
	.emcs-table th,
	.emcs-table td{ border-top:0; padding:0 0 var(--wp--preset--spacing--30) }
	.emcs-table td:last-child{ padding-bottom:0 }

	/* The terms line reads as metadata under the name, not as a column. */
	.emcs-table tbody td:first-of-type{
		color:var(--wp--preset--color--navy-mid);
		font-size:var(--wp--preset--font-size--xs);
		letter-spacing:.06em; text-transform:uppercase;
	}
}

/* ── procurement checklist ────────────────────────────── */
.emcs-checklist{ list-style:none; margin:0; padding:0; display:grid; gap:var(--wp--preset--spacing--60); max-width:52ch }

/* ── the body measure, as a floor ─────────────────────── */
/* 02 §14: no line of body copy exceeds 68 characters. On 2026-09-23 every
   page on the site was over — 86 blocks, worst 163 characters — because a
   paragraph dropped straight into a 1200px section inherits no measure at
   all, and because every cap that DID exist was written in `ch`, which is
   27% wider per character than Archivo's prose average.

   SPECIFICITY MATTERS HERE and the first attempt got it wrong. Core emits
   `.is-layout-constrained > :where(:not(.align*)){ max-width: … }` — the
   leading CLASS makes that 0,1,0, so a zero-specificity floor lost to it and
   two paragraphs on /pricing/ stayed at 163 and 152 characters while the
   rule sat in the served stylesheet doing nothing. `:not(.emcs-eyebrow)`
   buys the class-level specificity needed to win, and excludes the eyebrows,
   which are labels rather than prose. A max-width never widens, so inside a
   narrow column this does nothing.

   52ch, not 54: Archivo's ratio puts 54ch at 68.6 characters, which is over
   by a rounding error. `li` and `dd` are included because the check counts
   them and the spec tables and checklists were among the worst offenders.

   Verified by tests/measure.mjs, which counts characters on the rendered
   line rather than reading this stylesheet — the distinction that hid the
   problem in the first place.

   NOT THE FRAMED BANDS (2026-10-07). An `li` is not always prose. The
   framed sequence on /homebuilders/, /education/, /dealerships/ and
   /services/ is a list whose items are two-column grids — claim left,
   photograph right — and this cap crushed each one to 536px for two weeks:
   "breadcrumb / s" broken mid-word, photographs at 207px, 780px of empty
   navy beside them. measure.mjs could not see it, because squeezing only
   ever makes lines shorter. The copy inside a band keeps its own 46ch cap
   (.emcs-vns__copy p), so excluding the container loses no measure. */
main :is(p, li, dd):not(.emcs-eyebrow, .emcs-vns__band){ max-width:52ch }

/* ── measured prose in a 1200px section ───────────────── */
/* 02 §14: no line of body copy exceeds 68 characters. A paragraph dropped
   into a full-width section with no measure runs to 98 on /education/ at
   1440. Core's `contentSize` would cap it but CENTRES what it caps, which
   leaves the paragraph offset from its own heading — so the cap is here and
   the alignment is left, which is the site's default.

   54ch, NOT 68ch. The `ch` unit is the advance width of the "0" glyph, and
   in Archivo that is 10.31px against an 8.15px average prose character —
   a ratio of 1.27. So `max-width:68ch` renders about 86 characters, and
   every measure on this site set in `ch` is loose by the same 27%. Measured,
   not assumed: tests/measure.mjs counts characters per rendered line. */
.emcs-measured{ max-width:52ch }
.emcs-checklist li{
	padding-left:var(--wp--preset--spacing--70); position:relative;
	color:var(--wp--preset--color--ink-soft);
}
/* THE SAME CONSTRAINED-LAYOUT TRAP THE LEAD HAS, found 2026-09-08.
   `max-width:52ch` above makes this list narrower than the 1200px container,
   and WordPress's constrained layout auto-centres any child that is — so the
   list sat 239px to the right of its own H2 on /pricing/, /education/ and
   /events/. It reads as an indent nobody chose.

   Pre-existing on the latter two; /pricing/ just made it obvious by putting a
   checklist directly under a heading for the first time. Same remedy as
   `.emcs-lead`: cap the measure, keep the left edge. Core emits
   `margin-left:auto !important` for these, which is why !important is the
   correct tool here rather than a smell, and the max() collapses to 0 below
   wide-size. */
.is-layout-constrained > .emcs-checklist{
	margin-inline-start:max(0px, calc((100% - var(--wp--style--global--wide-size,1200px)) / 2)) !important;
}

/* A rule, not an icon. 02 §7 bans decorative icons beside bullets. */
.emcs-checklist li::before{
	content:""; position:absolute; left:0; top:.7em;
	width:var(--wp--preset--spacing--50); height:2px;
	background:var(--wp--preset--color--periwinkle);
}
.emcs-checklist b{ color:var(--wp--preset--color--navy) }

/* ── FAQ: native details, keyboard-operable for free ──── */
.emcs-faq details{ border-top:1px solid var(--wp--preset--color--line) }
.emcs-faq details:last-of-type{ border-bottom:1px solid var(--wp--preset--color--line) }
.emcs-faq summary{
	cursor:pointer; padding:var(--wp--preset--spacing--60) 0;
	font-stretch:125%; font-weight:600; color:var(--wp--preset--color--navy);
	list-style:none; display:flex; justify-content:space-between; gap:var(--wp--preset--spacing--60);
}
.emcs-faq summary::-webkit-details-marker{ display:none }
.emcs-faq summary::after{ content:"+"; color:var(--wp--preset--color--navy-mid); font-weight:400 }
.emcs-faq details[open] summary::after{ content:"–" }
.emcs-faq p{ margin:0 0 var(--wp--preset--spacing--60); color:var(--wp--preset--color--ink-soft); max-width:52ch }

/* ── service entry ────────────────────────────────────── */
.emcs-service h3{ margin:0 }

/* ── VNS diagram ──────────────────────────────────────── */
/* Redrawn 2026-10-08 as the products themselves (see the pattern docblock).
   The old version scrolled sideways below 700px because its labels were SVG
   text; the names now live in an HTML key, so the drawing can simply scale.
   At a 342px column the band labels (19 units) render at 13.5px.

   Desktop: drawing and key side by side. Phone: the key follows the drawing. */
.emcs-diagram{ margin:0 }
.emcs-diagram__svg{ width:100%; height:auto; display:block }
.emcs-diagram--keyed{ display:grid; gap:var(--wp--preset--spacing--80); grid-template-columns:minmax(0,1fr) }
@media (min-width:820px){
	.emcs-diagram--keyed{ grid-template-columns:minmax(0,520px) minmax(0,1fr); align-items:center }
}
.emcs-diagram figcaption{
	margin-top:var(--wp--preset--spacing--60); color:var(--wp--preset--color--ink-soft);
	font-size:var(--wp--preset--font-size--sm); max-width:47ch;
}
.emcs-vnsd__list{ margin:0; padding:0; list-style:none; counter-reset:vnsd; display:grid; gap:var(--wp--preset--spacing--60) }
.emcs-vnsd__list li{ counter-increment:vnsd; position:relative; padding-left:2.5rem; max-width:52ch }
.emcs-vnsd__list li::before{
	content:counter(vnsd); position:absolute; left:0; top:.1em;
	width:1.6rem; height:1.6rem; border-radius:50%; display:grid; place-items:center;
	background:var(--wp--preset--color--navy); color:var(--wp--preset--color--white);
	font-size:var(--wp--preset--font-size--sm); font-weight:700;
}
/* Drawing. Every colour a theme.json token. Font sizes here are SVG user
   units (they scale with the drawing), so they are numbers, not type tokens. */
.emcs-vnsd__bands line{ stroke:var(--wp--preset--color--periwinkle); stroke-width:3; stroke-dasharray:6 8 }
.emcs-vnsd__ground{ stroke:var(--wp--preset--color--navy); stroke-width:2 }
.emcs-vnsd__label text{
	fill:var(--wp--preset--color--navy); font-family:var(--wp--preset--font-family--primary-text);
	font-size:19px; font-weight:600;
}
.emcs-vnsd__break{ stroke:var(--wp--preset--color--navy); stroke-width:2; fill:none }
.emcs-vnsd__sign{ fill:var(--wp--preset--color--navy-mid) }
.emcs-vnsd__signtext{
	fill:var(--wp--preset--color--white); font-family:var(--wp--preset--font-family--primary-text);
	font-size:17px; font-weight:600; text-anchor:middle;
}
.emcs-vnsd__b{ stroke:var(--wp--preset--color--navy); stroke-width:1 }
.emcs-vnsd__b--navy{ fill:var(--wp--preset--color--navy) }
.emcs-vnsd__b--peri{ fill:var(--wp--preset--color--periwinkle) }
.emcs-vnsd__b--white{ fill:var(--wp--preset--color--white) }
.emcs-vnsd__hl{ fill:var(--wp--preset--color--white); opacity:.45; pointer-events:none }
.emcs-vnsd__metal{ fill:var(--wp--preset--color--navy-mid) }
.emcs-vnsd__pole{ stroke:var(--wp--preset--color--navy-mid); stroke-width:3 }
.emcs-vnsd__line{ stroke:var(--wp--preset--color--navy-mid); stroke-width:1.5 }
.emcs-vnsd__ribbon{ stroke:var(--wp--preset--color--periwinkle); stroke-width:2; fill:none }
.emcs-vnsd__pennant{ fill:var(--wp--preset--color--periwinkle); stroke:var(--wp--preset--color--navy-mid); stroke-width:.75 }
.emcs-vnsd__num circle{ fill:var(--wp--preset--color--navy) }
.emcs-vnsd__num text{
	fill:var(--wp--preset--color--white); font-family:var(--wp--preset--font-family--primary-text);
	font-size:16px; font-weight:700; text-anchor:middle;
}

/* ── case card ────────────────────────────────────────── */
.emcs-case img{ width:100%; height:auto; display:block; border-radius:8px }
.emcs-case .emcs-eyebrow{ margin-top:var(--wp--preset--spacing--60) }
.emcs-case h3{ margin:0 0 var(--wp--preset--spacing--50) }

/* ── gallery band ─────────────────────────────────────── */
/* RETIRED FROM EVERY PAGE as of 2026-09-11. /events/ was the last to carry
   the three-up strip, and it went the way it went on /, /homebuilders/,
   /education/ and /dealerships/: a mosaic beside the copy it illustrates.
   The rules stay because emcs/gallery-band is still a registered pattern an
   editor can insert. If it is ever placed again, read 02 §7 first — "three
   equal frames in a row" is the device every page found a better answer to. */
.emcs-band__grid{ display:grid; grid-template-columns:minmax(0,1fr) }
@media (min-width:760px){ .emcs-band__grid{ grid-template-columns:repeat(3,minmax(0,1fr)) } }
/* The home page band carries one frame per vertical, and there are four. Two
   rows of two below 1100px so a 16:9 frame does not shrink to a letterbox
   strip; four across above it, where each still gets ~360px. */
@media (min-width:760px){ .emcs-band--four .emcs-band__grid{ grid-template-columns:repeat(2,minmax(0,1fr)) } }
@media (min-width:1100px){ .emcs-band--four .emcs-band__grid{ grid-template-columns:repeat(4,minmax(0,1fr)) } }
.emcs-band__item{ margin:0 }
.emcs-band__item img{ width:100%; height:100%; object-fit:cover; aspect-ratio:16/9; display:block }

/* ── logo wall + testimonial (gated; emcs-core) ───────── */
.emcs-logo-wall{ padding:var(--wp--preset--spacing--110) var(--wp--preset--spacing--60) }
.emcs-logo-wall > *{ max-width:1200px; margin-inline:auto }
.emcs-logo-wall__heading{
	font-size:var(--wp--preset--font-size--sm); font-weight:600; letter-spacing:.1em;
	text-transform:uppercase; color:var(--wp--preset--color--navy-mid); font-stretch:100%;
	margin:0 0 var(--wp--preset--spacing--80);
}
.emcs-logo-wall__list{
	list-style:none; margin:0; padding:0; display:grid; align-items:center;
	gap:var(--wp--preset--spacing--80); grid-template-columns:repeat(2,minmax(0,1fr));
}
@media (min-width:700px){ .emcs-logo-wall__list{ grid-template-columns:repeat(4,minmax(0,1fr)) } }
.emcs-logo-wall__item img{ max-width:100%; height:auto; display:block; margin:0 auto }

.emcs-testimonial{ margin:0; max-width:34ch }
.emcs-testimonial__quote{
	margin:0; border-left:3px solid var(--wp--preset--color--periwinkle);
	padding-left:var(--wp--preset--spacing--70);
}
.emcs-testimonial__quote p{
	font-stretch:125%; font-weight:700; font-size:var(--wp--preset--font-size--xxl);
	line-height:1.15; letter-spacing:-.02em; color:var(--wp--preset--color--navy); margin:0;
}
.emcs-testimonial__by{
	margin-top:var(--wp--preset--spacing--60); padding-left:var(--wp--preset--spacing--70);
	font-size:var(--wp--preset--font-size--sm); color:var(--wp--preset--color--navy); font-weight:600;
}
.emcs-testimonial__role{ display:block; color:var(--wp--preset--color--ink-soft); font-weight:400 }


/* ── long-form article: /homebuilders/vns/ ────────────── */
/* A field explanation, not a sales page (03 §3), so it is set like one: a
   measured column for reading, and the section number carried out into a
   gutter so the structure is visible without the prose being interrupted. */
.emcs-article{ counter-reset:emcs-sec }
.emcs-article__sec{
	padding-block:var(--wp--preset--spacing--100);
	border-top:1px solid var(--wp--preset--color--line);
}
.emcs-article__sec > *{ max-width:52ch }
.emcs-article__sec > h2{ margin-top:0 }
.emcs-article__sec::before{
	counter-increment:emcs-sec;
	content:counter(emcs-sec, decimal-leading-zero);
	display:block;
	font-stretch:125%; font-weight:700;
	font-size:var(--wp--preset--font-size--xl);
	font-variant-numeric:tabular-nums;
	line-height:1; color:var(--wp--preset--color--periwinkle);
	margin-bottom:var(--wp--preset--spacing--50);
}
@media (min-width:1040px){
	.emcs-article__sec{
		display:grid; grid-template-columns:minmax(0,110px) minmax(0,1fr);
		column-gap:var(--wp--preset--spacing--80);
	}
	.emcs-article__sec > *{ grid-column:2 }
	.emcs-article__sec::before{ grid-column:1; grid-row:1 / span 99; margin-bottom:0 }
}
/* An inline note that is part of the argument, not an aside to skim past. */
.emcs-note{
	border-left:3px solid var(--wp--preset--color--periwinkle);
	padding:var(--wp--preset--spacing--50) 0 var(--wp--preset--spacing--50) var(--wp--preset--spacing--60);
	color:var(--wp--preset--color--ink-soft);
	margin-block:var(--wp--preset--spacing--70);
}
.emcs-note b{ color:var(--wp--preset--color--navy) }
.emcs-figure{ margin:0; padding-block:var(--wp--preset--spacing--90) }
.emcs-figure img{ width:100%; height:auto; display:block; aspect-ratio:21/9; object-fit:cover }
.emcs-figure figcaption{
	max-width:52ch; margin-top:var(--wp--preset--spacing--50);
	padding-inline:var(--wp--preset--spacing--60);
	font-size:var(--wp--preset--font-size--xs);
	color:var(--wp--preset--color--ink-soft);
}

/* ── case studies: /work/ ─────────────────────────────── */
.emcs-filter{ margin-top:var(--wp--preset--spacing--80) }
.emcs-filter__list{
	list-style:none; margin:0; padding:0;
	display:flex; flex-wrap:wrap; gap:var(--wp--preset--spacing--50);
}
.emcs-filter__link{
	display:inline-block; text-decoration:none;
	padding:var(--wp--preset--spacing--40) var(--wp--preset--spacing--60);
	border:1px solid var(--wp--preset--color--line);
	border-radius:4px;
	font-size:var(--wp--preset--font-size--sm); font-weight:600;
	color:var(--wp--preset--color--navy);
}
.emcs-filter__link:hover{ border-color:var(--wp--preset--color--navy) }
.emcs-filter__link.is-current{
	background:var(--wp--preset--color--navy);
	border-color:var(--wp--preset--color--navy);
	color:var(--wp--preset--color--white);
}
/* The post-template asks for three columns, which is right at the target
   count and leaves an empty third of the row when there are two. auto-fit
   fills whatever exists — two studies read as two, six read as three up. */
.emcs-cases{
	gap:var(--wp--preset--spacing--80) !important;
	grid-template-columns:repeat(auto-fit, minmax(min(300px, 100%), 1fr)) !important;
}
.emcs-case img{ width:100%; height:auto; display:block }
.emcs-case .emcs-eyebrow{ margin-top:var(--wp--preset--spacing--50) }
.emcs-case h2{ margin:0 0 var(--wp--preset--spacing--50) }
.emcs-case p{ color:var(--wp--preset--color--ink-soft) }
.emcs-casefacts{ margin:0 }

/* ── contact: the direct line ─────────────────────────── */
.emcs-directline{ display:grid; gap:var(--wp--preset--spacing--70) }
@media (min-width:760px){ .emcs-directline{ grid-template-columns:repeat(2,minmax(0,1fr)) } }
.emcs-directline__item{ margin:0 }
.emcs-directline__label{
	display:block; font-size:var(--wp--preset--font-size--xs); font-weight:600;
	letter-spacing:.1em; text-transform:uppercase; font-stretch:100%;
	color:var(--wp--preset--color--navy-mid);
	margin-bottom:var(--wp--preset--spacing--30);
}
.emcs-directline__value{
	display:inline-block;
	font-size:var(--wp--preset--font-size--xl); font-stretch:125%; font-weight:700;
	line-height:1.15; letter-spacing:-.02em;
	color:var(--wp--preset--color--navy);
	text-decoration:none; border-bottom:2px solid var(--wp--preset--color--periwinkle);
	overflow-wrap:anywhere;
}
.emcs-directline__value:hover{ border-bottom-color:var(--wp--preset--color--navy) }
.emcs-directline__note{
	margin:var(--wp--preset--spacing--60) 0 0;
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--ink-soft);
}

/* ── footer: navigation, contact, assurance ──────────────
   01 §4 asks for every primary link here and the footer had none. Three
   columns of routes plus the two assurance lines, which are set apart rather
   than made a fourth column: "can you work here and are you covered" is a
   different question from "where do I go next", and a procurement reader
   scanning a page bottom is looking for the first one. */
.emcs-footer{
	display:grid; gap:var(--wp--preset--spacing--80);
	margin-bottom:var(--wp--preset--spacing--80);
}
/* The footer is a core group, so core emits `> * + * { margin-block-start:
   var(--wp--style--block-gap) }` for its flow layout. This element is a GRID,
   where that margin means nothing except to push the second grid item down —
   the contact column's heading sat 24px below the other two, which is exactly
   the kind of misalignment nobody can name but everybody sees. Gap owns the
   spacing here. */
.emcs-footer > *{ margin-block-start:0 }
.emcs-footer__nav{ display:grid; gap:var(--wp--preset--spacing--80) }

@media (min-width:600px){
	.emcs-footer__nav{ grid-template-columns:repeat(2,minmax(0,1fr)) }
}
@media (min-width:900px){
	/* The nav pair takes two thirds and the contact block one, so the two link
	   columns stay adjacent and read as one group rather than as three
	   unrelated stacks. minmax(0,1fr) throughout — a grid item defaults to
	   min-width:auto and a long line would otherwise widen its track and push
	   the row into a horizontal scroll. */
	.emcs-footer{ grid-template-columns:minmax(0,2fr) minmax(0,1fr); gap:var(--wp--preset--spacing--90) }
}

.emcs-footer__head{
	margin:0 0 var(--wp--preset--spacing--40);
	font-size:var(--wp--preset--font-size--xs);
	font-stretch:125%; font-weight:700; letter-spacing:0.06em; text-transform:uppercase;
	color:var(--wp--preset--color--navy);
}

.emcs-footer__list{ margin:0; padding:0; list-style:none }
.emcs-footer__list li + li{ margin-top:var(--wp--preset--spacing--30) }

.emcs-footer__list a,
.emcs-footer__direct a{
	color:var(--wp--preset--color--ink-soft);
	font-size:var(--wp--preset--font-size--sm);
	text-decoration:none;
	/* Underline on hover rather than a colour change alone: colour is not
	   available to every reader, and 1.4.1 asks that it never be the only
	   carrier of meaning. */
	text-underline-offset:0.25em;
}
.emcs-footer__list a:hover,
.emcs-footer__direct a:hover{ color:var(--wp--preset--color--navy); text-decoration:underline }

/* Focus is not hover. A keyboard user needs the ring, and the theme's link
   colour alone would not pass 2.4.7 against white. */
.emcs-footer a:focus-visible{
	outline:2px solid var(--wp--preset--color--navy);
	outline-offset:3px; border-radius:2px;
}

.emcs-footer__address,
.emcs-footer__direct{
	margin:0 0 var(--wp--preset--spacing--40);
	font-style:normal; /* <address> italicises by default; this is a postal address, not an aside. */
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--ink-soft);
	line-height:1.7;
}

.emcs-footer__social{ margin-top:var(--wp--preset--spacing--60) }

.emcs-footer__assurance{
	display:grid; gap:var(--wp--preset--spacing--60);
	margin:0 0 var(--wp--preset--spacing--80);
	padding-top:var(--wp--preset--spacing--70);
	border-top:1px solid var(--wp--preset--color--line);
}
@media (min-width:760px){
	.emcs-footer__assurance{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--wp--preset--spacing--80) }
}
.emcs-footer__assurance dt{
	font-size:var(--wp--preset--font-size--xs);
	font-stretch:125%; font-weight:700; letter-spacing:0.06em; text-transform:uppercase;
	color:var(--wp--preset--color--navy);
	margin-bottom:var(--wp--preset--spacing--20);
}
.emcs-footer__assurance dd{
	margin:0;
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--ink-soft);
}

.emcs-footer__legal a{ color:inherit; text-underline-offset:0.25em }
.emcs-footer__legal a:hover{ color:var(--wp--preset--color--navy) }


/* ═══════════════════════════════════════════════════════════════════════
   IMAGERY AND MOTION — added 2026-09-07 (B33, and 02 §15's three candidates)
   ═══════════════════════════════════════════════════════════════════════ */

/* ── /services/: the catalog plate ────────────────────────
   RETIRED FROM /services/ as of 2026-09-11 — the rules stay for the reveal
   list and for anything that still carries the class. The reasoning below was
   honest about the material on 09-07, and 09-11's import changed the
   material: 31 clean masters, most of them 6000px wide. Eric's read of the
   page that day — "pretty amateur, has to be more imagery to showcase our
   capabilities and specific products" — is the plate's epitaph: a reference
   frame at 340px is not a showcase. The page now runs the pair, the framed
   sequence and the mosaic, at the sizes the other pages run them.

   The page listed eight physical products in prose and carried no photograph
   at all, on the URL /balloon-decorations/ redirects to — the second-most-seen
   address on the old domain. Eight frames now sit under the eight headings.

   THEY ARE SMALL DELIBERATELY. 02 §14 records that roughly five images in the
   library are strong enough to run large and this page needed eight, so the
   plate is a reference frame at ~386px rather than a feature image: honest
   about the material, right for a catalog, and clear of the alternating
   left-right feature list §7 bans past six sections.

   4:3 via the `emcs-catalog` crop rather than the site's usual 3:2 — in a 34%
   column a 3:2 frame reads as a strip. aspect-ratio is declared here too so
   the box is reserved before the file arrives; CLS stays at 0. */
.emcs-service__plate{ margin:0; max-width:100% }

/* SOME ENTRIES CARRY TWO FRAMES. Eric, 2026-09-08: show range within a product
   type rather than scaling one photograph up. An entrance arch beside a stage
   arch answers a question a single big arch does not.

   This also replaced the full-bleed treatment that briefly lived here, and it
   keeps what that was for: an entry with two plates is visibly taller than one
   with a single plate, so 02 §6's deliberate variation in row height survives —
   it moved from image scale to entry depth.

   Only four of the eight entries have a second usable frame, and that is a fact
   about the library rather than a design decision. Do not pad the others. */
.emcs-service__plate + .emcs-service__plate{ margin-top:var(--wp--preset--spacing--50) }

/* Stacked, the plate follows the copy — so it needs the gap back that the
   column layout was providing horizontally. Core stacks columns at 781px. */
@media (max-width:781px){
	.emcs-service__plate:first-of-type{ margin-top:var(--wp--preset--spacing--60) }
}

/* ── the two mirrored entries ─────────────────────────────
   02 §7 allows the alternating feature row "twice at most", and this is
   exactly twice: positions 3 and 7. Eight would have been the pattern the
   design system bans; two is an accent, and both are entries carrying a pair
   of frames, so the mirrored side has the weight to look deliberate.

   THE MIRROR IS CSS, NOT MARKUP, and the first cut got that wrong. Reordering
   the columns in the document served a flipped entry as photograph -> heading
   -> copy, which reads correctly on screen and wrongly everywhere else: a
   screen reader, a reader mode and a text browser would all get "picture, then
   what the picture is of". WCAG 1.3.2 asks the programmatic sequence to be the
   meaningful one. The document now always reads heading -> copy -> plates, and
   only the painted order changes.

   That also means the phone needs no rule. Core stacks columns at 781px and
   the source order is already the one a stacked entry wants, flipped or not. */
@media (min-width:782px){
	.emcs-service--flip .wp-block-column:last-child{ order:-1 }
}
.emcs-service__plate img{
	width:100%; height:auto; display:block;
	aspect-ratio:4/3; object-fit:cover;
}

/* ── a wide figure that must keep its sky ─────────────────
   `.emcs-figure` crops to 21/9, which is right for a street-level frame and
   destroys an aerial one: on /homebuilders/vns/ the subject is five anchors in
   the top third, and a 21/9 centre crop returns a photograph of a road. 16/9
   with the window biased upward keeps them. */
.emcs-figure--sky img{ aspect-ratio:16/9; object-position:50% 22% }

/* ── entry reveal ─────────────────────────────────────────
   02 §11: fade-and-rise, 12px, 400ms, cubic-bezier(0.16,1,0.3,1), ONCE and
   never on scroll-back.

   GATED BEHIND `[data-reveal="on"]`, which assets/js/reveal.js never sets —
   a 150-byte snippet in wp_head does, before first paint, and only when the
   reader has not asked for reduced motion and the browser has
   IntersectionObserver. Without the attribute nothing below applies, so no
   JavaScript means a finished page rather than an invisible one.

   This is why it is not `animation-timeline: view()`, which 02 §15 proposed:
   a view-progress timeline is a function of scroll position, so it reverses on
   scroll-back and parks half-finished when the reader stops. §11 forbids both. */
/* THE `:where()` IS LOAD-BEARING, and it cost a build to find out. Written as
   a plain selector list this reads `:root[data-reveal="on"] .emcs-figure img`
   — specificity (0,3,1) — while the rule that un-hides it is
   `:root[data-reveal="on"] [data-revealed="true"]` at (0,3,0). The hiding rule
   wins, so the observer fired, the attribute landed, and the photograph on
   /homebuilders/vns/ stayed at `opacity:0` inside a correctly reserved 810px
   box. It looked exactly like a broken image URL.

   `:where()` contributes zero specificity, so every selector in this list is
   (0,2,0) and the reveal at (0,3,0) always wins — including for anything added
   to the list later, which a hand-balanced selector pair would not survive. */
:root[data-reveal="on"] :where(
	[data-reveal-child],
	.emcs-figure,
	.emcs-service__plate,
	.emcs-evidence figure,
	.emcs-diagram,
	.emcs-spec__photo,
	.emcs-vns__frame
){
	opacity:0;
	transform:translateY(12px);
	transition:
		opacity 400ms cubic-bezier(.16,1,.3,1),
		transform 400ms cubic-bezier(.16,1,.3,1);
	will-change:opacity, transform;
}
:root[data-reveal="on"] [data-revealed="true"],
:root[data-reveal="on"] [data-revealed="true"] > [data-reveal-child]{
	opacity:1;
	transform:none;
	will-change:auto;
}

/* THE STAGGER. Eric, 2026-09-10: "create some motion with the flow of the
   images and the text, more towards an apple.com type motion."

   A group is observed; its children move. `--emcs-stagger` is set per child by
   reveal.js from its index, capped at six steps so a long checklist does not
   end up with a two-second tail. 60ms is under the ~100ms at which a stagger
   stops reading as deliberate and starts reading as lag.

   That sequencing is most of what the reference actually feels like. The rest
   of it — scroll-linked scale, pinned sections, parallax — is banned outright
   by §7 and §11, and none of it is missed: content arriving in the order you
   would read it is the legible part. */
:root[data-reveal="on"] [data-reveal-child]{
	transition-delay:var(--emcs-stagger, 0ms);
}

/* ── the anchor rises past the sign ───────────────────────
   02 §15's second candidate, and the one it calls the §7 test passing rather
   than failing: this does not animate a number to imply importance, it draws
   the proportion the company competes on. The sign is static — it is the
   reference — and the anchor climbs from the ground to more than a hundred
   feet while its tether draws behind it.

   1100ms rather than §11's 400ms, and that is the one deliberate divergence
   here: 400ms is the right duration for a section arriving, and far too fast
   for a distance to register as a distance. The delay lets the figure's own
   fade land first.

   Tether length is 418 user units (y 560 -> 142), so the dash array is 418;
   the anchor starts 418 units down, at the ground. Redrawn 2026-10-08. */
:root[data-reveal="on"] .emcs-diagram__anchor{ transform:translateY(418px) }
:root[data-reveal="on"] .emcs-diagram__tether{ stroke-dasharray:418; stroke-dashoffset:418 }
:root[data-reveal="on"] .emcs-diagram[data-revealed="true"] .emcs-diagram__anchor{
	animation:emcs-anchor-rise 1100ms cubic-bezier(.16,1,.3,1) 180ms both;
}
:root[data-reveal="on"] .emcs-diagram[data-revealed="true"] .emcs-diagram__tether{
	animation:emcs-tether-draw 1100ms cubic-bezier(.16,1,.3,1) 180ms both;
}
@keyframes emcs-anchor-rise{ from{ transform:translateY(418px) } to{ transform:none } }
@keyframes emcs-tether-draw{ from{ stroke-dashoffset:418 } to{ stroke-dashoffset:0 } }

/* ── page-to-page ─────────────────────────────────────────
   One rule, no JavaScript, no bytes on the wire. §7 bans a long list of moving
   decorations and addresses none of this, because this is not decoration ON a
   page — it is the transition BETWEEN pages, and a site whose navigation hard-
   cuts feels like a set of documents rather than one product.

   Chromium and Safari do it; Firefox ignores the rule and navigates exactly as
   it does today. Nothing degrades.

   The reduced-motion override is explicit because it has to be: the global
   `*` rule in inc/assets.php cannot reach a ::view-transition pseudo-element,
   so without these two lines a reader who asked for no motion would still get
   the cross-fade. */
@view-transition{ navigation:auto }
@media (prefers-reduced-motion:reduce){
	::view-transition-old(root),
	::view-transition-new(root){ animation-duration:1ms !important }
}


/* ═══════════════════════════════════════════════════════════════════════
   SCROLL-LINKED MOTION — added 2026-09-10 at Eric's request, PENDING HIS
   SIGN-OFF on the design-system amendment. 02 §7 bans parallax and §11 bans
   anything that moves while being read; both are his rules to change, and he
   asked to see it before deciding. If the answer is no, delete this block —
   nothing else depends on it.
   ═══════════════════════════════════════════════════════════════════════ */

/* THE MECHANISM IS `animation-timeline: view()`, and it is the right one HERE
   for exactly the reason it was the wrong one for the entry reveal.

   A view-progress timeline ties the animation to scroll position. For a
   one-shot reveal that is a defect — it reverses on scroll-back and parks
   half-done. For a continuous drift it is the entire point: the image is
   *supposed* to track the scroll, forwards and backwards, and there is no
   "finished" state to be caught between.

   It also costs nothing. No scroll listener, no rAF loop, no JavaScript at
   all; the compositor runs it off the main thread. A scroll-handler parallax
   is what makes this effect feel cheap on a mid-range phone, and this is not
   one.

   Chromium and Safari 26 run it. Firefox does not, and there it is simply
   absent — no fallback needed, because the page is complete without it. */

@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {

		/* ── Parallax on the hero ─────────────────────────────────────
		   The image is held at scale(1.14) so a ±3.5% drift never exposes
		   an edge, and it travels against the scroll. 7% of total travel
		   across a full viewport is a long way below the point where it
		   reads as an effect rather than as depth. */
		.emcs-hero__media img {
			/* LONGHANDS, WITH `animation-duration: auto`.
			   Per spec the `animation` shorthand resets duration to 0s, and
			   `auto` is the defined value for a timeline-driven animation. I
			   first wrote this comment claiming the shorthand froze the
			   effect — then A/B'd it, and Chrome renders both forms
			   identically, to the pixel. So this is explicitness, not a fix
			   for an observed bug, and it is written that way because the
			   other version was a confident wrong explanation.

			   HOW TO TEST THIS AT ALL. getComputedStyle and
			   getBoundingClientRect both report a frozen midpoint here: a
			   scroll-driven transform lives on the compositor and never
			   reaches the main thread's style. Every measurement of this
			   effect said it was dead while it was working. The only thing
			   that settles it is pixels — screenshot the same scroll position
			   with prefers-reduced-motion on and off and diff the two. */
			animation-name: emcs-parallax;
			animation-duration: auto;
			animation-timing-function: linear;
			animation-fill-mode: both;
			animation-timeline: view();
			animation-range: cover 0% cover 100%;
			will-change: transform;
		}

		/* ── Scale on the full-bleed figures ──────────────────────────
		   These are the page's largest photographs and they hold the
		   screen longest. 1.00 to 1.06 across the whole pass is slow
		   enough that you notice the photograph, not the movement. */
		.emcs-figure { overflow: hidden }
		.emcs-figure img {
			animation-name: emcs-slowzoom;
			animation-duration: auto;
			animation-timing-function: linear;
			animation-fill-mode: both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
			will-change: transform;
		}
	}
}

@keyframes emcs-parallax {
	from { transform: scale(1.14) translate3d(0, -3.5%, 0) }
	to   { transform: scale(1.14) translate3d(0,  3.5%, 0) }
}
@keyframes emcs-slowzoom {
	from { transform: scale(1.00) }
	to   { transform: scale(1.06) }
}


/* ── the mosaic: photography that is not another strip ────
   Eric, 2026-09-10: "just bands of different photos is a bit repetitive."

   Fair, and 02 §7 says so first — "vary the count and shape, asymmetric
   two-up, a wide-plus-narrow split" is listed there as the ANSWER to exactly
   this, and the site had been answering everything with one 16:9 three-up.

   One wide frame and two stacked narrow ones, constrained to 1200 rather than
   full-bleed, so it differs from a band in shape AND in width. The large slot
   is 4:3 because the frames that suit it are nearly square — a sculpture, a
   room — and a 16:9 slot was cropping the top off them. */
.emcs-mosaic{
	display:grid;
	gap:var(--wp--preset--spacing--60);
	grid-template-columns:minmax(0,1fr);
}
@media (min-width:820px){
	.emcs-mosaic{ grid-template-columns:minmax(0,1.62fr) minmax(0,1fr) }
}
.emcs-mosaic figure{ margin:0 }
.emcs-mosaic img{ width:100%; height:auto; display:block; object-fit:cover }
.emcs-mosaic__lead img{ aspect-ratio:4/3 }
.emcs-mosaic__stack{ display:grid; gap:var(--wp--preset--spacing--60); align-content:start }
.emcs-mosaic__stack img{ aspect-ratio:3/2 }
/* The stack sits a little low against the lead frame — a deliberate offset, so
   the pair reads as a composition rather than as two columns that failed to
   line up. Only where there is room for it to be read as intentional. */
@media (min-width:1040px){
	.emcs-mosaic__stack{ margin-top:var(--wp--preset--spacing--90) }
}


/* ═══════════════════════════════════════════════════════════════════════
   HOME PAGE COMPOSITION — 2026-09-10, after Eric's read of the page:
   "bands of different photos is a bit repetitive", "two dark colour groups
   need more splitting them up", "some colour splashed throughout".
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the pair: two halves, two shapes ─────────────────────
   The headline above it says "Half of it is getting people there. Half of
   it is the room they walk into." So the photography is two frames of near-
   equal width and DIFFERENT shape — a 3:2 landscape for the road, a squarer
   4:3 for the doorway — and their heights match: with columns in the ratio
   9:8, a 3:2 frame and a 4:3 frame come out the same height to the pixel
   (9/1.5 = 8/1.333). It reads as a pair rather than as a feature with a
   sidekick, which is what the copy is saying.

   Full-bleed, so the width changes after the constrained text above it.
   Captions sit BELOW on the page's own ground, not over the photograph:
   tests/contrast.mjs cannot measure text on pixels, and a scrim under a
   caption is the hero device repeated. The caption is inset by the root
   padding token so it lines up with the page edge, as .emcs-figure does. */
.emcs-pair{ display:grid; grid-template-columns:minmax(0,1fr); gap:var(--wp--preset--spacing--80) }
@media (min-width:860px){
	.emcs-pair{ grid-template-columns:minmax(0,9fr) minmax(0,8fr); gap:var(--wp--preset--spacing--30) }
}
.emcs-pair figure{ margin:0 }
.emcs-pair img{ width:100%; height:auto; display:block; object-fit:cover }
.emcs-pair__road img{ aspect-ratio:3/2 }
.emcs-pair__door img{ aspect-ratio:4/3 }
.emcs-pair figcaption{
	padding:var(--wp--preset--spacing--50) var(--wp--preset--spacing--60) 0;
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--ink-soft);
	max-width:44ch; text-wrap:pretty;
}
.emcs-pair figcaption b{ color:var(--wp--preset--color--navy); font-stretch:125% }

/* ── the specification block, with the night beside it ────
   Six rows of procurement detail on navy-deep were the page's longest
   stretch without a photograph, and it sat directly above the navy CTA
   band — the "two dark colour groups". The install-window row says outdoor
   routes go in while the site is empty; this puts the install next to the row
   that claims it. The frame is square because a 3:2 landscape at a third of
   the container is a strip beside a 600px list, and it is sticky so it
   stays level with whichever row is being read.

   `.emcs-spec` itself is shared with /about/ and is not touched. */
.emcs-spec__split{ display:grid; grid-template-columns:minmax(0,1fr); gap:var(--wp--preset--spacing--90) }
@media (min-width:1000px){
	.emcs-spec__split{
		grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);
		gap:var(--wp--preset--spacing--100); align-items:start;
	}
}
.emcs-spec__photo{ margin:0 }
@media (min-width:1000px){ .emcs-spec__photo{ position:sticky; top:var(--wp--preset--spacing--80) } }
.emcs-spec__photo img{ width:100%; height:auto; display:block; aspect-ratio:3/2; object-fit:cover }
/* Square only beside the list. Stacked under it on a tablet the same square
   is 800px on a side, which is a poster, not a figure. */
@media (min-width:1000px){ .emcs-spec__photo img{ aspect-ratio:1/1 } }

/* ── the vertical selector, image-led ─────────────────────
   Four bordered cards followed by a four-up strip of "the four,
   photographed" was the same information twice, in two devices. The
   photograph now lives IN the selector, one per vertical, and the box goes:
   no border, no padding, no fill — image, heading, two lines, link. That is
   the SOM grid 02 §13.5 marks as directly applicable, and it keeps the
   photography arguing for the vertical it routes to.

   THE CHECKERBOARD. A 12-column grid, 7/5 on the first row and 5/7 on the
   second. Developers are three quarters of revenue and get the wide frame
   first; the second row mirrors so the split moves and the block reads as a
   composition rather than a table. The narrow frame in each row is cover-
   cropped to the wide frame's height — near-square — which is what makes
   the two frames in a row share top and bottom edges at every width.

   THE NARROW FRAME'S HEIGHT IS COMPUTED, NOT INHERITED. The first cut used
   subgrid rows and `height:100%` on the narrow image, and Chrome resolved
   the percentage into a 1500px frame — a cyclic percentage inside a stretched
   grid item, and not a fight worth having. The height is now written out:
   the wide frame is (7 columns + 6 gaps) / 1.5 tall, and with the selector as
   a size container that is a plain calc in `cqw`. Exact at every width, no
   cycle. A browser without container units drops the declaration and gets a
   3:2 frame, which lines up nothing but breaks nothing. */
.emcs-verticals--photo{ gap:var(--wp--preset--spacing--100) var(--wp--preset--spacing--70); container-type:inline-size }
@media (min-width:700px){ .emcs-verticals--photo{ grid-template-columns:repeat(2,minmax(0,1fr)) } }
@media (min-width:1000px){
	.emcs-verticals--photo{ grid-template-columns:repeat(12,minmax(0,1fr)) }
	.emcs-verticals--photo .emcs-vertical{ grid-column:span 5 }
	.emcs-verticals--photo .emcs-vertical--wide{ grid-column:span 7 }
	.emcs-verticals--photo .emcs-vertical:not(.emcs-vertical--wide) .emcs-vertical__media img{
		aspect-ratio:auto;
		height:calc((7 * (100cqw - 11 * var(--wp--preset--spacing--70)) / 12 + 6 * var(--wp--preset--spacing--70)) / 1.5);
	}
}
.emcs-verticals--photo .emcs-vertical{ border:0; border-radius:0; padding:0; background:none }
.emcs-verticals--photo .emcs-vertical:hover{ background:none }
.emcs-vertical__media{ margin:0 0 var(--wp--preset--spacing--60) }
.emcs-vertical__media img{ width:100%; height:auto; display:block; aspect-ratio:3/2; object-fit:cover }
.emcs-verticals--photo .emcs-vertical p{ margin-bottom:var(--wp--preset--spacing--50) }
/* Colour only, on hover: the box that used to change is gone, so the title
   and the link carry the state instead. Underline on the link as well, so
   the change is not colour alone (1.4.1). */
.emcs-verticals--photo .emcs-vertical:hover h3{ color:var(--wp--preset--color--navy-deep) }
.emcs-verticals--photo .emcs-vertical:hover .emcs-vertical__go{ text-decoration:underline; text-underline-offset:.25em }


/* ═══════════════════════════════════════════════════════════════════════
   /homebuilders/ COMPOSITION — 2026-09-11, after Eric's read of the page:
   "the navigation system block should have photos of each level" and "the
   photo bands are on top of each other". Same vocabulary as the home page —
   the pair, the spec split, the full-bleed figure — plus the one thing this
   page has that no other does: the altitude bands.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the altitude bands, each with its own frame ──────────
   The block listed four altitude bands beside ONE photograph, of the top
   band only. Each band now carries a frame of the thing it describes, so the
   reader descends the system the way the driver's eye does: a lone anchor in
   open sky, clusters flown at second-storey height, a cluster on a pole at a
   decision point, a column standing at the entrance. The sticky aside goes —
   with a frame per band there is nothing left to pin, and the descent
   through four photographs IS the scroll sequence 02 §6 asks for.

   Copy on the left against the periwinkle spine, so the four altitudes line
   up down one edge; the frame on the right. Equal halves, deliberately: the
   photograph is the evidence and gets the same width as the claim. Under
   900px the frame goes FIRST visually (order) while the DOM keeps altitude →
   heading → copy → figure, which is the order a screen reader should hear.

   Keyed on a modifier so emcs/vns-reveal — still on the pattern-proof page
   with its single sticky frame — renders exactly as it did. */
.emcs-vns__bands--framed .emcs-vns__band{ display:grid; gap:var(--wp--preset--spacing--60); align-items:start }
@media (min-width:900px){
	.emcs-vns__bands--framed .emcs-vns__band{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--wp--preset--spacing--100) }
}
.emcs-vns__frame{ margin:0; order:-1 }
@media (min-width:900px){ .emcs-vns__frame{ order:0 } }
.emcs-vns__frame img{ width:100%; height:auto; display:block; aspect-ratio:3/2; object-fit:cover }
/* 58ch: the copy column is ~550px at 1200, which is 60ch at body size and a
   line or two over §14's ceiling. The part line underneath is exempt — at
   `sm` the same cap lands short of the rule above it and looks accidental. */
.emcs-vns__copy p:not(.emcs-vns__part){ max-width:46ch }

/* ── crop direction ──────────────────────────────────────
   Two frames whose subject is not in the middle. The door frame's Sales
   Office sign sits left of centre and the library watermark sits at the
   right edge, so the 4:3 window is anchored left and the edge goes. The
   night frame beside the spec has its two lit anchors right of centre; a
   centred square cut one in half at the frame edge. */
.emcs-pair__door--left img{ object-position:0 50% }
.emcs-spec__photo--right img{ object-position:100% 50% }


/* ═══════════════════════════════════════════════════════════════════════
   /education/ COMPOSITION — 2026-09-11. Same vocabulary as the home page
   and /homebuilders/ — the pair, the spec split, the framed sequence, and
   the mosaic, which was built on 2026-09-10 and had not been used anywhere.
   Nothing new is drawn; two rules below are all the page needed.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the framed sequence, carrying packages ──────────────
   On /homebuilders/ each band leads with an altitude — a datum at xxl —
   and the tier name sits under it at xl. A package has no datum, so the
   name takes the xxl slot itself and stays a heading. The class is the
   same; the element underneath it is an h3, so its UA margin is reset.
   Keyed on --framed rather than --packages since 2026-09-11: the lot items
   on /dealerships/ lead with an h3 the same way, and the rule describes
   "a framed band whose lead is a heading", not a package. */
.emcs-vns__bands--framed h3.emcs-vns__alt{ margin:0 0 var(--wp--preset--spacing--50) }

/* ── the procurement split, on white ─────────────────────
   .emcs-spec__split is a grid and a sticky frame; nothing in it assumes a
   dark ground. The checklist's own 70ch cap still applies inside the
   1.6fr column. On a tablet the frame stacks under the list at 3:2 and
   the heading above has already said what it is, so no caption. */
.emcs-spec__split .emcs-checklist{ max-width:none }


/* ═══════════════════════════════════════════════════════════════════════
   /dealerships/ COMPOSITION — 2026-09-11. The full-bleed figure, the
   mosaic and the framed sequence, all as built for the pages before it.
   Nothing new is drawn; three crop-direction rules are all the page needed.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── crop direction ──────────────────────────────────────
   The showroom figure hangs its subject from the ceiling, and a centred
   21:9 window cuts the top cluster in half while keeping a strip of floor
   nobody needs. The window sits high — 30% of the overflow taken from the
   top, 70% from the bottom — which also drops the library watermark that
   sits in the bottom-right corner of every frame from this shoot.

   The two stacked frames in the mosaic: a 4:3 showroom whose ornaments
   hang from the ceiling and whose watermark sits bottom-left, anchored to
   the top; and a portrait column in a landscape slot, anchored near the
   top so the window holds the character on top of the column rather than
   its midsection. */
.emcs-figure--ceiling img{ object-position:50% 30% }
.emcs-mosaic__top img{ object-position:50% 0 }
.emcs-mosaic__high img{ object-position:50% 20% }


/* ── the framed sequence on a light ground ────────────────
   Added 2026-09-11. /dealerships/ ended navy-deep (the lot) directly above
   navy (the ASK) — the exact stacking Eric objected to on the home page on
   09-10, and the /dealerships/ pass flagged it rather than solving it.

   Reordering does not help: every arrangement of that page puts two darks
   together somewhere. The sequence itself has to be able to sit on light.

   Three overrides and one caveat. The spine stays PERIWINKLE and that is
   deliberate, not an oversight: 02 §3 forbids periwinkle as text or as a
   meaning-bearing element at small size on a light ground, and permits it as a
   stroke at 3px or more. The spine is a 3px border, which is exactly the
   permitted case — and keeping it means the active-band affordance reads the
   same on both grounds. */
.emcs-vns--light{ background:var(--wp--preset--color--surface) }
.emcs-vns--light .emcs-eyebrow{ color:var(--wp--preset--color--navy-mid) }
.emcs-vns--light .emcs-vns__alt,
.emcs-vns--light .emcs-vns__h,
.emcs-vns--light h2{ color:var(--wp--preset--color--navy) }
.emcs-vns--light .emcs-vns__band p,
.emcs-vns--light p{ color:var(--wp--preset--color--ink-soft) }
.emcs-vns--light .emcs-vns__part{
	color:var(--wp--preset--color--ink-soft) !important;
	border-top-color:var(--wp--preset--color--line);
}


/* ═══════════════════════════════════════════════════════════════════════
   /services/ COMPOSITION — 2026-09-11, after Eric's read of the page:
   "pretty amateur — has to be more imagery to showcase our capabilities
   and specific products." Same vocabulary as the pages before it — the
   pair, the framed sequence on a light ground, the mosaic — plus two small
   things this page needed that none of them had: a catalog list, and a
   caption on the mosaic.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the catalog list ─────────────────────────────────────
   The product names are the most specific thing on the page — the fixed
   client-facing vocabulary, guardrail 4 — and they were set as one run-on
   line at `sm` in ink-soft under the paragraph: the smallest, palest type
   on the page. Now one name per line, a rule between, in the display face
   at 600 — the treatment .emcs-spec__row dt and .emcs-table tbody th
   already give to the name of a thing. Under the entry heading rather than
   under the copy, because the names belong to the heading: they are what
   "balloon columns" means once it is quoted.

   `li` picks up the text face from the global reset in inc/assets.php; the
   class wins on specificity, which is the point of the rule being on the
   list item and not on the list. */
.emcs-service__products{
	list-style:none; margin:var(--wp--preset--spacing--60) 0 0; padding:0;
}
.emcs-service__products li{
	padding:var(--wp--preset--spacing--40) 0;
	border-top:1px solid var(--wp--preset--color--line);
	font-stretch:125%; font-weight:600; line-height:1.35;
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--navy);
	text-wrap:balance;
}
.emcs-service__products li:last-child{ border-bottom:1px solid var(--wp--preset--color--line) }
/* Inside a framed band the list follows the paragraph, and the band's own
   paragraph colour must not bleed into it: the names are navy on both
   grounds because they are names, not prose. The cross-link line under the
   list gets the same gap the list took above it. */
.emcs-vns__copy .emcs-service__products{ max-width:46ch }
.emcs-vns__copy .emcs-service__products + p{ margin-top:var(--wp--preset--spacing--60) }

/* ── the mosaic, captioned ────────────────────────────────
   On /events/ and /dealerships/ the mosaic sits under a heading that has
   already said what the room is. Here its three frames stand for three
   entries — the ceiling, the sculpture, the branded room — and the reader
   needs the map. Same caption as the pair: below the frame, on the page's
   own ground, the name in the display face, so tests/contrast.mjs can
   measure it and no scrim is needed.

   The stack loses its offset when captioned. A caption under each stacked
   frame already carries the stack past the lead; the offset on top of that
   stopped reading as deliberate and started reading as a gap. */
.emcs-mosaic figcaption{
	padding:var(--wp--preset--spacing--50) 0 0;
	font-size:var(--wp--preset--font-size--sm);
	color:var(--wp--preset--color--ink-soft);
	max-width:44ch; text-wrap:pretty;
}
.emcs-mosaic figcaption b{ color:var(--wp--preset--color--navy); font-stretch:125% }
@media (min-width:1040px){
	.emcs-mosaic--captioned .emcs-mosaic__stack{ margin-top:0 }
}

/* ── crop direction ───────────────────────────────────────
   The registration bouquets are a 16:9 export in a 3:2 frame, so the window
   loses a sixth of the width — and the foreground bouquet is hard against
   the left edge. Anchor the window left and the loss comes off the carpet
   on the right, where nothing is. */
.emcs-vns__frame--left img{ object-position:0 50% }

/* ── /about/: Eric's portrait ────────────────────────────
   Sits under "One person owns the project." in the 38% column, so the
   answer to "who is on site?" is a face, not a claim. 4:5 keeps both the
   highflier and Eric in frame; the crop leans right, where he stands. */
.emcs-portrait{ margin:var(--wp--preset--spacing--70) 0 0 }
.emcs-portrait img{
	width:100%; height:auto; display:block;
	aspect-ratio:4/5; object-fit:cover; object-position:62% 55%;
}
.emcs-portrait figcaption{
	margin-top:var(--wp--preset--spacing--50);
	font-size:var(--wp--preset--font-size--xs);
	color:var(--wp--preset--color--ink-soft);
}
@media (max-width:860px){ .emcs-portrait{ max-width:420px } }
