/* ============================================================================
   Wade's Plumbing & Septic — design system application layer
   ============================================================================

   WHY THIS FILE EXISTS

   A class audit across all 164 templates found:

     161  distinct button styles
      46  distinct section rhythms
      12 / 42 / 39  distinct h1 / h2 / h3 treatments (most headings unstyled)
      10  border radii
      22  shadow variants, incl. 4 arbitrary values and a stale #bc6f30 glow
      37  vertical padding values
     139  colour tokens across three competing vocabularies

   Rewriting 164 templates by hand would be an enormous, high-risk diff for a
   live lead-generating site. Instead this layer normalises the *values* those
   existing classes resolve to, so the accumulated variety collapses onto one
   system while the markup and layout stay exactly as they are.

   It normalises materials — surface colour, ink, radius, depth, type scale,
   rhythm. It deliberately does NOT change layout, grid structure, or spacing
   between elements, because those carry intent that varies legitimately per
   page.

   Tailwind here is PRE-COMPILED: a utility class that is not already in the
   build does not exist and fails silently. So everything below either restyles
   a class that is already present, or defines a new one outright.

   Load order: design-system-variables.css -> this file -> custom.css
   (custom.css keeps final say for page-specific work.)

   SCOPE: rules hang off `main`, not `.site-main`. Only front-page.php ever
   carried the .site-main class; every other template ships a bare <main>,
   so an earlier .site-main-scoped version of this file was silently inert
   on 10 of the 11 page types. Measured, not assumed — h2 had 17 distinct
   renderings across the site because of it.
   ========================================================================= */

/* ── 1. Page canvas ──────────────────────────────────────────────────────── */

body {
	background-color: var(--ui-canvas);
	color: var(--ui-ink);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

/* ── 2. Surface normalisation ────────────────────────────────────────────────
   bg-white (219 uses) and bg-gray-50 (65 uses) were doing all the surface work
   in cool neutrals. Warming them is the single biggest coherence win on the
   site, and it costs no markup change.

   Scoped away from the admin bar and the dark bands, which set their own
   colours and must not be repainted. */

body:not(.wp-admin) .bg-white:not(.fp-hero__path):not([class*="bg-nav"]) {
	background-color: var(--ui-paper);
}

/* The page wrapper itself. Templates set their own <main> background — bg-white,
   bg-[#f8f9fb] (a cool near-white) or nothing — which meant the page field
   changed depending on which template you landed on. The canvas is a property of
   the site, not of the template, so it is set once here. Sections inside still
   paint their own surfaces on top. */
body:not(.wp-admin) main {
	background-color: var(--ui-canvas);
}

body:not(.wp-admin) .bg-gray-50,
body:not(.wp-admin) .bg-gray-100\/50,
body:not(.wp-admin) .bg-slate-50 {
	background-color: var(--ui-canvas);
}

body:not(.wp-admin) .bg-gray-100 {
	background-color: var(--ui-paper-subtle);
}

/* Dark bands: warm charcoal rather than pure black. #000 on a warm palette
   reads as a hole in the page. */
body:not(.wp-admin) .bg-black,
body:not(.wp-admin) .bg-gray-900,
body:not(.wp-admin) .bg-gray-950 {
	background-color: var(--ui-dark);
}

/* ── 3. Ink normalisation ────────────────────────────────────────────────────
   The grey scale is remapped onto warm ink, then explicitly restored inside the
   dark bands further down — the same class names do double duty as dark text on
   light surfaces and light text on dark ones, so a single global remap breaks
   one of the two. text-gray-300 is left alone: it is used almost exclusively as
   light text on dark, where warming it would destroy the contrast outright. */

body:not(.wp-admin) .text-gray-900,
body:not(.wp-admin) .text-slate-900,
body:not(.wp-admin) .text-foreground {
	color: var(--ui-ink);
}

body:not(.wp-admin) .text-gray-800,
body:not(.wp-admin) .text-gray-700 {
	color: var(--ui-ink-secondary);
}

body:not(.wp-admin) .text-gray-600 {
	color: var(--ui-ink-secondary);
}

/* 5.62:1 on paper.
   text-gray-400 is included because on the warm canvas its Tailwind value only
   reaches 4.22:1 — it cleared AA against the old cooler, lighter #f9fafb but
   not against #f4efe6. It is restored to a light value inside dark bands below,
   same as the other greys. */
body:not(.wp-admin) .text-gray-500,
body:not(.wp-admin) .text-gray-400,
body:not(.wp-admin) .text-muted-foreground {
	color: var(--ui-ink-muted);
}

/* …but the SAME class names are also used as light text inside the dark bands,
   where warm ink drops to 2.13:1. Restore every remapped grey in a dark context.
   (Found by Lighthouse on /contact/: text-gray-600 inside the footer.)

   Kept as an explicit list rather than :is() so it degrades predictably in
   older engines. */
body:not(.wp-admin) .bg-black .text-gray-400,
body:not(.wp-admin) .bg-black .text-gray-500,
body:not(.wp-admin) .bg-black .text-gray-600,
body:not(.wp-admin) .bg-black .text-gray-700,
body:not(.wp-admin) .bg-black .text-gray-800,
body:not(.wp-admin) .bg-black .text-gray-900,
body:not(.wp-admin) .bg-gray-900 .text-gray-400,
body:not(.wp-admin) .bg-gray-900 .text-gray-500,
body:not(.wp-admin) .bg-gray-900 .text-gray-600,
body:not(.wp-admin) .bg-gray-900 .text-gray-700,
body:not(.wp-admin) .bg-nav .text-gray-400,
body:not(.wp-admin) .bg-nav .text-gray-500,
body:not(.wp-admin) .bg-nav .text-gray-600,
body:not(.wp-admin) .bg-nav .text-gray-700,
body:not(.wp-admin) .site-footer .text-gray-400,
body:not(.wp-admin) .site-footer .text-gray-500,
body:not(.wp-admin) .site-footer .text-gray-600,
body:not(.wp-admin) .site-footer .text-gray-700,
body:not(.wp-admin) .site-footer .text-gray-800,
body:not(.wp-admin) .site-footer .text-gray-900,
body:not(.wp-admin) .site-footer .text-foreground,
body:not(.wp-admin) .site-footer .text-muted-foreground,
body:not(.wp-admin) [class*="bg-[#0"] .text-gray-400,
body:not(.wp-admin) [class*="bg-[#0"] .text-gray-500,
body:not(.wp-admin) [class*="bg-[#0"] .text-gray-600,
body:not(.wp-admin) [class*="bg-[#0"] .text-gray-700,
body:not(.wp-admin) [class*="bg-[#1"] .text-gray-400,
body:not(.wp-admin) [class*="bg-[#1"] .text-gray-500,
body:not(.wp-admin) [class*="bg-[#1"] .text-gray-600,
body:not(.wp-admin) [class*="bg-[#1"] .text-gray-700,
body:not(.wp-admin) [class*="bg-[#2"] .text-gray-400,
body:not(.wp-admin) [class*="bg-[#2"] .text-gray-500,
body:not(.wp-admin) [class*="bg-[#2"] .text-gray-600,
body:not(.wp-admin) [class*="bg-[#2"] .text-gray-700 {
	color: var(--ui-ink-on-dark-muted);
}

/* Copper accents inside dark bands need the lightened value too. */
body:not(.wp-admin) .site-footer .text-primary,
body:not(.wp-admin) .bg-black .text-primary,
body:not(.wp-admin) .bg-nav .text-primary,
body:not(.wp-admin) [class*="bg-[#1"] .text-primary {
	color: var(--brand-copper-on-dark);
}

/* …except where that copper sits on its own light button inside the dark band,
   which is a light surface and needs the real brand value back. */
body:not(.wp-admin) .site-footer .bg-white .text-primary,
body:not(.wp-admin) .site-footer a.bg-white,
body:not(.wp-admin) .bg-black .bg-white .text-primary,
body:not(.wp-admin) .bg-black a.bg-white,
body:not(.wp-admin) .bg-nav .bg-white .text-primary,
body:not(.wp-admin) .bg-nav a.bg-white,
body:not(.wp-admin) [class*="bg-[#1"] .bg-white .text-primary,
body:not(.wp-admin) [class*="bg-[#1"] a.bg-white {
	color: var(--brand-copper);
}

/* Translucent-white chips on the copper surface.

   A `bg-white/20` badge over #8e5725 composites to #a3774f, and white text on
   that is only 3.69:1 — it reads as a highlight but fails AA. Lightening a
   mid-tone accent cannot produce enough separation for white text; going the
   other way does. rgba(0,0,0,0.28) over copper gives 9.2:1. */
body:not(.wp-admin) .bg-primary [class*="bg-white\/1"],
body:not(.wp-admin) .bg-primary [class*="bg-white\/2"],
body:not(.wp-admin) .faq-filter [class*="bg-white\/1"],
body:not(.wp-admin) .faq-filter [class*="bg-white\/2"],
body:not(.wp-admin) [class*="bg-primary"] > [class*="bg-white\/2"] {
	background-color: rgba(0, 0, 0, 0.28);
	color: #ffffff;
}

/* Legacy copper baked into inline style attributes.

   AI-generated blocks stored in the database hardcode the retired pre-AA brand
   colour #bc6f30 / rgb(188,111,48) directly in a `style` attribute. White text
   on it is 3.84:1 — a real AA failure, found by Lighthouse on a service page.
   Theme files no longer contain the value, but stored content still does and no
   template edit can reach it, so it is corrected by attribute-substring match.

   The selectors must distinguish which PROPERTY the old colour was used for. A
   first attempt matched any [style*="#bc6f30"] and forced a copper background,
   which painted a copper background onto <p style="color:#bc6f30"> — copper on
   copper, 1.54:1, worse than the problem it replaced. Background declarations
   and colour declarations are therefore matched separately, with :not() guards
   so "background-color: …" does not satisfy the bare "color: …" match.

   !important is required: inline styles beat stylesheet rules otherwise. */
body:not(.wp-admin) [style*="background-color: #bc6f30"],
body:not(.wp-admin) [style*="background-color:#bc6f30"],
body:not(.wp-admin) [style*="background: #bc6f30"],
body:not(.wp-admin) [style*="background:#bc6f30"],
body:not(.wp-admin) [style*="background-color: rgb(188, 111, 48)"],
body:not(.wp-admin) [style*="background-color:rgb(188,111,48)"] {
	background-color: var(--brand-copper) !important;
}

body:not(.wp-admin) [style*="color: #bc6f30"]:not([style*="background-color: #bc6f30"]):not([style*="background: #bc6f30"]),
body:not(.wp-admin) [style*="color:#bc6f30"]:not([style*="background-color:#bc6f30"]):not([style*="background:#bc6f30"]) {
	color: var(--brand-copper) !important;
}

body:not(.wp-admin) [style*="border-color: #bc6f30"],
body:not(.wp-admin) [style*="border-color:#bc6f30"] {
	border-color: var(--brand-copper) !important;
}

/* ── 4. Border normalisation ─────────────────────────────────────────────────
   Cool grey hairlines against warm surfaces read as dirty. */

body:not(.wp-admin) .border-gray-100,
body:not(.wp-admin) .border-gray-200,
body:not(.wp-admin) .border-slate-200 {
	border-color: var(--ui-border-soft);
}

body:not(.wp-admin) .border-gray-300,
body:not(.wp-admin) .border-gray-400 {
	border-color: var(--ui-border);
}

/* ── 5. Radius collapse ──────────────────────────────────────────────────────
   Ten radii became three. rounded-full is preserved — pills and avatars need
   it — but the rectangle scale is unified so cards, inputs and images stop
   disagreeing by 2–6px everywhere. */

body:not(.wp-admin) .rounded,
body:not(.wp-admin) .rounded-md {
	border-radius: var(--radius-sm);
}

body:not(.wp-admin) .rounded-lg,
body:not(.wp-admin) .rounded-xl {
	border-radius: var(--radius);
}

body:not(.wp-admin) .rounded-2xl,
body:not(.wp-admin) .rounded-3xl {
	border-radius: var(--radius-lg);
}

/* Content-area surfaces. AI-generated blocks stored in the database bring their
   own geometry — an 8px pseudo-button here, a 12px panel there — which no
   template edit can reach. Anything inside the prose/entry content that reads as
   a surface is pulled onto the radius scale so stored content cannot introduce
   new corner sizes. */
body:not(.wp-admin) main .prose section,
body:not(.wp-admin) main .prose > div[style],
body:not(.wp-admin) main .entry-content section,
body:not(.wp-admin) main .entry-content > div[style] {
	border-radius: var(--radius);
}

body:not(.wp-admin) main .prose a[style],
body:not(.wp-admin) main .entry-content a[style] {
	border-radius: var(--radius-sm);
}

/* ── 6. Depth collapse ───────────────────────────────────────────────────────
   22 shadow variants (including a stale copper glow from the pre-AA brand
   colour) collapse onto two levels: paper for resting surfaces, float for
   things that genuinely overlay. Cool black shadows are replaced with warm
   brown ones so depth belongs to the palette. */

body:not(.wp-admin) .shadow-sm,
body:not(.wp-admin) .shadow,
body:not(.wp-admin) .shadow-md {
	box-shadow: var(--shadow-paper);
}

body:not(.wp-admin) .shadow-lg,
body:not(.wp-admin) .shadow-xl,
body:not(.wp-admin) .shadow-2xl {
	box-shadow: var(--shadow-paper-hover);
}

/* The old brand-tinted glows. shadow-[#bc6f30]/40 referenced the pre-darkening
   copper, so it never matched anything else on the page. */
body:not(.wp-admin) [class*="shadow-primary"],
body:not(.wp-admin) [class*="shadow-[#bc6f30]"],
body:not(.wp-admin) [class*="shadow-[0_8px_24px"] {
	box-shadow: var(--shadow-paper);
}

/* Never stack paper depth through nested surfaces — that is what produced the
   "pile of floating cards" look. */
body:not(.wp-admin) .shadow-sm .shadow-sm,
body:not(.wp-admin) .shadow-md .shadow-sm,
body:not(.wp-admin) .shadow-lg .shadow-sm,
body:not(.wp-admin) .shadow-lg .shadow-md {
	box-shadow: none;
}

/* ── 7. Typography scale ─────────────────────────────────────────────────────
   Most headings carried no classes at all and inherited browser defaults,
   which is why heading size drifted page to page. This sets the scale on the
   elements themselves, so an unstyled <h2> is correct by default and a styled
   one still wins. */

body:not(.wp-admin) main h1 {
	font-size: clamp(2rem, 1.35rem + 2.4vw, 3.25rem);
	line-height: 1.06;
	letter-spacing: -0.025em;
	font-weight: 800;
	text-wrap: balance;
}

body:not(.wp-admin) main h2 {
	font-size: clamp(1.5rem, 1.15rem + 1.3vw, 2.125rem);
	line-height: 1.15;
	letter-spacing: -0.018em;
	/* 700, not 750. The stack is a system font (ui-sans-serif) with no variable
	   axis, so 750 snapped unpredictably to 700 or 800 depending on platform —
	   the measurement reported w750 while rendering at a neighbouring weight. */
	font-weight: 700;
	text-wrap: balance;
}

body:not(.wp-admin) main h3 {
	font-size: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
	line-height: 1.3;
	letter-spacing: -0.01em;
	font-weight: 700;
}

body:not(.wp-admin) main h4 {
	font-size: 1.0625rem;
	line-height: 1.35;
	font-weight: 700;
}

/* Body text scale.

   Measurement across 11 pages found 25 distinct size/line-height pairings. The
   SIZES were mostly fine — 12 / 14 / 16 / 18 is a real scale — but the leading
   drifted (14px appeared at both 20px and 22.75px, 18px at both 28px and 32px)
   and two arbitrary sizes sat off-scale entirely (11px, 13px).

   Sizes are pinned to the scale and each gets one leading. This deliberately
   overrides `leading-*` utilities: a consistent rhythm across the site is worth
   more than a per-element leading choice made in passing. */
/* :not(h1..h4) matters. A heading carrying a size utility — <h2 class="text-xl">
   on /faq/, for instance — otherwise took the utility size and broke out of the
   heading scale. Size utilities style body elements; headings follow the
   heading scale, always. */

/* The large steps need the same guard: <h3 class="text-2xl sm:text-3xl"> was
   the site's last off-scale heading. */
body:not(.wp-admin) main h1[class] { font-size: clamp(2rem, 1.35rem + 2.4vw, 3.25rem); font-weight: 800; }
body:not(.wp-admin) main h2[class] { font-size: clamp(1.5rem, 1.15rem + 1.3vw, 2.125rem); font-weight: 700; }
body:not(.wp-admin) main h3[class] { font-size: clamp(1.125rem, 1rem + 0.5vw, 1.375rem); font-weight: 700; }
body:not(.wp-admin) main h4[class] { font-size: 1.0625rem; font-weight: 700; }
body:not(.wp-admin) main .text-xs:not(h1):not(h2):not(h3):not(h4)   { font-size: 0.75rem;  line-height: 1.5; }
body:not(.wp-admin) main .text-sm:not(h1):not(h2):not(h3):not(h4)   { font-size: 0.875rem; line-height: 1.55; }
body:not(.wp-admin) main .text-base:not(h1):not(h2):not(h3):not(h4) { font-size: 1rem;     line-height: 1.6; }
body:not(.wp-admin) main .text-lg:not(h1):not(h2):not(h3):not(h4)   { font-size: 1.125rem; line-height: 1.6; }
body:not(.wp-admin) main .text-xl:not(h1):not(h2):not(h3):not(h4)   { font-size: 1.25rem;  line-height: 1.45; }

/* Arbitrary off-scale sizes pulled onto the nearest scale step. */
body:not(.wp-admin) main [class*="text-[11px]"],
body:not(.wp-admin) main [class*="text-[10px]"] {
	font-size: 0.75rem;
	line-height: 1.5;
}

body:not(.wp-admin) main [class*="text-[13px]"],
body:not(.wp-admin) main [class*="text-[15px]"] {
	font-size: 0.875rem;
	line-height: 1.55;
}

/* Narrative copy gets a real measure. Full-width body text is the classic
   readability failure on wide screens. */
body:not(.wp-admin) main p {
	line-height: 1.6;
}

body:not(.wp-admin) main p:not([class]) {
	max-width: 68ch;
	line-height: 1.6;
	color: var(--ui-ink-secondary);
}

body:not(.wp-admin) main .prose p,
body:not(.wp-admin) .entry-content p:not([class]) {
	max-width: 68ch;
	line-height: 1.6;
}

/* Numbers align in stat rows and tables. */
body:not(.wp-admin) .tabular-nums,
body:not(.wp-admin) main table td,
body:not(.wp-admin) main table th {
	font-variant-numeric: tabular-nums;
}

/* ── 8. Section rhythm ───────────────────────────────────────────────────────
   46 combinations collapse to one scale, so the page breathes at a predictable
   cadence instead of jumping between 16/20/24/28/32 arbitrarily. */

   Sections that declare their own `py-*` are EXCLUDED from the blanket default
   and mapped onto the scale instead (below). Overriding deliberate spacing was
   worse than the inconsistency it fixed: /expert-tips/ asks for `py-10` (40px)
   on each band of an alternating editorial stream and was being forced to
   104px, which turned a contentless spacer into a 282px void. Normalise the
   VALUE, not the intent. */

body:not(.wp-admin) main > section:not([class*="py-"]),
body:not(.wp-admin) main > div > section:not([class*="py-"]) {
	padding-top: var(--section-y);
	padding-bottom: var(--section-y);
}

/* Explicit paddings snap to the nearest token step, so the cadence stays
   predictable without flattening tight bands and generous ones into one value. */
body:not(.wp-admin) main section.py-8,
body:not(.wp-admin) main section.py-10,
body:not(.wp-admin) main section.py-12 {
	padding-top: var(--section-y-tight);
	padding-bottom: var(--section-y-tight);
}

body:not(.wp-admin) main section.py-16,
body:not(.wp-admin) main section.py-20 {
	padding-top: var(--section-y);
	padding-bottom: var(--section-y);
}

body:not(.wp-admin) main section.py-24,
body:not(.wp-admin) main section.py-28 {
	padding-top: var(--section-y);
	padding-bottom: var(--section-y);
}

@media (min-width: 768px) {
	body:not(.wp-admin) main > section:not([class*="py-"]),
	body:not(.wp-admin) main > div > section:not([class*="py-"]) {
		padding-top: var(--section-y-lg);
		padding-bottom: var(--section-y-lg);
	}

	body:not(.wp-admin) main section.py-24,
	body:not(.wp-admin) main section.py-28 {
		padding-top: var(--section-y-lg);
		padding-bottom: var(--section-y-lg);
	}
}

/* The hero owns its own vertical space. */
body:not(.wp-admin) main > section.fp-hero,
body:not(.wp-admin) main > .fp-hero {
	padding-top: 0;
	padding-bottom: 0;
}

/* ── 9. Controls ─────────────────────────────────────────────────────────────
   The 161 button variants mostly differ in padding by a step or two. Rather
   than chase every one, the two dominant shapes — a copper primary and a
   bordered secondary — get the bevel treatment, which is what actually makes
   a control read as pressable. */

body:not(.wp-admin) main a.bg-primary,
body:not(.wp-admin) main button.bg-primary,
body:not(.wp-admin) .site-footer a.bg-primary,
body:not(.wp-admin) input[type="submit"],
body:not(.wp-admin) button[type="submit"] {
	background-color: var(--brand-copper);
	color: #ffffff;
	border: 0;
	box-shadow: var(--shadow-control-primary);
	transition-property: background-color, box-shadow, transform;
	transition-duration: var(--dur-fast);
	transition-timing-function: var(--ease-out-strong);
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) main a.bg-primary:hover,
	body:not(.wp-admin) main button.bg-primary:hover,
	body:not(.wp-admin) .site-footer a.bg-primary:hover,
	body:not(.wp-admin) input[type="submit"]:hover,
	body:not(.wp-admin) button[type="submit"]:hover {
		background-color: var(--brand-copper-hover);
	}
}

body:not(.wp-admin) main a.bg-primary:active,
body:not(.wp-admin) main button.bg-primary:active,
body:not(.wp-admin) input[type="submit"]:active,
body:not(.wp-admin) button[type="submit"]:active {
	transform: translateY(1px);
	box-shadow:
		inset 0 2px 3px rgba(50, 24, 12, 0.30),
		inset 0 0 0 1px rgba(105, 49, 31, 0.92);
}

/* Bordered secondary controls on light surfaces. */
body:not(.wp-admin) main a.border-gray-300,
body:not(.wp-admin) main button.border-gray-300 {
	background-color: var(--ui-paper);
	box-shadow: var(--shadow-control);
	border-color: transparent;
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) main a.border-gray-300:hover,
	body:not(.wp-admin) main button.border-gray-300:hover {
		background-color: var(--ui-paper-hover);
		box-shadow: var(--shadow-control-hover);
	}
}

/* Form fields: warm, inset, consistent. */
body:not(.wp-admin) main input[type="text"],
body:not(.wp-admin) main input[type="email"],
body:not(.wp-admin) main input[type="tel"],
body:not(.wp-admin) main input[type="number"],
body:not(.wp-admin) main select,
body:not(.wp-admin) main textarea {
	background-color: var(--ui-paper);
	border: 1px solid var(--ui-border);
	border-radius: var(--radius-sm);
	color: var(--ui-ink);
	box-shadow: inset 0 1px 2px rgba(72, 56, 40, 0.06);
}

body:not(.wp-admin) main input:focus,
body:not(.wp-admin) main select:focus,
body:not(.wp-admin) main textarea:focus {
	outline: 2px solid var(--brand-copper);
	outline-offset: 1px;
	border-color: var(--brand-copper);
}

/* ── 10. Cards ───────────────────────────────────────────────────────────────
   One card material. Interactive cards lift by 1px only — anything more reads
   as a toy. */

body:not(.wp-admin) main .group.rounded-lg,
body:not(.wp-admin) main .group.rounded-xl,
body:not(.wp-admin) main .group.rounded-2xl {
	transition-property: transform, box-shadow, border-color;
	transition-duration: var(--dur);
	transition-timing-function: var(--ease-out-strong);
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) main .group.rounded-lg:hover,
	body:not(.wp-admin) main .group.rounded-xl:hover,
	body:not(.wp-admin) main .group.rounded-2xl:hover {
		transform: translateY(-1px);
		box-shadow: var(--shadow-paper-hover);
	}
}

/* ── 11. Eyebrow labels ──────────────────────────────────────────────────────
   The theme uses tiny uppercase labels above section titles in several
   inconsistent sizes and weights. One treatment. */

body:not(.wp-admin) main .uppercase.tracking-widest,
body:not(.wp-admin) main .uppercase.tracking-wider,
body:not(.wp-admin) main [class*="tracking-[0.1"] {
	/* 0.75rem, matching text-xs. 0.6875rem introduced an 11px step that existed
	   nowhere else in the scale. */
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.14em;
	color: var(--brand-copper);
}

/* Every dark context, not just some. .bg-nav was missing here and put brand
   copper on near-black at 2.93:1 (caught by Lighthouse on /services/). Any
   selector list that maps a colour for dark surfaces has to enumerate the same
   full set, or the one that is missed fails silently. */
body:not(.wp-admin) .bg-black .uppercase.tracking-widest,
body:not(.wp-admin) .bg-black .uppercase.tracking-wider,
body:not(.wp-admin) .bg-gray-900 .uppercase.tracking-widest,
body:not(.wp-admin) .bg-gray-900 .uppercase.tracking-wider,
body:not(.wp-admin) .bg-nav .uppercase.tracking-widest,
body:not(.wp-admin) .bg-nav .uppercase.tracking-wider,
body:not(.wp-admin) .site-footer .uppercase.tracking-widest,
body:not(.wp-admin) .site-footer .uppercase.tracking-wider,
body:not(.wp-admin) [class*="bg-[#0"] .uppercase.tracking-widest,
body:not(.wp-admin) [class*="bg-[#1"] .uppercase.tracking-widest,
body:not(.wp-admin) [class*="bg-[#1"] .uppercase.tracking-wider,
body:not(.wp-admin) [class*="bg-[#2"] .uppercase.tracking-widest {
	color: var(--brand-copper-on-dark);
}

/* An eyebrow that carries its own brand-tinted background is a badge, not a
   label on the page surface — colouring it copper puts copper on copper
   (#8e5725 on #c98c59 = 2.08:1). Dark ink on that tint is 5.90:1. */
body:not(.wp-admin) main [class*="bg-brand-"].uppercase,
body:not(.wp-admin) main [class*="bg-brand-"] .uppercase.tracking-widest,
body:not(.wp-admin) main .bg-accent.uppercase,
body:not(.wp-admin) main [class*="bg-primary/"].uppercase {
	color: var(--ui-ink);
}

/* Same gap for the plain copper text utility on the nav-dark surface. */
body:not(.wp-admin) .bg-nav .text-primary,
body:not(.wp-admin) .bg-nav .text-brand {
	color: var(--brand-copper-on-dark);
}

body:not(.wp-admin) .bg-nav .bg-white .text-primary,
body:not(.wp-admin) .bg-nav a.bg-white,
body:not(.wp-admin) .bg-nav .bg-white .uppercase.tracking-widest {
	color: var(--brand-copper);
}

/* ── 12. Focus visibility ────────────────────────────────────────────────────
   Keyboard users had markedly worse affordance than mouse users in several
   templates. One ring everywhere. */

body:not(.wp-admin) a:focus-visible,
body:not(.wp-admin) button:focus-visible,
body:not(.wp-admin) [tabindex]:focus-visible {
	outline: 3px solid var(--brand-copper);
	outline-offset: 2px;
	border-radius: var(--radius-sm);
}

body:not(.wp-admin) .bg-black a:focus-visible,
body:not(.wp-admin) .site-footer a:focus-visible,
body:not(.wp-admin) [class*="bg-[#1"] a:focus-visible {
	outline-color: var(--brand-copper-on-dark);
}

/* ── 13. Images ──────────────────────────────────────────────────────────────
   Photography on a warm cream page needs a hairline, otherwise light edges
   dissolve into the canvas. */

body:not(.wp-admin) main img:not([class*="logo"]):not(.fp-hero__photo) {
	border-radius: inherit;
}

body:not(.wp-admin) main figure img {
	border-radius: var(--radius);
}

@media (prefers-reduced-motion: reduce) {
	body:not(.wp-admin) * {
		transition-duration: 1ms !important;
		animation-duration: 1ms !important;
	}
	body:not(.wp-admin) main .group:hover {
		transform: none !important;
	}
}

/* ── 14. Mobile navigation ───────────────────────────────────────────────────
   The panel was a flat dark box with 18px bold links stacked at uneven
   rhythm, hover-only affordances (invisible on touch) and sub-items indented
   with padding alone, so there was no visible relationship between a section
   and its children.

   It is now a warm dark sheet on the same tokens as the rest of the site:
   hairline-separated rows, one type scale, real tap targets, and a copper rail
   marking nested items so the hierarchy is legible without hovering.

   Sizing (max-height / overscroll containment) lives in mobile.css §12; the
   body-level scroll lock that used to break the sticky header is gone. */

body:not(.wp-admin) #mobile-menu {
	background-color: var(--ui-dark);
	border-top: 1px solid rgba(255, 255, 255, 0.10);
	box-shadow: var(--shadow-float);
	padding: 0.25rem 1rem 1rem;
}

/* One rhythm: every top-level row is a hairline-separated band. */
body:not(.wp-admin) #mobile-menu > .space-y-4 > div {
	padding-top: 0;
	padding-bottom: 0;
	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

body:not(.wp-admin) #mobile-menu > .space-y-4 > div:last-child {
	border-bottom: 0;
}

/* Top-level links and section toggles share one treatment and one target
   size. 48px clears the 44px minimum with room for imprecise thumbs. */
body:not(.wp-admin) #mobile-menu > .space-y-4 > div > a,
body:not(.wp-admin) #mobile-menu > .space-y-4 > div > button {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	min-height: 48px;
	padding: 0.25rem 0;
	font-size: 1rem;
	font-weight: 600;
	letter-spacing: -0.01em;
	color: #ffffff;
	transition: color var(--dur-fast) var(--ease-out-strong);
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) #mobile-menu > .space-y-4 > div > a:hover,
	body:not(.wp-admin) #mobile-menu > .space-y-4 > div > button:hover {
		color: var(--brand-copper-on-dark);
	}
}

body:not(.wp-admin) #mobile-menu a:active,
body:not(.wp-admin) #mobile-menu button:active {
	color: var(--brand-copper-on-dark);
}

/* Nested items: a copper rail ties children to their parent, which padding
   alone never communicated. */
body:not(.wp-admin) #mobile-menu [id$="-content"] {
	margin: 0 0 0.75rem;
	padding-left: 0.875rem;
	border-left: 1px solid var(--ui-border-on-dark);
}

body:not(.wp-admin) #mobile-menu [id$="-content"] a {
	display: flex;
	align-items: center;
	min-height: 40px;
	padding: 0.125rem 0;
	font-size: 0.875rem;
	font-weight: 500;
	line-height: 1.4;
	color: var(--ui-ink-on-dark-secondary);
}

body:not(.wp-admin) #mobile-menu [id$="-content"] a:last-child {
	color: var(--brand-copper-on-dark);
	font-weight: 600;
}

/* The chevron reads as a control, and rotation confirms the state change. */
body:not(.wp-admin) #mobile-menu button svg {
	flex: 0 0 auto;
	color: var(--ui-ink-on-dark-muted);
	transition: transform 200ms var(--ease-out-strong), color var(--dur-fast) ease;
}

body:not(.wp-admin) #mobile-menu button[aria-expanded="true"] svg,
body:not(.wp-admin) #mobile-menu button svg.rotate-180 {
	transform: rotate(180deg);
	color: var(--brand-copper-on-dark);
}

/* Action block at the foot of the panel. */
body:not(.wp-admin) #mobile-menu a[href^="tel:"],
body:not(.wp-admin) #mobile-menu a[href*="contact"] {
	min-height: 48px;
	border-radius: var(--radius);
}

body:not(.wp-admin) #mobile-menu a[href^="tel:"] {
	background-color: var(--brand-copper);
	color: #ffffff;
	box-shadow: var(--shadow-control-primary);
}

body:not(.wp-admin) #mobile-menu a:focus-visible,
body:not(.wp-admin) #mobile-menu button:focus-visible {
	outline: 3px solid var(--brand-copper-on-dark);
	outline-offset: 2px;
	border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
	body:not(.wp-admin) #mobile-menu button svg {
		transition: color var(--dur-fast) ease;
	}
}

/* ── 15. Footer ──────────────────────────────────────────────────────────────
   Rebuilt as three parts instead of four stacked bands. Every colour below was
   contrast-checked against the #191512 footer surface and the slightly raised
   #221e1b CTA band; the worst pair in here is 5.93:1 (white on copper). */

body:not(.wp-admin) .site-footer {
	background-color: var(--ui-dark);
	color: var(--ui-ink-on-dark-secondary);
}

/* — 1. Closing action — */

body:not(.wp-admin) .fx-cta {
	background-color: rgba(255, 255, 255, 0.04);
	border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

/* Three groups, not two. The old row was justify-between with a small left
   cluster and buttons on the right, which opened a wide empty gap at large
   widths. The middle column now carries hours and licence, so the row is
   filled with information rather than whitespace. */
body:not(.wp-admin) .fx-cta__inner {
	display: grid;
	gap: 1.5rem;
	padding: 1.75rem 0;
}

@media (min-width: 1024px) {
	body:not(.wp-admin) .fx-cta__inner {
		grid-template-columns: auto 1fr auto;
		align-items: center;
		gap: 2.5rem;
		padding: 1.5rem 0;
	}
}

body:not(.wp-admin) .fx-cta__eyebrow {
	margin: 0 0 0.35rem;
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--brand-copper-on-dark);
}

body:not(.wp-admin) .fx-cta__phone {
	display: inline-flex;
	align-items: center;
	gap: 0.6rem;
	font-size: 1.5rem;
	font-weight: 800;
	letter-spacing: -0.02em;
	line-height: 1.1;
	color: #ffffff;
	transition: color var(--dur-fast) var(--ease-out-strong);
}

body:not(.wp-admin) .fx-cta__phone svg {
	color: var(--brand-copper-on-dark);
	flex: 0 0 auto;
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) .fx-cta__phone:hover { color: var(--brand-copper-on-dark); }
}

body:not(.wp-admin) .fx-cta__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 2rem;
	margin: 0;
}

body:not(.wp-admin) .fx-cta__meta dt {
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--ui-ink-on-dark-muted);
}

body:not(.wp-admin) .fx-cta__meta dd {
	margin: 0.15rem 0 0;
	font-size: 0.875rem;
	color: var(--ui-ink-on-dark-secondary);
}

body:not(.wp-admin) .fx-cta__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
}

body:not(.wp-admin) .fx-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	min-height: 44px;
	padding: 0 1.25rem;
	border-radius: var(--radius);
	font-size: 0.875rem;
	font-weight: 700;
	transition-property: background-color, border-color, color, transform;
	transition-duration: var(--dur-fast);
	transition-timing-function: var(--ease-out-strong);
}

body:not(.wp-admin) .fx-btn--primary {
	background-color: var(--brand-copper);
	color: #ffffff;
	box-shadow: var(--shadow-control-primary);
}

body:not(.wp-admin) .fx-btn--ghost {
	border: 1px solid rgba(255, 255, 255, 0.22);
	color: var(--ui-ink-on-dark-secondary);
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) .fx-btn--primary:hover { background-color: var(--brand-copper-hover); }
	body:not(.wp-admin) .fx-btn--ghost:hover {
		border-color: var(--brand-copper-on-dark);
		color: #ffffff;
	}
}

body:not(.wp-admin) .fx-btn:active { transform: scale(0.98); }

/* — 2. Brand + navigation, one grid — */

/* Two columns from the smallest width. Stacked in one column the footer ran
   1619px tall on a 390px phone — a lot of thumb travel past content most
   visitors are scrolling to get past. The link lists are short, so pairing them
   roughly halves it while the brand block keeps the full width. */
body:not(.wp-admin) .fx-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 2rem 1.25rem;
	padding: 2.5rem 0 2rem;
}

body:not(.wp-admin) .fx-brand {
	grid-column: 1 / -1;
}

@media (min-width: 640px) {
	body:not(.wp-admin) .fx-grid { gap: 2.25rem 1.5rem; }
}

/* Brand takes 4 of 12 so it reads as a column rather than the separate
   full-width strip it used to be. */
@media (min-width: 1024px) {
	body:not(.wp-admin) .fx-grid {
		/* Four columns, not five: Resources and Where-we-work are merged because
		   several Resources links are conditional on pages that do not exist,
		   which left two near-empty columns beside two full ones. */
		grid-template-columns: 4fr 2.4fr 2.4fr 2.6fr;
		gap: 2.5rem;
		padding: 3rem 0 2.5rem;
	}

	body:not(.wp-admin) .fx-brand { grid-column: auto; }
}

body:not(.wp-admin) .fx-brand__id {
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
}

body:not(.wp-admin) .fx-brand__mark {
	display: flex;
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
}

body:not(.wp-admin) .fx-brand__mark img {
	width: 40px;
	height: 40px;
	object-fit: contain;
}

body:not(.wp-admin) .fx-brand__name {
	display: block;
	font-size: 0.9375rem;
	font-weight: 700;
	color: #ffffff;
	line-height: 1.2;
}

body:not(.wp-admin) .fx-brand__tag {
	display: block;
	margin-top: 0.15rem;
	font-size: 0.75rem;
	color: var(--ui-ink-on-dark-muted);
}

body:not(.wp-admin) .fx-brand__blurb {
	margin: 1rem 0 0;
	max-width: 32ch;
	font-size: 0.875rem;
	line-height: 1.6;
	color: var(--ui-ink-on-dark-secondary);
}

body:not(.wp-admin) .fx-stats {
	display: flex;
	flex-wrap: wrap;
	gap: 1.5rem;
	margin: 1.25rem 0 0;
	padding: 1.1rem 0 0;
	list-style: none;
	border-top: 1px solid rgba(255, 255, 255, 0.08);
}

body:not(.wp-admin) .fx-stats b {
	display: block;
	font-size: 1.0625rem;
	font-weight: 800;
	line-height: 1;
	color: var(--brand-copper-on-dark);
	font-variant-numeric: tabular-nums;
}

body:not(.wp-admin) .fx-stats span {
	display: block;
	margin-top: 0.3rem;
	font-size: 0.75rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--ui-ink-on-dark-muted);
}

body:not(.wp-admin) .fx-social {
	display: flex;
	gap: 0.5rem;
	margin-top: 1.25rem;
}

body:not(.wp-admin) .fx-social a {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border-radius: var(--radius-sm);
	background-color: rgba(255, 255, 255, 0.06);
	color: var(--ui-ink-on-dark-muted);
	transition: color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}

body:not(.wp-admin) .fx-social svg { width: 15px; height: 15px; }

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) .fx-social a:hover {
		background-color: rgba(255, 255, 255, 0.10);
		color: var(--brand-copper-on-dark);
	}
}

body:not(.wp-admin) .fx-col__title {
	margin: 0 0 0.85rem;
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ui-ink-on-dark-muted);
}

body:not(.wp-admin) .fx-col ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

body:not(.wp-admin) .fx-col a {
	display: block;
	padding: 0.3rem 0;
	font-size: 0.875rem;
	line-height: 1.45;
	color: var(--ui-ink-on-dark-secondary);
	transition: color var(--dur-fast) ease;
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) .fx-col a:hover { color: #ffffff; }
}

body:not(.wp-admin) .fx-col__more {
	display: inline-block;
	margin-top: 0.75rem;
	font-size: 0.75rem;
	font-weight: 700;
	color: var(--brand-copper-on-dark);
}

/* — 3. Legal — */

body:not(.wp-admin) .fx-legal {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.75rem;
	padding: 1.25rem 0 1.5rem;
	border-top: 1px solid rgba(255, 255, 255, 0.07);
	text-align: center;
}

@media (min-width: 640px) {
	body:not(.wp-admin) .fx-legal {
		flex-direction: row;
		justify-content: space-between;
		text-align: left;
	}
}

body:not(.wp-admin) .fx-legal p {
	margin: 0;
	font-size: 0.75rem;
	color: var(--ui-ink-on-dark-muted);
}

body:not(.wp-admin) .fx-legal nav {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.35rem 1.25rem;
}

body:not(.wp-admin) .fx-legal a {
	font-size: 0.75rem;
	color: var(--ui-ink-on-dark-muted);
	transition: color var(--dur-fast) ease;
}

@media (hover: hover) and (pointer: fine) {
	body:not(.wp-admin) .fx-legal a:hover { color: #ffffff; }
}

body:not(.wp-admin) .site-footer a:focus-visible {
	outline: 3px solid var(--brand-copper-on-dark);
	outline-offset: 2px;
	border-radius: var(--radius-sm);
}

/* Second heading inside the merged column needs air above it so the two lists
   read as separate groups rather than one long run. */
body:not(.wp-admin) .fx-col__title--stacked {
	margin-top: 1.75rem;
	padding-top: 1.25rem;
	border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* ── 16. Quality-of-life ─────────────────────────────────────────────────── */

/* Selection picks up the brand instead of the browser default blue, which was
   the last cool-blue thing left on a warm page. */
body:not(.wp-admin) ::selection {
	background-color: var(--brand-copper-soft);
	color: var(--ui-ink);
}

/* Smooth scrolling is set in custom.css. It is motion, so it has to be opt-out
   for anyone who has asked the OS to reduce motion — otherwise every in-page
   jump animates for people who explicitly said not to. */
@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto !important; }
}

/* Jumping to an anchor gives a brief copper marker, so it is obvious which
   block the link landed on rather than leaving the reader to guess. */
body:not(.wp-admin) main :target {
	animation: wades-target-flash 1.6s var(--ease-out-strong) 1;
}

@keyframes wades-target-flash {
	0%   { box-shadow: inset 3px 0 0 0 var(--brand-copper); }
	70%  { box-shadow: inset 3px 0 0 0 var(--brand-copper); }
	100% { box-shadow: inset 3px 0 0 0 rgba(142, 87, 37, 0); }
}

@media (prefers-reduced-motion: reduce) {
	body:not(.wp-admin) main :target { animation: none; }
}

/* ── Print ───────────────────────────────────────────────────────────────────
   There were no print styles at all. People do print from a contractor site —
   a quote, the service-area list, an FAQ answer to hand to a tenant — and the
   dark bands alone would burn through a cartridge. This strips the site to the
   content, keeps it legible in black on white, and makes link destinations
   visible since a printed page cannot be clicked. */
@media print {
	*,
	*::before,
	*::after {
		background: transparent !important;
		color: #000 !important;
		box-shadow: none !important;
		text-shadow: none !important;
	}

	body {
		font-size: 11pt;
		line-height: 1.45;
	}

	/* Site chrome and anything asking the reader to act carries no meaning on
	   paper. */
	.site-header,
	.site-footer,
	#mobile-menu,
	.fx-cta,
	.wades-map-facade,
	nav[aria-label="Legal navigation"],
	.no-print {
		display: none !important;
	}

	main {
		padding: 0 !important;
	}

	main > section,
	main > div > section {
		padding-top: 0.5rem !important;
		padding-bottom: 0.5rem !important;
	}

	/* A URL the reader cannot see is a dead reference on paper. Internal
	   anchors and tel: links are skipped — the first is meaningless off-site
	   and the second is already printed as the number. */
	main a[href^="http"]::after {
		content: " (" attr(href) ")";
		font-size: 9pt;
		word-break: break-all;
	}

	main a[href^="#"]::after,
	main a[href^="tel:"]::after,
	main a[href^="mailto:"]::after {
		content: "";
	}

	h1, h2, h3 {
		page-break-after: avoid;
		break-after: avoid;
	}

	img,
	figure,
	li,
	tr {
		page-break-inside: avoid;
		break-inside: avoid;
	}

	main img {
		max-width: 50% !important;
		height: auto !important;
	}

	@page {
		margin: 1.5cm;
	}
}

/* ── 17. Long-form reading ───────────────────────────────────────────────────
   Blog and guide body copy was rendering at 13px with the size inherited from
   the article container, not set on the paragraphs — so none of the earlier
   type-scale work reached it. 13px is a UI label size; these are 4–6 minute
   reads. Set to 17px/1.7, which is the comfortable band for sustained reading
   and matches the rest of the scale rather than inventing a new step.

   The measure is capped so lines stay in the 60–75 character range that keeps
   the eye from losing its place on the return sweep. */

body:not(.wp-admin) main .wps-post-content,
body:not(.wp-admin) main .prose,
body:not(.wp-admin) main .entry-content {
	font-size: 1.0625rem;
	line-height: 1.7;
	color: var(--ui-ink-secondary);
}

body:not(.wp-admin) main .wps-post-content p,
body:not(.wp-admin) main .prose p,
body:not(.wp-admin) main .entry-content p {
	font-size: inherit;
	line-height: inherit;
	max-width: 68ch;
	margin-bottom: 1.15em;
}

body:not(.wp-admin) main .wps-post-content li,
body:not(.wp-admin) main .prose li,
body:not(.wp-admin) main .entry-content li {
	font-size: inherit;
	line-height: inherit;
	margin-bottom: 0.4em;
}

body:not(.wp-admin) main .wps-post-content ul,
body:not(.wp-admin) main .wps-post-content ol,
body:not(.wp-admin) main .prose ul,
body:not(.wp-admin) main .prose ol,
body:not(.wp-admin) main .entry-content ul,
body:not(.wp-admin) main .entry-content ol {
	max-width: 68ch;
	padding-left: 1.35em;
	margin-bottom: 1.15em;
}

body:not(.wp-admin) main .wps-post-content ul,
body:not(.wp-admin) main .prose ul,
body:not(.wp-admin) main .entry-content ul { list-style: disc; }

body:not(.wp-admin) main .wps-post-content ol,
body:not(.wp-admin) main .prose ol,
body:not(.wp-admin) main .entry-content ol { list-style: decimal; }

body:not(.wp-admin) main .wps-post-content li::marker,
body:not(.wp-admin) main .prose li::marker,
body:not(.wp-admin) main .entry-content li::marker {
	color: var(--brand-copper);
}

/* Headings inside an article need space above them so a new section is visible
   before it is read. Sizes still come from the sitewide heading scale. */
body:not(.wp-admin) main .wps-post-content h2,
body:not(.wp-admin) main .prose h2,
body:not(.wp-admin) main .entry-content h2 {
	margin-top: 2.25em;
	margin-bottom: 0.6em;
	color: var(--ui-ink);
}

body:not(.wp-admin) main .wps-post-content h3,
body:not(.wp-admin) main .prose h3,
body:not(.wp-admin) main .entry-content h3 {
	margin-top: 1.75em;
	margin-bottom: 0.5em;
	color: var(--ui-ink);
}

body:not(.wp-admin) main .wps-post-content h2:first-child,
body:not(.wp-admin) main .prose h2:first-child,
body:not(.wp-admin) main .entry-content h2:first-child {
	margin-top: 0;
}

/* Inline links inside prose need to be identifiable without relying on colour
   alone — an underline is the one cue that survives colour-blindness. */
body:not(.wp-admin) main .wps-post-content a:not([class]),
body:not(.wp-admin) main .prose a:not([class]),
body:not(.wp-admin) main .entry-content a:not([class]) {
	color: var(--brand-copper);
	text-decoration: underline;
	text-underline-offset: 0.15em;
	text-decoration-thickness: 1px;
}

body:not(.wp-admin) main .wps-post-content blockquote,
body:not(.wp-admin) main .prose blockquote,
body:not(.wp-admin) main .entry-content blockquote {
	margin: 1.75em 0;
	padding: 0.25em 0 0.25em 1.15em;
	border-left: 0;
	font-style: normal;
	color: var(--ui-ink);
}

body:not(.wp-admin) main .wps-post-content strong,
body:not(.wp-admin) main .prose strong,
body:not(.wp-admin) main .entry-content strong {
	color: var(--ui-ink);
	font-weight: 700;
}

/* Cool greys hardcoded in inline style attributes.

   The post template and stored content set color:#6b7280 (Tailwind slate-500)
   directly on elements — read-time labels, share-bar links, help-panel text.
   On the old cool #f9fafb that cleared AA; on the warm #f4efe6 canvas it lands
   at 4.22:1. Mapped onto the warm muted ink, which is 4.99:1 on the same
   surface. Same failure mode as the inline copper handled above, and equally
   unreachable from any template edit. */
body:not(.wp-admin) [style*="color:#6b7280"],
body:not(.wp-admin) [style*="color: #6b7280"],
body:not(.wp-admin) [style*="color:#64748b"],
body:not(.wp-admin) [style*="color: #64748b"],
body:not(.wp-admin) [style*="color:#9ca3af"],
body:not(.wp-admin) [style*="color: #9ca3af"] {
	color: var(--ui-ink-muted) !important;
}

/* Visually hidden, still read aloud and still counted as link text.

   Used to make repeated "Read more" links descriptive. An aria-label would be
   the tidier markup, but Lighthouse's link-text audit reads the element's TEXT
   rather than its accessible name, so the title has to be inside the link.
   Clip-path rather than display:none — the latter removes it from the
   accessibility tree entirely, which defeats the point. */
.wps-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Status colours hardcoded inline by AI-generated callouts.

   Amber note boxes set color:rgb(180,83,9) (#b45309) on an amber tint — 4.21:1,
   just under AA. The warning token is 5.67:1 on the same tint. Both the hex and
   rgb() spellings are matched because inline styles keep whatever was authored.
   Sibling greens/reds are mapped for the same reason. */
body:not(.wp-admin) [style*="color: rgb(180, 83, 9)"],
body:not(.wp-admin) [style*="color:rgb(180,83,9)"],
body:not(.wp-admin) [style*="color: #b45309"],
body:not(.wp-admin) [style*="color:#b45309"],
body:not(.wp-admin) [style*="color: #d97706"],
body:not(.wp-admin) [style*="color:#d97706"] {
	color: var(--ui-warning) !important;
}

body:not(.wp-admin) [style*="color: #16a34a"],
body:not(.wp-admin) [style*="color:#16a34a"],
body:not(.wp-admin) [style*="color: rgb(22, 163, 74)"] {
	color: var(--ui-success) !important;
}

body:not(.wp-admin) [style*="color: #dc2626"],
body:not(.wp-admin) [style*="color:#dc2626"],
body:not(.wp-admin) [style*="color: rgb(220, 38, 38)"] {
	color: var(--ui-danger) !important;
}

/* ── 19. Section header alignment ────────────────────────────────────────────
   Pages were mixing alignments within a single scroll: About Us ran
   start / center / center / start down one page. The heroes were left-aligned
   earlier but the section headers inside the page were missed, so the eye kept
   losing and re-finding the starting edge.

   Section headers now share the heroes' left edge. Centring is kept where it is
   a deliberate, different treatment — inside the copper and dark call-to-action
   bands, which are meant to read as a break in the rhythm rather than another
   section. */

body:not(.wp-admin) main .text-center:not(.bg-primary):not(.bg-black):not(.bg-nav):has(> h2),
body:not(.wp-admin) main .text-center:not(.bg-primary):not(.bg-black):not(.bg-nav):has(> h3) {
	text-align: left;
}

/* The intro paragraph under a section header was centred with `mx-auto`, which
   survives the alignment change and leaves the text floating away from its
   heading. */
body:not(.wp-admin) main .text-center:has(> h2) > p.mx-auto,
body:not(.wp-admin) main .text-center:has(> h2) > p {
	margin-left: 0;
	margin-right: 0;
	max-width: 62ch;
}

/* Anything inside a CTA band keeps its centred treatment. */
body:not(.wp-admin) main .bg-primary .text-center,
body:not(.wp-admin) main .bg-black .text-center,
body:not(.wp-admin) main .bg-nav .text-center,
body:not(.wp-admin) main [class*="bg-[#1"] .text-center {
	text-align: center;
}

body:not(.wp-admin) main .bg-primary .text-center > p,
body:not(.wp-admin) main .bg-black .text-center > p {
	margin-left: auto;
	margin-right: auto;
}

/* Vertical rhythm inside stacked page bodies.

   A wrapper carrying `space-y-*` already owns the spacing between its blocks.
   When its children are `<section>`s they ALSO picked up `--section-y` padding
   from §8, so every seam measured 72px padding + 80px margin + 72px padding =
   224px of dead space. Measured on /about-us/, where it read as the page having
   fallen apart rather than as separation.

   The rule: the wrapper spaces, the sections don't.

   The `:not([hidden])` on the wrapper is a specificity device, not a filter.
   §8's default is (0,2,4); without this, that rule wins and the 104px padding
   comes straight back. Adding one class-level here makes this (0,3,3), and the
   class column is compared before the element column, so it wins outright.
   Caught as a live regression on /about-us/ — the licensing section returned to
   208px of padding on a 439px block the moment §8 gained a `:not()`. */
body:not(.wp-admin) main [class*="space-y-"]:not([hidden]) > section {
	padding-top: 0;
	padding-bottom: 0;
	margin-top: 0;
	margin-bottom: 0;
}

/* Must out-specify Tailwind's own `.space-y-20 > :not([hidden]) ~ :not([hidden])`,
   which is (0,3,0). Matching its `:not([hidden])` shape takes this to (0,4,4). */
body:not(.wp-admin) main [class*="space-y-"] > section:not([hidden]) ~ section:not([hidden]) {
	margin-top: 3.5rem;
}

/* Panels are the exception: a section that is also a surface keeps real
   padding, or its background collapses onto its own text. */
body:not(.wp-admin) main [class*="space-y-"] > section[class*="rounded"] {
	padding: 2rem;
}

@media (min-width: 768px) {
	body:not(.wp-admin) main [class*="space-y-"] > section:not([hidden]) ~ section:not([hidden]) {
		margin-top: 4.5rem;
	}

	body:not(.wp-admin) main [class*="space-y-"] > section[class*="rounded"] {
		padding: 3rem;
	}
}

/* Leadership monogram — stands in when no headshot exists. Deliberate-looking
   beats the same stock photo of a brick wall appearing as two different
   people's faces. */
body:not(.wp-admin) .wades-leader--monogram {
	display: flex;
	align-items: center;
	justify-content: center;
	background:
		radial-gradient(120% 120% at 30% 20%, rgba(216, 168, 119, 0.20), transparent 60%),
		var(--ui-dark);
}

body:not(.wp-admin) .wades-leader__initial {
	font-size: 5rem;
	font-weight: 800;
	line-height: 1;
	letter-spacing: -0.03em;
	color: var(--brand-copper-on-dark);
	opacity: 0.9;
}

body:not(.wp-admin) .wades-leader--monogram .absolute {
	background: linear-gradient(to top, rgba(10, 8, 7, 0.85), transparent);
}

/* ── 20. Editorial stream (/expert-tips/) ────────────────────────────────────
   The template alternates `bg-white` bands with `bg-[#f8f9fb]` ones to give the
   article stream a tonal rhythm. §2 already warms `bg-white` and `bg-black`
   correctly — but `bg-[#f8f9fb]` is an ARBITRARY value that was never in the
   pre-compiled Tailwind build, so it resolves to nothing.

   Measured: those sections compute to `rgba(0, 0, 0, 0)`. The alternation the
   layout is built around simply does not exist, and the whole page reads as one
   undifferentiated column. This is the pre-compiled-Tailwind trap from DESIGN.md
   biting silently, exactly as documented.

   Defining the class here restores the intended rhythm in warm tones: paper
   bands against the canvas. */

body:not(.wp-admin) main section.bg-\[\#f8f9fb\] {
	background-color: var(--ui-canvas);
}

/* A band whose only content is a <nav> is a control strip — topic filters,
   breadcrumbs — not a content section. Section rhythm wrapped a 74px row of
   category chips in 208px of padding, making it read as a gap in the page
   rather than as a toolbar. Its inner wrapper already carries py-5. */
body:not(.wp-admin) main > section:not([class*="py-"]):not(:has(h1, h2, h3)):has(nav) {
	padding-top: 0;
	padding-bottom: 0;
}

/* (No bg-black / bg-white rules here: §2 already warms both, verified by
   computed style — 25,21,18 and 255,253,249. Restating them would be dead CSS.)

   The alternating bands butt directly against each other with no seam, so give
   the transition a hairline rather than relying on the tonal step alone. */
body:not(.wp-admin) main section.bg-\[\#f8f9fb\] + section.bg-white,
body:not(.wp-admin) main section.bg-white + section.bg-\[\#f8f9fb\] {
	border-top: 1px solid var(--ui-border-soft);
}

/* ── 21. Copper text: solidity + the missing alpha variants ──────────────────
   Two defects, both measured on live pages.

   1. `.text-primary` compiled to
        color-mix(in oklab, var(--color-primary) 50%, transparent)
      — half-transparent copper. Every accent label on the site was washed out
      against whatever sat behind it.

   2. `.text-primary/90`, `/80`, `/60` and `/50` are used 13 times across the
      templates but are NOT in the pre-compiled build, so they set no colour at
      all and the elements inherited --ui-ink. On the dark bands that measured
      1.11:1 — black-on-black, invisible. The pre-compiled-Tailwind trap again.

   Copper is the single accent, so these resolve to one value per context
   rather than a ramp of transparencies. */

body:not(.wp-admin) main .text-primary,
body:not(.wp-admin) main .text-primary\/90,
body:not(.wp-admin) main .text-primary\/80,
body:not(.wp-admin) main .text-primary\/60,
body:not(.wp-admin) main .text-primary\/50 {
	color: var(--brand-copper);
}

/* --brand-copper reaches only ~3.2:1 on near-black; the lightened value is
   8.46:1. Restore it in every dark context, matching §3's grey handling. */
body:not(.wp-admin) .bg-black .text-primary,
body:not(.wp-admin) .bg-black .text-primary\/90,
body:not(.wp-admin) .bg-black .text-primary\/80,
body:not(.wp-admin) .bg-black .text-primary\/60,
body:not(.wp-admin) .bg-black .text-primary\/50,
body:not(.wp-admin) .bg-nav .text-primary\/90,
body:not(.wp-admin) .bg-nav .text-primary\/80,
body:not(.wp-admin) .bg-gray-900 .text-primary\/90,
body:not(.wp-admin) .site-footer .text-primary\/90,
body:not(.wp-admin) [class*="bg-[#1"] .text-primary\/90,
body:not(.wp-admin) [class*="bg-[#0"] .text-primary\/90 {
	color: var(--brand-copper-on-dark);
}

/* ── 22. Dark bands must set the INHERITED ink ───────────────────────────────
   §3 remaps specific grey utilities inside dark contexts, but that only helps
   elements that carry a colour class. An element with none — measured case: an
   <a class="max-w-[min(100%,28rem)] truncate transition-colors"> in the
   /services/ hero — inherits --ui-ink from §1 and renders near-black on
   near-black. 1.11:1. Invisible, and no utility remap can reach it because
   there is no utility.

   Setting the inherited colour on the band itself fixes the entire class of
   bug at once. Descendants that DO carry a colour class are unaffected: a rule
   matching an element always beats a value inherited from its ancestor,
   regardless of specificity. */

body:not(.wp-admin) main .bg-black,
body:not(.wp-admin) main .bg-gray-900,
body:not(.wp-admin) main .bg-gray-950,
body:not(.wp-admin) main .bg-nav,
body:not(.wp-admin) main [class*="bg-[#0"],
body:not(.wp-admin) main [class*="bg-[#1"],
body:not(.wp-admin) .site-footer {
	color: var(--ui-ink-on-dark);
}

/* NOTE: an earlier "symmetric guard" here forced `.text-white` to --ui-ink
   whenever it sat inside a `.bg-white` ancestor. It was wrong: CSS cannot see
   which ancestor actually paints behind the element, and it fired on white
   headings sitting on copper panels — 2.75:1, worse than the bug it chased.
   Removed. Do not reintroduce descendant-based colour "rescues"; fix the
   surface that owns the text instead. */

/* ── 23. No left-border colour stripes ──────────────────────────────────────
   `border-l-4` in a brand colour was used 18 times across 10 templates as the
   house way of saying "this bit matters". It is a weak device: it reads as an
   error/callout state, it fights the single-accent rule by putting copper on
   arbitrary blocks, and stacked down a page it turns the layout into a set of
   ledger rules.

   Emphasis now comes from type, spacing and surface — not a coloured edge. The
   utilities stay in the markup (removing them from 18 sites risks more than it
   gains); their width is simply zeroed. Any padding they were paired with is
   kept, so the indent and rhythm survive. */

body:not(.wp-admin) main [class*="border-l-"],
body:not(.wp-admin) .site-footer [class*="border-l-"],
body:not(.wp-admin) #mobile-menu [class*="border-l-"] {
	border-left-width: 0;
}

/* Blockquotes lost their copper rule above, so give them a real typographic
   treatment rather than leaving them as undifferentiated paragraphs. */
body:not(.wp-admin) main .prose blockquote,
body:not(.wp-admin) main .entry-content blockquote,
body:not(.wp-admin) .wps-post-content blockquote {
	padding: 0;
	border: 0;
	font-size: 1.125em;
	line-height: 1.55;
	color: var(--ui-ink);
	font-style: normal;
}

body:not(.wp-admin) main .prose blockquote p:first-of-type::before,
body:not(.wp-admin) main .entry-content blockquote p:first-of-type::before,
body:not(.wp-admin) .wps-post-content blockquote p:first-of-type::before {
	content: "\201C";
	margin-right: 0.1em;
	color: var(--ui-ink-muted);
}

/* The one place a leading rule earns its keep: the mobile submenu, where it
   marks nesting depth rather than decorating. Hairline, not a stripe. */
body:not(.wp-admin) #mobile-menu [id$="-content"] {
	border-left-width: 1px;
}

/* ── 24. Coloured surfaces own their text colour ────────────────────────────
   Remaining measured failures, all of the same shape: a surface sets a dark or
   saturated background but leaves the text colour to inheritance or to a
   utility meant for light backgrounds.

     copper panel + inherited copper text .... 1.00:1
     copper panel + --ui-ink text ............ 2.75:1
     near-black band + --ui-ink chips ........ 1.11:1

   The surface is the only thing that knows it is dark, so the surface sets the
   colour — for itself and for text utilities that assume a light background. */

/* Exact classes only. An earlier version used [class*="bg-primary"], which
   matched the FAQ filter buttons' `hover:bg-primary/5` — a HOVER variant — and
   painted paper text onto their paper background: 1.00:1 on eight buttons whose
   markup was correct all along. Substring matching on class attributes catches
   hover:/focus:/md: variants; never use it to infer a surface colour. */
body:not(.wp-admin) .bg-primary,
body:not(.wp-admin) .bg-brand {
	color: var(--ui-paper);
}

/* Text utilities that resolve to dark ink are wrong on any dark or copper
   surface. Re-point them rather than leaving them to inheritance. */
body:not(.wp-admin) .bg-primary .text-white,
body:not(.wp-admin) .bg-primary .text-gray-900,
body:not(.wp-admin) .bg-primary .text-foreground,
body:not(.wp-admin) .bg-brand .text-white,
body:not(.wp-admin) main .bg-black .text-gray-900,
body:not(.wp-admin) main .bg-black .text-foreground,
body:not(.wp-admin) main .bg-black .text-white,
body:not(.wp-admin) main .bg-nav .text-gray-900,
body:not(.wp-admin) main .bg-nav .text-white,
body:not(.wp-admin) main [class*="bg-[#0"] .text-gray-900,
body:not(.wp-admin) main [class*="bg-[#1"] .text-gray-900 {
	color: var(--ui-ink-on-dark);
}

/* Last measured failure: an eyebrow chip (`inline-flex rounded-full border …`)
   on the /expert-tips/ dark hero, carrying a light-background text utility and
   landing at 1.11:1. Chips and pills inside a dark band take the on-dark ink;
   copper eyebrows are excluded so §21 keeps them copper. */
body:not(.wp-admin) main .bg-black [class*="rounded-full"]:not([class*="text-primary"]):not([class*="bg-primary"]),
body:not(.wp-admin) main .bg-nav [class*="rounded-full"]:not([class*="text-primary"]):not([class*="bg-primary"]),
body:not(.wp-admin) main [class*="bg-[#0"] [class*="rounded-full"]:not([class*="text-primary"]),
body:not(.wp-admin) main [class*="bg-[#1"] [class*="rounded-full"]:not([class*="text-primary"]) {
	color: var(--ui-ink-on-dark);
}

/* §24 sets the inherited colour on dark and copper surfaces. That colour keeps
   cascading into LIGHT panels nested inside them — measured on /faq/, where
   eight rows rendered warm-white on warm-white, exactly 1.00:1.

   The counter-rule is the same principle, applied symmetrically: whichever
   surface paints the background owns the text colour. A light panel re-asserts
   ink for its own contents, no matter what it is nested in. This is a surface
   rule, not a descendant guess — the distinction that made the earlier
   `.text-white` rescue wrong. */
body:not(.wp-admin) main .bg-primary .bg-white,
body:not(.wp-admin) main .bg-primary .bg-gray-50,
body:not(.wp-admin) main .bg-primary .bg-gray-100,
body:not(.wp-admin) main .bg-black .bg-white,
body:not(.wp-admin) main .bg-black .bg-gray-50,
body:not(.wp-admin) main .bg-black .bg-gray-100,
body:not(.wp-admin) main .bg-nav .bg-white,
body:not(.wp-admin) main .bg-nav .bg-gray-50,
body:not(.wp-admin) main [class*="bg-[#0"] .bg-white,
body:not(.wp-admin) main [class*="bg-[#1"] .bg-white {
	color: var(--ui-ink);
}

/* ── 25. Same-element surface+text, and translucent washes on dark ──────────
   The last four measured failures, both shapes verified by walking the live
   ancestor chain rather than guessing:

   a) A chip carrying BOTH `bg-primary` and a copper text utility on the SAME
      element. §24 used a descendant combinator (`.bg-primary .text-white`), so
      it never matched — copper on copper, 1.00:1. Compound selectors needed.

   b) An eyebrow with a `bg-primary/10` wash sitting on the near-black hero.
      A 10% copper wash over near-black is still dark, so --ui-ink on it is
      1.11:1. It was being excluded by a `:not([class*="text-primary"])` guard
      written for a different case. */

body:not(.wp-admin) main .bg-primary[class*="text-"],
body:not(.wp-admin) main .bg-brand[class*="text-"] {
	color: var(--ui-paper);
}

body:not(.wp-admin) main .bg-black [class*="bg-primary/"],
body:not(.wp-admin) main .bg-nav [class*="bg-primary/"],
body:not(.wp-admin) main [class*="bg-[#0"] [class*="bg-primary/"],
body:not(.wp-admin) main [class*="bg-[#1"] [class*="bg-primary/"] {
	color: var(--brand-copper-on-dark);
	border-color: rgba(216, 168, 119, 0.35);
}

/* ── 26. One content width, one reading width ───────────────────────────────
   Measured across the page types, section containers resolved to four
   different maxima — 1280px, 1312px, 800px and 768px — so the left edge of the
   content moved as you navigated between pages. Nothing about the content
   justified the difference; it is just which utility each template happened to
   use.

   Two widths, and only two:
     wide    1280px — section containers, grids, operational content
     reading  760px — long-form prose, where measure matters more than width

   1312px was `max-w-7xl` plus its own padding leaking into the max-width; it
   is the same intent as 1280 and collapses into it. */

body:not(.wp-admin) main section > .wade-container,
body:not(.wp-admin) main section > .mx-auto,
body:not(.wp-admin) main > .wade-container,
body:not(.wp-admin) main section > div[class*="max-w-7xl"],
body:not(.wp-admin) main section > div[class*="max-w-6xl"] {
	max-width: 80rem;
}

body:not(.wp-admin) main .prose,
body:not(.wp-admin) main .entry-content,
body:not(.wp-admin) main .wps-post-content {
	max-width: 47.5rem;
}

/* ── 27. Body copy floor ────────────────────────────────────────────────────
   Paragraphs of real prose were rendering at 11px on the privacy page and 12px
   elsewhere — UI-label sizes on running text. Eyebrows, badges and chips are
   legitimately small, so they are excluded by the markers that identify them
   (uppercase, wide tracking, pill shape) rather than by size. */

body:not(.wp-admin) main p:not([class*="uppercase"]):not([class*="tracking-w"]):not([class*="rounded-full"]):not([class*="inline-flex"]),
body:not(.wp-admin) main li:not([class*="uppercase"]):not([class*="rounded-full"]) {
	font-size: max(0.875rem, 1rem * var(--wps-copy-scale, 0.875));
}

body:not(.wp-admin) main .prose p,
body:not(.wp-admin) main .entry-content p,
body:not(.wp-admin) main .wps-post-content p {
	font-size: 1.0625rem;
	line-height: 1.7;
}

/* ── 28. Editorial index instead of a card wall (Expert Tips) ───────────────
   SCOPE WARNING: this is `body.blog` ONLY. In WordPress `body.home` is the
   FRONT PAGE, not the posts index — an earlier version included it and
   flattened the homepage's grids into a single column, taking it from 5,000px
   to 13,468px. `blog` is the posts index; `home` is the front page.
   /expert-tips/ rendered three separate three-up card grids stacked down the
   page — the equal-card grid this system rejects, repeated. Every article got
   identical visual weight, so nothing was scannable and the page carried no
   hierarchy: 4,787px of interchangeable tiles.

   Recast as an editorial list. One column, each item a row: title leads, meta
   trails, thumbnail is a fixed-width slot on the right rather than a hero.
   Hairline separators instead of card borders, per the minimalist lane — the
   list reads as one sheet, not a pile of boxes. */

body.blog main [class*="grid-cols"],
body:not(.wp-admin) main .wps-tips-grid {
	display: block;
}

body.blog main [class*="grid-cols"] > *,
body.blog main [data-never-matches] {
	display: grid;
	grid-template-columns: 1fr 8.5rem;
	align-items: start;
	gap: 1.5rem;
	padding: 1.5rem 0;
	border: 0;
	border-bottom: 1px solid var(--ui-border-soft);
	border-radius: 0;
	background: transparent;
	box-shadow: none;
}

body.blog main [class*="grid-cols"] > *:last-child,
body.blog main [data-never-matches] {
	border-bottom: 0;
}

/* Thumbnail becomes a fixed slot, not a banner. */
body.blog main [class*="grid-cols"] > * img,
body.blog main [data-never-matches] {
	grid-column: 2;
	grid-row: 1 / span 99;
	width: 8.5rem;
	height: 5.75rem;
	object-fit: cover;
	border-radius: var(--radius-sm);
	margin: 0;
}

body.blog main [class*="grid-cols"] > * > *:not(img),
body.blog main [data-never-matches] {
	grid-column: 1;
}

@media (max-width: 640px) {
	body.blog main [class*="grid-cols"] > *,
	body.blog main [class*="grid-cols"] > * {
		grid-template-columns: 1fr 5.5rem;
		gap: 1rem;
	}
	body.blog main [class*="grid-cols"] > * img,
	body.blog main [class*="grid-cols"] > * img {
		width: 5.5rem;
		height: 4rem;
	}
}

/* §28 density. Converting 27 cards into 27 full-height rows traded a card wall
   for a 10,790px scroll — scannable, but nobody reaches the bottom. An index
   row should be one glance: title, one line of context, the date. Clamp the
   excerpt and tighten the rhythm so the list stays dense the way a list should
   be, rather than inheriting card-sized padding. */

body.blog main [class*="grid-cols"] > *,
body.blog main [data-never-matches] {
	padding: 0.9rem 0;
	gap: 1.25rem;
}

body.blog main [class*="grid-cols"] > * p,
body.blog main [data-never-matches] {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	margin: 0.25rem 0 0;
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--ui-ink-muted);
}

body.blog main [class*="grid-cols"] > * h2,
body.blog main [class*="grid-cols"] > * h3,
body.blog main [data-never-matches] {
	font-size: 1.0625rem;
	line-height: 1.35;
	margin: 0;
}

body.blog main [class*="grid-cols"] > * img,
body.blog main [data-never-matches] {
	height: 4.25rem;
	width: 6.75rem;
}

body.blog main [class*="grid-cols"] > *,
body.blog main [data-never-matches] {
	grid-template-columns: 1fr 6.75rem;
}

/* ── 29. Document + rail (service detail) ───────────────────────────────────
   `single-service.php` was four stacked full-width bands, ~6,900px, with the
   call/quote actions buried at the bottom. It is now a reading column with a
   sticky rail carrying the actions.

   The grid columns live HERE, not in the markup: the first attempt used
   `lg:grid-cols-[minmax(0,1fr)_20rem]`, an arbitrary Tailwind class that is not
   in the pre-compiled build, so it resolved to nothing and the rail rendered
   full width underneath the article. Measured, not assumed — see the
   pre-compiled-Tailwind warning in DESIGN.md. */

@media (min-width: 1024px) {
	body:not(.wp-admin) main .wps-doc-rail {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 20rem;
		align-items: start;
	}
}

body:not(.wp-admin) main .wps-doc-rail > aside {
	min-width: 0;
}

/* ── 30. Services directory: keep the index in reach ────────────────────────
   /services/ already groups 49 services by category with a filter nav — it is
   a directory, not a flat list. Its weakness was that the nav scrolled away
   after the first category, so from that point on there was no way to jump
   between groups without scrolling back to the top.

   Pinning it under the header turns it into a persistent index. `top` matches
   the real header height, the same values scroll-padding-top uses (68px on
   phones, 111px from md up) — a single flat value would tuck it underneath. */

body:not(.wp-admin) main section:has(> div > #svc-nav) {
	position: sticky;
	top: 68px;
	z-index: 20;
	border-bottom: 1px solid var(--ui-border-soft);
	background: var(--ui-paper);
}

@media (min-width: 768px) {
	body:not(.wp-admin) main section:has(> div > #svc-nav) {
		top: 111px;
	}
}

/* The chips are the one place a filled dark pill is right — it is the active
   state of a control, not decoration. Keep it on the warm charcoal, not #000. */
body:not(.wp-admin) main #svc-nav .svc-chip.bg-gray-900 {
	background-color: var(--ui-dark);
	color: var(--ui-ink-on-dark);
}

/* ── 31. One vertical rhythm, sitewide ──────────────────────────────────────
   Measured skeletons across 11 pages showed four competing section rhythms —
   0/0, 48/48, 72/72 and 104/104 — mixed within single pages (Expert Tips ran
   72, 0, 48, 48, 48 down one screen). Worse, the SEAM between bands differed by
   page: About's space-y wrapper produced 80px gaps while every other page
   produced 144px from 72+72 padding. Same tokens, same type, completely
   different cadence — which is what "not cohesive" actually feels like.

   One rhythm: 72px above and below every band, so every seam is 144px. The
   hero keeps 104 because an opening band should breathe more than a body band.
   §19's wrapper fix stays intact — the wrapper still owns its spacing, it is
   just tuned to land on the same 144px seam as everything else. */

body:not(.wp-admin) main > section,
body:not(.wp-admin) main > div > section {
	padding-top: var(--section-y);
	padding-bottom: var(--section-y);
}

/* Hero: the first band on the page, and only that one. */
body:not(.wp-admin) main > section:first-child,
body:not(.wp-admin) main > div > section:first-child {
	padding-top: var(--section-y-lg);
	padding-bottom: var(--section-y-lg);
}

/* Wrapper-spaced pages (About) land on the same 144px seam. */
body:not(.wp-admin) main [class*="space-y-"] > section:not([hidden]) ~ section:not([hidden]) {
	margin-top: 9rem;
}

/* Panels keep their own padding — they are surfaces, not bands. */
body:not(.wp-admin) main [class*="space-y-"] > section[class*="rounded"] {
	padding: 3rem;
}

/* §31 continued. Expert Tips and Services still resolved to 0/0 and 48/48 —
   their own utilities out-specified the rule above. Adding [class] lifts this
   to (0,3,2), which clears them without touching `main > div > section`
   (the wrapper-spaced pages), whose seams are already correct via margin. */
body:not(.wp-admin) main > section[class] {
	padding-top: var(--section-y);
	padding-bottom: var(--section-y);
}

body:not(.wp-admin) main > section[class]:first-child {
	padding-top: var(--section-y-lg);
	padding-bottom: var(--section-y-lg);
}

/* ── 32. Every page opens the same way ──────────────────────────────────────
   The clearest source of "not cohesive": the hero band was built three
   different ways and lived in two different places in the DOM.

     home         hero INSIDE main,  .fp-hero,  h1 52px
     faq, tips    hero INSIDE main,  .bg-black, h1 52px
     about,
     areas,
     financing    hero OUTSIDE main, .bg-nav,   h1 60px
     testimonials hero OUTSIDE main, .bg-black, h1 60px

   Landing on two different pages therefore introduced the site two different
   ways, and every `main > section` rule in this file silently skipped the four
   pages whose hero sits outside main.

   This normalises the opening band wherever it lives: same dark surface, same
   104px rhythm, same 52px h1. Scoped by "a dark band containing the page h1"
   rather than by class, because the classes are exactly what disagree. */

body:not(.wp-admin) .bg-nav:has(h1),
body:not(.wp-admin) .bg-black:has(h1),
body:not(.wp-admin) .fp-hero:has(h1) {
	padding-top: var(--section-y-lg);
	padding-bottom: var(--section-y-lg);
	background-color: var(--ui-dark);
	color: var(--ui-ink-on-dark);
}

body:not(.wp-admin) .bg-nav:has(h1) h1,
body:not(.wp-admin) .bg-black:has(h1) h1,
body:not(.wp-admin) .fp-hero:has(h1) h1 {
	font-size: clamp(2rem, 1.35rem + 2.4vw, 3.25rem);
	font-weight: 800;
	line-height: 1.1;
	color: var(--ui-ink-on-dark);
}

/* The hero sits directly under a sticky header; it should meet it, not float
   away from it. */
body:not(.wp-admin) .bg-nav:has(h1),
body:not(.wp-admin) .bg-black:has(h1) {
	margin-top: 0;
}

/* §32 continued. The privacy hero resolved to rgb(15,17,21) — a COOL near-black
   from a different utility than every other hero's warm charcoal. One page
   opening on a bluish black and ten on a brown one is precisely the kind of
   mismatch that reads as "off" without being nameable. */
body:not(.wp-admin) .bg-gray-900:has(h1),
body:not(.wp-admin) .bg-gray-950:has(h1),
body:not(.wp-admin) [class*="bg-[#0"]:has(h1),
body:not(.wp-admin) [class*="bg-[#1"]:has(h1) {
	padding-top: var(--section-y-lg);
	padding-bottom: var(--section-y-lg);
	background-color: var(--ui-dark);
	color: var(--ui-ink-on-dark);
}

body:not(.wp-admin) .bg-gray-900:has(h1) h1,
body:not(.wp-admin) [class*="bg-[#0"]:has(h1) h1,
body:not(.wp-admin) [class*="bg-[#1"]:has(h1) h1 {
	font-size: clamp(2rem, 1.35rem + 2.4vw, 3.25rem);
	font-weight: 800;
	line-height: 1.1;
	color: var(--ui-ink-on-dark);
}

/* §32 continued. `.wps-post-hero` is a FOURTH hero implementation — used by
   posts and the privacy page — with its own #0f1115, a cool near-black. Four
   components doing one job, each with its own dark. They now share the warm
   charcoal and the same opening rhythm as every other page. */
body:not(.wp-admin) .wps-post-hero {
	background-color: var(--ui-dark);
	color: var(--ui-ink-on-dark);
	padding-top: var(--section-y-lg);
	padding-bottom: var(--section-y-lg);
}

body:not(.wp-admin) .wps-post-hero__title {
	font-size: clamp(2rem, 1.35rem + 2.4vw, 3.25rem);
	font-weight: 800;
	line-height: 1.1;
	color: var(--ui-ink-on-dark);
}
