@font-face { font-family: "Archivo Expanded"; src: url("/assets/fonts/archivo-expanded-900.woff2") format("woff2"); font-weight: 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Hanken Grotesk"; src: url("/assets/fonts/hanken-grotesk-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Hanken Grotesk"; src: url("/assets/fonts/hanken-grotesk-600.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
:root {
  --surface: #fafafa;
  --surface-raised: #ffffff;
  --surface-sunken: #eff0ef;
  --surface-overlay: #e7e8e7;
  --border-hairline: #dddedc;
  --border: #cccfcb;
  --border-strong: #a8aca7;
  --ink: #1d201d;
  --ink-muted: #4f544e;
  --ink-subtle: #6b706a;
  --brand: #02602d;
  --brand-ink: #ffffff;
  --brand-wash: #edefed;
  --accent: #4c9963;
  --accent-ink: #0a0a0a;
  --accent-wash: #e9ecea;
  --focus: #4c9963;
  --ok: #397247;
  --warn: #936823;
  --error: #a03f3c;
  --brand-text: #02602d;
  --accent-text: #2c7b47;
  --link: #02602d;
  --link-hover: #00471f;
  --color-brand: var(--brand);
  --color-brand-contrast: var(--brand-ink);
  --color-accent: var(--accent);
  --color-accent-text: var(--accent-text);
  --color-accent-ink: var(--accent-ink);
  --color-link: var(--link);
  --color-link-hover: var(--link-hover);
  --color-ink: var(--ink);
  --color-muted: var(--ink-muted);
  --color-faint: var(--ink-subtle);
  --color-surface: var(--surface);
  --color-surface-alt: var(--surface-sunken);
  --color-card: var(--surface-raised);
  --color-border: var(--border);
  --card-border: var(--border-strong);
  --sheen: rgba(255,255,255,0.65);
  --scrim: rgba(15,23,42,0.46);
  --overlay-ink: #ffffff;
  --overlay-ink-muted: rgba(255,255,255,0.84);
  --scrim-color: color-mix(in srgb, var(--color-brand) 10%, #0c0e12);
  --scrim-tint: color-mix(in srgb, var(--color-brand) 22%, var(--scrim-color));
  --scrim-alpha: 0.62;
  --scrim-alpha-max: 0.78;
  --overlay-text-shadow: 0 1px 2px rgba(0,0,0,0.35), 0 0 24px rgba(0,0,0,0.22);
  --band-alt-bg: var(--color-surface-alt);
  --band-cta-bg: color-mix(in srgb, var(--color-brand) 6%, var(--color-surface));
  --band-cta-rule-top: 3px solid var(--color-brand);
  --band-cta-rule-bottom: 1px solid var(--color-border);
  --band-cred-bg: var(--color-surface);
  --hero-bg: var(--color-surface);
  --hero-bg-noimg: color-mix(in srgb, var(--color-brand) 6%, var(--color-surface));
  --hero-rule-top: 3px solid var(--color-brand);
  --font-heading: "Archivo Expanded", ui-sans-serif, system-ui, "Segoe UI", sans-serif;
  --font-body: "Hanken Grotesk", ui-sans-serif, system-ui, "Segoe UI", sans-serif;
  --radius-sm: 2px;
  --radius: 4px;
  --radius-lg: 6.8px;
  --radius-pill: 999px;
  --content-max: 1080px;
  --measure: 65ch;
  --measure-lede: 44ch;
  --measure-wide: 78ch;
  --shadow-1: 0 1px 2px rgba(15,23,42,0.08), 0 6px 16px -8px rgba(15,23,42,0.14);
  --shadow-2: 0 4px 10px -4px rgba(15,23,42,0.10), 0 20px 40px -16px rgba(15,23,42,0.20);
  --shadow-3: 0 12px 24px -10px rgba(15,23,42,0.14), 0 38px 84px -30px rgba(15,23,42,0.28);
  --elevation-card: 0 1px 2px rgba(15,23,42,0.08), 0 6px 16px -8px rgba(15,23,42,0.14);
  --elevation-card-hover: 0 4px 10px -4px rgba(15,23,42,0.10), 0 20px 40px -16px rgba(15,23,42,0.20);
  --dur-fast: 160ms;
  --dur-base: 240ms;
  --dur-slow: 400ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --utility-h: 44px;
  --header-h: 64px;
  --scroll-offset: calc(var(--utility-h) + var(--header-h) + var(--space-2));
  --callbar-h: 72px;
  --heading-tracking: -0.006em;
  --fs-caption: 0.8rem;
  --fs-body: 1rem;
  --fs-1: 1.25rem;
  --fs-2: 1.563rem;
  --fs-3: clamp(1.563rem, 1.396rem + 0.743vw, 1.953rem);
  --fs-4: clamp(1.8rem, 1.525rem + 1.221vw, 2.441rem);
  --fs-5: clamp(1.95rem, 1.478rem + 2.099vw, 3.052rem);
  --fs-6: clamp(2.15rem, 1.436rem + 3.171vw, 3.815rem);
  --type-display: var(--fs-6);
  --type-hero: clamp(1.95rem, 1.713rem + 1.053vw, 2.503rem);
  --hero-measure: 26ch;
  --hero-lh: 1.05;
  --type-title: var(--fs-4);
  --type-heading: var(--fs-2);
  --type-subhead: var(--fs-1);
  --type-lede: var(--fs-3);
  --type-body: var(--fs-body);
  --type-caption: var(--fs-caption);
  --pad-major: clamp(var(--space-6), 9vw, calc(var(--space-7) * 1.25));
  --pad-default: clamp(var(--space-4), 4.5vw, var(--space-6));
  --pad-compact: clamp(var(--space-3), 2.5vw, var(--space-4));
  --space-0: 4px;
  --space-1: 8px;
  --space-2: 12px;
  --space-3: 16px;
  --space-4: 24px;
  --space-5: 32px;
  --space-6: 48px;
  --space-7: 64px;
}
*, *::before, *::after { box-sizing: border-box; }
/* Focus Not Obscured (WCAG 2.4.11): scroll-padding on the container keeps a Tab/anchor-focused
   control out from under the sticky utility-bar + header (and the bottom callbar on mobile). */
html { font-size: 100%; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: var(--scroll-offset); }
body {
  margin: 0; font-family: var(--font-body); font-size: var(--fs-body);
  color: var(--color-ink); background: var(--color-surface);
  line-height: 1.65; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  display: flex; flex-direction: column; min-height: 100vh;
}
::selection { background: var(--color-brand); color: var(--color-brand-contrast); }

/* ---- cross-page view transitions (same-origin MPA; zero-JS, progressive) ------
   Chrome/Safari cross-fade between page loads for an app-like feel on our static MPA;
   browsers without support ignore these rules and navigate instantly (no penalty, no
   JS). The cross-fade is capped at --dur-base; reduced-motion disables it (instant nav). */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--dur-base); animation-timing-function: var(--ease-standard); }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
/* Heading line-height scales by ROLE: tighter as the size grows (display vs sub-head),
   staying within the 1.1–1.3 band. text-wrap: balance kills orphan/runt heading lines
   (degrades to normal wrap where unsupported). */
h1, h2, h3, h4 { font-family: var(--font-heading); line-height: 1.15; letter-spacing: var(--heading-tracking); color: var(--color-ink); margin: 0 0 var(--space-3); font-weight: 700; text-wrap: balance; }
/* Elements consume TYPE ROLES (P-1 §3a), never fs-N directly. h2 drops a tier (title=fs-4, was
   fs-5) and h3 drops to heading=fs-2 (was fs-3) — killing the h3 = hero-lede collision. */
h1 { font-size: var(--type-display); line-height: 1.1; }
h2 { font-size: var(--type-title); line-height: 1.14; }
h3 { font-size: var(--type-heading); line-height: 1.22; }
h4 { font-size: var(--type-subhead); line-height: 1.28; }
/* Prose: pretty wrap (no runts/orphans; degrades to normal) + hanging punctuation (Safari
   honors it, others safely ignore). Body line-height 1.65 sits in the 1.5–1.7 comfort band. */
p { margin: 0 0 var(--space-3); max-width: var(--measure); text-wrap: pretty; }
p, .lede, blockquote, .section dd { hanging-punctuation: first allow-end; }
/* Constrain prose + content lists to a readable measure (not the full ~1080px column). */
ul.checks, .steps, .faq, .section ul:not([class]), .section ol:not([class]) { max-width: var(--measure); }
a { color: var(--color-brand); text-decoration: none; text-underline-offset: 0.18em; transition: color var(--dur-fast) var(--ease-standard); }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; border-radius: 2px; }
img { max-width: 100%; height: auto; display: block; }
strong { font-weight: 650; }
.muted { color: var(--color-muted); }
.lede { font-size: var(--type-lede); line-height: 1.5; color: var(--color-ink); max-width: var(--measure-lede); font-weight: 450; text-wrap: pretty; }
/* LOUDNESS MODULATION by section role (P-1 §3a): support sections recede — their h2 drops to the
   heading role so showcase sections read louder. Semantics untouched: the element stays a level-2
   heading for heading-order + AEO; only the visual tier changes. The per-section assignments now
   live in the per-archetype block below (R-1) so they never collide across page types. */

/* ---- inline / prose links: mode-aware AA contrast + PERSISTENT underline -------
   Scoped to text contexts (paragraphs, plain lists, FAQ answers, contact details) so
   nav, breadcrumb, footer, card (.link-grid/.review) and button (.btn) links keep their
   own styling. --color-link is the accent on dark (~9:1) / brand on light (>=4.5:1). */
.section p a:not(.btn),
.section ul:not([class]) a,
.section dd a,
.contact-details a { color: var(--color-link); text-decoration-line: underline; text-decoration-thickness: 0.07em; text-underline-offset: 0.2em; }
.section p a:not(.btn):hover,
.section ul:not([class]) a:hover,
.section dd a:hover,
.contact-details a:hover { color: var(--color-link-hover); text-decoration-thickness: 0.12em; }

/* ---- skip link + layout shell ------------------------------------------- */
.skip-link { position: absolute; left: -9999px; top: var(--space-2); background: var(--color-brand); color: var(--color-brand-contrast); padding: var(--space-2) var(--space-4); border-radius: var(--radius); z-index: 200; }
.skip-link:focus { left: var(--space-2); }
/* A-4: sr-only utility — visible to assistive tech, off-screen for sighted users (logo-link name). */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.container { width: 100%; max-width: var(--content-max); margin-inline: auto; padding-inline: var(--space-4); }
main { flex: 1 0 auto; }
.section { padding-block: var(--pad-default); background: var(--color-surface); }
/* RHYTHM + LOUDNESS MODULATION, per archetype (R-1, doc §3.1): showcase sections breathe (major),
   support sections tuck in (compact); everything else keeps the default tier. Generated from the
   composition table (compositions.ts) and scoped to the data-archetype axis on the main element,
   so repeated section ids never inherit a neighbor archetype's tier. The page stops being a
   uniform stack — air grows around each TYPE's own proof moments. */
main[data-archetype="home"] .section[data-section="services"] { padding-block: var(--pad-major); }
main[data-archetype="home"] .section[data-section="gallery"] { padding-block: var(--pad-major); }
main[data-archetype="home"] .section[data-section="reviews"] { padding-block: var(--pad-major); }
main[data-archetype="home"] .section[data-section="credentials"] { padding-block: var(--pad-compact); }
main[data-archetype="home"] .section[data-section="areas"] { padding-block: var(--pad-compact); }
main[data-archetype="home"] .section[data-section="credentials"] h2 { font-size: var(--type-heading); }
main[data-archetype="home"] .section[data-section="areas"] h2 { font-size: var(--type-heading); }
main[data-archetype="about"] .section[data-section="story"] { padding-block: var(--pad-major); }
main[data-archetype="about"] .section[data-section="values"] { padding-block: var(--pad-compact); }
main[data-archetype="about"] .section[data-section="credentials"] { padding-block: var(--pad-compact); }
main[data-archetype="about"] .section[data-section="usps"] h2 { font-size: var(--type-heading); }
main[data-archetype="service"] .section[data-section="process"] { padding-block: var(--pad-major); }
main[data-archetype="service"] .section[data-section="faqs"] { padding-block: var(--pad-major); }
main[data-archetype="service"] .section[data-section="signs"] { padding-block: var(--pad-compact); }
main[data-archetype="service"] .section[data-section="cities"] { padding-block: var(--pad-compact); }
main[data-archetype="service"] .section[data-section="related"] { padding-block: var(--pad-compact); }
main[data-archetype="service"] .section[data-section="areas"] { padding-block: var(--pad-compact); }
main[data-archetype="service"] .section[data-section="signs"] h2 { font-size: var(--type-heading); }
main[data-archetype="service"] .section[data-section="cities"] h2 { font-size: var(--type-heading); }
main[data-archetype="service"] .section[data-section="related"] h2 { font-size: var(--type-heading); }
main[data-archetype="service"] .section[data-section="areas"] h2 { font-size: var(--type-heading); }
main[data-archetype="service-city"] .section[data-section="local-context"] { padding-block: var(--pad-major); }
main[data-archetype="service-city"] .section[data-section="faqs"] { padding-block: var(--pad-major); }
main[data-archetype="service-city"] .section[data-section="related-services"] { padding-block: var(--pad-compact); }
main[data-archetype="service-city-zip"] .section[data-section="zip-local"] { padding-block: var(--pad-major); }
main[data-archetype="city"] .section[data-section="local"] { padding-block: var(--pad-major); }
main[data-archetype="city"] .section[data-section="city-faqs"] { padding-block: var(--pad-major); }
main[data-archetype="city"] .section[data-section="nearby"] { padding-block: var(--pad-compact); }
main[data-archetype="city"] .section[data-section="nearby"] h2 { font-size: var(--type-heading); }
main[data-archetype="contact"] .section[data-section="disclaimers"] { padding-block: var(--pad-compact); }
main[data-archetype="contact"] .section[data-section="disclaimers"] h2 { font-size: var(--type-heading); }
main[data-archetype="person"] .section[data-section="bio"] { padding-block: var(--pad-major); }
main[data-archetype="person"] .section[data-section="credentials"] { padding-block: var(--pad-compact); }
main[data-archetype="person"] .section[data-section="connections"] { padding-block: var(--pad-compact); }
main[data-archetype="services-index"] .section[data-section="services"] { padding-block: var(--pad-major); }
main[data-archetype="services-index"] .section[data-section="credentials"] { padding-block: var(--pad-compact); }
main[data-archetype="services-index"] .section[data-section="credentials"] h2 { font-size: var(--type-heading); }
main[data-archetype="areas-index"] .section[data-section="areas"] { padding-block: var(--pad-major); }
/* One deliberate BREATHER on home: a widened gap above the concentrated proof (reviews), the
   in-CSS stand-in until the M-D statement band ships. Archetype-scoped to match the generated
   home reviews-major rule's specificity, so this padding-top refinement still wins by source order. */
main[data-archetype="home"] .section[data-section="reviews"] { padding-top: calc(var(--pad-major) + var(--space-5)); }
/* Legible, theme-aware band contrast: alternating sections sit on a distinctly tinted
   surface (a touch of brand), so the page reads as intentional banding, not one flat field. */
.section--alt { background: var(--band-alt-bg); }
.section + .section { border-top: 1px solid color-mix(in srgb, var(--color-border) 80%, transparent); }
.section h2 { max-width: 22ch; }
/* ASYMMETRIC SPLIT (P-2 §3b): a section with two content natures (the trust band = team photo +
   USPs/guarantees) reads as a composed 55/45 two-column at desktop instead of a stacked dump —
   IMAGE-GATED via :has(> img), so it only activates when a real team photo is present (today's
   sites ship none, so the image is pruned and the layout stays single-column = byte-stable). */
@media (min-width: 861px) {
  .section[data-section="trust"] > .container:has(> img) { display: grid; grid-template-columns: minmax(0, 1.22fr) minmax(0, 1fr); column-gap: var(--space-6); align-items: start; }
  .section[data-section="trust"] > .container:has(> img) > * { grid-column: 1; }
  .section[data-section="trust"] > .container:has(> img) > img { grid-column: 2; grid-row: 1 / span 99; align-self: stretch; height: 100%; object-fit: cover; border-radius: var(--radius-lg); margin: 0; }
}

/* ---- section eyebrow (kicker) + two-tone headline accent ---------------- */
/* Accent DISCIPLINE: the accent means "act here", so kickers + headline emphasis are NEUTRAL —
   the eyebrow reads in muted with a neutral tick, and a highlighted headline word is set by
   WEIGHT, never the brand color. The accent is reserved for CTAs, links, focus + affordances. */
.eyebrow { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-2); font-family: var(--font-heading); font-size: var(--fs-caption); font-weight: 700; line-height: 1.35; text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-muted); text-wrap: balance; }
.eyebrow::before { content: ""; flex: 0 0 auto; width: 1.6em; height: 2px; border-radius: 2px; background: color-mix(in srgb, var(--color-muted) 55%, transparent); }
.hl { font-weight: 800; color: var(--color-ink); }

/* ---- home trust strip: a CSS-only flowing MARQUEE (zero-JS ticker, CSP-safe) ----------
   The renderer emits the trait set TWICE inside .mq-track; translating the track -50% loops it
   seamlessly (set 2 takes set 1's place). The container masks both edges so traits melt in/out.
   prefers-reduced-motion freezes it to a static, wrapping, fully-readable row. No JS, no inline
   style, no external request. Calm 38s cycle — it sits under the hero and must not read frantic. */
.trust-strip { border-block: 1px solid var(--color-border); background: color-mix(in srgb, var(--color-surface-alt) 45%, transparent); }
.trust-strip > .container { overflow: hidden; padding-block: var(--space-3); -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.mq-track { display: flex; width: max-content; animation: mq-scroll 38s linear infinite; will-change: transform; }
.mq-set { display: flex; align-items: center; flex: 0 0 auto; }
.mq-item { white-space: nowrap; font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-ink); }
.mq-dot { flex: 0 0 auto; color: var(--color-brand); margin-inline: 1.6rem; font-size: 0.5em; line-height: 1; }
@keyframes mq-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* Reduced-motion: freeze the loop, drop the (now redundant) duplicate set, and let the real set
   wrap as a plain static row — readable, never clipped. */
@media (prefers-reduced-motion: reduce) {
  .mq-track { animation: none; width: auto; }
  .mq-set { flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
  .mq-set[aria-hidden="true"] { display: none; }
  .trust-strip > .container { overflow: visible; -webkit-mask-image: none; mask-image: none; }
}

/* ---- designed closing CTA band: contrasting brand-tinted gradient, centered ---------- */
.section[data-section="cta"] { background: var(--band-cta-bg); border-top: var(--band-cta-rule-top); border-bottom: var(--band-cta-rule-bottom); }
/* Centered flex column so EVERY child — text, the constrained-width button <p>, AND the trust chip —
   centers on the true axis (text-align only centered inline text, leaving the block button/chip pinned
   left). align-items:center + margin-inline:auto on each child centers them all on the headline's axis. */
.section[data-section="cta"] > .container { max-width: 760px; text-align: center; display: flex; flex-direction: column; align-items: center; }
.section[data-section="cta"] > .container > * { margin-left: auto; margin-right: auto; }
/* Centered cta eyebrow: STACK the leading dash ABOVE the label (flex column) so the TEXT centers on
   the true axis (matching the h2/support/button), instead of a leading-dash row whose justify-center
   left the visible text off-axis. Scoped to the cta band only — left-aligned eyebrows keep their
   leading-dash row (the base .eyebrow is untouched). */
.section[data-section="cta"] .eyebrow { flex-direction: column; gap: var(--space-1); }
/* In the flex-column container, margins no longer COLLAPSE, so zero the h2 bottom + the button <p>
   block margins and let each gap come from ONE side (the following item's top margin) — restoring the
   original collapsed rhythm: eyebrow→h2 space-2, h2→support space-3, support→button space-5, button→chip space-3. */
.section[data-section="cta"] h2 { max-width: none; font-size: var(--type-title); margin: 0; }
.section[data-section="cta"] > .container > p:not([class]) { margin-block: 0; }
/* Support is a QUIET sub-line beneath the h2 (not a second headline): body size (the system sub-line
   token, as the hero cta-support uses), not the near-headline --fs-2. A clear gap before the buttons. */
.section[data-section="cta"] .cta-band-support { max-width: 48ch; margin: var(--space-3) auto var(--space-5); color: var(--color-muted); font-size: var(--fs-body); line-height: 1.5; }
/* CTA supporting line (benefit-first) + a single trust signal beside the primary CTA. */
.cta-support { margin: var(--space-3) 0 0; color: var(--color-muted); font-size: var(--fs-caption); }
.section[data-section="cta"] .cta-support { max-width: 46ch; margin-inline: auto; font-size: var(--fs-body); }
.cta-trust { margin: var(--space-3) 0 0; }
.cta-trust-chip { display: inline-flex; align-items: center; gap: var(--space-1); padding: 5px var(--space-3); border: 1px solid color-mix(in srgb, var(--color-accent) 30%, var(--card-border)); border-radius: var(--radius-pill); background: color-mix(in srgb, var(--color-accent) 8%, var(--color-card)); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); color: var(--color-ink); }
.cta-trust-chip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--color-accent); flex: 0 0 auto; }
.section[data-section="cta"] > .container > p:last-of-type { justify-content: center; }

/* ---- section rhythm: a deliberate accent beat for the proof/credentials band ---------
   Surface to surface-alt alternation (via the renderer's alt flags) carries the base
   pacing; the credentials band gets a faint accent wash so the page has one intentional
   emphasis moment between the plain bands instead of reading as a single flat field. */
.section[data-section="credentials"] { background: var(--band-cred-bg); }

/* ---- buttons ------------------------------------------------------------ */
/* Every button is touch-comfortable (WCAG 2.5.5/2.5.8): a >=44px target in the header, hero,
   footer band, and callbar alike — padding/copy unchanged, the min-height just guarantees the size. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); min-height: 44px; padding: 0.85em 1.5em; border-radius: var(--radius-pill); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-body); line-height: 1; text-decoration: none; border: 1px solid transparent; cursor: pointer; transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard); position: relative; overflow: hidden; }
.btn::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, var(--sheen), transparent 60%); opacity: .5; pointer-events: none; }
.btn:hover { transform: translateY(-2px); text-decoration: none; box-shadow: var(--shadow-2); }
.btn:active { transform: translateY(1px); }
.btn .svg-icon { width: 18px; height: 18px; flex: 0 0 auto; }
/* Primary is clearly dominant: filled brand, heavier weight, a touch larger; secondary recedes. */
.btn--primary { background: var(--color-brand); color: var(--color-brand-contrast); box-shadow: var(--shadow-2); font-weight: 700; padding: 0.95em 1.7em; }
/* Primary button HOVER: a brightness step on the brand face (P-3 §3c), on top of the shared lift. */
.btn--primary:hover { background: color-mix(in srgb, var(--color-brand) 92%, #fff); }
.btn--secondary { background: transparent; color: var(--color-ink); border-color: var(--color-border); }
.btn--secondary::after { opacity: .25; }
.btn--secondary:hover { border-color: var(--color-brand); color: var(--color-brand); }

/* ---- header / nav (sticky, CSS-only mobile menu) ------------------------ */
.site-header { position: sticky; top: var(--utility-h); z-index: 100; background: color-mix(in srgb, var(--color-surface) 86%, transparent); backdrop-filter: saturate(1.4) blur(12px); border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); }
/* The header BAR spans the full viewport (not capped at --content-max) so brand + nav +
   CTAs share one row for any brand-name length; page content below stays at --content-max. */
.site-header > .container { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--space-3); min-height: 64px; max-width: none; }
.brand { display: inline-flex; align-items: center; min-height: 44px; gap: var(--space-2); text-decoration: none; color: var(--color-ink); font-family: var(--font-heading); font-weight: 700; flex: 0 1 auto; min-width: 0; }
.brand:hover { text-decoration: none; }
.brand-mark { flex: 0 0 auto; filter: drop-shadow(var(--shadow-1)); }
/* Never wrap mid-phrase; shrink the type a touch on tight widths, ellipsis only as a
   last resort so an extreme name can't break the row. */
.brand-name { font-size: clamp(1rem, 0.62rem + 0.7vw, var(--fs-1)); letter-spacing: -0.02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Real brand logo (when the KB ships one): height-capped to the header line, width auto to keep its
   aspect; replaces the monogram + wordmark. The home link keeps its accessible name via the alt. */
/* Real brand logo (when the KB ships one): a WIDE banner that already carries the name — shown
   whole, height-capped to the header line, no wordmark beside it. The header grows to fit it and
   --header-h tracks so the sticky offsets + mobile menu sheet stay aligned (Eugene/Xtreme have no
   logo, so these :has rules never match → their 64px header is byte-stable). */
.site-brand-logo { display: block; flex: 0 0 auto; height: 56px; width: auto; max-width: min(300px, 50vw); object-fit: contain; }
html:has(.site-brand-logo) { --header-h: 76px; }
.site-header > .container:has(.site-brand-logo) { min-height: var(--header-h); }
.nav { margin-left: auto; position: relative; }
.nav-toggle { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.nav-burger { cursor: pointer; min-height: 44px; padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); color: var(--color-ink); font-family: var(--font-heading); font-weight: 600; display: inline-flex; align-items: center; gap: var(--space-2); }
.burger-bars { width: 18px; height: 2px; background: currentColor; position: relative; }
.burger-bars::before, .burger-bars::after { content: ""; position: absolute; left: 0; width: 18px; height: 2px; background: currentColor; }
.burger-bars::before { top: -5px; } .burger-bars::after { top: 5px; }
.nav-toggle:focus-visible ~ .nav-burger { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* Hamburger to X morph when the menu opens (pure CSS checkbox-hack, no JS): the middle bar fades,
   the two pseudo-bars slide to centre and rotate into a cross. It reverses on close. The transition
   routes through the motion token, which prefers-reduced-motion zeroes, so it snaps instantly there. */
.burger-bars, .burger-bars::before, .burger-bars::after { transition: transform var(--dur-fast) var(--ease-standard), top var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard); }
.nav-toggle:checked ~ .nav-burger .burger-bars { background-color: transparent; }
.nav-toggle:checked ~ .nav-burger .burger-bars::before { top: 0; transform: rotate(45deg); }
.nav-toggle:checked ~ .nav-burger .burger-bars::after { top: 0; transform: rotate(-45deg); }
/* Dimming scrim behind the open mobile menu — a tap-to-close <label for="nav-toggle">. Hidden by
   default + on desktop; only shown when the menu is open under the mobile breakpoint (below). */
.nav-scrim { display: none; }
.site-nav ul { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); padding: 0; margin: 0; }
/* Nav links + group labels get a >=24px hit area (WCAG 2.2 Target-Size 2.5.8) via min-height +
   inline-flex centering — the target grows, the visible text weight/size is unchanged. */
.site-nav a { display: inline-flex; align-items: center; min-height: 32px; color: var(--color-muted); font-family: var(--font-heading); font-weight: 500; font-size: 0.96rem; white-space: nowrap; }
.site-nav a:hover { color: var(--color-ink); text-decoration: none; }
.nav-group { position: relative; }
.nav-group-label { display: inline-flex; align-items: center; min-height: 32px; color: var(--color-muted); font-family: var(--font-heading); font-weight: 500; font-size: 0.96rem; cursor: default; }
/* Zero-JS hover-intent dropdown: the submenu is always LAID OUT (absolute, out of flow) but hidden
   via visibility/opacity — which are transitionable, so the HIDDEN state carries a close DELAY
   (transition-delay 300ms) → the menu lingers ~300ms after the cursor leaves, surviving the diagonal
   move to a submenu item (the old display:none toggle vanished instantly = "impossible to click"). It
   opens IMMEDIATELY on hover/focus (no delay). A transparent ::before BRIDGE fills the trigger→menu
   gap so crossing it keeps :hover. :focus-within keeps it fully keyboard-operable. Pure CSS (CSP-safe). */
.nav-group > ul { position: absolute; top: 100%; left: 0; min-width: 220px; display: flex; visibility: hidden; opacity: 0; pointer-events: none; transition: opacity 0ms 300ms, visibility 0ms 300ms; flex-direction: column; gap: 0; padding: var(--space-2); margin-top: var(--space-2); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-2); }
.nav-group > ul::before { content: ""; position: absolute; left: 0; right: 0; top: calc(-1 * var(--space-2)); height: var(--space-2); }
.nav-group:hover > ul, .nav-group:focus-within > ul { visibility: visible; opacity: 1; pointer-events: auto; transition: opacity 130ms 0ms, visibility 0ms 0ms; }
.nav-group > ul a { display: block; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); }
.nav-group > ul a:hover { background: var(--color-surface-alt); }
/* Active-page marker (aria-current="page"): NOT colour-only — a heavier weight AND a branded
   underline carry the state for users who can't perceive the colour shift, while the ink colour
   keeps it AA-contrast on the header surface. Applies to a top-level link or a section group label. */
.site-nav a.is-active, .nav-group-label.is-active { color: var(--color-ink); font-weight: 700; text-decoration: underline; text-decoration-color: var(--color-brand); text-decoration-thickness: 2px; text-underline-offset: 0.3em; }
.header-cta { display: flex; flex: 0 0 auto; align-items: center; gap: var(--space-2); margin-left: var(--space-1); }
.header-cta .btn { padding: 0.6em 1.05em; font-size: 0.92rem; white-space: nowrap; }

/* ---- sticky-header scroll-state morph (2026 zero-JS pattern; progressive) ------
   When the utility-bar + header stack is STUCK to the top, condense it: a stronger /
   more-opaque background and a soft shadow, so it reads as a distinct floating bar over
   scrolled content. The morph lives on the inner FULL-WIDTH .container because a scroll-state
   @container styles its DESCENDANTS, not the container element itself. Only background +
   shadow change (NO height/padding change) → guaranteed zero CLS, no layout jump. Transitioned
   through the motion tokens, so prefers-reduced-motion makes it instant (never broken).
   @supports-guarded: unsupported browsers keep the plain static bar. */
@supports (container-type: scroll-state) {
  .utility-bar, .site-header { container-type: scroll-state; }
  .site-header > .container { transition: background var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard); }
  .utility-bar > .container { transition: background var(--dur-base) var(--ease-standard); }
  @container scroll-state(stuck: top) {
    .site-header > .container { background: color-mix(in srgb, var(--color-surface) 97%, transparent); box-shadow: var(--shadow-2); }
    .utility-bar > .container { background: color-mix(in srgb, var(--color-surface-alt) 90%, var(--color-ink)); }
  }
}

/* ---- hero trust row (home hero only; subtle, accent-dot separators) ------ */
/* Hero stat cards (home hero only): big accent number + small uppercase label. */
.hero-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(116px, 1fr)); gap: var(--space-3); margin-top: var(--space-5); }
.hero-stat { display: flex; flex-direction: column; gap: 2px; padding: var(--space-3) var(--space-4); border: 1px solid var(--card-border); border-radius: var(--radius); background: var(--color-card); box-shadow: var(--elevation-card); }
.hero-stat-num { font-family: var(--font-heading); font-weight: 800; font-size: var(--fs-3); line-height: 1; letter-spacing: -0.02em; color: var(--color-ink); }
.hero-stat-label { font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.08em; line-height: 1.35; color: var(--color-faint); }
/* Inner-hero fact card (service/city) — fills the right column; a designed panel, no image. */
.hero-aside { margin-top: var(--space-5); }
.hero-aside-card { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--card-border); border-radius: var(--radius-lg); background:
  linear-gradient(180deg, var(--sheen), transparent 55%), var(--color-card); box-shadow: var(--shadow-2); }
.hero-aside-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.hero-aside-row + .hero-aside-row { border-top: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent); padding-top: var(--space-2); }
/* Trust-chip row — closes the hero cluster (aside fact card + home hero) with proof points. */
.hero-trust { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.hero-aside-card .hero-trust { padding-top: var(--space-3); border-top: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent); }
.hero-trust-chip { display: inline-flex; align-items: center; gap: var(--space-1); padding: 4px var(--space-2); border: 1px solid color-mix(in srgb, var(--color-accent) 35%, var(--card-border)); border-radius: var(--radius-pill); background: color-mix(in srgb, var(--color-accent) 8%, var(--color-card)); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); color: var(--color-ink); }
.hero-trust-chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--color-accent); flex: 0 0 auto; }
.hero-aside-k { font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.08em; line-height: 1.35; color: var(--color-faint); }
.hero-aside-v { font-family: var(--font-heading); font-weight: 700; color: var(--color-ink); text-align: right; }

/* ---- breadcrumb --------------------------------------------------------- */
.breadcrumb { font-size: var(--fs-caption); padding-top: var(--space-4); }
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: 0; margin: 0; color: var(--color-muted); }
.breadcrumb li + li::before { content: "/"; margin-right: var(--space-2); color: var(--color-border); }
.breadcrumb a { color: var(--color-muted); }
.breadcrumb [aria-current] { color: var(--color-ink); }
/* In-hero variant: sits on the hero gradient above the page headline so inner pages
   flow header to hero to first section exactly like home (no flat breadcrumb gap). */
.breadcrumb--hero { padding-top: 0; margin-bottom: var(--space-4); }

/* ---- HERO (designed shell + gradient/sheen fallback, asymmetric) -------- */
.section[data-section="hero"] { position: relative; overflow: clip; padding-top: clamp(var(--space-6), 6vw, var(--space-7)); padding-bottom: clamp(var(--space-5), 3.5vw, var(--space-6)); background: var(--hero-bg); }
/* The right-anchored skewed sheen is the "photo panel" — only render it WHEN a hero
   image is present, so the no-image hero has no empty slot shape on the right. */
.section[data-section="hero"]:not(.section--photo):not(.section--photo-split):has(.hero-img)::after { content: ""; position: absolute; right: -8%; top: -20%; width: 46%; height: 150%; background: linear-gradient(135deg, var(--sheen), transparent 55%); transform: skewX(-12deg); opacity: .5; pointer-events: none; }
/* No image → a deliberate full-width atmospheric field (centered radial + a top-edge
   sheen band), replacing the right-anchored gradients so nothing reads as a photo slot. */
.section[data-section="hero"]:not(:has(.hero-img)) { background: var(--hero-bg-noimg); border-top: var(--hero-rule-top); }
/* Left-aligned still (service/city heroes carry a prose intro); just let the headline
   breathe wider since there is no image column to balance against. */
.section[data-section="hero"]:not(:has(.hero-img)) h1 { max-width: 26ch; }
.section[data-section="hero"]:not(:has(.hero-img)) .lede { max-width: 56ch; }
.section[data-section="hero"] .container { position: relative; z-index: 1; }
/* (P0 Identity overhaul: the one-time hero gloss/sheen SWEEP — a trend-fragile "AI-site-builder"
   tell — was removed. The hero keeps its static radial-gradient wash; no animated ::before.) */
.section[data-section="hero"] h1 { font-size: var(--type-hero); max-width: var(--hero-measure); line-height: var(--hero-lh); margin-bottom: var(--space-4); }
.section[data-section="hero"] .lede { font-size: var(--type-lede); max-width: var(--measure-lede); color: var(--color-muted); }
.section[data-section="hero"] .hero-img { width: 100%; aspect-ratio: 16 / 8; object-fit: cover; border-radius: var(--radius-lg); border: 1px solid var(--color-border); box-shadow: var(--shadow-2); margin-block: var(--space-4); }
.section[data-section="hero"] > .container > p:last-of-type { margin-top: var(--space-4); display: flex; flex-wrap: wrap; gap: var(--space-3); }
/* ---- FULL-BLEED PHOTO HERO (image-forward M-A) -------------------------------------------
   Server-stamped .section--photo when the KB ships a hero photo. The picture element is a
   DECORATIVE absolutely-positioned cover layer (.section-media, z -2) behind a brand-tinted scrim
   (.section-scrim, z -1) behind the copy (z 1). Light-on-dark overlay text via --overlay-* +
   text-shadow insurance. Height is content-driven with a viewport clamp — never 100vh. The
   media layer is absolute so it can't reflow → zero CLS. CSP-clean: no style attributes. */
.section--photo[data-section="hero"] { position: relative; isolation: isolate; overflow: clip; display: flex; align-items: center; min-height: clamp(420px, 72svh, 820px); color: var(--overlay-ink); text-shadow: var(--overlay-text-shadow); background: var(--scrim-color); border-bottom: 3px solid var(--color-brand); }
.section--photo > .section-media { position: absolute; inset: 0; z-index: -2; overflow: clip; }
.section--photo > .section-media picture { display: block; width: 100%; height: 100%; }
.section--photo[data-section="hero"] .section-media .hero-img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: auto; border: 0; border-radius: 0; box-shadow: none; margin: 0; display: block; }
.section--photo > .section-scrim { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
/* SHAPED gradient scrims (not a flat veil): solid-ish over the copy region → ~35% of alpha at
   the far edge. Brand-tinted by default (Tier-1 cohesion). --scrim-alpha is the static M-A floor. */
.section-scrim--left { background: linear-gradient(90deg, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 100%), transparent) 0%, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 100%), transparent) 42%, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 35%), transparent) 100%); }
.section-scrim--bottom { background: linear-gradient(0deg, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 100%), transparent) 0%, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 100%), transparent) 30%, color-mix(in srgb, var(--scrim-tint) calc(var(--scrim-alpha) * 35%), transparent) 100%); }
/* HERO photo scrim — a STRONGER two-gradient stack (live-tuned): a dark brand-warm wash across the
   headline column (layer 1, horizontal) keeps the white overlay text crisp even over a bright photo on
   a LIGHT page, plus a gentle bottom anchor (layer 2, vertical) for the lede/CTA — while the photo stays
   clearly visible on the right. Anchored on --scrim-color (brand-warm near-black), fixed alphas (the old
   --scrim-alpha-faded shape was the bug). Scoped to the HERO photo scrim ONLY (the bare .section-scrim--*
   variants + the split hero are unaffected). */
.section--photo[data-section="hero"] .section-scrim--left {
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--scrim-color) 92%, transparent) 0%, color-mix(in srgb, var(--scrim-color) 86%, transparent) 38%, color-mix(in srgb, var(--scrim-color) 55%, transparent) 70%, color-mix(in srgb, var(--scrim-color) 30%, transparent) 100%),
    linear-gradient(0deg, color-mix(in srgb, var(--scrim-color) 55%, transparent) 0%, color-mix(in srgb, var(--scrim-color) 10%, transparent) 45%, transparent 75%);
}
.section--photo[data-section="hero"] h1, .section--photo[data-section="hero"] .eyebrow { color: var(--overlay-ink); }
/* Full-bleed photo hero h1 honors the per-recipe hero measure (P2 §5.1) — same token as the general
   hero h1, so a wide/condensed display face wraps to its tuned line-count over the photo. */
.section--photo[data-section="hero"] h1 { max-width: var(--hero-measure); }
.section--photo[data-section="hero"] .lede, .section--photo[data-section="hero"] p { color: var(--overlay-ink-muted); }
.section--photo[data-section="hero"] .lede { max-width: 48ch; }
/* GLASS panels for hero stat cards + trust chips over the photo (translucent near-black + blur,
   light text). Scoped under .section--photo only — non-photo hero cards are untouched. */
.section--photo .hero-stat, .section--photo .hero-trust-chip, .section--photo .hero-aside-card { background: color-mix(in srgb, #0b0d10 52%, transparent); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); border-color: color-mix(in srgb, #ffffff 20%, transparent); color: var(--overlay-ink); box-shadow: none; }
.section--photo .hero-stat-num { color: var(--overlay-ink); }
.section--photo .hero-stat-label { color: var(--overlay-ink-muted); }
.section--photo .hero-trust-chip::before { background: var(--overlay-ink); }
/* R-2 glass aside card (price/turnaround fact card over the photo hero): light-on-glass like the
   stat cards. The k/v rows + inner trust divider re-tint to the overlay palette over the photo. */
.section--photo .hero-aside-k { color: var(--overlay-ink-muted); }
.section--photo .hero-aside-v { color: var(--overlay-ink); }
.section--photo .hero-aside-row + .hero-aside-row, .section--photo .hero-aside-card .hero-trust { border-color: color-mix(in srgb, #ffffff 20%, transparent); }

/* ---- R-2 SPLIT photo hero (about / spoke): photo BESIDE the copy, not a bleed -----------
   The about/spoke archetypes (composition table photoHero: "split") earn an asymmetric hero —
   the media slot sits in its own column at >=861px while the copy keeps normal ink on the
   surface (no overlay, no scrim-for-legibility). Below 861px the photo stacks as a top band so
   it is never lost. Same media slot + decorative img as the full bleed; absent ref ⇒ no class. */
.section--photo-split { position: relative; isolation: isolate; overflow: clip; }
/* The copy + boxed photo are TWO COLUMNS inside the centered .container (constrained to content-max,
   NOT the full-bleed section), so the text keeps normal dark ink on the surface — it sits BESIDE,
   never over, the un-scrimmed photo (no scrim is emitted for the split). Below 861px the container
   stacks: copy first, then the image. */
.section--photo-split > .container { display: grid; gap: var(--space-5); }
.section--photo-split .section-media .hero-img { width: 100%; object-fit: cover; aspect-ratio: 16 / 9; border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-1); margin: 0; display: block; }
@media (min-width: 861px) {
  .section--photo-split > .container { grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr); column-gap: var(--space-6); gap: 0; align-items: center; }
  .section--photo-split > .container > .hero-copy { grid-column: 1; }
  .section--photo-split > .container > .section-media { grid-column: 2; align-self: stretch; }
  .section--photo-split .section-media .hero-img { height: 100%; min-height: 320px; aspect-ratio: auto; }
}
/* ---- spoke hero ("intro"): a DELIBERATE single-column hero band ----------------------
   Matrix service×city spokes carry no fact-card aside (hub/canonical-only by design), so
   their first section is styled as an intentional single-column hero — same atmospheric
   field + heading/lede sizing as the no-image hero — and is NEVER a two-column grid. That
   removes the awkward empty right column the plain section left where an aside would sit.
   CSS-only; the rendered page text is unchanged. */
.section[data-section="intro"] { position: relative; overflow: clip; background:
  radial-gradient(135% 110% at 50% -15%, color-mix(in srgb, var(--color-brand) 16%, transparent), transparent 60%),
  radial-gradient(90% 90% at 12% 125%, color-mix(in srgb, var(--color-accent) 11%, transparent), transparent 58%),
  linear-gradient(180deg, var(--sheen), transparent 16%),
  var(--color-surface); }
.section[data-section="intro"] h1 { max-width: 26ch; line-height: 1.06; margin-bottom: var(--space-4); }
.section[data-section="intro"] .lede { font-size: var(--fs-3); max-width: 56ch; color: var(--color-muted); }
.section[data-section="intro"] .hero-img { width: 100%; aspect-ratio: 16 / 8; object-fit: cover; border-radius: var(--radius-lg); border: 1px solid var(--color-border); box-shadow: var(--shadow-2); margin-block: var(--space-4); }
/* Hero layout BRANCHES on image presence. With a hero image the desktop hero is an
   asymmetric two-column composition (copy left, image right). With NO image the hero
   picture is pruned (quality/assets pruneDeadAssetRefs), so :has(.hero-img) fails to
   match and the hero stays a full-width single column — no reserved empty gutter.
   The boxed two-column photo-hero branch was REMOVED in image-forward M-A: a KB with a hero
   photo now renders the full-bleed .section--photo treatment above (image in the media slot,
   never inside .container), and a KB without one keeps the designed gradient hero. */
@media (min-width: 861px) {
  /* No image but stats present (the gradient home hero): copy + CTAs left, stat cards FILL the
     right column so the hero reads balanced. EXCLUDED for .section--photo (full-bleed = single
     column over the scrim). */
  .section[data-section="hero"]:not(.section--photo) > .container:not(:has(.hero-img)):has(.hero-stats, .hero-aside) { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.8fr); column-gap: var(--space-6); align-items: center; }
  .section[data-section="hero"]:not(.section--photo) > .container:not(:has(.hero-img)):has(.hero-stats, .hero-aside) > * { grid-column: 1; }
  .section[data-section="hero"]:not(.section--photo) > .container:not(:has(.hero-img)):has(.hero-stats, .hero-aside) > .hero-stats { grid-column: 2; grid-row: 1 / span 99; align-self: center; margin-top: 0; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .section[data-section="hero"]:not(.section--photo) > .container:not(:has(.hero-img)):has(.hero-aside) > .hero-aside { grid-column: 2; grid-row: 1 / span 99; align-self: center; margin-top: 0; }
}

/* ---- cards / link grids ------------------------------------------------- */
.link-grid, .review-grid { list-style: none; display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); padding: 0; margin: 0; }
.link-grid li { display: flex; }
.link-grid a { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); width: 100%; padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); color: var(--color-ink); font-family: var(--font-heading); font-weight: 600; box-shadow: var(--elevation-card); transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
.link-grid a::after { content: "→"; color: var(--color-accent); transition: transform var(--dur-fast) var(--ease-standard); }
.link-grid a:hover { transform: translateY(-2px); box-shadow: var(--elevation-card-hover); border-color: color-mix(in srgb, var(--color-brand) 50%, var(--color-border)); text-decoration: none; }
.link-grid a:hover::after { transform: translateX(4px); }
/* R-3 area cards (opt-in): a city name + a line of real GeoCity facts (county / drive time /
   neighborhood count). Same card affordance as the link grid, with a quiet facts sub-line. */
.area-grid { list-style: none; display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); padding: 0; margin: 0; }
.area-card { display: flex; flex-direction: column; gap: var(--space-1); width: 100%; padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); color: var(--color-ink); box-shadow: var(--elevation-card); transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
.area-card:hover { transform: translateY(-2px); box-shadow: var(--elevation-card-hover); border-color: color-mix(in srgb, var(--color-brand) 50%, var(--color-border)); text-decoration: none; }
.area-card-name { font-family: var(--font-heading); font-weight: 600; }
.area-card-facts { font-size: var(--fs-caption); color: var(--color-muted); }
/* ---- R-3 coverage map (decorative inline SVG; themed via tokens, no style attrs) -------------
   A plot of the served city points + the service-area radius circle + the base point, with the
   nearest cities labelled. aria-hidden — the link grid beside it is the accessible surface. */
.coverage-map { display: block; width: 100%; max-width: 460px; height: auto; margin: 0 auto var(--space-5); overflow: visible; }
.coverage-map-area { fill: color-mix(in srgb, var(--color-accent) 12%, transparent); stroke: var(--color-accent); stroke-width: 1.5; stroke-dasharray: 5 4; }
.coverage-map-dot { fill: color-mix(in srgb, var(--color-brand) 80%, var(--color-ink)); }
.coverage-map-base { fill: var(--color-accent); stroke: var(--color-surface); stroke-width: 2; }
.coverage-map-label { fill: var(--color-ink); font-family: var(--font-heading); font-weight: 600; font-size: 11px; paint-order: stroke; stroke: var(--color-surface); stroke-width: 3px; stroke-linejoin: round; }
/* Service cards: name → muted one-line description → quiet trailing arrow. Equal height
   (the description row flexes), with a considered lift/border hover. */
.service-grid { list-style: none; display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); padding: 0; margin: 0; }
.service-grid li { display: flex; }
/* Card reads icon → name → description; equal height (the description row flexes). The
   trailing arrow is folded into a subtle hover affordance on the name, not an orphan. */
.service-card { height: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); width: 100%; padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); color: var(--color-ink); box-shadow: var(--elevation-card); transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
.service-card:hover { transform: translateY(-2px); box-shadow: var(--elevation-card-hover); border-color: color-mix(in srgb, var(--color-brand) 50%, var(--color-border)); text-decoration: none; }
.service-card-icon { display: inline-grid; place-items: center; width: 3em; height: 3em; border-radius: var(--radius); color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.service-card-icon .svg-icon { width: 32px; height: 32px; stroke-width: 1.85; }
.service-card-title { display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-1); letter-spacing: -0.01em; line-height: 1.2; }
.service-card-title::after { content: "→"; color: var(--color-accent); font-weight: 700; opacity: 0; transform: translateX(-4px); transition: opacity var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard); }
.service-card:hover .service-card-title::after { opacity: 1; transform: translateX(0); }
.service-card-desc { color: var(--color-muted); font-size: var(--fs-caption); line-height: 1.55; max-width: 42ch; }
/* KB-driven price signal — quiet accent, sits under the name/description. */
.service-card-price { font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-caption); letter-spacing: 0.01em; color: var(--color-ink); }
/* "Learn more →" affordance: text is CSS-supplied (gate-neutral), pinned to the card bottom. */
.service-card-more { margin-top: auto; display: inline-flex; align-items: center; font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); color: var(--color-brand); }
.service-card-more::after { content: "Learn more →"; }
.service-card:hover .service-card-more { text-decoration: underline; text-underline-offset: 0.2em; }
/* IMAGE-LED service cards (a grid where EVERY service has a photo): the card leads with a
   cover-cropped image; text padded below. Icon-tile cards (no images) are unchanged. */
.service-card--image { padding: 0; gap: 0; overflow: clip; }
.service-card-media { display: block; width: 100%; }
.service-card-img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; display: block; }
.service-card--image .service-card-title { padding: var(--space-3) var(--space-4) 0; }
.service-card--image .service-card-desc, .service-card--image .service-card-price { padding-inline: var(--space-4); }
.service-card--image .service-card-more { padding: 0 var(--space-4) var(--space-4); }

/* "Our Work" GALLERY — before/after pairs or single showcases with captions. KB-gated. */
.gallery-grid { list-style: none; display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); margin: var(--space-3) 0 0; padding: 0; max-width: none; }
.gallery-item { margin: 0; display: flex; flex-direction: column; background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); box-shadow: var(--elevation-card); overflow: clip; }
.gallery-item--pair { grid-column: span 2; }
.gallery-pair { display: grid; grid-template-columns: 1fr 1fr; }
.gallery-shot { position: relative; min-width: 0; }
.gallery-tag { position: absolute; top: var(--space-2); left: var(--space-2); z-index: 1; padding: 3px var(--space-2); border-radius: var(--radius-pill); background: color-mix(in srgb, #000 58%, transparent); color: #fff; font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.06em; }
.gallery-img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.gallery-pair .gallery-img { aspect-ratio: 1 / 1; }
.gallery-caption { padding: var(--space-3) var(--space-4); margin: 0; color: var(--color-muted); font-size: var(--fs-caption); max-width: none; }
@media (max-width: 560px) { .gallery-item--pair { grid-column: span 1; } }

/* Overflow button shown under a grid (services / testimonials). */
.grid-more { margin: var(--space-4) 0 0; }

/* ---- feature blocks: USPs (icon+text), guarantees (icon+title+desc), credential chips -- */
.feature-grid { list-style: none; display: grid; gap: var(--space-3) var(--space-4); grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.feature { display: flex; align-items: flex-start; gap: var(--space-3); }
.feature-icon { flex: 0 0 auto; display: inline-grid; place-items: center; width: 2.2em; height: 2.2em; border-radius: var(--radius); background: color-mix(in srgb, var(--color-brand) 12%, transparent); color: var(--color-brand); }
.feature-icon .svg-icon { width: 20px; height: 20px; }
.feature-text { line-height: 1.5; }
.feature-blocks { list-style: none; display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.feature-block { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); box-shadow: var(--elevation-card); }
.feature-block .feature-icon { background: color-mix(in srgb, var(--color-accent) 16%, transparent); color: var(--color-accent); }
.feature-body { display: flex; flex-direction: column; gap: 2px; }
.feature-title { font-family: var(--font-heading); font-weight: 700; }
.feature-desc { color: var(--color-muted); font-size: var(--fs-caption); line-height: 1.55; }
.chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-1) var(--space-3); border: 1px solid var(--card-border); border-radius: var(--radius-pill); background: var(--color-card); box-shadow: var(--elevation-card); font-size: var(--fs-caption); font-weight: 600; }
.chip-icon { display: inline-flex; color: var(--color-accent); }
.chip-icon .svg-icon { width: 16px; height: 16px; }

/* ---- credential / badge band ------------------------------------------- */
.cred-band { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
/* Credential badge is NON-INTERACTIVE (static proof chip): resting elevation only, no hover/transition
   (P-3 §3c — only interactive elements respond; the D5 lint enforces this). */
.cred-badge { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border: 1px solid var(--card-border); border-radius: var(--radius-lg); background: var(--color-card); box-shadow: var(--elevation-card); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); }
.cred-badge-icon { display: inline-flex; color: var(--color-accent); }
.cred-badge-icon .svg-icon { width: 20px; height: 20px; }
.cred-badge-label { color: var(--color-ink); }

/* ---- identity / values band -------------------------------------------- */
.values-lead { font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-2); line-height: 1.3; color: var(--color-ink); max-width: var(--measure); margin: 0; }
.values-list { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.value-chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border: 1px solid color-mix(in srgb, var(--color-accent) 35%, var(--color-border)); border-radius: var(--radius-pill); background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-alt)); font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); }
.value-icon { display: inline-flex; color: var(--color-accent); }
.value-icon .svg-icon { width: 18px; height: 18px; }

/* ---- team / expert bios (About) ---------------------------------------- */
.team-grid { list-style: none; display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.bio { display: grid; grid-template-columns: auto 1fr; gap: var(--space-3) var(--space-4); align-items: start; padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); box-shadow: var(--elevation-card); }
/* Designed initials fallback — shown unless a real photo survived the asset pipeline. */
.bio-avatar { display: inline-flex; align-items: center; justify-content: center; width: 64px; height: 64px; border-radius: 50%; background: linear-gradient(140deg, color-mix(in srgb, var(--color-brand) 22%, var(--color-surface)), var(--color-surface-alt)); border: 1px solid var(--color-border); color: var(--color-brand); font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-1); letter-spacing: 0.02em; }
.bio-photo { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; border: 1px solid var(--color-border); }
.bio:has(.bio-photo) .bio-avatar { display: none; } /* real photo present ⇒ hide the fallback */
.bio-body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.bio-name { font-family: var(--font-heading); font-size: var(--fs-1); margin: 0; }
.bio-role { font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-muted); margin: 0; }
.bio-creds { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0; margin: var(--space-2) 0 0; max-width: none; }
.bio-text { color: var(--color-muted); margin: var(--space-2) 0 0; max-width: var(--measure); }
.bio-more { margin: var(--space-2) 0 0; font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); }
.bio-more a { color: var(--color-link); }

/* ---- team HUB roster + PERSON bio page (same elevation + card craft) ---------------- */
.team-roster { list-style: none; display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(264px, 1fr)); padding: 0; margin: var(--space-3) 0 0; max-width: none; }
.person-card { display: flex; }
.person-card-link { display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-4); align-items: center; width: 100%; padding: var(--space-4); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); box-shadow: var(--elevation-card); color: var(--color-ink); transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
.person-card-link:hover { transform: translateY(-2px); box-shadow: var(--elevation-card-hover); border-color: color-mix(in srgb, var(--color-brand) 50%, var(--card-border)); text-decoration: none; }
.person-card .bio-avatar, .person-card .bio-photo { grid-row: 1 / span 99; align-self: start; }
.person-card-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.person-card-name { font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-1); line-height: 1.2; }
.person-card-role { font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-muted); }
.person-card-tag { font-size: var(--fs-caption); color: var(--color-muted); }
.person-card-more { font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); color: var(--color-brand); margin-top: var(--space-1); }
.person-card-link:hover .person-card-more { text-decoration: underline; }

/* Person hero: text left, a photo/initials medallion right (never a dead column). */
.person-hero-media { display: inline-flex; align-items: center; justify-content: center; margin-top: var(--space-4); }
.person-hero-media .bio-avatar { width: 132px; height: 132px; font-size: var(--fs-4); box-shadow: var(--shadow-2); }
.person-hero-media:has(.person-hero-photo) .bio-avatar { display: none; }
.person-hero-photo { width: 100%; max-width: 260px; aspect-ratio: 1; object-fit: cover; border-radius: var(--radius-lg); border: 1px solid var(--card-border); box-shadow: var(--shadow-2); }
.bg-block + .bg-block { margin-top: var(--space-4); }
.bg-h { font-family: var(--font-heading); font-size: var(--fs-1); margin: 0 0 var(--space-2); }
.bg-langs { color: var(--color-muted); margin-top: var(--space-3); }
@media (min-width: 861px) {
  .section[data-section="hero"] > .container:has(.person-hero-media) { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.7fr); column-gap: var(--space-6); align-items: center; }
  .section[data-section="hero"] > .container:has(.person-hero-media) > * { grid-column: 1; }
  .section[data-section="hero"] > .container:has(.person-hero-media) > .person-hero-media { grid-column: 2; grid-row: 1 / span 99; margin-top: 0; }
}

/* ---- transparent price table ------------------------------------------- */
.price-table { width: 100%; max-width: 30rem; border-collapse: collapse; margin: var(--space-2) 0 var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden; }
.price-table caption { text-align: left; font-size: var(--fs-caption); color: var(--color-muted); padding: 0 0 var(--space-2); caption-side: top; }
.price-table thead th { background: var(--color-surface-alt); font-family: var(--font-heading); font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-muted); text-align: left; }
.price-table th, .price-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border); }
.price-table tbody tr:last-child th, .price-table tbody tr:last-child td { border-bottom: 0; }
.price-table tbody th { font-weight: 500; text-align: left; }
.price-table td { text-align: right; font-family: var(--font-heading); font-weight: 700; color: var(--color-ink); }
.price-range { font-size: var(--fs-2); }
.card { background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); padding: var(--space-4); box-shadow: var(--elevation-card); }
.review-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.review { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); background: var(--color-card); border: 1px solid var(--card-border); border-radius: var(--radius-lg); padding: var(--space-4); box-shadow: var(--elevation-card); }
.review-stars { color: var(--color-accent); letter-spacing: 0.12em; font-size: var(--fs-1); line-height: 1; }
.review blockquote { margin: 0; }
.review blockquote p { margin: 0; max-width: none; }
.review figcaption { display: flex; flex-direction: column; gap: 2px; margin-top: auto; }
.review-author { font-family: var(--font-heading); font-weight: 600; color: var(--color-ink); }
.review-meta { font-size: var(--fs-caption); color: var(--color-faint); }

/* ---- lists: checks, steps, FAQ ------------------------------------------ */
ul.checks { list-style: none; padding-left: 0; display: grid; gap: var(--space-2); }
ul.checks li { padding-left: 1.9em; position: relative; }
ul.checks li::before { content: "✓"; position: absolute; left: 0; top: 0.05em; display: inline-grid; place-items: center; width: 1.4em; height: 1.4em; border-radius: 50%; background: color-mix(in srgb, var(--color-brand) 16%, transparent); color: var(--color-brand); font-weight: 800; font-size: 0.8em; }
.faq dt { font-family: var(--font-heading); font-weight: 600; margin-top: var(--space-4); }
.faq dd { margin: var(--space-1) 0 var(--space-2); color: var(--color-muted); max-width: var(--measure); }
.steps { counter-reset: step; list-style: none; padding-left: 0; display: grid; gap: var(--space-3); }
.steps li { counter-increment: step; padding-left: 2.6em; position: relative; }
.steps li::before { content: counter(step); position: absolute; left: 0; top: 0; display: inline-grid; place-items: center; width: 1.8em; height: 1.8em; border-radius: 50%; background: var(--color-brand); color: var(--color-brand-contrast); font-family: var(--font-heading); font-weight: 700; font-size: 0.82em; box-shadow: var(--elevation-card); }
.hours { border-collapse: collapse; width: 100%; max-width: 30rem; }
.hours th, .hours td { text-align: left; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border); }

/* ---- lead / quote form (zero-JS; single column, persistent top labels) ------ */
/* C-2: routed review card (the most relevant testimonial on a money page; absent when none tagged). */
.routed-review { margin: 0; max-width: var(--measure); }
.routed-review .review-stars { color: var(--color-accent); letter-spacing: 0.12em; margin: 0 0 var(--space-1); }
.routed-review blockquote { margin: 0 0 var(--space-2); font-style: italic; }
.routed-review figcaption { color: var(--color-muted); font-size: var(--fs-caption); }
.review-via { color: var(--color-faint); }
/* C-1: the quote-form band sits OUTSIDE main (gate-neutral global block, like the utility/call bar). */
.quote-band { background: var(--color-surface-alt); border-top: 1px solid var(--color-border); padding: var(--pad-default) 0; }
.quote-band h2 { margin-top: 0; }
.quote-band-lede { color: var(--color-muted); max-width: var(--measure); margin-top: var(--space-1); }
.lead-form { display: grid; gap: var(--space-4); max-width: 34rem; margin-top: var(--space-3); }
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field label { font-family: var(--font-heading); font-weight: 600; font-size: var(--fs-caption); color: var(--color-ink); }
.field-opt { font-weight: 400; color: var(--color-faint); text-transform: none; letter-spacing: 0; }
.field input, .field select, .field textarea { width: 100%; min-height: 44px; padding: 0.7em 0.9em; font: inherit; color: var(--color-ink); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); transition: border-color var(--dur-fast) var(--ease-standard); }
.field textarea { resize: vertical; min-height: 6.5em; }
/* C-1: native select keeps the system arrow (zero-JS) but matches the input chrome. */
.field select { appearance: none; -webkit-appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--color-muted) 50%), linear-gradient(135deg, var(--color-muted) 50%, transparent 50%); background-position: calc(100% - 1.1em) 1.1em, calc(100% - 0.75em) 1.1em; background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; padding-right: 2.2em; }
/* Form fields are interactive: a hover border-brighten completes the state set (P-3 section 3c). */
.field input:hover, .field select:hover, .field textarea:hover { border-color: color-mix(in srgb, var(--color-brand) 45%, var(--color-border)); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; border-color: var(--color-brand); }
.field input:user-invalid, .field textarea:user-invalid { border-color: var(--color-brand); }
.field-hint { font-size: var(--fs-caption); color: var(--color-muted); }
/* Honeypot: off-screen (not display:none, so bots still fill it) + hidden from AT. */
.lf-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-privacy { font-size: var(--fs-caption); color: var(--color-muted); margin: 0; max-width: var(--measure); }
.lead-form .btn { justify-self: start; }

/* ---- footer (brand block + grouped columns + CTA band + closing band) -------------- */
/* margin-top tightened (was space-7): the closing CTA section is the single, designed closing
   moment now, so only a small gap separates it from the footer (no large dead band). */
.site-footer { flex: 0 0 auto; background: var(--color-surface-alt); border-top: 1px solid var(--color-border); margin-top: var(--space-4); padding-block: var(--space-6); }
/* ONE even grid: brand + each nav column are siblings (the nav box is display:contents below, so its
   columns flow into THIS grid). Brand ~1.4fr, each nav column 1fr via auto-fit (fits the ACTUAL column
   count per client — no phantom empty track, no middle void). align-items:start; even gutters. */
.footer-top { display: grid; gap: var(--space-6); grid-template-columns: 1fr; }
@media (min-width: 861px) { .footer-top { grid-template-columns: minmax(220px, 1.4fr) repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-6) var(--space-7); align-items: start; } }
.footer-brand { display: flex; flex-direction: column; gap: var(--space-3); max-width: 40ch; }
.footer-brand-link { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--color-ink); font-family: var(--font-heading); font-weight: 700; }
.footer-brand-link:hover { text-decoration: none; }
.footer-brand-name { font-size: var(--fs-1); letter-spacing: -0.02em; }
/* Footer brand logo (transparent ⇒ surface-agnostic): the whole banner at a calmer size. */
.footer-brand-logo { display: block; height: 52px; width: auto; max-width: min(280px, 70vw); object-fit: contain; }
.footer-positioning { color: var(--color-muted); margin: 0; font-size: var(--fs-caption); line-height: 1.6; }
.footer-contact { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); }
.footer-contact-item { display: flex; align-items: center; gap: var(--space-2); color: var(--color-muted); font-size: var(--fs-caption); }
.footer-contact-item .svg-icon { width: 16px; height: 16px; color: var(--color-accent); flex: 0 0 auto; }
.footer-contact-item a { display: inline-flex; align-items: center; min-height: 24px; color: var(--color-ink); font-weight: 600; }
.footer-social { list-style: none; display: inline-flex; flex-wrap: wrap; gap: var(--space-2); padding: 0; margin: var(--space-1) 0 0; }
.footer-social a { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 1px solid var(--card-border); border-radius: var(--radius); color: var(--color-muted); background: var(--color-card); box-shadow: var(--elevation-card); transition: color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard); }
.footer-social a:hover { color: var(--color-brand); border-color: color-mix(in srgb, var(--color-brand) 50%, var(--card-border)); transform: translateY(-2px); }
.footer-social .svg-icon { width: 18px; height: 18px; }
/* display:contents — the nav generates no box, so its .footer-col children become direct grid items of
   .footer-top (one even brand+columns grid). The <nav aria-label="Footer"> landmark is preserved in the
   a11y tree (display:contents keeps the element there in modern browsers). */
.footer-nav { display: contents; }
.footer-nav ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-1); }
/* Footer link hit areas meet the 24px target floor (WCAG 2.5.8) — inline-flex + min-height,
   text unchanged. (Brand link + 38px social tiles already exceed it.) */
.footer-nav a { display: inline-flex; align-items: center; min-height: 32px; color: var(--color-muted); }
.footer-nav a:hover { color: var(--color-ink); }
.footer-meta a { display: inline-flex; align-items: center; min-height: 24px; }
.footer-h { font-family: var(--font-heading); font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-muted); margin-bottom: var(--space-2); }
/* Footer legal = a BOTTOM BAR (not a tall left stack): the disclaimer sentences span the top, then a
   baseline row of [© city-list | Accessibility | Made by 2k] — © fills the left (wraps naturally), the
   right items stay anchored. Uses the full footer width; ~215px→~160px. Grid-areas map to the markup
   order (© <p> first → copy, ul.muted → notes, .footer-meta → access, .footer-made → made). A missing
   child just leaves its cell empty (graceful). */
.footer-legal { display: grid; grid-template-columns: 1fr auto auto; grid-template-areas: "notes notes notes" "copy access made"; gap: 4px var(--space-4); align-items: baseline; margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border); color: var(--color-muted); font-size: var(--fs-caption); }
.footer-legal > ul.muted { grid-area: notes; list-style: none; padding: 0; margin: 0 0 var(--space-3); display: grid; gap: var(--space-1); color: var(--color-faint); }
.footer-legal > p:first-child { grid-area: copy; margin: 0; max-width: 70ch; }
.footer-legal > .footer-meta { grid-area: access; margin: 0; white-space: nowrap; }
.footer-legal > .footer-made { grid-area: made; margin: 0; white-space: nowrap; display: inline-flex; align-items: center; gap: 6px; }
/* "Made by 2k" maker's-mark flag: a small inline SVG (CSP-clean, no external image). The white
   stripes need a hairline frame so they read on a LIGHT footer (AAG) and stay crisp on DARK
   (Xtreme/sample); currentColor (= --color-muted here) adapts the border to either surface. */
.flag-2k { flex: 0 0 auto; display: block; border-radius: 2px; border: 1px solid color-mix(in srgb, currentColor 28%, transparent); }
/* (footer-legal mobile collapse lives in the shared ≤600px block in responsiveCss, so there's a single
   max-width:600px @media block.) */

/* ---- top utility bar (NAP quick-strip; sticky above the header) --------- */
.utility-bar { position: sticky; top: 0; z-index: 101; min-height: var(--utility-h); display: flex; align-items: center; background: var(--color-surface-alt); border-bottom: 1px solid var(--color-border); font-size: var(--fs-caption); }
.utility-bar > .container { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0 var(--space-4); max-width: none; padding-block: var(--space-1); }
.util-info { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-4); padding: 0; margin: 0; }
.util-item { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; color: var(--color-muted); font-weight: 600; }
.util-item .svg-icon { width: 16px; height: 16px; color: var(--color-accent); flex: 0 0 auto; }
/* Centered value strip: fills the space between hours (left) and phone (right). Shrinks to an
   ellipsis at medium widths rather than wrapping the bar to a second row; hidden with the whole
   utility bar at <=600px. Bullet-separated real KB facts (built in chrome.ts). */
.util-value { flex: 1 1 auto; min-width: 0; margin: 0; padding-inline: var(--space-3); text-align: center; color: var(--color-muted); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.util-actions { display: inline-flex; align-items: center; gap: var(--space-4); flex: 0 0 auto; }
.util-social { list-style: none; display: inline-flex; align-items: center; gap: var(--space-1); padding: 0; margin: 0; }
.util-social a { display: inline-flex; align-items: center; justify-content: center; min-width: 24px; min-height: 24px; color: var(--color-muted); border-radius: var(--radius-sm); transition: color var(--dur-fast) var(--ease-standard); }
.util-social a:hover { color: var(--color-brand); }
.util-social .svg-icon { width: 18px; height: 18px; }

/* Sticky-stack anchor offset so a deep-link target clears the utility bar + header. */
#main, [data-section], [id] { scroll-margin-top: var(--scroll-offset); }

/* ---- sticky mobile call-bar (phone-first businesses) -------------------- */
.callbar { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 120; padding: var(--space-2) var(--space-3) calc(var(--space-2) + env(safe-area-inset-bottom)); background: color-mix(in srgb, var(--color-surface) 92%, transparent); backdrop-filter: blur(12px); border-top: 1px solid var(--color-border); gap: var(--space-2); }
.callbar .btn { flex: 1; justify-content: center; }

/* ---- motion: hero load reveal + zero-JS scroll reveal ------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
  /* The HERO reveals on load — it's above the fold, so there's no scroll to trigger it. */
  .section[data-section="hero"] h1 { animation: rise calc(var(--dur-slow) * 1.6) var(--ease-out) both .04s; }
  .section[data-section="hero"] .lede { animation: rise calc(var(--dur-slow) * 1.6) var(--ease-out) both .12s; }
  .section[data-section="hero"] > .container > p:last-of-type { animation: rise calc(var(--dur-slow) * 1.6) var(--ease-out) both .2s; }
}
/* Modern zero-JS scroll reveal: each non-hero section fades + rises AS it enters the
   viewport, driven by its own view() progress timeline (so they stagger naturally as you
   scroll). Doubly guarded — @supports (the feature) AND no-preference (the user). Where
   EITHER fails, no animation is applied at all and every section is fully static/visible. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes reveal-in { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
    main > .section:not([data-section="hero"]) {
      animation: reveal-in both var(--ease-out);
      animation-timeline: view();
      /* Completes within the entry phase, so a section already on-screen at load reads as
         fully revealed (only below-fold sections start hidden and rise in on scroll). */
      animation-range: entry 0% entry 55%;
    }
  }
}
@media (prefers-reduced-motion: reduce) {
  /* Zero the motion tokens (every transition routes through them) and hard-stop animations. */
  :root { --dur-fast: 0ms; --dur-base: 0ms; --dur-slow: 0ms; }
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}

/* ---- responsive --------------------------------------------------------- */
@media (min-width: 861px) {
  /* Desktop: no hamburger, no toggle; the nav always shows inline on a single row. */
  .nav-toggle, .nav-burger { display: none; }
  .site-nav { display: block; }
  .site-nav ul { flex-wrap: nowrap; }
}
@media (max-width: 860px) {
  /* Mobile: the burger is visible; the checkbox reveals the nav as a FULL-WIDTH sheet (edge to
     edge — no side gaps for the page to bleed through) behind a dimming, tap-to-close scrim. */
  /* CONTAINING-BLOCK FIX: ANY of container-type / backdrop-filter / filter / transform / perspective
     makes an element the containing block for its fixed descendants, which collapsed the scrim's
     bottom:0 onto the header box (0px tall). Under the mobile breakpoint — the only place the fixed
     menu + scrim exist — the header drops BOTH its scroll-state container role AND its frosted
     backdrop-filter, so the overlay resolves against the VIEWPORT. Removing the frost would let
     content show through the translucent bar as the page scrolls under it, so the mobile header gets
     a SOLID opaque surface bg. Desktop (861px+) keeps the frost + the scroll-morph, untouched. */
  .site-header { container-type: normal; backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--color-surface); }
  .nav-burger { display: inline-flex; }
  .site-nav { display: none; position: fixed; left: 0; right: 0; top: calc(var(--utility-h) + var(--header-h)); z-index: 130; max-height: calc(100dvh - var(--utility-h) - var(--header-h)); overflow-y: auto; }
  .nav-toggle:checked ~ .site-nav { display: block; }
  /* The scrim: a full-viewport veil BELOW the panel (z 125) but ABOVE the z-120 callbar; it's a
     <label for="nav-toggle"> so a tap closes the menu. Sits below the sticky header so the burger
     stays usable. */
  .nav-toggle:checked ~ .nav-scrim { display: block; position: fixed; left: 0; right: 0; top: calc(var(--utility-h) + var(--header-h)); bottom: 0; z-index: 125; background: var(--scrim); border: 0; cursor: pointer; }
  .site-nav ul { flex-direction: column; align-items: stretch; gap: 0; padding: var(--space-3) var(--space-4); margin: 0; background: var(--color-surface); border-bottom: 1px solid var(--color-border); box-shadow: var(--shadow-3); }
  .nav-group > ul { position: static; display: flex; visibility: visible; opacity: 1; pointer-events: auto; box-shadow: none; border: 0; margin: 0 0 0 var(--space-3); padding: 0; background: transparent; }
  .nav-group > ul::before { content: none; }
  .site-nav a, .nav-group-label { padding: var(--space-2); }
}
@media (max-width: 600px) {
  .header-cta { display: none; }
  .callbar { display: flex; }
  /* Footer legal bottom-bar collapses to a single column on phones (so the 3-col baseline row can't crush). */
  .footer-legal { grid-template-columns: 1fr; grid-template-areas: "notes" "copy" "access" "made"; gap: var(--space-2) var(--space-4); }
  /* Review masthead: the FEATURED pull-quote (--fs-3) is oversized on phones — its fluid floor sits at
     ~fs-2 already, so step it to --fs-1 (still clearly the featured quote, just not huge) and trim the
     decorative open-quote glyph to match. Desktop (>600px) keeps --fs-3 / --fs-6. Supporting review
     cards (.review-support) are a separate selector and are untouched.
     SPECIFICITY: scope to ".review-feature .review-quote" (0,2,0) so it beats the base ".review-quote"
     (0,1,0) regardless of source order — responsiveCss is emitted BEFORE the masthead component CSS, so
     an equal-specificity rule here would lose the cascade to the later base rule (the prior bug). */
  .review-feature .review-quote { font-size: var(--fs-1); }
  .review-feature .review-quote::before { font-size: var(--fs-4); }
  /* DROP THE TOP UTILITY BAR ON MOBILE — its click-to-call is the callbar's job and its hours live
     in the footer, so it only double-stacks at narrow widths. Collapsing --utility-h to 0 makes
     every derived offset (sticky header top, menu/scrim top, scroll-padding) reduce cleanly with
     NO leftover gap: the header tops out at 0 and the overlay anchors at the header's bottom. */
  .utility-bar { display: none; }
  :root { --utility-h: 0px; }
  /* HIDE THE CALLBAR WHILE THE MENU IS OPEN — the full-screen menu owns the screen during nav, and
     this removes the element occluding its last item without fighting cross-stacking-context z-index
     (the callbar is at body level, not a sibling of #nav-toggle, so target it via :has()). It
     reappears on close. */
  body:has(.nav-toggle:checked) .callbar { display: none; }
  /* Reserve the callbar's full height (+ safe area) so the last footer row clears it — was 64px,
     which is under the callbar's actual ~68px height and tucked the footer underneath. */
  body { padding-bottom: calc(var(--callbar-h) + env(safe-area-inset-bottom)); }
  html { scroll-padding-bottom: calc(var(--callbar-h) + env(safe-area-inset-bottom)); }
  .section[data-section="hero"]:not(.section--photo):not(.section--photo-split):has(.hero-img)::after { opacity: .3; }
  /* PROOF-FORWARD FIRST FOLD: tighten the hero's vertical rhythm and let the (now ~34px) H1 wrap to
     the full width (~3 lines) so the stat row / next section rises to ≈ the fold instead of sitting
     900px+ down behind an oversized headline. Content-sized — the hero is never pinned to the vh. */
  .section[data-section="hero"] { padding-top: var(--space-4); padding-bottom: var(--space-4); }
  .section[data-section="hero"] h1 { max-width: none; margin-bottom: var(--space-3); }
  .section[data-section="hero"] .lede { margin-top: var(--space-2); }
  .hero-stats { margin-top: var(--space-4); }
  /* Shrink the wordmark a step so it fits beside the Menu button without clipping. */
  .brand-name { font-size: 0.88rem; }
  /* The logo shrinks a touch so it sits comfortably beside the Menu button at phone widths
     (it carries the name, so it stays visible — never collapsed to a mark like the wordmark). */
  .site-brand-logo { height: 54px; max-width: 50vw; }
  html:has(.site-brand-logo) { --header-h: 76px; }
  /* ---- R-5 mobile composition pass (doc §2.2) — all rule-additions, no markup ---------------- */
  /* Photo heroes (home / service hub / city hub): a tighter height clamp so the hero + callbar +
     the first proof beat share the first scroll. The scrim floor is alpha math (width-independent),
     so it needs no mobile delta. */
  .section--photo[data-section="hero"] { min-height: clamp(360px, 64svh, 560px); }
  /* Glass stat/aside cards: one column, capped to ~2 visible rows so a phone hero never becomes a
     card wall. The trust chip is not a hero-aside-row, so capping rows never hides it. */
  .section--photo .hero-stats { grid-template-columns: 1fr; }
  .section--photo .hero-stat:nth-child(n+3) { display: none; }
  .section--photo .hero-aside-row:nth-child(n+3) { display: none; }
  /* Areas grid → a 2-col compact chip grid (scan, not read) so a long city list isn't N viewports
     of scroll. (County-hidden would need separate fact spans in the R-3 card markup — out of this
     CSS-only pass; here all facts stay, just compact.) */
  .area-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .area-card { padding: var(--space-3); }
  .area-card-facts { font-size: 0.72rem; }
  /* Image-led service cards: a shorter 4/3 crop keeps the cards scannable on a phone. */
  .service-card--image .service-card-img { aspect-ratio: 4 / 3; }
  /* Person hero photo medallion caps so it never dominates the stacked mobile hero. */
  .person-hero-photo { max-width: 200px; }
  /* Pad tiers compress (~0.6x) so major sections don't cost two swipes of whitespace on a phone.
     Mobile-scoped override of the tier tokens only — the base (desktop) token values are unchanged. */
  :root { --pad-major: clamp(var(--space-4), 7vw, var(--space-6)); --pad-default: clamp(var(--space-3), 3.5vw, var(--space-5)); --pad-compact: clamp(var(--space-2), 2vw, var(--space-3)); }
}
/* R-5: at the tightest widths the price/hours tables scroll horizontally instead of clipping a long
   tier label at 360px. display:block makes the table its own scroll container (no wrapper markup);
   the caption stays visible above. AT note: a display:block table can read slightly differently in a
   few screen readers — flag for the on-device a11y check (markup/semantics are otherwise intact). */
@media (max-width: 480px) {
  .price-table, .hours { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
  .price-table > caption, .hours > caption { display: block; }
}
/* Very narrow: drop to the logo MARK only (the wordmark would clip beside Menu). The name stays
   in the accessibility tree (visually-hidden, not display:none), so the home link keeps its name. */
@media (max-width: 380px) {
  .brand-name { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  /* The logo carries the name, so it stays visible — just capped so it can never overlap Menu. */
  .site-brand-logo { height: 44px; max-width: 52vw; }
  html:has(.site-brand-logo) { --header-h: 64px; }
}
/* ---- A-6: print — black-on-white content, no ink-flooded photo heroes or sticky chrome --------
   Full-bleed photo heroes + scrims print as gray ink floods, and the sticky utility bar / nav have
   no print value. Drop them; keep the article content and the footer NAP (phone + address) as plain
   visible text. CSS-only, zero-JS, CSP-neutral. */
@media print {
  *, *::before, *::after { background: transparent !important; color: #000 !important; box-shadow: none !important; text-shadow: none !important; }
  body { background: #fff !important; }
  /* Sticky/interactive chrome + decoration with no print value */
  .skip-link, .utility-bar, .nav-toggle, .site-nav, .hero-aside, .coverage-map, .section-scrim { display: none !important; }
  /* Photo heroes: remove the decorative cover image + reset the band so the copy prints on white */
  .section-media { display: none !important; }
  .section--photo, .section--photo-split { min-height: 0 !important; }
  /* Links readable in print; CTA buttons become plain outlined text, not filled ink blocks */
  a { text-decoration: underline; }
  .btn, .cta-button, .ctas a { border: 1px solid #000 !important; background: transparent !important; }
  /* The footer NAP (address + click-to-call) is the canonical contact block — keep it as plain text */
  .site-footer, .site-footer a, .footer-h { color: #000 !important; }
}
/* ---- STAT-BAND skin (§5.2) — tree-shaken: present only when a recipe assigns it -------------- */
.section--stat-band { background: color-mix(in srgb, var(--color-brand) 6%, var(--color-surface)); border-block: 1px solid var(--color-border); }
.stat-band { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-4) var(--space-5); align-items: end; }
.stat-band-item { display: flex; flex-direction: column; gap: var(--space-1); }
.stat-band-num { font-family: var(--font-heading); font-weight: 800; font-size: clamp(2.4rem, 1.4rem + 4.4vw, 4.4rem); line-height: 0.95; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; color: var(--color-ink); }
.stat-band-label { font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.1em; line-height: 1.3; color: var(--color-muted); }
/* ---- PROCESS-RAIL skin (§5.2) — tree-shaken: present only when a recipe assigns it ----------- */
.section[data-section="process"] .steps { position: relative; gap: var(--space-4); }
.section[data-section="process"] .steps::before { content: ""; position: absolute; left: 0.9em; top: 0.9em; bottom: 0.9em; width: 2px; background: color-mix(in srgb, var(--color-brand) 24%, var(--color-border)); }
/* ---- REVIEW-MASTHEAD skin (§5.2/§5.3) — tree-shaken: present only when a recipe assigns it ---- */
.review-masthead { display: grid; gap: var(--space-4); }
.review-feature { display: grid; gap: var(--space-3); padding: var(--space-5); border: 1px solid var(--card-border); border-left: 3px solid var(--color-brand); border-radius: var(--radius-lg); background: var(--color-card); box-shadow: var(--elevation-card); }
.review-rating { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); }
.review-rating-score { font-family: var(--font-heading); font-weight: 800; font-size: var(--fs-4); line-height: 1; color: var(--color-ink); font-variant-numeric: tabular-nums; }
.review-rating-stars { color: var(--color-accent); font-size: var(--fs-2); letter-spacing: 0.04em; }
.review-rating-count { font-size: var(--fs-caption); text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-muted); }
.review-quote { position: relative; margin: 0; font-size: var(--fs-3); line-height: 1.4; color: var(--color-ink); max-width: 46ch; }
.review-quote::before { content: "\201C"; font-family: var(--font-heading); font-weight: 800; font-size: var(--fs-6); line-height: 0.8; color: color-mix(in srgb, var(--color-brand) 45%, transparent); display: block; margin-bottom: -0.18em; }
.review-quote p { margin: 0; }
.review-feature figcaption { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; }
.review-feature .review-author { font-family: var(--font-heading); font-weight: 600; color: var(--color-ink); }
.review-feature .review-meta { font-size: var(--fs-caption); color: var(--color-muted); }
.review-support .review { font-size: var(--fs-caption); }
.review-support .review blockquote p { font-size: var(--fs-body); }
/* ---- POSTER HERO (§5.1) — typographic no-photo hero; tree-shaken (present only for poster clients) */
.section[data-section="hero"]:not(:has(.hero-img)) { background: var(--color-surface); border-top: 4px solid var(--color-brand); border-bottom: 1px solid var(--color-border); }
