/* ═══════════════════════════════════════════════════════════════════════════
   COASTLINE MOLD & AIR — stylesheet

   Design direction: HVAC service documentation. The visual language is the
   equipment manual and the psychrometric chart — hairline rules, mono labels,
   measured numbers, schematic linework. Warm rather than clinical, because the
   audience is an anxious homeowner, not a technician.

   Palette is drawn from the subject: coil patina teal, condensate haze, and
   the burnt amber of equipment warning tags (used only where risk is marked).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Fonts ─────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Archivo';
  src: url('/fonts/archivo-latin-var.woff2') format('woff2');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Two static instances rather than the variable font. Google serves Source
   Serif 4 as a full variable face with its optical-size axis attached, which is
   122KB — and this site only ever renders the serif at one weight, so all of
   that was being paid for and none of it used. A pinned instance is 20KB.
   opsz is fixed at 16: body runs 17-19px and the lede 18-22px, so a single
   optical size covers the whole serif range here.

   The 600 is declared but never currently referenced, so the browser will not
   download it. It exists so that adding bold body copy later degrades visibly
   instead of silently — `font-synthesis-weight: none` on body means a missing
   weight would otherwise render identically to regular. */
@font-face {
  font-family: 'Source Serif 4';
  src: url('/fonts/sourceserif4-latin-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* There is deliberately no bold Source Serif. Nothing on the site renders body
   serif above 400: emphasis in prose is carried by the mono-uppercase labels,
   and every <b> on the site resolves to IBM Plex Mono 500, which is a real face
   rather than a synthesised one. A 600 weight was declared and shipped here for
   a long time and never once matched — 21,792 bytes in every deploy.

   If you add <strong> to body copy, the browser will synthesise bold from the
   400 face and it will look it. Put this face back at that point rather than
   living with the fake one. */
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/fonts/plexmono-latin-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/fonts/plexmono-latin-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ── Tokens ────────────────────────────────────────────────────────────────── */
:root {
  /* Colour — sampled from the Palm Breeze Mold logo.
     Navy #051F46 / #000F32 (frond + "Palm"/"Mold"), sky #7BB8C4 ("Breeze" +
     the wave), on the logo's cool near-white ground.

     The one colour NOT in the logo is --flag. It is kept deliberately and used
     nowhere except where something is being marked as a risk or an action:
     the five hotspots on the air-path diagram, the specialty card, and the call
     button. Blue-on-blue destroys the diagram's whole semantic, and a single
     warm accent against navy is what makes the call button the loudest thing on
     the page. Say the word and it goes. */
  --haze: #edf0f4;
  --haze-deep: #e1e7ee;
  --paper: #f8fafc;
  --ink: #0a2247;
  --ink-soft: #20395c;
  --slate: #4c6076;
  --mist: #56687c;   /* 4.9:1 on --haze. Was #8a9bad at 2.49:1 — under AA. */
  --brand: #155e7c;
  --brand-deep: #0d3f5a;
  --brand-wash: #dceaef;
  --sky: #7bb8c4;
  --flag: #c0651a;
  --flag-deep: #9e4e0d;  /* 4.8:1 on --flag-wash, 5.1:1 on --haze, 5.9:1 under white */
  --flag-wash: #f6e7d8;
  --rule: #cbd5de;
  --rule-soft: #dde4ea;

  /* Type */
  --font-display: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-body: 'Source Serif 4', Charter, Georgia, 'Times New Roman', serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Rhythm */
  --gut: clamp(1.25rem, 4vw, 2.5rem);
  --sec-pad: clamp(3.25rem, 5.5vw, 5.25rem);
  --wrap: 1180px;
  --radius: 3px;

  /* Layered, not floaty — this is paper on a desk, not a card in space */
  --lift: 0 1px 2px rgba(10, 34, 71, 0.05);
  --lift-2: 0 2px 12px -4px rgba(10, 34, 71, 0.14);
}

/* ── Reset & base ──────────────────────────────────────────────────────────── */
*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header */
  scroll-padding-top: 5.5rem;
}

body {
  background: var(--haze);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: clamp(1.0625rem, 1rem + 0.22vw, 1.1875rem);
  line-height: 1.62;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  min-height: 100svh;
  /* Inherited by every text element. The same 200%-text overflow the headings
     had turns up in body copy wherever a token has no break opportunity —
     "Aspergillus/Penicillium" in an FAQ summary, a step in ol.steps, a value in
     ul.signals. break-word only acts when a word cannot fit a line by itself,
     so ordinary text sizes are untouched; the two places that need to break
     mid-token regardless (config identifiers, definition terms) set the more
     aggressive `anywhere` themselves and still win on specificity. */
  overflow-wrap: break-word;
}

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

/* A flex or grid item's automatic minimum size is its min-content width, so an
   item will refuse to shrink below its longest word and push its container past
   the viewport. It is invisible at ordinary text sizes because the words fit;
   at 200% text it put five separate layouts into horizontal overflow and failed
   WCAG 1.4.4 on 25 of 36 pages. The wide-viewport rules for .steps and
   .cta-band__grid already say minmax(0, 1fr), which is the same release applied
   to the track instead of the item — these are the single-column cases below
   those breakpoints, plus the two flex rows. Listed explicitly rather than
   applied globally: a blanket min-width: 0 would also silently disable the
   automatic minimum in layouts that are relying on it. */
.signals > li,
.steps > li > *,
.btn-row > *,
.related > a,
.cta-band__grid > * { min-width: 0; }

img { height: auto; }

a { color: var(--brand); text-underline-offset: 0.18em; text-decoration-thickness: from-font; }
a:hover { color: var(--brand-deep); }

:where(a, button, summary, input, textarea, select):focus-visible {
  outline: 2px solid var(--flag-deep);
  outline-offset: 3px;
  border-radius: 2px;
}
/* The same ring on the dark grounds measures 2.66:1 against --ink and 1.21:1
   against --brand — both under the 3:1 that WCAG 2.4.11 asks of a focus
   indicator, and the footer alone puts about thirty links on that ground on
   every page. --brand-wash gives 12.8:1 and 5.8:1 respectively. The filled
   call button is listed separately because it is orange on light sections too,
   where a --flag-deep ring on --flag is nearly invisible. */
.mhero :focus-visible,
.cta-band :focus-visible,
.ftr :focus-visible,
.btn--call:focus-visible,
.card:focus-visible,
.skip:focus-visible,
.callbar :focus-visible {
  outline-color: var(--brand-wash);
}

::selection { background: var(--flag-wash); color: var(--ink); }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--haze);
  padding: 0.75rem 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  z-index: 200;
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

/* ── Layout primitives ─────────────────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
}
.wrap--mid { max-width: 62rem; }

.section { padding-block: var(--sec-pad); }
.section--ruled { border-top: 1px solid var(--rule); }
.section--wash { background: var(--haze-deep); }

/* ── Type scale ────────────────────────────────────────────────────────────── */
.eyebrow {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--flag-deep);
}
.eyebrow--mist { color: var(--mist); }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.028em;
  line-height: 1.08;
  text-wrap: balance;
  /* Headings are set in rem, so at 200% text .h1 resolves to ~69px and a single
     long word ("homeowners", "remediation") is wider than the column it sits
     in. With nothing permitting a break it painted past the viewport and put
     the whole document into horizontal overflow — the cause on most of the 25
     pages that failed WCAG 1.4.4. break-word rather than anywhere: it breaks a
     word only when the word cannot fit on a line of its own, and leaves
     min-content sizing alone, so nothing shifts at ordinary text sizes. */
  overflow-wrap: break-word;
}

.h1 {
  font-size: clamp(2.15rem, 1.5rem + 2.5vw, 3.45rem);
  line-height: 1.01;
  letter-spacing: -0.036em;
}
.h2 { font-size: clamp(1.85rem, 1.35rem + 2vw, 2.85rem); letter-spacing: -0.032em; }
.h3 { font-size: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem); font-weight: 650; letter-spacing: -0.022em; }

.lede {
  font-size: clamp(1.15rem, 1.05rem + 0.55vw, 1.4rem);
  line-height: 1.48;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.muted { color: var(--slate); }

/* ── Section headers — the field-document device ───────────────────────────── */
.sec-head { margin-bottom: clamp(2rem, 4vw, 3rem); }
.sec-head .eyebrow { display: block; margin-bottom: 0.9rem; }
.sec-head .h2 { max-width: 22ch; }
.sec-head__note {
  margin-top: 1rem;
  color: var(--slate);
  max-width: 58ch;
  font-size: 1.05rem;
}
.sec-head--wide .h2 { max-width: 30ch; }

/* ── Buttons ───────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-weight: 620;
  font-size: 1rem;
  letter-spacing: -0.01em;
  line-height: 1;
  padding: 0.95rem 1.4rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease, transform 0.16s ease;
}
.btn:active { transform: translateY(1px); }

.btn--call {
  background: var(--flag-deep);
  color: #fff;
  border-color: var(--flag-deep);
}
.btn--call:hover { background: #8f460b; border-color: #8f460b; color: #fff; }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule);
}
.btn--ghost:hover { border-color: var(--ink); color: var(--ink); background: var(--paper); }

.btn--onink {
  background: transparent;
  color: var(--haze);
  border-color: rgba(237, 240, 236, 0.35);
}
.btn--onink:hover { background: rgba(237, 240, 236, 0.1); color: #fff; border-color: rgba(237, 240, 236, 0.6); }

.btn__num { font-family: var(--font-mono); font-weight: 500; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}


/* ── Header ────────────────────────────────────────────────────────────────── */
.hdr {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--haze) 88%, transparent);
  backdrop-filter: blur(10px) saturate(1.3);
  border-bottom: 1px solid var(--rule);
}
@supports not (backdrop-filter: blur(1px)) {
  .hdr { background: var(--haze); }
}

.hdr__bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4.25rem;
}

.brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  text-decoration: none;
  color: var(--ink);
  margin-right: auto;
  min-width: 0;
}
.brand:hover { color: var(--ink); }
/* The lockup is a fixed-aspect raster, so it is sized by height and lets width
   follow. The explicit width/height on the <img> still reserves the right box
   before it loads, which is what keeps the header from shifting. */
.brand__logo {
  height: 2.5rem;
  width: auto;
  max-width: 100%;
}
.brand picture { display: block; flex-shrink: 0; min-width: 0; }
.brand__sub {
  display: block;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 0.63rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mist);
  margin-top: 0.3rem;
  white-space: nowrap;
}

.nav { display: flex; align-items: center; gap: 0.15rem; }
.nav a {
  font-family: var(--font-display);
  font-weight: 520;
  font-size: 0.925rem;
  letter-spacing: -0.008em;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius);
  white-space: nowrap;
}
.nav a:hover { background: var(--haze-deep); color: var(--ink); }
.nav a[aria-current] {
  color: var(--brand-deep);
  box-shadow: inset 0 -2px 0 var(--brand);
  border-radius: 0;
}

.hdr__call {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  text-decoration: none;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--flag);
  border-radius: var(--radius);
  color: var(--flag-deep);
  flex-shrink: 0;
}
.hdr__call:hover { background: var(--flag-wash); color: var(--flag-deep); }
.hdr__call span {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
  line-height: 1.3;
}

.hdr__toggle {
  display: none;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  /* 44px, not the 24px WCAG 2.5.8 requires. The small uppercase mono label sets
     its own height at about 32px, which passes AA and is still the wrong size
     for the one control every mobile reader has to hit before they can go
     anywhere — and this is a phone-first audience looking for a contractor.
     min-height with centring rather than more padding, so the label keeps its
     proportions and only the hit area grows. */
  min-height: 44px;
  align-items: center;
  padding: 0.55rem 0.7rem;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}

/* 1140, not 1080: between 1081 and ~1130 the desktop nav had appeared while
   the brand block was still being squeezed, so the tagline ran under the nav
   and the active item's underline struck through it. */
@media (max-width: 1140px) {
  .js .hdr__toggle { display: inline-flex; justify-content: center; }
  .js .nav {
    display: none;
    position: absolute;
    inset-inline: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
    padding: 0.5rem var(--gut) 1rem;
    box-shadow: var(--lift-2);
    max-height: calc(100svh - 4.25rem);
    overflow-y: auto;
  }
  .js .nav[data-open] { display: flex; }
  /* No JavaScript: the toggle cannot open anything, so drop it and let the nav
     sit in flow as a wrapping row. The links were reachable via the footer's 33
     links either way, but a Menu button that does nothing is a broken promise. */
  html:not(.js) .hdr__toggle { display: none; }
  html:not(.js) .hdr__bar { flex-wrap: wrap; }
  html:not(.js) .nav {
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: 0.1rem;
    border-top: 1px solid var(--rule-soft);
    padding: 0.35rem 0 0.5rem;
  }
  .nav a { padding: 0.85rem 0.25rem; border-bottom: 1px solid var(--rule-soft); border-radius: 0; }
  .nav a[aria-current] { box-shadow: inset 3px 0 0 var(--brand); padding-left: 0.75rem; }
}
@media (max-width: 700px) {
  .hdr__call { padding: 0.45rem 0.65rem; }
  .hdr__call span { font-size: 0.86rem; }
  .brand__sub { display: none; }
  .brand__logo { height: 2rem; }
  .hdr__bar { gap: 0.55rem; }
  .hdr__toggle { padding: 0.5rem 0.55rem; font-size: 0.66rem; }
}
@media (max-width: 420px) {
  /* Below this the phone number in the header competes with the sticky call
     bar for the same tap. Keep the bar, drop the duplicate. */
  .hdr__call { display: none; }
}

/* ── Media hero ────────────────────────────────────────────────────────────────
   Type over a still or a looping clip. The still always renders and always
   paints first; the video sits on top at opacity 0 and fades in only once it is
   genuinely playing, so the LCP element is a small responsive WebP rather than
   a video file — and the hero is complete with no JS, on a slow link, under
   data-saver, and under prefers-reduced-motion. */
.mhero {
  position: relative;
  isolation: isolate;
  background: var(--ink);
  color: var(--haze);
  display: grid;
  /* minmax(0, 1fr), not the implicit auto track. `.assure` inside carries
     minmax(7.5rem, …) tracks whose min-content contribution would otherwise
     push this column wider than the viewport — and because .mhero clips, that
     shows up as text cut off at the right edge rather than as page overflow. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  min-height: clamp(28rem, 60svh, 38rem);
  padding-block: clamp(3rem, 6vw, 4.5rem) 0;
  overflow: hidden;
}

.mhero__bg { position: absolute; inset: 0; z-index: 0; }
.mhero__bg picture { display: block; height: 100%; }
.mhero__still,
.mhero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
}
.mhero__video {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1s ease;
}
.mhero__video[data-playing] { opacity: 1; }

/* Scrim. Two gradients: one across, so the type side stays dark enough to read
   at any crop; one up from the base, to seat the caption row. */
.mhero__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg, rgba(10, 34, 71, 0.95) 0%, rgba(10, 34, 71, 0.88) 34%, rgba(10, 34, 71, 0.55) 68%, rgba(10, 34, 71, 0.42) 100%),
    linear-gradient(to top, rgba(10, 34, 71, 0.85) 0%, rgba(10, 34, 71, 0) 45%);
}

.mhero__inner {
  position: relative;
  z-index: 1;
  align-self: center;
  padding-block: clamp(1.5rem, 3vw, 2.5rem);
}
.mhero__inner .eyebrow { display: block; margin-bottom: 1.1rem; }
.mhero__inner .h1 { color: #fff; max-width: 17ch; margin-bottom: 1.3rem; }
.mhero__inner .lede { color: #cfdce8; max-width: 46ch; }
.mhero__actions { margin-top: 2rem; }
/* Lightened from #e8a163. At 12px over a hero photograph this measured
   3.44–4.38:1 on 19 pages — a real 1.4.3 failure the audit could not see
   until it started measuring painted pixels, because the photo is an <img>
   in a sibling rather than an ancestor background. Kept amber: it is the
   brand's risk/action colour and the only warm note on the page. The
   desktop crop shows more of the photograph, so two pages still fell
   short at #f6be94 — this clears 4.5:1 at 390 and 1440 on all 37. */
.eyebrow--onink { color: #facaa5; }

.mhero .assure--onink {
  border-top-color: rgba(207, 220, 215, 0.25);
  margin-top: 2.25rem;
  max-width: 34rem;
}
.assure--onink .assure__label { color: #8ba0b5; }
.assure--onink .assure__val { color: #eef3f8; }

.mhero__foot {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: clamp(1.1rem, 2.5vw, 1.6rem);
}
/* Links inside the hero caption. They inherit --brand otherwise, which measures
   2.19:1 against the hero ground — the contrast check caught it the moment the
   captions started carrying links. --brand-wash gives 12.8:1, and the underline
   carries the affordance so colour is not doing that job alone. */
.mhero__cap a {
  color: var(--brand-wash);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.mhero__cap a:hover { color: #fff; }

.mhero__cap {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  line-height: 1.6;
  letter-spacing: 0.02em;
  color: #8fa4b8;
  max-width: 52ch;
  border-left: 2px solid var(--flag);
  padding-left: 0.75rem;
}
.mhero__toggle {
  flex-shrink: 0;
  background: rgba(10, 34, 71, 0.6);
  border: 1px solid rgba(207, 220, 215, 0.35);
  border-radius: var(--radius);
  color: #cfdce8;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  backdrop-filter: blur(4px);
}
.mhero__toggle:hover { color: #fff; border-color: rgba(207, 220, 215, 0.7); }

@media (max-width: 700px) {
  .mhero { min-height: auto; padding-block: clamp(2.25rem, 6vw, 3rem) 0; }
  /* Vertical rather than diagonal on a narrow screen, since the type runs the
     full width. Held at 0.74 at the lightest point: enough for white body copy
     to clear 4.5:1 even over the brightest image in the set (the ochre stucco
     bungalow), without flattening the photograph into a dark rectangle. */
  .mhero__bg::after {
    background:
      linear-gradient(180deg, rgba(10, 34, 71, 0.74) 0%, rgba(10, 34, 71, 0.82) 55%, rgba(10, 34, 71, 0.9) 100%);
  }
  .mhero__inner .h1 { max-width: none; }
  .mhero__cap { font-size: 0.64rem; }
}

/* Mechanism section: copy beside the schematic */
.mech { display: grid; gap: clamp(1.75rem, 3.5vw, 3rem); align-items: start; }
@media (min-width: 1000px) {
  .mech { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}

.assure {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: 1.1rem 1.5rem;
  border-top: 1px solid var(--rule);
  padding-top: 1.1rem;
}
/* Three columns need 3 × 7.5rem + 2 × 1.5rem of grid before auto-fit will take
   them, so on a phone it settles on two and drops the third onto a row of its
   own. A two-up strip with a single orphan underneath reads as a layout bug
   rather than a decision, and it squeezed "Independent clearance testing" onto
   two ragged lines. Below that threshold, stack: each line gets the full width
   and the strip is deliberate at any size. */
@media (max-width: 30rem) {
  .assure { grid-template-columns: 1fr; }
}
.assure__label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
  display: block;
  margin-bottom: 0.2rem;
}
.assure__val {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.015em;
  color: var(--ink-soft);
  margin: 0;
  text-wrap: balance;
}

/* ── The signature: air path diagram ───────────────────────────────────────── */
.diagram {
  margin: 0;
  min-width: 0;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(0.85rem, 2vw, 1.35rem);
  box-shadow: var(--lift);
}
.diagram__scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.diagram__svg { width: 100%; height: auto; }

/* Below ~700px the diagram would scale to about 0.42x, which puts the marker
   numbers and component labels under 6px — unreadable on any device. Hold a
   floor width and let the figure scroll sideways instead of shrinking past
   legibility. No tabindex: current browsers make an overflow container
   keyboard-focusable on its own, and only when it actually scrolls. */
@media (max-width: 700px) {
  .diagram { padding: 0.75rem; }
  .diagram__svg { min-width: 34rem; }
  .diagram__scroll { margin-inline: -0.15rem; }
  /* A visual affordance only — the SVG's own <desc> already describes the whole
     air path for anyone not looking at it. */
  .diagram figcaption::before {
    content: 'Scroll the diagram sideways to see the full air path. ';
    color: var(--flag-deep);
  }
}
.diagram figcaption {
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule-soft);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  line-height: 1.55;
  letter-spacing: 0.02em;
  color: var(--slate);
  text-transform: none;
}

/* Schematic linework */
.dg-shell { fill: none; stroke: var(--rule); stroke-width: 1; }
.dg-room { fill: none; stroke: var(--rule); stroke-width: 1; stroke-dasharray: 3 4; }
.dg-body { fill: var(--brand-wash); stroke: var(--brand-deep); stroke-width: 1.25; }
.dg-duct { fill: #eaf1ef; stroke: var(--brand-deep); stroke-width: 1.25; }
.dg-part { fill: none; stroke: var(--brand-deep); stroke-width: 1.25; }
.dg-hatch { fill: none; stroke: var(--brand); stroke-width: 1; opacity: 0.55; }
.dg-zone { fill: var(--haze-deep); opacity: 0.5; }
.dg-label {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.09em;
  fill: var(--slate);
  text-transform: uppercase;
}
.dg-label--faint { fill: var(--mist); }
.dg-label--room { fill: var(--mist); text-anchor: middle; }
.dg-air { fill: none; stroke: var(--flag); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; opacity: 0.32; }

/* Air path */
.dg-flow {
  fill: none;
  stroke: var(--flag);
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Resting state is fully drawn, so the diagram is correct even if the
     animation never runs (background tabs, reduced motion, no JS). */
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
  animation: dg-draw 2.4s cubic-bezier(0.36, 0.02, 0.2, 1) 0.25s backwards;
}
.dg-flow--b { animation-delay: 1.5s; animation-duration: 0.7s; }
.dg-flow--c { animation-delay: 1.75s; animation-duration: 0.7s; }
.dg-flow--d { animation-delay: 2s; animation-duration: 0.7s; }

@keyframes dg-draw {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

/* A slow travelling pulse that reads as air moving through the system */
.dg-pulse {
  fill: none;
  stroke: #f0b072;
  stroke-width: 4.5;
  stroke-linecap: round;
  stroke-dasharray: 0.045 0.955;
  /* One traverse, not infinite: WCAG 2.2.2 (A) allows motion that stops by
     itself inside five seconds, and 2.6s + 4.5s of looping animation beside
     body copy otherwise needs a pause control the diagram does not have. */
  animation: dg-travel 4.5s linear 2.6s 1;
  opacity: 0;
}
@keyframes dg-travel {
  0% { stroke-dashoffset: 1; opacity: 0; }
  8% { opacity: 0.85; }
  92% { opacity: 0.85; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

.dg-node { fill: var(--paper); stroke: var(--flag); stroke-width: 1.6; }
.dg-node-n {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 500;
  fill: var(--flag-deep);
  text-anchor: middle;
}
.dg-ring {
  fill: none;
  stroke: var(--flag);
  stroke-width: 1.2;
  transform-box: fill-box;
  transform-origin: center;
  /* Single ping, same 2.2.2 reasoning. The 100% keyframe lands on opacity 0,
     so the marker needs a resting opacity below or it vanishes when the one
     run finishes. */
  animation: dg-ping 3.4s ease-out 1;
  opacity: 0.35;
}
@keyframes dg-ping {
  0%   { transform: scale(0.75); opacity: 0.55; }
  70%  { transform: scale(1.75); opacity: 0; }
  100% { transform: scale(1.75); opacity: 0; }
}


/* ── Motion ────────────────────────────────────────────────────────────────
   Scroll-driven, in CSS, and only where the browser can drive it from the
   compositor: opacity and transform, nothing that lays out. Everything in this
   block is a progressive enhancement gated twice — on the user not asking for
   reduced motion, and on real support for view timelines including
   animation-range (the range check filters out partial implementations). Where
   either gate fails, the page is exactly what it was before this block existed:
   the diagram plays its single bounded pass, sections simply appear, and no
   IntersectionObserver polyfill is shipped because none of this is content.

   The one deliberate risk is the diagram. Its thesis is that mold travels
   through the ductwork, and the reader now drives that: as the mechanism
   section scrolls into view, the flow line draws itself, the air pulse
   travels return → coil → pan → plenum → registers, and each numbered marker
   lights up as the air reaches it. The reader's thumb is the timeline. */
@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {

    /* 1. Section reveals — a short rise and fade as a block enters. The range
          ends at 35% of entry so a section is fully present long before it is
          in the reader's eye line; nothing "pops" mid-read. Cards stagger by
          their position via --i where the template supplies it. */
    /* `translate`, the individual property, not `transform`. With
       animation-fill-mode: both the finished reveal keeps applying its end
       value forever, and when that value was `transform: none` it outranked
       a.card:hover { transform: translateY(-2px) } — the hover lift vanished
       for motion users while reduced-motion users still had it. Individual
       transform properties compose with `transform` instead of replacing it,
       so the reveal owns `translate` and the hover keeps `transform`. */
    @keyframes rise-in {
      from { opacity: 0; translate: 0 14px; }
      to   { opacity: 1; translate: 0 0; }
    }
    .sec-head,
    .card,
    .signals > li,
    .steps > li,
    .deflist > li,
    .related > a,
    .faq details {
      animation: rise-in auto cubic-bezier(0.2, 0.6, 0.2, 1) both;
      animation-timeline: view();
      animation-range: entry 0% entry 35%;
    }
    /* Never on anything above the fold or fixed: the hero, header, call bar
       and the CTA band are always fully visible on arrival. */
    .mhero *, .hdr *, .callbar *, .cta-band * { animation-timeline: auto; }

    /* 2. Hero still — a restrained parallax on the photograph only. The scrim
          and copy do not move, so text contrast is untouched. Scale is applied
          up front so the image never reveals an edge while it translates. */
    .mhero__bg > picture,
    .mhero__bg > .mhero__video {
      animation: hero-drift auto linear both;
      animation-timeline: scroll(root);
      animation-range: 0 100vh;
    }
    @keyframes hero-drift {
      from { transform: scale(1.06) translateY(0); }
      to   { transform: scale(1.06) translateY(6%); }
    }

    /* 3. The air-path diagram, driven by its own visibility.
          The named timeline lives on the .mech grid so the SVG's descendants
          and the legend beside it can share one clock. */
    /* The diagram <figure> is the timeline subject on both pages that carry
       it — the home page wraps it in .mech beside the thesis copy, the HVAC
       page places it alone after the intro. */
    figure.diagram { view-timeline: --airpath block; }
    /* The legend is a sibling of .mech, not a descendant, so the named
       timeline is out of its scope. timeline-scope on the shared ancestor
       (the section's .wrap) hoists the name so both can read one clock. */
    #mechanism > .wrap, .section > .wrap:has(> figure.diagram) { timeline-scope: --airpath; }
    figure.diagram .dg-flow {
      animation: dg-draw auto cubic-bezier(0.36, 0.02, 0.2, 1) both;
      animation-timeline: --airpath;
      animation-range: entry 10% cover 22%;
    }
    figure.diagram .dg-flow--b, figure.diagram .dg-flow--c, figure.diagram .dg-flow--d {
      animation-range: cover 18% cover 30%;
    }
    figure.diagram .dg-pulse {
      animation: dg-travel-scroll auto linear both;
      animation-timeline: --airpath;
      animation-range: cover 18% cover 48%;
    }
    @keyframes dg-travel-scroll {
      0%   { stroke-dashoffset: 1; opacity: 0; }
      6%   { opacity: 0.9; }
      94%  { opacity: 0.9; }
      100% { stroke-dashoffset: 0; opacity: 0; }
    }
    /* Markers light in airflow order. Each one's window is offset by its index
       so 1 lights first and 5 last, tracking the pulse's arrival. */
    /* Windows sit where each marker is actually on screen. The first schedule
       (20% + i×13%, ten-point windows, marker 5 at 72–82%) lit 4 and 5 after
       they had scrolled under the sticky header on every viewport measured —
       marker 5's centre was at viewport y −79 at 1440×900 when it hit full
       opacity, and y −41 on a 390×844 phone. The climax played off-screen.
       The anchor is where a reader actually stops: measured with
       scrollIntoView({block:'center'}) the figure's top settles at cover
       31–43% (phone 31%, home desktop 43%, the tall HVAC figure 32%) — not
       50%, because centring puts the top edge mid-viewport and a phone will
       not scroll the figure under its sticky header. So the sequence runs
       24–48%: 1 lights 24–29, 2 29–34, 3 34–39, 4 39–44, 5 44–49. Arriving,
       the reader sees 1–2 lit and the air moving; a small nudge down brings
       3, 4 and 5 in — all with the figure still on screen. */
    figure.diagram .dg-mark {
      --lead: calc(24% + var(--i) * 5%);
      animation: dg-arrive auto ease-out both;
      animation-timeline: --airpath;
      animation-range: cover var(--lead) cover calc(var(--lead) + 5%);
    }
    @keyframes dg-arrive {
      from { opacity: 0.35; }
      to   { opacity: 1; }
    }
    figure.diagram .dg-ring {
      animation: dg-ping-scroll auto ease-out both;
      animation-timeline: --airpath;
      animation-range: cover var(--lead) cover calc(var(--lead) + 8%);
    }
    @keyframes dg-ping-scroll {
      0%   { transform: scale(0.75); opacity: 0.55; }
      70%  { transform: scale(1.75); opacity: 0; }
      100% { transform: scale(1.75); opacity: 0; }
    }
    /* The legend entries beside the diagram arrive with their marker. */
    /* The legend sits ~740px below the diagram on the home page — it cannot
       light "with its marker" in the same viewport, and on a phone it was
       lighting entirely below the fold, which is an animation nobody sees.
       It reveals on its own entry like any other list, staggered by index. */
    #mechanism .legend > li {
      animation: rise-in auto cubic-bezier(0.2, 0.6, 0.2, 1) both;
      animation-timeline: view();
      animation-range: entry calc(var(--i, 0) * 6%) entry calc(30% + var(--i, 0) * 6%);
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .dg-flow, .dg-pulse, .dg-ring { animation: none; }
  .dg-pulse { display: none; }
  .dg-ring { opacity: 0.35; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Diagram legend — numbered because the air genuinely moves in this order */
.legend {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--rule-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 720px) { .legend { grid-template-columns: 1fr 1fr; } }
.legend li:last-child:nth-child(odd) { grid-column: 1 / -1; }
.legend li {
  background: var(--paper);
  padding: 1.15rem 1.25rem;
  display: grid;
  grid-template-columns: 1.9rem 1fr;
  gap: 0 0.85rem;
  align-items: start;
}
.legend__n {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8rem;
  color: var(--flag-deep);
  border: 1px solid var(--flag);
  border-radius: 50%;
  width: 1.65rem;
  height: 1.65rem;
  display: grid;
  place-items: center;
  margin-top: 0.1rem;
}
.legend__t {
  font-family: var(--font-display);
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -0.018em;
  margin-bottom: 0.3rem;
}
.legend__d { font-size: 0.94rem; line-height: 1.55; color: var(--slate); grid-column: 2; }

/* ── Signals — the measured numbers ────────────────────────────────────────── */
.signals {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border-block: 1px solid var(--rule);
  list-style: none;
  padding: 0;
}
@media (min-width: 780px) { .signals { grid-template-columns: repeat(3, 1fr); } }
.signals li { background: var(--haze); padding: 1.6rem clamp(1rem, 2.5vw, 1.75rem); }
.signals__v {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.85rem, 1.3rem + 1.7vw, 2.6rem);
  letter-spacing: -0.04em;
  color: var(--brand-deep);
  line-height: 1;
  margin-bottom: 0.7rem;
  display: block;
}
/* display: block so the declared line-height actually governs. As an inline
   span its line-height never sets the line box — the parent li's 1.6 did,
   so the label rendered at 31px leading against the 22.6px declared here,
   noticeably airier than .legend__d, .card__d and .deflist__d, which are
   all blockified and honour theirs. It is the only multi-line body text on
   the site that was not obeying its own leading. */
.signals__l { display: block; font-size: 0.94rem; line-height: 1.5; color: var(--slate); }

/* ── Cards ─────────────────────────────────────────────────────────────────── */
.grid { display: grid; gap: clamp(1rem, 2vw, 1.4rem); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }

.card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.35rem, 2.5vw, 1.85rem);
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
  position: relative;
}
a.card:hover {
  border-color: var(--brand);
  transform: translateY(-2px);
  box-shadow: var(--lift-2);
  color: inherit;
}
.card__eyebrow {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
}
.card__t {
  font-family: var(--font-display);
  font-weight: 680;
  font-size: 1.22rem;
  letter-spacing: -0.026em;
  line-height: 1.15;
}
.card__d { font-size: 0.98rem; line-height: 1.55; color: var(--slate); }
.card__more {
  margin-top: auto;
  padding-top: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
}
a.card:hover .card__more { color: var(--brand-deep); }

/* The specialty card carries the amber marker — it is the one thing we lead with */
.card--flag { border-color: var(--flag); background: linear-gradient(180deg, var(--flag-wash) 0%, var(--paper) 46%); }
.card--flag .card__eyebrow { color: var(--flag-deep); }
a.card--flag:hover { border-color: var(--flag-deep); }
.card--flag .card__more { color: var(--flag-deep); }

/* ── Photo cards ───────────────────────────────────────────────────────────────
   The card previews the page it links to: each background is the same
   photograph that page leads with, so the grid reads as a set of doors rather
   than as decoration. Text sits at the bottom over the heaviest part of the
   scrim, which is also where the gradient is doing the most work. */
.card--photo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  justify-content: flex-end;
  min-height: 17.5rem;
  padding: clamp(1.35rem, 2.5vw, 1.75rem);
  background: var(--ink);
  border-color: rgba(10, 34, 71, 0.5);
  color: var(--haze);
}
.card__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  overflow: hidden;
}
.card__bg picture { display: block; height: 100%; }
.card__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Two cities can legitimately share a photograph when their housing stock is
     genuinely the same. A different focal point keeps them from reading as one
     image used twice. */
  object-position: var(--card-focus, center);
  transition: transform 0.6s cubic-bezier(0.2, 0, 0.2, 1);
}
/* The gradient holds ~0.9 through the bottom 72% and then opens up fast, which
   is what lets the top of the photograph read instead of being flattened.
   72%, not the 45% this used to say: the comment claimed "the text block is
   clamped so it always lands inside that opaque band" and that was measured
   false. Cards sit at their 17.5rem min-height and the eyebrow's top lands
   68.5% up from the card bottom, where the scrim had already opened to ~0.55.
   The small eyebrow measured 3.6:1 mean and 2.0:1 over the brightest pixels of
   a light photograph — 11 of 18 city cards and 4 of 6 service cards failed
   1.4.3. audit.mjs cannot see it: .card--photo has an opaque background, so it
   scores the label against solid #0a2247 and passes at 8.9:1, while the actual
   photograph is a sibling .card__bg it never looks at. */
.card__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(10, 34, 71, 0.95) 0%,
    rgba(10, 34, 71, 0.9) 72%,
    rgba(10, 34, 71, 0.6) 84%,
    rgba(10, 34, 71, 0.28) 100%
  );
}

/* Lifted from #b3c5d6: at 9.9px this is the smallest type on the card and the
   one that sits highest in the scrim, so it needs the most headroom. */
.card--photo .card__eyebrow { color: #d6e2ec; }

/* Deep-linked FAQ answers only. site.js marks the <details> before focusing it,
   because :focus-visible never matches programmatic focus and the ring is the
   only signal that the link landed where it said it would. */
.faq details[data-deeplinked] > summary:focus {
  outline: 2px solid var(--flag-deep);
  outline-offset: 2px;
}
.card--photo .card__t { color: #fff; }
.card--photo .card__d { color: #c8d7e4; }

/* Three lines, so a long city lede cannot push the block up out of the scrim
   and so a grid of cards stays scannable. The full sentence is one click away. */
.card--photo .card__d {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card--photo .card__more { color: #e8a163; margin-top: 0; padding-top: 0.35rem; }

a.card--photo:hover {
  border-color: rgba(232, 161, 99, 0.6);
  box-shadow: var(--lift-2);
}
a.card--photo:hover .card__bg img { transform: scale(1.045); }
a.card--photo:hover .card__more { color: #f3b87c; }

/* The specialty card keeps its identity over a photograph through an amber rule
   rather than the amber wash, which a photo would swallow. */
.card--flag.card--photo {
  background: var(--ink);
  border-color: var(--flag);
  box-shadow: inset 0 3px 0 var(--flag);
}
.card--flag.card--photo .card__eyebrow { color: #e8a163; }
a.card--flag.card--photo:hover { border-color: #e8a163; box-shadow: inset 0 3px 0 var(--flag), var(--lift-2); }

@media (prefers-reduced-motion: reduce) {
  .card__bg img { transition: none; }
  a.card--photo:hover .card__bg img { transform: none; }
}

.grid--services { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
@media (min-width: 900px) {
  .grid--services { grid-template-columns: repeat(3, 1fr); }
}

/* ── Definition-style lists (findings, watch items) ────────────────────────── */
.deflist { display: grid; gap: 0; border-top: 1px solid var(--rule); list-style: none; padding: 0; margin: 0; }
.deflist > li {
  border-bottom: 1px solid var(--rule-soft);
  padding-block: 1.15rem;
  display: grid;
  gap: 0.3rem 1.75rem;
}
@media (min-width: 800px) {
  .deflist > li { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
}
/* At 200% text an unbreakable token — help@palmbreezemold.com is the one that
   does it — is wider than the column, and the whole wrap widens to fit it,
   taking the page into horizontal overflow. WCAG 1.4.4 asks for no loss of
   content at 200%; a page you have to scroll sideways to read is loss. */
.deflist__t, .deflist__d { overflow-wrap: anywhere; }
/* The notice quotes config identifiers — contact.form.action, hasBackend: true —
   which are single unbreakable tokens in a monospace face. At 200% they are
   wider than the column and take the page into horizontal scroll. */
.notice { overflow-wrap: anywhere; }

.deflist__t {
  font-family: var(--font-display);
  font-weight: 650;
  font-size: 1.02rem;
  letter-spacing: -0.02em;
  line-height: 1.3;
}
.deflist__d { color: var(--slate); font-size: 1rem; line-height: 1.58; max-width: 68ch; }

/* ── Steps — numbered because a remediation genuinely is a sequence ────────── */
.steps { list-style: none; padding: 0; margin: 0; counter-reset: step; }
.steps > li {
  counter-increment: step;
  display: grid;
  gap: 0.45rem 1.5rem;
  padding-block: 1.5rem;
  border-top: 1px solid var(--rule-soft);
  position: relative;
}
.steps > li:first-child { border-top: 1px solid var(--rule); }

@media (min-width: 820px) {
  .steps > li { grid-template-columns: 3.5rem minmax(0, 1fr); }
  .steps > li::before { grid-row: 1 / span 4; }
}
.steps > li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.82rem;
  color: var(--flag-deep);
  line-height: 1.7;
}
.steps__t {
  font-family: var(--font-display);
  font-weight: 660;
  font-size: 1.12rem;
  letter-spacing: -0.022em;
}
/* Measured at 103ch without this. Body prose elsewhere on the site holds
   66-68ch; these surfaces carry most of the page content and were the only
   ones running to the full column width. */
.steps__d { color: var(--slate); line-height: 1.6; max-width: 68ch; }
.steps__meta {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mist);
}
.steps__check {
  border-left: 2px solid var(--brand);
  padding: 0.15rem 0 0.15rem 0.9rem;
  font-size: 0.95rem;
  color: var(--ink-soft);
  background: var(--brand-wash);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding-block: 0.6rem;
  padding-right: 0.9rem;
  /* Was 129ch — the widest text on the site, and tinted, so the band ran the
     full column too. Constraining it fixes the measure and the heaviness. */
  max-width: 72ch;
}
.steps__check b {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.64rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--brand-deep);
  display: block;
  margin-bottom: 0.25rem;
}

/* ── Flagged callout — used where we are contradicting industry practice ───── */
.flagbox {
  border: 1px solid var(--flag);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--flag-wash);
  padding: clamp(1.25rem, 3vw, 1.9rem);
}
.flagbox .h3 { margin-bottom: 0.75rem; }
.flagbox__note { color: var(--ink-soft); opacity: 0.8; font-size: 0.98rem; max-width: 58ch; }
.flagbox p { color: var(--ink-soft); }
.flagbox > * + * { margin-top: 0.9em; }

/* ── FAQ accordion ─────────────────────────────────────────────────────────── */
.faq { border-top: 1px solid var(--rule); }
.faq details {
  border-bottom: 1px solid var(--rule-soft);
}
.faq summary {
  list-style: none;
  cursor: pointer;
  padding: 1.25rem 2.5rem 1.25rem 0;
  font-family: var(--font-display);
  font-weight: 640;
  font-size: 1.075rem;
  letter-spacing: -0.02em;
  line-height: 1.35;
  position: relative;
  text-wrap: pretty;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '';
  position: absolute;
  right: 0.35rem;
  top: 1.65rem;
  width: 0.7rem;
  height: 0.7rem;
  border-right: 1.5px solid var(--brand);
  border-bottom: 1.5px solid var(--brand);
  transform: rotate(45deg);
  transition: transform 0.2s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg); top: 1.85rem; }
.faq summary:hover { color: var(--brand-deep); }
/* No right padding: the 2.5rem on the summary exists to clear the chevron,
   which the answer sits below rather than beside. Inheriting it cost the
   answer 40px of width and made it the narrowest text on a phone (35ch vs
   40-44ch everywhere else). max-width still holds the desktop measure. */
.faq__a { padding: 0 0 1.5rem; color: var(--slate); max-width: 70ch; }
.faq__a > * + * { margin-top: 0.9em; }

/* In-page navigation, used by the FAQ and by any page with four or more
   sections. A 2,000-word page on a phone is a long scroll with no map; this is
   the map. Counts appear where they are meaningful (question totals on the
   FAQ) and are omitted where they are not. */
.jump-nav {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  padding: 1.15rem 1.35rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}
/* On the FAQ the nav opens the section, so the gap belongs below it. On a
   service or guide page it follows the intro prose, so the gap belongs above —
   otherwise it compounds with the next section's padding into dead space. */
.jump-nav:not(:first-child) {
  margin-top: clamp(2rem, 4vw, 3rem);
  margin-bottom: 0;
}
.jump-nav__label {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
  margin-bottom: 0.85rem;
}
.jump-nav ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 0.15rem;
}
@media (min-width: 720px) {
  .jump-nav ul { grid-template-columns: 1fr 1fr; gap: 0.15rem 1.5rem; }
}
.jump-nav a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  /* 0.42rem gave a 40px row. These are stacked full-width targets with a
     hairline between them, so a vertical mis-tap lands on the neighbouring
     section rather than missing — the padding buys the row up to 44px without
     disturbing the baseline alignment a min-height would have unbalanced.
     0.6rem rather than 0.55: the shorter labels landed a pixel under. */
  padding: 0.6rem 0;
  text-decoration: none;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--rule-soft);
  font-size: 0.98rem;
}
.jump-nav li:last-child a { border-bottom: 0; }
@media (min-width: 720px) {
  .jump-nav li:nth-last-child(2):nth-child(odd) a { border-bottom: 0; }
}
.jump-nav a:hover { color: var(--brand-deep); }
.jump-nav a span {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--mist);
  flex-shrink: 0;
}
/* The heading is a focus target for the jump link, so it must not draw a ring
   when reached by mouse — only when the link was followed by keyboard. */
.faq-group__t:focus, .sec-head .h2:focus, .flagbox .h3:focus { outline: none; }
.faq-group__t:focus-visible, .sec-head .h2:focus-visible, .flagbox .h3:focus-visible { outline: 2px solid var(--flag-deep); outline-offset: 4px; }

.faq-group + .faq-group { margin-top: clamp(2.5rem, 5vw, 3.75rem); }
.faq-group__t {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--flag-deep);
  margin-bottom: 1rem;
}

/* ── Breadcrumbs ───────────────────────────────────────────────────────────── */
.crumbs {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--mist);
  padding-top: 1.5rem;
}
.crumbs ol { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.crumbs a { color: var(--slate); text-decoration: none; }
.crumbs a:hover { color: var(--brand-deep); text-decoration: underline; }
.crumbs li[aria-current] { color: var(--ink-soft); }
.crumbs li + li::before { content: '/'; margin-right: 0.4rem; color: var(--rule); }

/* ── Page header (interior pages) ──────────────────────────────────────────── */
.phead { padding-block: clamp(2rem, 4vw, 3rem) clamp(2.25rem, 4vw, 3.25rem); }
.phead .eyebrow { display: block; margin-bottom: 0.9rem; }
.phead h1 {
  font-size: clamp(2rem, 1.35rem + 2.6vw, 3.3rem);
  letter-spacing: -0.035em;
  line-height: 1.03;
  max-width: 20ch;
  margin-bottom: 1.25rem;
}
.phead .lede { max-width: 52ch; }

/* ── Coverage / area lists ─────────────────────────────────────────────────── */
.county + .county { margin-top: clamp(2.5rem, 5vw, 4rem); }
.county__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.9rem;
  margin-bottom: 1.5rem;
}
.county__head h2 { font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem); }
.county__count {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mist);
}

.chips { display: flex; flex-wrap: wrap; gap: 0.45rem; list-style: none; padding: 0; }
.chips a,
.chips span {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  padding: 0.42rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  text-decoration: none;
  color: var(--ink-soft);
}
.chips a:hover { border-color: var(--brand); color: var(--brand-deep); background: var(--brand-wash); }
.chips span { color: var(--mist); background: transparent; }

.ziplist {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--slate);
  letter-spacing: 0.02em;
  line-height: 1.9;
}

/* ── CTA band ──────────────────────────────────────────────────────────────── */
.cta-band { background: var(--ink); color: var(--haze); }
.cta-band__grid {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: center;
}
@media (min-width: 860px) {
  .cta-band__grid { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}
.cta-band h2 { font-size: clamp(1.7rem, 1.3rem + 1.7vw, 2.5rem); color: #fff; max-width: 18ch; }
.cta-band p { color: #b6c6d6; margin-top: 1rem; max-width: 46ch; }
.cta-band .eyebrow { color: #e29a5c; display: block; margin-bottom: 0.9rem; }
.cta-band__phone {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 1.3rem + 2vw, 2.6rem);
  letter-spacing: -0.035em;
  color: #fff;
  text-decoration: none;
  display: inline-block;
}
.cta-band__phone:hover { color: #f0b072; }
.cta-band__meta {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: #8ba0b5;
  margin-top: 0.75rem;
  line-height: 1.8;
}

/* ── Related links ─────────────────────────────────────────────────────────── */
.related { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; }
@media (min-width: 760px) {
  .related { grid-template-columns: repeat(3, 1fr); }
  /* The 1px gap over a --rule background is what draws the hairlines between
     cards, which means an empty grid track is not empty — it paints as a slab
     of grey. Match the track count to the card count so that cannot happen.
     Without :has() support this degrades to exactly the previous rendering. */
  .related:has(a:only-child) { grid-template-columns: 1fr; }
  .related:has(a:nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }
  /* Past three cards the grid wraps, and a short final row has the same
     problem one row down. Let the last card absorb the slack: with four cards
     it takes the whole second row, with five it takes two thirds of it. */
  .related:has(a:nth-child(4)) a:nth-child(3n + 1):last-child { grid-column: span 3; }
  .related:has(a:nth-child(4)) a:nth-child(3n + 2):last-child { grid-column: span 2; }
}
.related a {
  background: var(--paper);
  padding: 1.35rem 1.4rem;
  text-decoration: none;
  color: inherit;
  display: block;
  transition: background-color 0.15s ease;
}
.related a:hover { background: var(--brand-wash); color: inherit; }
.related__e {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
  display: block;
  margin-bottom: 0.5rem;
}
.related__t { font-family: var(--font-display); font-weight: 650; font-size: 1.05rem; letter-spacing: -0.022em; }

/* ── Forms ─────────────────────────────────────────────────────────────────── */
.form { display: grid; gap: 1.1rem; }
.field { display: grid; gap: 0.4rem; }
.field label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--slate);
}
.field input,
.field select,
.field textarea {
  font-family: var(--font-body);
  font-size: 1rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  width: 100%;
}
.field input:focus,
.field select:focus,
.field textarea:focus { border-color: var(--brand); }
.field textarea { min-height: 8rem; resize: vertical; }
.field__hint { font-size: 0.88rem; color: var(--mist); margin-top: 0.45rem; max-width: 60ch; }
.field-row { display: grid; gap: 1.1rem; }
@media (min-width: 600px) { .field-row { grid-template-columns: 1fr 1fr; } }

.notice {
  border: 1px dashed var(--flag);
  background: var(--flag-wash);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  font-size: 0.94rem;
  color: var(--ink-soft);
}
.notice b {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.64rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--flag-deep);
  display: block;
  margin-bottom: 0.35rem;
}

/* ── Footer ────────────────────────────────────────────────────────────────── */
.ftr { background: var(--ink); color: #b6c6d6; padding-block: clamp(3rem, 6vw, 4.5rem) 2rem; }
.ftr a { color: #d3dfea; text-decoration: none; }
.ftr a:hover { color: #fff; text-decoration: underline; }
.ftr__grid {
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
}
.ftr__brand { grid-column: 1 / -1; }
/* Explicit tracks once there is room for all five: auto-fit was giving four
   columns and wrapping Company underneath the brand, which left a hole in the
   middle of the footer. The brand column carries the address and hours, so it
   gets the extra width. */
/* Between the stacked phone layout and the five-track desktop one, auto-fit
   resolves to three columns for four link blocks. That orphans Company on a row
   of its own and leaves a hole beside the short Services list, because Services
   has six items and each county column has ten. Four divides evenly into two. */
@media (min-width: 560px) and (max-width: 999px) {
  .ftr__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1000px) {
  .ftr__grid { grid-template-columns: 1.6fr 1fr 1fr 1fr 0.9fr; }
  .ftr__brand { grid-column: span 1; max-width: none; }
}
.ftr__name { margin-bottom: 1rem; }
.ftr__logo { height: 2.25rem; width: auto; }
.ftr__name picture { display: block; }
.ftr h2 {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.66rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #7f93a8;
  margin-bottom: 1rem;
}
.ftr ul { list-style: none; padding: 0; display: grid; gap: 0.55rem; font-size: 0.95rem; }
.ftr__meta { font-size: 0.92rem; line-height: 1.65; }
.ftr__meta dt {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: #7f93a8;
  margin-top: 0.85rem;
}
.ftr__meta dd { margin: 0.15rem 0 0; color: #d3dfea; }
.ftr__bottom {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(185, 200, 195, 0.18);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: #7f93a8;
}

.print-contact { display: none; }

/* ── Mobile call bar ───────────────────────────────────────────────────────── */
/* Height reserved at the foot of the page so the fixed bar never sits on top of
   the last line of content. Referenced by both the bar and the body reserve. */
:root { --callbar-h: 4.25rem; }
.callbar {
  display: none;
  flex-wrap: wrap;
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 90;
  background: var(--ink);
  border-top: 1px solid rgba(185, 200, 195, 0.2);
  /* Capped in px, not rem. --gut is rem-based, so at 200% text it claimed 80px
     of a 390px bar and the gap another 19px — 99px of chrome before either
     button got a pixel, which pushed the bar past the viewport and the whole
     document into horizontal scroll. A px cap does not scale with the text. */
  padding: 0.6rem min(var(--gut), 18px);
  gap: min(0.6rem, 8px);
  padding-bottom: max(0.6rem, env(safe-area-inset-bottom));
}
.callbar a {
  /* flex-basis 0, not auto: auto sizes each button to its content, and at 200%
     text that content is wider than half the bar, so the bar itself grew past
     the viewport and took the document into horizontal overflow with it. A zero
     basis lets them shrink and the text wrap inside them. */
  flex: 1 1 0;
  min-width: 0;
  justify-content: center;
  text-align: center;
  padding: 0.85rem 0.6rem;
  font-size: 0.94rem;
  /* Wraps rather than truncates. These were nowrap with an ellipsis, which at
     200% text size clipped the phone number on all thirty-six pages — the one
     control a reader who has enlarged their text most needs to be able to read.
     WCAG 1.4.4 asks for no loss of content at 200%, and a truncated phone
     number is loss of content. At normal sizes both labels still fit on one
     line, so nothing changes visually. */
  overflow-wrap: anywhere;
  display: block;
  line-height: 1.2;
}
.callbar a:first-child { flex-grow: 1.5; }
@media (max-width: 760px) {
  .callbar { display: flex; min-height: var(--callbar-h); }
  /* At 320px the second label rendered as "Get inspec…". The ellipsis meant
     nothing reported an overflow — the truncation is the styling working — so
     the only way to see it was to look at the bar on a 320px screen. Drop the
     type a little and even out the flex share below 360px so both labels fit. */
  @media (max-width: 360px) {
    .callbar a { font-size: 0.86rem; padding-inline: 0.35rem; letter-spacing: -0.01em; }
    .callbar a:first-child { flex-grow: 1.15; }
  }
  /* Same token on both sides. These were two independent numbers that happened
     to agree — the bar measures 67px and the reserve was 4.75rem — so any change
     to the bar's padding or font size would have silently eaten into the gap at
     the end of every page, with nothing to catch it. Measured clearance on the
     tightest page is 40px. */
  body { padding-bottom: calc(var(--callbar-h) + 0.5rem); }
}

/* ── Utilities ─────────────────────────────────────────────────────────────── */
.mt-lg { margin-top: clamp(2rem, 4vw, 3rem); }
.mt-md { margin-top: clamp(1.25rem, 2.5vw, 1.75rem); }
.maxprose { max-width: 68ch; }

/* Long content bodies inside data-driven sections */
.body-block > * + * { margin-top: 1.05em; }
.body-block p { color: var(--ink-soft); text-wrap: pretty; }
.body-block { max-width: 68ch; }

/* `.defer` is a hook left on long below-the-fold sections.
   It deliberately does nothing. `content-visibility: auto` was tried here and
   removed: with sections this size the estimated intrinsic height is wrong
   enough that in-page anchor links (/#services) land in the wrong place, and
   the layout jumps as you scroll past. The pages are small enough that it buys
   nothing measurable. Left in place so it is obvious this was considered. */

/* Print — people do print these pages for insurance files */
@media print {
  .hdr, .callbar, .cta-band, .ftr__grid { display: none; }
  body { background: #fff; font-size: 11pt; padding: 0; }
  .section { padding-block: 1rem; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 8pt; color: #555; }

  /* Browsers drop background colours when printing, so the hero lost its navy
     block while keeping its white type: the h1 measured 1.00:1 against the
     paper and the lede 1.39:1. The page title printed invisibly on every page
     with a media hero, which is most of the site.

     Reset to ink on white rather than forcing the background to print —
     print-color-adjust would put a full-bleed navy rectangle on the first page
     of anything anyone prints, which is not a kindness. */
  .mhero { min-height: 0; padding-block: 0 1rem; }
  .mhero__bg, .mhero__video, .mhero__toggle, .mhero__cap, .mhero__actions { display: none; }
  .mhero__inner .h1 { color: var(--ink); max-width: none; }
  .mhero__inner .lede { color: var(--ink-soft); }
  .eyebrow--onink { color: var(--flag-deep); }
  .mhero .assure--onink { border-color: var(--rule); }
  .assure--onink .assure__label { color: var(--slate); }
  .assure--onink .assure__val { color: var(--ink); }

  /* Same problem on the photo cards, and on more pages than the hero: the type
     is white over a background image, and neither the image nor the scrim
     prints. Twenty-three of thirty-six pages printed the card titles at 1.00:1.
     Drop the photo, keep the words. */
  .card__bg { display: none; }
  .card--photo, .card { background: none; border: 1px solid var(--rule); }
  .card--photo .card__t, .card__t { color: var(--ink); }
  .card--photo .card__d, .card__d { color: var(--ink-soft); }
  /* .card--flag.card--photo .card__eyebrow is 0,3,0 and outranked the print
     reset at 0,2,0, so that one eyebrow kept printing at 2.16:1 on white. */
  .card--flag.card--photo .card__eyebrow { color: var(--flag-deep); }
  .card--photo .card__eyebrow, .card__eyebrow { color: var(--slate); }
  .card--photo .card__more, .card__more { color: var(--flag-deep); }

  /* The contact form's submit is white on the flag orange, which also does not
     print. It is a disabled control on paper regardless, so it prints as an
     outline rather than a solid block. */
  .btn--call, button.btn { color: var(--ink); background: none; border: 1px solid var(--rule); }

  /* Where pages are allowed to break. Without this a process step splits from
     its own "how to check" line, a definition term lands at the foot of one
     page with its description on the next, and a heading prints alone at the
     bottom of a sheet. All three are worse on paper than on screen, because you
     cannot scroll to reunite them. */
  .steps li, .deflist li, .legend li, .faq details, .card { break-inside: avoid; }
  h1, h2, h3, .h1, .h2, .h3, .sec-head { break-after: avoid; }
  figure, .diagram { break-inside: avoid; }
  /* A section heading immediately followed by a page break is the same orphan
     in reverse — keep it with what introduces it. */
  .section { break-before: auto; }
  p, li { orphans: 3; widows: 3; }

  /* The one thing a printed page must carry. */
  .print-contact {
    display: block;
    margin-top: 1rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--rule);
    font-family: var(--font-mono);
    font-size: 9pt;
    color: var(--ink);
  }
  .print-contact a { color: var(--ink); text-decoration: none; }
  .print-contact a[href^='tel:']::after { content: none; }
}

/* A single onward link at the foot of a question block. Muted, because it is a
   next step rather than an answer. */
.see-also {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  color: var(--mist);
  font-size: 0.95rem;
  max-width: 62ch;
}

/* Which fields are actually required. The `required` attribute alone tells a
   screen reader but shows a sighted user nothing until the form rejects them,
   so the word is on screen and inside the label — announced as part of the
   field's name rather than as a separate, easily-missed node. */
.field__req {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--flag-deep);
  margin-left: 0.4rem;
}

/* Date line on a guide. Small and muted: it answers "is this current" without
   competing with the first sentence, which is the thing worth reading. */
.byline {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mist);
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
