/* ==========================================================================
   Baudi Landscaping Inc. — material-led editorial
   Palette is drawn from what the company actually works in: evergreen and
   moss for the planting side, warm limestone paper and soil-dark ink for the
   masonry side. The greens are deep and desaturated — pulled from cut cedar
   hedge and wet moss, not from turf. No mint, no chemical lawn-care green.
   Every text pair below is contrast-verified against WCAG AA before it was
   written down; the numbers in the comments are measured, not estimated.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Fonts — self-hosted

   These used to come from fonts.googleapis.com: a render-blocking stylesheet on
   a third-party origin, which then referenced woff2 files on a SECOND
   third-party origin. Two extra DNS+TLS handshakes before any text could paint,
   a measured 204ms for the stylesheet alone and +84ms on FCP, for 95KB.

   Only the latin subsets are pulled. Google's unicode-range split means the
   cyrillic, greek and vietnamese subsets never actually fire on this site, so
   fetching them would be pure waste. Both faces are variable, so Geist's four
   declared weights are one file and one @font-face with a weight range.

   Both are SIL Open Font License, which permits self-hosting.

   The -fallback faces below are metric-matched to the real ones using the
   actual hhea values read out of the font binaries (Fraunces upm 2000,
   asc 1956, desc -510; Geist upm 1000, asc 1005, desc -295). That is what stops
   the swap from reflowing the page: the fallback occupies the same vertical
   space as the webfont, so when the real face arrives nothing moves.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/geist-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Fraunces-fallback';
  src: local('Times New Roman'), local('Times');
  ascent-override: 97.80%;
  descent-override: 25.50%;
  line-gap-override: 0%;
  size-adjust: 105%;
}
@font-face {
  font-family: 'Geist-fallback';
  src: local('Helvetica Neue'), local('Arial');
  ascent-override: 100.50%;
  descent-override: 29.50%;
  line-gap-override: 0%;
  size-adjust: 100%;
}

:root {
  /* Paper — warm limestone. Unchanged: it is what keeps the greens from
     reading as a generic lawn-care site. */
  --paper:        #F4F1EA;
  --paper-2:      #E9E3D6;
  --paper-3:      #DED7C6;

  /* Ink — warm, soil-derived, never a cool grey */
  --ink:          #1A1815;
  --ink-2:        #3E3830;
  --muted:        #5F584C;   /* 6.23 on paper, 5.50 on paper-2 */
  --stone:        #68614F;   /* 5.46 on paper, 4.81 on paper-2 */

  /* The dark family — evergreen rather than blue-slate. Carries the hero,
     announcement rail, footer and call bar, so it is the single biggest
     colour signal on the page. */
  --evergreen:    #16211B;
  --pine:         #24352C;   /* the one lighter dark, for section--dark */

  /* One accent, desaturated, drawn from moss and new growth */
  --moss:         #4A6134;   /* 6.10 on paper, 5.38 on paper-2, 4.80 on paper-3 */
  --sage:         #A9C08D;   /* 8.37 on evergreen, 6.55 on pine */

  /* Lines. --line and --line-2 are decorative hairlines and section rules; at
     1.33:1 and 1.60:1 on paper they are a deliberate editorial choice and WCAG
     does not govern them. --line-3 exists because the BORDER OF AN INTERACTIVE
     CONTROL does fall under 1.4.11 non-text contrast, which wants 3:1. Chips,
     review buttons, form fields and related-page links use it; nothing else
     should. Measured 3.14:1 on paper, 3.03:1 on the composited card surface. */
  --line:         #D9D2C3;
  --line-2:       #C8C0AE;
  --line-3:       #9A9080;
  --line-dark:    rgba(244, 241, 234, 0.16);

  --error:        #8E3122;   /* 7.13 on paper — warm brick, reads as a warning
                                against the greens without going fire-engine */

  /* Type — a high-contrast display serif against a neutral grotesk.
     Fraunces has real drawing character; the previous geometric sans
     read as template shorthand. */
  --display: 'Fraunces', 'Fraunces-fallback', 'Iowan Old Style', Georgia, serif;
  --body:    'Geist', 'Geist-fallback', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono:    ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Layout */
  --wrap: 1280px;
  --gut:  clamp(20px, 4vw, 64px);
  --header-h: 84px;

  /* Spacing — one 8px scale (2026-08-02). Before this the file carried ~40
     bespoke clamps for the same handful of jobs, which is how a 10,000px
     homepage happens: nobody can see the rhythm, so every block gets its own.
     Use these for section-level rhythm and block separation. Type-level
     spacing (label to heading, heading to paragraph) stays local. */
  --sp-1: 8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 40px;
  --sp-6: 48px;
  --sp-7: 56px;
  --sp-8: 64px;

  /* Section padding tiers. Two, in a strict 3:2 relationship, plus the
     denser homepage pair — the homepage runs eleven sections back to back
     and the generated pages run four or five. */
  --sec-pad:            clamp(48px, 6vw, 88px);
  --sec-pad-tight:      clamp(32px, 4vw, 56px);
  --sec-pad-home:       clamp(34px, 4vw, 56px);
  --sec-pad-home-tight: clamp(24px, 2.6vw, 32px);

  /* One radius, everywhere (luxury pass 2026-08-01). The hard-square era
     ("no pill anywhere") ended here: photos, cards, form fields and buttons
     all share this single soft-rect radius, and media wrappers clip to it
     with overflow:hidden. Do not mint a second radius. */
  --r: 14px;

  /* Layering — header, dropdowns, call bar, mobile menu */
  --z-header: 50;
  --z-dropdown: 60;
  --z-callbar: 70;
  --z-mobile-nav: 80;

  /* Focus — verified 3:1+ on every surface it lands on */
  --focus:        #1A1815;   /* 15.71 on paper */
  --focus-light:  #C9DCAE;   /* 11.31 on evergreen, 8.84 on pine */

  /* Expo-out family (reference-rhythm pass 2026-08-01): fast start, long
     settle. One token so every hover, header state and reveal shares the
     same motion character. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

* { box-sizing: border-box; }

html {
  /* Deliberately not `smooth`. The estimate anchor is ~17,300px down on a phone, and
     smooth scrolling animated that at a measured 1,477ms — a second and a half of
     travel between tapping the primary CTA and being able to type. */
  scroll-behavior: auto;
  scroll-padding-top: 96px;
  scroll-padding-bottom: 88px;   /* clear the fixed call bar on anchor jumps */
  -webkit-text-size-adjust: 100%;
}

/* Global anchor offset: keep in-page anchor targets clear of the sticky
   header. :where() keeps specificity at zero so it never fights component
   rules; honeypot fields (.honeypot) are excluded as they are not scroll
   targets. */
:where(section[id], [id]:not(.honeypot *)) {
  scroll-margin-top: calc(var(--header-h) + 24px);
}

body {
  margin: 0;
  background: var(--paper);
  font-family: var(--body);
  color: var(--ink);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

h1, h2, h3, h4 { margin: 0; font-family: var(--display); font-weight: 400; }
p, blockquote, figure, ul, ol { margin: 0; }
ul { padding: 0; list-style: none; }
img { max-width: 100%; display: block; }
input, select, textarea, button { font-family: inherit; font-size: inherit; }

a { color: var(--moss); text-decoration: none; }
a:hover { color: var(--ink); }

::selection { background: var(--moss); color: var(--paper); }

/* --------------------------------------------------------------------------
   THE TYPE SCALE — strict, audited 2026-08-01. Every font-size in this file
   is one of these steps (or a clamp between two adjacent steps). If a size
   you want isn't here, use the nearest step — do not mint a new one; the
   half-pixel ladder this replaced (11.5/12.5/13.5/14.5/15.5/16.5/17/17.5)
   is exactly how a system dies.

   Display (Fraunces 500)      lh      tracking
     60  homepage hero H1      1.1     -0.015em   (multi-line variant 1.15)
     42  page H1, homepage H2  1.15    -0.015em
     32  internal H2, numerals 1.2     -0.01em    (single-line numerals lh 1)
     25  H3 tier (.d3)         1.3     -0.01em
     20  card titles --t-title 1.35    none       (as pull-quote: lh 1.4)

   Text (Geist)                lh      tracking
     18  ledes, mobile-nav     1.55    0
     16  body, prose, inputs   1.65    0
     15  secondary --t-sm      1.6     0
     14  compact UI, nav       1.55    0
     13  captions, meta        1.5     0
     12  micro uppercase       1.4     0.12em

   All uppercase labels track 0.12em regardless of size. Fraunces at these
   sizes takes weight 500 — 400 loses the presence a bold grotesque carries
   at the same height; the variable file covers 400-600. */
.d1 {
  font-family: var(--display);
  font-size: clamp(38px, 4.5vw, 60px);
  line-height: 1.1; letter-spacing: -0.015em; font-weight: 500;
  text-wrap: pretty;
}
/* Hero H1 is multi-line over a photo. Fraunces WONK lets y-descenders and the
   decorative ampersand collide with the next line. Sober the axes and give the
   lines room — 1.14 matches the reference hero leading. */
.hero .d1 {
  line-height: 1.15;
  letter-spacing: -0.015em;
  max-width: 18ch;
  text-wrap: balance;
  font-variation-settings: "WONK" 0, "SOFT" 30, "opsz" 72;
  font-feature-settings: "swsh" 0, "ss01" 0, "ss02" 0;
}
.d2 {
  font-family: var(--display);
  font-size: clamp(28px, 3.2vw, 42px);
  line-height: 1.15; letter-spacing: -0.015em; font-weight: 500;
  text-wrap: balance;
}
.d3 {
  font-family: var(--display);
  font-size: clamp(20px, 1.9vw, 25px);
  line-height: 1.3; letter-spacing: -0.01em; font-weight: 500;
}

/* Small caps label — replaces the pill-with-a-dot entirely */
.label {
  font-family: var(--body);
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--stone);
}
.label--light { color: var(--sage); }
/* Mono survives for true tabular numerals only — the phone number in the
   header, footer and menus. It used to badge section counters and step
   numbers too, which read as engineering chrome on an estate page (luxury
   v2): every label is the Geist .label recipe above. */
.num {
  font-family: var(--mono); font-size: 12px;
  color: var(--stone); font-variant-numeric: tabular-nums;
}

.lede {
  font-size: clamp(16px, 1.25vw, 18px);
  line-height: 1.55; color: var(--ink-2); max-width: 36em;
  text-wrap: pretty;
}
.body-sm { font-size: var(--t-sm); line-height: 1.6; color: var(--muted); }

/* Two tokens for secondary text, not five. Measured at 1440 the scale ran
   106.56 / 62 / 38.88 / 30.24 / 23 / 22.32 / 20.16 / 17 / 16.5 / 16 / 15.5 / 15 /
   14.5 / 14 / 13.5 / 13 / 12.5 / 12 — eighteen steps, ten of them 1.03 apart.
   The top of that scale is dramatic and correct and is left alone. The bottom
   had .head__intro p at 16.5, .work__meta p at 16, .faq p at 15.5 and .steps p
   at 15 all doing the identical job of "secondary paragraph". Nobody perceives a
   3% step; the page just stops reading as a system.
     --t-intro  section-level intro and lede paragraphs
     --t-sm     in-card and in-column body
     --t-title  the four card-title sizes that sat within 14% of each other */
:root {
  --t-intro: 16px;
  --t-sm:    15px;
  --t-title: clamp(18px, 1.4vw, 20px);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
.on-dark :focus-visible { outline-color: var(--focus-light); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --------------------------------------------------------------------------
   Shell
   -------------------------------------------------------------------------- */

.page { width: 100%; overflow-x: clip; position: relative; }

/* Paper grain — subtle, and no blend mode (that forced a full-viewport
   recomposite on every scroll frame in the previous build). */
/* Scoped to paper surfaces rather than fixed over the whole viewport. As a fixed
   full-page layer it sat above the photographs too, which is why every image read
   slightly veiled — grain belongs on paper, not on a photograph of a patio. Off
   the fixed layer it can also carry a little more weight without muddying anything. */
.section--paper, .section--band { position: relative; }
.section--paper::before, .section--band::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  pointer-events: none; opacity: 0.05;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
}
/* Keep content above the grain layer. */
.section--paper > *, .section--band > * { position: relative; z-index: 1; }

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--paper);
  padding: 12px 18px; font-size: 14px; font-weight: 600; z-index: 200;
}
.skip-link:focus { left: 0; color: var(--paper); }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gut); width: 100%; }
.wrap--wide { max-width: 1560px; }

/* --------------------------------------------------------------------------
   Rules — the primary structural device, replacing cards
   -------------------------------------------------------------------------- */

.rule { border: 0; border-top: 1px solid var(--line); margin: 0; }
.rule--dark { border-top-color: var(--line-dark); }
.rule--heavy { border-top-width: 2px; border-top-color: var(--ink); }

/* --------------------------------------------------------------------------
   Buttons — soft rects on the shared --r radius (luxury pass 2026-08-01).
   Moss stays the ONE primary CTA colour; ghost and outline secondaries pick
   up the same radius. The square "no pill anywhere" era is over.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block; border: 0; border-radius: var(--r);
  font-family: var(--body); font-weight: 600; font-size: 15px;
  letter-spacing: 0; cursor: pointer; text-align: center;
  padding: 17px 30px;
  transition: background 200ms var(--ease), color 200ms var(--ease),
              transform 120ms var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--solid  { background: var(--moss); color: var(--paper); }
.btn--solid:hover  { background: var(--ink); color: var(--paper); }

.btn--ink    { background: var(--ink); color: var(--paper); }
.btn--ink:hover { background: var(--moss); color: var(--paper); }

.btn--outline {
  background: transparent; color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}
.btn--outline:hover { background: var(--ink); color: var(--paper); }

.btn--onphoto {
  background: var(--paper); color: var(--ink);
}
.btn--onphoto:hover { background: var(--moss); color: var(--paper); }

.btn--ghost-light {
  background: transparent; color: var(--paper);
  box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.5);
}
.btn--ghost-light:hover { background: var(--paper); color: var(--ink); }

/* Header phone as a filled control (adopted from Bellantoni’s yellow phone CTA).
   Uses Baudi moss/paper — never competitor gold. */
.btn--tel {
  background: var(--moss); color: var(--paper);
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  padding: 12px 18px; font-size: 14px; white-space: nowrap;
}
.btn--tel:hover { background: var(--ink); color: var(--paper); }

/* Text link with a drawn underline that grows on hover */
.tlink {
  font-size: 15px; font-weight: 600; color: var(--moss);
  display: inline-block; position: relative; padding-bottom: 2px;
}
.tlink::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: currentColor; transform-origin: left;
  /* Full width at rest, faded. It used to sit at scaleX(0.35), which reads as a
     BROKEN underline rather than a deliberate one — a third of a rule under a
     link looks like a rendering fault, not a design decision. Growing opacity
     instead of length keeps the hover gesture without the ambiguity at rest. */
  transform: scaleX(1); opacity: 0.4;
  transition: opacity 260ms var(--ease);
}
.tlink:hover::after { opacity: 1; }
.tlink:hover { color: var(--ink); }
.tlink--light { color: var(--sage); }
.tlink--light::after { opacity: 0.55; }
.tlink--light:hover { color: var(--paper); }
.tlink--light:hover::after { opacity: 1; }

/* --------------------------------------------------------------------------
   Announcement + header
   -------------------------------------------------------------------------- */


/* Transparent over the hero so the photograph starts at pixel zero. The opaque
   bar sitting on top of a full-bleed image was the single loudest "CMS theme"
   signal in the composition — it flattened the fold into three stacked bands
   (chrome / photo / trust rail) instead of one image with content over it.
   Every page in the build opens on a dark hero — the homepage photo carries a
   0.62-0.74 alpha scrim at the top edge, and the service and town pages that
   have no photo use section--dark — so light chrome is legible on all 21.
   backdrop-filter only runs in the scrolled state now; over the hero there is
   nothing behind it to blur, and it was costing a compositing layer on every
   scroll frame for nothing. */
.header {
  top: 0; left: 0; right: 0;
  z-index: var(--z-header);
  transition: background 240ms var(--ease), border-color 240ms var(--ease),
              box-shadow 240ms var(--ease);
}

/* Homepage only: fixed transparent bar over the full-bleed hero photo. */
.header--overlay {
  position: fixed;
  background: transparent;
  border-bottom: 1px solid transparent;
}
.header--overlay:not(.is-scrolled) .nav > a,
.header--overlay:not(.is-scrolled) .nav__btn { color: var(--paper); }
.header--overlay:not(.is-scrolled) .nav > a:hover,
.header--overlay:not(.is-scrolled) .nav__btn:hover { color: var(--sage); }
.header--overlay:not(.is-scrolled) .nav__caret { border-color: var(--paper); }
.header--overlay:not(.is-scrolled) .burger span,
.header--overlay:not(.is-scrolled) .burger span::before,
.header--overlay:not(.is-scrolled) .burger span::after { background: var(--paper); }
.header--overlay:not(.is-scrolled) .logo__img--dark { display: none; }
.header--overlay.is-scrolled .logo__img--light { display: none; }
.header--overlay:not(.is-scrolled) .btn--tel {
  background: transparent; color: var(--paper);
  box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.45);
}
.header--overlay:not(.is-scrolled) .btn--tel:hover {
  background: rgba(244, 241, 234, 0.12); color: var(--paper);
  box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.7);
}
.header--overlay:not(.is-scrolled) .btn--solid {
  background: var(--paper); color: var(--ink);
  box-shadow: none;
}
.header--overlay:not(.is-scrolled) .btn--solid:hover { background: #fff; }
.header--overlay.is-scrolled {
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  --header-h: 62px;
  background: rgba(244, 241, 234, 0.95);
  border-bottom-color: var(--line-2);
  box-shadow: 0 6px 20px -8px rgba(26, 24, 21, 0.16);
}
.header--overlay.is-scrolled .logo__img { height: 34px; }
.header--overlay:not(.is-scrolled) .nav > a:hover,
.header--overlay:not(.is-scrolled) .nav__btn:hover {
  color: var(--paper);
  background: rgba(244, 241, 234, 0.10);
}
.header--overlay:not(.is-scrolled) .nav > a:focus-visible,
.header--overlay:not(.is-scrolled) .nav__btn:focus-visible {
  outline-color: var(--sage);
}
.header--overlay:not(.is-scrolled) .nav__btn[aria-expanded="true"] {
  color: var(--sage);
  background: rgba(244, 241, 234, 0.12);
}

/* Internal pages: opaque cream bar, independent of hero layout. Stays in
   document flow so split heroes never sit beneath transparent chrome. */
.header--solid {
  position: sticky;
  background: var(--paper);
  border-bottom: 1px solid var(--line-2);
  box-shadow: 0 1px 0 rgba(26, 24, 21, 0.04);
  color: var(--ink);
}
.header--solid .logo__img--light { display: none; }
.header--solid .logo__img--dark { display: block; }
.header--solid.is-scrolled {
  background: var(--paper);
  border-bottom-color: var(--line-2);
  box-shadow: 0 4px 18px -8px rgba(26, 24, 21, 0.14);
}
.header--solid .btn--tel {
  min-height: 44px;
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line-3);
}
.header--solid .btn--tel:hover {
  background: rgba(26, 24, 21, 0.04);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink-2);
}
.header--solid .btn--solid {
  min-height: 44px;
  background: var(--moss);
  color: var(--paper);
}
.header--solid .btn--solid:hover {
  background: var(--ink);
  color: var(--paper);
}

.header__inner {
  width: min(calc(100% - 2 * var(--gut)), 1320px);
  max-width: 1320px;
  margin-inline: auto;
  padding: 0 var(--gut);
  min-height: 80px;
  height: var(--header-h);
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: clamp(20px, 3vw, 48px);
  /* No height transition here (or on .logo__img below), deliberately: animating
     height re-runs layout on every frame of the is-scrolled shrink. The bar
     snaps to its scrolled size while the .header background/border/box-shadow
     transition carries the state change; snap also matches reduced-motion. */
}
.logo { grid-column: 1; display: flex; align-items: center; flex: 0 0 auto; }
.logo__img--light { display: block; }
.logo__img { height: 46px; width: auto; display: block; }
@media (max-width: 480px) { .logo__img { height: 36px; } }

/* Nav fills the middle and sits against the phone/CTA cluster — the old
   flex:0 packing left a dead band between "Reviews" and the number. */
.nav {
  grid-column: 2;
  display: flex; align-items: center; justify-content: flex-end;
  flex: 1 1 auto; gap: 2px; min-width: 0;
  margin: 0;
}
.nav > a,
.nav__btn {
  font-family: var(--body);
  font-size: 14px; font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
  padding: 9px 11px;
  border-radius: var(--r);
  border: 0;
  border-bottom: none;
  background: transparent;
  text-decoration: none;
  transition: color 160ms var(--ease), background 160ms var(--ease);
}
.nav > a:hover,
.nav__btn:hover {
  color: var(--ink);
  background: rgba(26, 24, 21, 0.05);
  border-bottom-color: transparent;
}
.nav > a:focus-visible,
.nav__btn:focus-visible {
  outline: 2px solid var(--moss);
  outline-offset: 2px;
}
.header__end {
  grid-column: 3;
  margin-left: 0;
  display: flex; align-items: center; gap: 12px;
  flex: 0 0 auto;
}
.header .btn { padding: 12px 18px; font-size: 14px; min-height: 44px; }
.header .btn--tel { padding: 12px 16px; }

.burger {
  grid-column: 3;
  justify-self: end;
  display: none;
  width: 48px; height: 48px; border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: transparent; cursor: pointer; padding: 0;
  align-items: center; justify-content: center;
}
.burger span { display: block; width: 22px; height: 2px; background: var(--ink); position: relative; }
.burger span::before, .burger span::after {
  content: ''; position: absolute; left: 0; width: 22px; height: 2px;
  background: var(--ink); transition: transform 220ms var(--ease);
}
.burger span::before { top: -7px; }
.burger span::after  { top: 7px; }
.burger[aria-expanded="true"] span { background: transparent; }
.burger[aria-expanded="true"] span::before { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span::after  { transform: translateY(-7px) rotate(-45deg); }



/* Mobile panel -------------------------------------------------------------
   Full-viewport overlay (covers header + hero). Short primary list, one
   optional Services expand with three hubs, utilities at the foot. Scrolls
   itself; body scroll is locked while open. */

.mobile-nav {
  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-mobile-nav);
  height: 100dvh;
  max-height: 100dvh;
  background: var(--evergreen);
  color: rgba(244, 241, 234, 0.9);
  flex-direction: column;
  overflow: hidden;
}
.mobile-nav[data-open="true"] {
  display: flex;
  animation: mnavIn 260ms var(--ease);
}
.mobile-nav:focus { outline: none; }
.mobile-nav:focus-visible { outline: 2px solid var(--sage); outline-offset: -4px; }
@keyframes mnavIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .mobile-nav[data-open="true"] { animation: none; }
}

.mnav__top {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding:
    calc(12px + env(safe-area-inset-top))
    var(--gut)
    12px;
  min-height: calc(64px + env(safe-area-inset-top));
}
.mnav__brand {
  display: flex;
  align-items: center;
  min-height: 48px;
}
.mnav__brand img {
  display: block;
  height: 34px;
  width: auto;
}
.mnav__close {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--paper);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r);
}
.mnav__close:hover { color: var(--sage); }
.mnav__close:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 2px;
}

.mnav__scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 8px var(--gut) calc(20px + env(safe-area-inset-bottom));
}

.mnav__primary {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.mnav__primary > li { border-bottom: 1px solid rgba(244, 241, 234, 0.12); }
.mnav__primary > li:last-child { border-bottom: 0; }
.mnav__primary > li > a,
.mnav__expand > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 64px;
  padding: 18px 0;
  font-family: var(--display);
  font-size: clamp(25px, 8.2vw, 32px);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--paper);
  text-wrap: balance;
}
/* Plain rows get the same quiet affordance the Services chevron gives. */
.mnav__primary > li > a::after {
  content: '';
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-top: 1.5px solid var(--sage);
  border-right: 1.5px solid var(--sage);
  transform: rotate(45deg);
}
.mnav__primary > li > a:hover,
.mnav__expand > summary:hover { color: var(--sage); }
.mnav__primary > li > a:focus-visible,
.mnav__expand > summary:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
}

.mnav__expand { margin: 0; }
.mnav__expand > summary {
  list-style: none;
  cursor: pointer;
  width: 100%;
}
.mnav__expand > summary::-webkit-details-marker { display: none; }
.mnav__chev {
  flex: 0 0 auto;
  color: var(--sage);
  transition: transform 200ms var(--ease);
}
.mnav__expand[open] > summary .mnav__chev {
  transform: rotate(180deg);
}

.mnav__sub {
  list-style: none;
  margin: 0 0 8px;
  padding: 0 0 6px;
  display: flex;
  flex-direction: column;
}
.mnav__subhead {
  margin: 16px 0 2px;
  font: 600 12px/1 var(--body);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sage);
}
.mnav__suball a { color: var(--sage); }
.mnav__sub a {
  display: flex;
  align-items: center;
  min-height: 52px;
  padding: 12px 0 12px 2px;
  font-family: var(--body);
  font-size: 18px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(244, 241, 234, 0.78);
}
.mnav__sub a:hover { color: var(--sage); }
.mnav__sub a:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
}

.mnav__util {
  /* Follows the link list at a fixed distance. margin-top:auto pinned this
     block to the overlay's bottom, opening a dead gap after "Reviews". */
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid rgba(244, 241, 234, 0.14);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}
.mnav__tel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 56px;
  padding: 14px 18px;
  border-radius: var(--r);
  box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.45);
  font-family: var(--body);
  font-size: clamp(18px, 4.8vw, 20px);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.mnav__tel:hover { color: var(--sage); }
.mnav__hours {
  margin: 0;
  align-self: center;
  font-family: var(--body);
  font-size: 14px;
  line-height: 1.55;
  color: rgba(244, 241, 234, 0.7);
}
.mnav__cta {
  margin-top: 12px;
  width: 100%;
  min-height: 56px;
  padding: 16px 18px;
  font-size: 16px;
  justify-content: center;
}

/* Lock page behind the overlay. position:fixed is applied in JS so iOS
   Safari does not keep rubber-banding the document underneath. */
body.mnav-open {
  overflow: hidden;
  touch-action: none;
}
/* Menu already has call + estimate — hide the persistent bar while open. */
body.mnav-open .callbar {
  visibility: hidden;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Hero — full bleed photograph, no pill, minimal scrim
   -------------------------------------------------------------------------- */

.hero { position: relative; background: var(--evergreen); overflow: hidden; }
.hero__media {
  position: absolute; inset: 0; width: 100%; height: 100%;
  /* The frontage frame: keep the wall line and the arborvitae rhythm in
     frame; bias slightly down so the midday sky band thins behind the
     transparent header and the dressed beds carry the lower half. */
  object-fit: cover; object-position: 50% 55%;
}
/* A single directional scrim, not a stack of radial + linear gradients.
   Warm soil-dark rather than blue-black (luxury v2: was a cooler 11,18,14
   green-black) so the midday frontage reads dusk-calm instead of overcast,
   and deepened ~0.04 at the type stops for the same reason. */
/* Scrim protects type on the left; right half of the frame must still read as
   finished outdoor work (PRD D-1). The 0.86 left stop read as a black
   rectangle laid over the photo rather than light falling off it (2026-08-02):
   dropped to 0.70 with a wider falloff, and the type carries a soft shadow
   instead. Measured white-on-scrim at the H1 and lede still clears 4.5:1. */
.hero__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg,
      rgba(24, 21, 14, 0.52) 0%,
      rgba(24, 21, 14, 0.26) 38%,
      rgba(24, 21, 14, 0.00) 66%),
    linear-gradient(78deg,
      rgba(24, 21, 14, 0.76) 0%,
      rgba(24, 21, 14, 0.70) 28%,
      rgba(24, 21, 14, 0.52) 50%,
      rgba(24, 21, 14, 0.24) 74%,
      rgba(24, 21, 14, 0.04) 92%);
}
.hero__inner {
  position: relative;
  width: min(calc(100% - 2 * var(--gut)), 1320px);
  max-width: 1320px;
  margin-inline: auto;
  /* Clear the fixed overlay header, then breathe before the eyebrow. */
  padding: calc(var(--header-h) + clamp(28px, 4.2vh, 52px)) var(--gut) clamp(40px, 5.5vh, 64px);
}
.hero__eyebrow {
  display: block; color: var(--sage);
  margin-bottom: clamp(22px, 3vh, 36px);
}
/* 18ch, not the old 16ch — that was tuned for a five-word H1. The current one
   is a full sentence; 18ch breaks it after "right —" into two balanced clauses. */
/* Type shadow, not a heavier scrim: it lifts the letterforms off the busiest
   part of the frame without darkening the photograph behind them. */
.hero h1 {
  color: var(--paper); max-width: 20ch;
  text-shadow: 0 1px 24px rgba(24, 21, 14, 0.35);
}
.hero__lede {
  margin-top: clamp(20px, 2.2vh, 28px); color: rgba(244, 241, 234, 0.88); max-width: 46ch;
  text-shadow: 0 1px 18px rgba(24, 21, 14, 0.35);
}
.hero__actions { margin-top: var(--sp-4); display: flex; gap: 12px; flex-wrap: wrap; }

/* One compact trust row under the actions — the rating and the licence, the
   two things a homeowner checks before they call. It replaces the 5.0 stat
   that used to sit in the strip below, so nothing is said twice. */
.hero__trust {
  margin-top: var(--sp-3);
  /* fit-content, not the full column: the box must not stretch into the
     bright right half of the photograph, where nothing is drawn anyway. */
  width: fit-content; max-width: 100%;
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 14px; line-height: 1.5;
  color: rgba(244, 241, 234, 0.88);
  text-shadow: 0 1px 16px rgba(24, 21, 14, 0.4);
}
.hero__trust-sep {
  width: 5px; height: 5px; transform: rotate(45deg);
  background: var(--sage); opacity: 0.7; flex: 0 0 auto;
}
.hero__trust a {
  color: var(--paper); font-weight: 600;
  text-decoration: underline;
  text-decoration-color: rgba(244, 241, 234, 0.45);
  text-underline-offset: 4px;
}
.hero__trust a:hover { color: #fff; text-decoration-color: var(--paper); }

/* Five drawn stars. aria-hidden: the rating that follows is the accessible
   name, so a screen reader hears "5.0, 22 Google reviews" and not ten glyphs. */
.stars { flex: 0 0 auto; display: block; }
.stars path { fill: var(--sage); }
.stars--ink path { fill: var(--moss); }

/* Proof strip under the hero — scannable stats (Bellantoni pattern), Baudi voice.
   Replaces the old .creds slash-separated line. Numbers are cream on evergreen;
   labels stay quiet. */
.stats {
  background: var(--evergreen); color: rgba(244, 241, 234, 0.78);
  border-top: 1px solid var(--line-dark);
}
.stats__grid {
  padding-top: var(--sp-4);
  padding-bottom: var(--sp-4);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 3vw, 40px);
  text-align: left;
}
.stats__num {
  display: block;
  font-family: var(--display);
  font-size: clamp(32px, 3.2vw, 42px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.015em;
  /* Cream, not sage: the numerals are the payload of the strip and sage
     (8.37:1) read washed out against evergreen next to the brighter body
     text around it. Paper is the brightest ink the palette owns. */
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}
/* A worded credential ("Licensed & insured") is not a numeral and cannot ride
   the 42 step without wrapping the cell. Drops to the 25/20 display steps. */
.stats__num--word { font-size: clamp(20px, 2vw, 25px); letter-spacing: -0.01em; }
.stats__label {
  display: block;
  margin-top: 10px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
  color: rgba(244, 241, 234, 0.72);
  /* Was max-width:18ch, which broke labels mid-phrase ("family-owned in
     Mount / Kisco") at exactly the widths the grid is most common. The cells
     are wide enough to hold each label on one or two natural lines. */
  text-wrap: balance;
}

/* The stat strip is gone. One editorial line does the same work. */
.creds {
  background: var(--evergreen); color: rgba(244, 241, 234, 0.78);
  border-top: 1px solid var(--line-dark);
}
.creds .wrap {
  padding-top: 22px; padding-bottom: 22px;
  display: flex; flex-wrap: wrap; gap: 10px 30px; align-items: baseline;
  font-size: 15px;
}
.creds b { color: var(--paper); font-weight: 600; font-variant-numeric: tabular-nums; }
.creds span:not(:last-child)::after {
  content: '/'; margin-left: 30px; color: rgba(244, 241, 234, 0.40);
}

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */

/* At 1440 the clamp resolves to 148px top and bottom on every section, so the seam
   between any two is 296px — ten times in a row. That is a metronome, not pacing,
   and it is worst where the sections are least equal: the process strip and the
   review quote each floated in roughly half their own height of empty paper (43%
   and 50% padding-to-content), while the services section ran at 10%.
   Two tiers instead: compress the terse functional blocks, and let the full
   sections keep their silence. Silence only registers as emphasis if the things
   around it are tighter. */
/* Reference-rhythm pass 2026-08-01: the old full tier ran 56→140px, which
   made the page breathe unevenly against the tight tier. The reference set
   keeps one even ~100px beat on every section; two tiers survive but sit in
   a strict 3:2 relationship so the meter stays audible. */
.section        { padding: var(--sec-pad) 0; }
.section--tight { padding: var(--sec-pad-tight) 0; }
.section--paper  { background: var(--paper); }
.section--band   { background: var(--paper-2); }
.section--dark   { background: var(--pine); color: var(--paper); }

/* Homepage-only density. The tiers above are shared with the twenty generated
   pages, whose short bodies need the air. The homepage runs twelve sections
   back to back, and measured 14,488px tall before the redesign — the same
   clamp that gives a service page room to breathe was a metronome of 296px
   seams here. Scoped to body.home so the generated pages are untouched.

   Tightened again 2026-07-31 (design-polish pass): at 1440 the full tier was
   still 96+96=192px between sections and the tight tier 128px, which left dead
   bands where a short section followed a long one (worst: why -> services, and
   around the reviews grid). Desktop caps dropped ~20-25% (96->74, 64->52);
   the vw slopes keep phone values within 2px of where they were. Type-level
   spacing inside sections is deliberately untouched.

   Reference-rhythm pass 2026-08-01: the 74px cap undershot the even beat the
   reference set keeps between sections. The display scale came down ~35% in
   the same pass, so the dead-band problem the 2026-07-31 tightening solved no
   longer reproduces at 92px — measured against the shorter headings. */
.home .section        { padding: var(--sec-pad-home) 0; }
.home .section--tight { padding: var(--sec-pad-home-tight) 0; }
/* The ask should feel calm, not cavernous — and the estimate slice is shared
   with the generated pages, so this stays scoped to the homepage. */
.home #estimate.section { padding: var(--sp-3) 0 var(--sp-5); }

/* Offset heading block — deliberately not centred, deliberately not a
   50/50 split. */
.head { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); gap: clamp(28px, 5vw, 88px); align-items: end; }
.head__intro { padding-bottom: 6px; }
.head__intro p { color: var(--muted); font-size: var(--t-intro); line-height: 1.65; text-wrap: pretty; }

/* Centred variant (luxury pass 2026-08-01) — the Lawnx pattern translated:
   small uppercase eyebrow, heading, one short lede, all on the axis. Used on
   the symmetric card sections (services, reviews); sections that are
   genuinely two-column (why, work case studies, areas, FAQ) keep the offset
   .head above. Variety with intent. */
.head--center {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  gap: 0;
  max-width: 660px;
  margin-inline: auto;
}
.head--center .label { display: block; margin-bottom: 14px; }
.head--center .head__intro { padding-bottom: 0; margin-top: 16px; }
.head--center .head__intro p { max-width: 58ch; }

/* --------------------------------------------------------------------------
   Services — three families, one photograph each.

   Was six equal-weight cards plus a second grid of six more: twelve choices
   before the visitor had decided anything, and the single longest section on
   the page. Three families is the whole decision a homepage should ask for;
   the fourteen service pages underneath carry the detail.

   NOTE ON A REVERTED EXPERIMENT, still applicable here — read before trying
   a feature plate (one card spanning 2x2) on this grid. It was built and
   reverted three times on the old six-card grid:
     1. A 2x2 item only resolves cleanly when it is FIRST in source order.
     2. The only photograph the company owns that survives being rendered at
        767px is sod-mulch-frontage, which belongs to Landscaping. Featuring
        masonry — the correct commercial choice — meant promoting a step run
        with a garden hose across it to the largest slot on the page.
     3. Fixing either means reordering the services to suit a visual effect.
   Revisit once the client shoots finished masonry at golden hour (item 1 on
   the shot list in the photography report).
   -------------------------------------------------------------------------- */

.fams {
  margin-top: var(--sp-4);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  /* One 24px gutter beat on every card grid — reference-rhythm pass. */
  gap: 24px;
}

/* Same plate recipe as the proof cards: an opaque surface and a three-layer
   warm shadow — tight contact shadow, long low ambient, inset hairline. The
   1px --line border this replaced measured 1.33:1 against paper, which reads
   as neither flat-and-confident nor elevated: a faint rectangle, which is what
   a CMS widget looks like. */
.fam {
  position: relative; display: flex; flex-direction: column;
  background: #FBFAF7;
  border-radius: var(--r);
  /* Clips the photo to the card's radius; nothing inside leaves the bounds. */
  overflow: hidden;
  box-shadow:
    0 1px 2px -1px rgba(26, 24, 21, 0.10),
    0 12px 28px -18px rgba(26, 24, 21, 0.30),
    inset 0 0 0 1px rgba(26, 24, 21, 0.055);
  transition: box-shadow 260ms var(--ease), transform 260ms var(--ease);
}
.fam:hover {
  transform: translateY(-4px);
  box-shadow:
    0 2px 4px -2px rgba(26, 24, 21, 0.12),
    0 28px 56px -28px rgba(26, 24, 21, 0.38),
    inset 0 0 0 1px rgba(26, 24, 21, 0.075);
}

.fam__media { overflow: hidden; aspect-ratio: 3 / 2; background: var(--paper-3); }
.fam__media img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  /* 2.5% over 350ms, per the polish pass: 4% over 700ms read as a slow zoom
     effect rather than a hover acknowledgment. */
  transition: transform 350ms var(--ease);
}
.fam:hover .fam__media img { transform: scale(1.025); }

.fam__body {
  flex: 1 1 auto; display: flex; flex-direction: column;
  padding: clamp(18px, 1.6vw, 24px) clamp(18px, 1.6vw, 24px) clamp(20px, 1.8vw, 26px);
}
.fam__name {
  font-family: var(--display); font-size: var(--t-title);
  line-height: 1.35; color: var(--ink);
}
.fam__name a { color: inherit; }
.fam__name a:hover { color: var(--moss); }

.fam__line {
  margin-top: 10px; color: var(--muted);
  font-size: var(--t-sm); line-height: 1.6; text-wrap: pretty;
}
/* The example links are the route into the service pages. margin-top:auto
   pins the list to the card bottom so all three lists start on one baseline
   regardless of how the description wrapped. Luxury v2: the per-row hairlines
   are gone — five rules inside a bordered+shadowed card read as scaffolding.
   One hairline separates the list from the description; the rows are quiet,
   on an 8px rhythm, with a drawn moss chevron that only appears on hover.
   Nothing depends on the hover: the link is fully readable without it. */
.fam__list {
  margin-top: auto; padding-top: 12px;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column;
}
.fam__list a {
  position: relative;
  display: block; padding: 4px 0;
  font-size: 15px; font-weight: 500; color: var(--ink-2);
  transition: color 180ms var(--ease), padding-left 180ms var(--ease);
}
.fam__list a::before {
  content: ''; position: absolute; left: 2px; top: 50%;
  width: 6px; height: 6px;
  border-right: 1.5px solid var(--moss);
  border-bottom: 1.5px solid var(--moss);
  transform: rotate(-45deg) translateY(-70%);
  opacity: 0;
  transition: opacity 180ms var(--ease);
}
.fam__list a:hover { color: var(--moss); padding-left: 16px; }
.fam__list a:hover::before { opacity: 1; }

/* Photo CTA — closes the section instead of trailing off into blank paper.
   Same surface and shadow recipe as .fam / .rquote (one card language, no
   border fighting the shadow); the radius clips the figure via overflow. */
.qcta {
  margin-top: var(--sp-5);
  display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  align-items: stretch;
  background: #FBFAF7;
  border-radius: var(--r);
  overflow: hidden;
  box-shadow:
    0 1px 2px -1px rgba(26, 24, 21, 0.10),
    0 12px 28px -18px rgba(26, 24, 21, 0.30),
    inset 0 0 0 1px rgba(26, 24, 21, 0.055);
}
.qcta__body { padding: clamp(24px, 2.6vw, 38px); align-self: center; }
.qcta__h {
  /* No eyebrow above this heading — the question itself is the hook. */
  margin-top: 0;
  font-family: var(--display); font-size: clamp(25px, 2vw, 32px);
  line-height: 1.2; color: var(--ink); max-width: 20ch;
}
.qcta__p {
  margin-top: 14px; max-width: 46ch;
  color: var(--ink-2); font-size: 16px; line-height: 1.65; text-wrap: pretty;
}
.qcta__actions { margin-top: 26px; display: flex; flex-wrap: wrap; gap: 12px; }
/* Compact estimate band — a contained dark card, not a full-bleed strip.
   Sibling of the .qcta--dark conversion panel on gallery pages (same pine
   surface, same moss primary), but without the photo and with a single
   decision plus one secondary route: the old three-button band stacked the
   actions far right and left the middle of the strip empty. Palette comes
   from .qcta--dark; the markup always carries both classes. */
.qcta--inline {
  margin-top: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 20px clamp(36px, 5vw, 80px);
  padding: clamp(26px, 3vw, 40px) clamp(24px, 3.4vw, 48px);
}
.qcta--inline .qcta__body { padding: 0; }
.qcta--inline .qcta__h { margin-top: 0; max-width: none; }
.qcta--inline .qcta__p { max-width: 54ch; }
.qcta--inline .qcta__actions {
  margin-top: 0;
  display: grid;
  gap: 4px;
  min-width: 250px;
}
.qcta__trust {
  margin: 16px 0 0;
  font-size: 13px;
  letter-spacing: 0;
  color: rgba(244, 241, 234, 0.62);
}
/* The phone route demotes to a text link so the band has one button, not
   three. Mono numerals match the header's phone control. */
.qcta__tel {
  display: block;
  padding: 10px 4px;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 15px; text-align: center;
  color: var(--paper);
  text-decoration: underline;
  text-decoration-color: rgba(244, 241, 234, 0.45);
  text-underline-offset: 4px;
}
.qcta__tel:hover { color: #fff; text-decoration-color: var(--paper); }
/* The image is taken out of flow so the panel height is set by the copy, not
   by the photograph's aspect ratio — a portrait source here stretched the
   panel to 1400px and reopened the dead space this block exists to close. */
.qcta__figure {
  margin: 0; position: relative; overflow: hidden;
  min-height: 260px; background: var(--paper-3);
}
.qcta__figure img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* --------------------------------------------------------------------------
   Featured work — before/after case studies (2026-08-02)

   Three single finished photos became three PAIRS. A finished patio is a
   photograph; a graded subgrade next to the same patio is an argument, and
   the argument is the thing this business actually competes on. Rows stay on
   one aligned grid: the pair fills the media column, the meta column carries
   kicker, title, copy and the route into the matching service page.

   Every frame is the same 4/3 cover crop. The sources are all portrait at
   roughly 0.72, so the crop takes the middle band of each — that is where
   the subject sits in all six, and it also removes the coloured video frame
   baked into the top and bottom edges of the stairs "before".
   -------------------------------------------------------------------------- */

.work { margin-top: var(--sp-4); display: grid; gap: var(--sp-5); }
.work__item { display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); gap: clamp(24px, 3.2vw, 48px); align-items: center; }
/* No alternation. A paired figure already carries its own left-to-right
   order (before, then after); flipping every second row put the "after"
   against the copy on one row and the "before" on the next, and staggered
   the 01/02/03 counters down the page. */

.work__figure { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.ba { margin: 0; display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.ba__label {
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--stone);
}
.ba--after .ba__label { color: var(--moss); }
.ba__frame {
  position: relative; overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: var(--r);
  background: var(--paper-3);
}
.ba__frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* The kicker: counter on its own quiet line, then the service family and the
   county, both in the .label recipe (13px / 600 / 0.12em uppercase). No town:
   which house each of these is on is not confirmed, and an invented town on a
   case study is the one claim on this page nobody could defend. */
.work__kicker {
  display: flex; flex-direction: column; gap: 3px;
  margin-bottom: 12px;
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--stone);
}
.work__index { color: var(--line-3); }
.work__place { color: var(--stone); }
.work__meta h3 { margin-bottom: 8px; }
.work__meta p { color: var(--muted); font-size: var(--t-intro); line-height: 1.65; text-wrap: pretty; }
.work__more { margin-top: var(--sp-2); }
/* The spec <dl> that used to sit under this paragraph was cut in the redesign:
   every row in it restated what the paragraph already says (surface, edge,
   drainage), and three tables cost ~390px to say nothing new. The technical
   detail stays — in the prose, where it reads as craft rather than a parts
   list. Do not re-add it without a spec the copy cannot carry. */

/* --------------------------------------------------------------------------
   Seasonal — one compact, configurable module.

   Was a full section with two service columns and two CTAs that ate major
   space year-round, then a ruled note on paper. Luxury v3 makes it a
   contained evergreen card on the paper run — the estimate card's language
   (shared --r radius, light text, sage eyebrow) at a slimmer weight: a
   concierge note, not a classified ad. Single row on desktop, stacked on
   mobile. Edited in a single commented block in index.html when the
   season turns. Stays typographic: no honest fall photo exists.
   -------------------------------------------------------------------------- */

.season-note {
  display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(20px, 3vw, 48px); align-items: center;
  background: var(--evergreen); color: var(--paper);
  border-radius: var(--r);
  padding: clamp(20px, 2.4vw, 30px) clamp(24px, 3.4vw, 44px);
  box-shadow:
    0 2px 4px -2px rgba(26, 24, 21, 0.16),
    0 24px 48px -32px rgba(26, 24, 21, 0.45);
}
.season-note h2 { margin-top: 12px; color: var(--paper); }
.season-note p {
  margin-top: 10px; max-width: 52ch;
  color: rgba(244, 241, 234, 0.78); font-size: var(--t-intro); line-height: 1.65; text-wrap: pretty;
}
.season-note__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 12px 22px; justify-content: flex-end;
}
@media (max-width: 900px) {
  .season-note { grid-template-columns: 1fr; }
  .season-note__actions { justify-content: flex-start; }
}

/* --------------------------------------------------------------------------
   Why — asymmetric, portrait, pull quote
   -------------------------------------------------------------------------- */

/* Row gap is deliberately smaller than the column gap: the review band spans
   both columns as the section's closing row, and at the full 96px column gap
   it drifted away from the columns it belongs to. */
.why { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); gap: var(--sp-5) clamp(32px, 6vw, 96px); align-items: stretch; }
.why__side { display: flex; min-height: 0; }
.why__figure { position: relative; flex: 1 1 auto; }
/* The figure fills the copy column's height rather than sitting at a fixed 3/4
   crop: the quote card moved out of this column to a full-width band below, so a
   fixed-aspect photo left a hole under it. Taken out of flow like .qcta__figure,
   so column height is set by the copy, not by the photograph. */
.why__figure img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  border-radius: var(--r);
}
.why__stamp {
  position: absolute; right: -18px; bottom: 28px;
  background: var(--paper); padding: 20px 24px;
  border-radius: var(--r);
  box-shadow: 0 22px 44px -26px rgba(26, 24, 21, 0.55);
}
.why__stamp b {
  display: block; font-family: var(--display); font-size: 32px;
  line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 400;
}
.why__stamp span { display: block; margin-top: 6px; font-size: 13px; color: var(--muted); line-height: 1.5; }

.why__cta { margin-top: var(--sp-3); }

/* The review closes the section as a full-width editorial quote (luxury v2):
   no card box — on the continuous paper run a boxed card read as a widget,
   so the quote is oversized Fraunces on the axis with a hairline above it
   and the attribution centered beneath. It used to trail the crew portrait
   in the right column, then ride as a card band; both left it smaller than
   the argument it makes (the only captured review that names masonry AND
   Nelson). Quote and attribution verbatim; only the treatment changed. */
.why__quote {
  grid-column: 1 / -1;
  margin: 0;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
  text-align: center;
}
.why__quote blockquote {
  margin: 0 auto; font-family: var(--display); font-weight: 400;
  font-size: clamp(25px, 2.6vw, 32px); line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--ink); text-wrap: balance; max-width: 34ch;
}
.why__quote figcaption {
  margin-top: 18px;
  display: flex; flex-direction: column; gap: 2px; align-items: center;
}
.why__quote figcaption b { font-size: 15px; font-weight: 600; color: var(--ink); }
.why__quote figcaption span { font-size: 13px; color: var(--stone); }

/* Two columns of two. As a single ruled column the four reasons ran ~460px and
   stretched the section past the photograph beside it; the grid matches the
   figure's height and reads faster. */
.reasons {
  margin-top: var(--sp-4); border-top: 1px solid var(--line-2);
  display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(24px, 3vw, 48px);
}
.reasons > div {
  display: grid; grid-template-columns: 36px 1fr; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--line); align-items: baseline;
}
.reasons h3 { font-family: var(--body); font-size: 16px; font-weight: 600; color: var(--ink); }
.reasons p { margin-top: 6px; color: var(--muted); font-size: var(--t-sm); line-height: 1.6; text-wrap: pretty; }

/* --------------------------------------------------------------------------
   Process — numeral timeline (luxury v3)

   The mono "Step 01" labels, the 2px top rule and the ruled column grid are
   gone. Each step is anchored by an oversized Fraunces numeral at the 32
   step in --stone; one horizontal hairline (--line-2) runs above the four
   numerals with a small moss diamond at each step — the same drawn mark as
   the towns strip. Below 1180px the grid wraps to two columns and the
   timeline drops (a line across a 2+2 wrap reads as broken); the numerals
   stay and carry the sequence alone.
   -------------------------------------------------------------------------- */

.steps {
  position: relative;
  margin-top: var(--sp-4);
  padding-top: 26px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 3vw, 40px) clamp(20px, 2.5vw, 36px);
}
@media (min-width: 1180px) {
  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* The timeline hairline only exists when all four steps sit on one row. */
  .steps::before {
    content: '';
    position: absolute;
    top: 26px; left: 0; right: 0;
    border-top: 1px solid var(--line-2);
  }
}
.step { position: relative; padding-top: 22px; }
@media (min-width: 1180px) {
  .step::before {
    content: '';
    position: absolute;
    top: -3px; left: 0;
    width: 7px; height: 7px;
    background: var(--moss);
    transform: rotate(45deg);
  }
}
.step__num {
  display: block;
  font-family: var(--display); font-weight: 500;
  font-size: 32px; line-height: 1; letter-spacing: -0.01em;
  color: var(--stone);
}
.steps h3 { font-family: var(--display); font-size: var(--t-title); margin: 14px 0 10px; }
.steps p { color: var(--muted); font-size: var(--t-sm); line-height: 1.6; text-wrap: pretty; }

/* --------------------------------------------------------------------------
   Reviews — three static quotes, no carousel.

   The rotator (7s autoplay, prev/next/pause, counter, a JS copy of the review
   text) was machinery around the most scannable content on the page, and it
   needed a second stacked-card implementation for phones. Three static quotes
   read faster, cost no JS, and cannot malfunction with scripts off.
   -------------------------------------------------------------------------- */

.rgrid {
  margin-top: var(--sp-4);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.rquote {
  display: flex; flex-direction: column; margin: 0;
  padding: clamp(18px, 1.8vw, 24px);
  background: #FBFAF7;
  border-radius: var(--r);
  box-shadow:
    0 1px 2px -1px rgba(26, 24, 21, 0.10),
    0 12px 28px -18px rgba(26, 24, 21, 0.30),
    inset 0 0 0 1px rgba(26, 24, 21, 0.055);
}
/* Oversized Fraunces opening quote hanging above the text (luxury v3).
   Decorative only, toned to the card's quietest paper/line colours so it
   reads as a watermark, not punctuation — the blockquote copy keeps its own
   quotation marks. */
.rquote::before {
  content: '\201C';
  font-family: var(--display); font-weight: 500;
  font-size: 42px; line-height: 0.7;
  color: var(--paper-3);
  margin-bottom: 10px;
}
.rquote blockquote {
  margin: 0; font-family: var(--display); font-weight: 400;
  font-size: clamp(18px, 1.5vw, 20px); line-height: 1.4;
  color: var(--ink); text-wrap: pretty;
}
.rquote figcaption {
  margin-top: auto; padding-top: 20px;
  display: flex; flex-direction: column; gap: 2px;
}
.rquote figcaption b { font-size: 15px; font-weight: 600; color: var(--ink); }
.rquote figcaption span { font-size: 13px; color: var(--stone); }

/* Score chip and stars are one row now, not a numeral floating beside two
   links: the stars are what carries "5.0" at a glance. */
.rgrid__foot {
  margin-top: var(--sp-4);
  display: flex; align-items: center; justify-content: center;
  gap: 10px 20px; flex-wrap: wrap; text-align: center;
}
.rgrid__rating { display: flex; align-items: center; gap: 10px; }
.rgrid__score {
  font-family: var(--display); font-size: 32px; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Areas — the single dark interlude (luxury v3)

   Towns regrouped by county under small sage eyebrows, matching the
   /service-areas/ hub (Putnam has no town pages yet, so those are names,
   not links). The per-row hairlines are gone; rows separate with spacing.
   The chevron is a hover/focus affordance only — under 900px it is always
   on, so touch users get the same cue. Behind it all, the sage topo
   contours from .svc-hero--field run at their native low opacity; the
   strokes sit at 7-10% alpha and do not move text contrast.
   -------------------------------------------------------------------------- */

#areas { position: relative; overflow: hidden; }
#areas::before {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(58vw, 760px);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 900 640'%3E%3Cpath d='M490.0 320.0L492.8 324.5L494.5 329.2L494.7 334.1L493.2 338.9L490.1 343.1L485.7 346.8L480.5 349.8L474.7 352.2L468.9 354.2L463.3 355.9L457.8 357.4L452.4 358.9L446.9 360.2L441.4 361.2L435.7 361.8L430.0 361.8L424.4 361.3L418.9 360.2L413.7 358.7L408.7 357.1L403.7 355.4L398.7 353.7L393.4 352.1L388.0 350.3L382.4 348.1L377.2 345.4L372.7 342.1L369.3 338.1L367.3 333.7L366.9 329.0L367.9 324.4L370.0 320.0L372.7 315.9L375.4 312.2L377.9 308.6L379.9 305.0L381.5 301.3L383.0 297.4L384.8 293.3L387.2 289.2L390.4 285.3L394.6 281.9L399.7 279.2L405.4 277.2L411.5 276.0L417.7 275.4L423.9 275.3L430.0 275.4L436.0 275.8L442.0 276.5L447.9 277.5L453.6 279.0L458.8 281.2L463.4 284.0L467.2 287.4L470.1 291.1L472.3 295.0L474.0 298.8L475.7 302.4L477.6 305.8L480.1 309.1L483.2 312.4L486.6 316.0L490.0 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M549.4 320.0L550.1 328.5L547.9 336.9L543.2 344.7L536.7 351.8L529.2 358.2L521.3 363.9L513.5 369.4L505.9 374.6L498.2 379.8L490.1 384.8L481.4 389.3L472.0 393.1L461.9 395.8L451.4 397.3L440.6 397.8L430.0 397.4L419.5 396.5L409.2 395.4L398.7 394.2L388.0 393.0L377.0 391.5L365.8 389.2L354.9 385.9L345.1 381.2L336.9 375.0L331.2 367.5L328.0 359.2L327.3 350.6L328.5 342.2L330.8 334.2L333.2 326.9L334.9 320.0L335.7 313.3L335.4 306.5L334.7 299.2L334.2 291.4L334.7 283.3L336.8 275.2L341.0 267.4L347.2 260.4L355.2 254.4L364.5 249.4L374.6 245.3L385.2 242.1L396.0 239.4L407.1 237.2L418.5 235.7L430.0 235.0L441.5 235.6L452.8 237.6L463.2 241.1L472.5 246.1L480.4 252.1L486.9 258.7L492.4 265.2L497.5 271.4L502.9 276.9L509.0 282.0L516.1 286.9L524.1 291.9L532.3 297.6L539.9 304.3L545.9 311.8L549.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M598.1 320.0L592.1 331.5L584.7 342.1L576.7 352.0L569.0 361.4L561.6 370.7L554.4 379.9L546.8 389.0L538.1 397.9L528.1 406.1L516.5 413.2L503.5 419.1L489.6 423.6L475.2 427.2L460.4 430.1L445.4 432.8L430.0 435.4L413.9 437.9L396.9 439.8L379.2 440.5L361.4 439.3L344.2 435.5L328.9 429.0L316.3 419.8L307.1 408.5L301.4 396.0L298.7 383.1L298.0 370.8L297.9 359.4L297.3 349.0L295.5 339.3L292.3 329.8L288.1 320.0L283.9 309.6L280.7 298.6L279.7 287.2L281.4 275.7L286.0 264.6L293.3 254.2L302.7 244.8L313.5 236.1L325.3 228.1L337.8 220.6L351.0 213.6L365.1 207.2L380.2 201.9L396.4 198.3L413.1 196.8L430.0 197.8L446.2 201.2L461.3 206.7L474.9 213.4L487.2 220.6L498.6 227.6L510.0 233.8L522.0 239.3L535.0 244.4L549.1 249.6L563.4 255.8L577.0 263.4L588.5 272.7L596.8 283.6L601.2 295.5L601.4 307.8L598.1 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M634.3 320.0L628.1 334.0L622.2 347.5L616.5 360.7L610.2 373.8L602.8 386.5L593.4 398.6L582.0 409.8L568.6 419.8L553.8 428.6L538.2 436.6L522.2 444.1L505.8 451.8L488.9 459.9L471.0 468.3L451.4 476.4L430.0 483.3L407.0 487.8L383.3 488.9L360.1 485.9L338.7 478.6L320.4 467.7L305.6 454.1L294.3 439.1L285.6 424.0L278.4 409.6L271.2 396.4L263.2 384.2L254.0 372.5L243.9 360.6L234.0 348.1L225.7 334.5L220.0 320.0L218.1 305.0L220.0 289.9L225.6 275.4L233.9 261.5L244.1 248.4L255.4 236.0L267.4 223.9L280.2 212.2L294.2 200.9L309.9 190.5L327.4 181.8L346.8 175.4L367.6 171.9L388.9 171.2L409.9 173.1L430.0 176.8L449.0 181.2L467.2 185.3L485.4 188.6L504.3 190.8L524.6 192.5L546.4 194.6L569.0 198.1L591.2 204.0L611.4 212.8L627.9 224.8L639.7 239.3L646.2 255.5L647.7 272.4L645.3 289.2L640.4 305.1L634.3 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M682.4 320.0L680.7 337.8L676.8 355.3L669.9 372.4L659.8 388.5L646.7 403.4L631.6 417.0L615.3 429.5L598.7 441.5L582.3 453.6L565.9 466.4L548.8 480.0L529.9 493.7L508.6 506.7L484.5 517.4L458.0 524.5L430.0 526.8L402.1 524.1L375.7 516.6L351.9 505.4L331.1 491.9L312.8 477.9L296.0 464.4L279.4 452.1L262.0 441.0L243.1 430.4L223.3 419.5L203.5 407.2L185.4 393.0L170.6 376.7L160.4 358.6L155.3 339.5L155.1 320.0L159.0 300.8L165.7 282.2L174.4 264.2L184.3 246.7L195.5 229.7L208.3 213.3L223.5 198.0L241.6 184.3L262.6 173.2L286.2 165.1L311.4 160.3L337.0 158.3L361.9 158.3L385.5 159.0L408.0 159.1L430.0 157.7L452.6 154.6L476.9 150.1L503.5 145.6L532.1 142.5L561.9 142.4L591.0 146.5L617.7 155.3L640.2 168.6L657.5 185.6L669.4 204.8L676.6 225.1L680.4 245.3L682.0 265.0L682.7 283.8L682.9 302.1L682.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M740.0 320.0L737.2 341.8L729.8 362.9L718.9 383.1L705.8 402.2L691.7 420.7L677.5 439.1L663.3 457.9L648.4 477.3L631.6 496.9L611.7 515.8L587.6 532.4L559.5 545.1L528.0 552.6L494.7 554.3L461.5 550.5L430.0 542.2L401.1 531.1L374.9 519.3L350.6 508.4L326.8 499.3L302.2 492.2L275.8 486.2L247.5 480.1L218.3 472.4L189.6 462.0L163.4 448.3L141.2 431.1L124.1 411.2L112.2 389.4L104.9 366.6L101.4 343.3L100.6 320.0L102.0 296.7L105.7 273.6L112.4 250.6L123.0 228.4L138.5 207.8L159.0 189.6L184.3 174.8L213.0 163.8L243.3 156.2L273.5 151.3L302.1 147.7L328.6 143.8L353.4 138.2L377.6 130.3L402.7 120.4L430.0 109.7L460.1 99.9L492.7 93.0L526.6 90.8L560.0 94.1L591.0 103.1L618.2 117.2L641.0 134.9L659.6 154.7L675.0 175.2L688.3 195.7L700.6 215.9L712.3 235.8L723.1 256.0L732.2 276.7L738.2 298.1L740.0 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M780.8 320.0L778.3 344.7L774.3 369.3L769.9 394.2L765.1 419.9L759.1 446.7L750.2 474.0L736.4 501.1L716.3 526.1L689.2 547.4L655.6 563.1L617.2 572.2L576.5 574.6L535.9 571.3L497.5 564.3L462.2 555.6L430.0 547.6L399.7 541.5L369.8 537.9L338.9 536.3L306.2 535.2L271.9 532.9L237.1 527.9L203.3 518.9L172.1 505.7L144.7 488.6L121.5 468.4L102.1 446.2L85.7 422.7L71.6 398.3L59.4 373.1L49.5 347.0L43.0 320.0L41.5 292.5L46.5 265.1L59.0 239.0L78.8 215.2L104.7 194.8L134.6 177.9L166.1 164.0L196.8 152.1L225.5 140.6L251.8 128.0L276.7 113.5L301.8 97.1L328.8 79.9L359.1 63.5L393.1 50.2L430.0 42.0L468.3 40.2L506.0 45.1L541.2 56.0L572.9 71.5L600.9 89.7L625.8 109.0L648.8 128.1L670.9 146.6L692.9 164.7L714.6 183.1L735.0 202.6L753.0 223.7L767.0 246.4L776.2 270.4L780.6 295.1L780.8 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M821.4 320.0L831.2 348.4L840.4 378.8L846.7 411.0L847.3 444.5L839.7 477.7L822.1 508.6L794.2 535.2L757.5 555.8L714.6 569.7L668.8 577.3L623.1 580.1L579.6 580.1L539.4 579.7L502.0 580.5L466.0 583.3L430.0 587.8L392.7 592.6L353.8 596.0L313.7 596.1L273.7 591.7L235.4 582.1L200.0 567.9L168.0 549.8L139.4 529.3L113.2 507.2L88.7 484.2L65.4 460.3L43.5 435.3L24.4 408.6L9.8 380.2L1.8 350.4L1.9 320.0L10.5 290.2L26.7 262.2L48.4 236.7L72.9 213.5L97.5 192.0L120.2 171.0L140.5 149.0L159.1 125.0L178.1 99.0L199.7 71.8L226.1 45.4L258.6 22.1L297.0 4.4L340.0 -6.0L385.1 -8.3L430.0 -3.0L472.6 8.4L511.8 23.7L547.6 40.9L580.5 58.3L611.8 75.1L642.2 91.4L672.0 107.7L700.6 125.1L727.1 144.4L750.2 165.9L769.1 189.5L783.7 214.5L794.8 240.3L803.8 266.5L812.3 292.9L821.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right -160px center;
  background-size: 900px auto;
  pointer-events: none;
}
#areas > .wrap { position: relative; z-index: 1; }
@media (max-width: 900px) {
  #areas::before {
    width: 100%;
    background-position: right -280px bottom -240px;
    background-size: 680px auto;
  }
}

.areas {
  display: grid;
  grid-template-columns: minmax(0, 4.5fr) minmax(0, 5.5fr);
  gap: clamp(30px, 6vw, 72px);
  align-items: start;
}
.areas__intro .label { display: block; }
.areas__intro .d2 { margin-top: 18px; max-width: 22ch; text-wrap: balance; }
.areas__lede {
  margin-top: 22px;
  max-width: 38ch;
  font-size: var(--t-sm);
  line-height: 1.65;
  color: rgba(244, 241, 234, 0.78);
  text-wrap: pretty;
}

.areas__panel {
  display: grid;
  gap: var(--sp-3);
}
.areas__county { display: block; margin-bottom: 8px; }

.areas__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(20px, 4vw, 44px);
}
.areas__link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  min-height: 48px;
  padding: 10px 0;
  color: rgba(244, 241, 234, 0.92);
  transition: color 180ms var(--ease);
}
.areas__link::after {
  content: '';
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg) translate(-4px, 1px);
  opacity: 0;
  transition: opacity 180ms var(--ease), transform 180ms var(--ease);
}
.areas__link:hover,
.areas__link:focus-visible {
  color: var(--sage);
}
.areas__link:hover::after,
.areas__link:focus-visible::after {
  opacity: 1;
  transform: rotate(-45deg) translate(2px, 1px);
}
/* No hover to depend on under 900px: the chevron is always on. */
@media (max-width: 900px) {
  .areas__link::after { opacity: 0.5; transform: rotate(-45deg) translateY(1px); }
}
.areas__name {
  font-family: var(--display);
  font-size: var(--t-title);
  line-height: 1.35;
}
/* Putnam towns are not links (no pages yet): same Fraunces tier, quieter
   tone so they read as names, not disabled controls. 0.65 alpha on pine
   composites to ~5.2:1 — AA. */
.areas__list--plain li { padding: 10px 0; min-height: 48px; display: flex; align-items: baseline; }
.areas__list--plain .areas__name { color: rgba(244, 241, 234, 0.65); }

.areas__foot { font-size: var(--t-sm); }

/* --------------------------------------------------------------------------
   FAQ — editorial split + accessible accordion (homepage)
   -------------------------------------------------------------------------- */

.faq-split {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
  gap: clamp(30px, 6vw, 80px);
  align-items: start;
}
.faq-split__intro .label { display: block; }
/* The homepage FAQ opens with the h2 directly (no eyebrow), so it carries no
   top margin — it must top-align with the accordion column beside it. */
.faq-split__intro .d2 { max-width: 20ch; text-wrap: balance; }
.faq-split__lede {
  margin-top: 20px;
  max-width: 34ch;
  font-size: var(--t-sm);
  line-height: 1.65;
  color: var(--muted);
  text-wrap: pretty;
}
.faq-split__phone {
  margin-top: 28px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink-2);
}
.faq-split__phone--mobile { display: none; }
.faq-split__phone a {
  color: var(--moss);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(74, 107, 79, 0.35);
}
.faq-split__phone a:hover { color: var(--ink); text-decoration-color: currentColor; }

/* Service pages keep the older two-column open list */
.faq:not(.faq--accordion) {
  margin-top: clamp(30px, 4vw, 52px);
  columns: 2;
  column-gap: clamp(36px, 5vw, 84px);
}
.faq:not(.faq--accordion) > details { break-inside: avoid; margin-bottom: clamp(20px, 2.2vw, 28px); }
.faq:not(.faq--accordion) .faq__item > summary {
  list-style: none; cursor: default;
  display: flex; align-items: baseline; gap: 12px;
}
.faq:not(.faq--accordion) .faq__item > summary::-webkit-details-marker { display: none; }
.faq:not(.faq--accordion) .faq__item > summary h3 { display: inline; }
.faq:not(.faq--accordion) .faq__item > summary::after { content: ''; }
.faq:not(.faq--accordion) h3 {
  font-family: var(--display); font-size: var(--t-title);
  line-height: 1.2; margin-bottom: 10px; color: var(--ink);
}
.faq:not(.faq--accordion) p { color: var(--muted); font-size: 15px; line-height: 1.6; text-wrap: pretty; }

.faq--accordion { margin-top: 0; border-top: 1px solid var(--line); }
.faq-acc__item { border-bottom: 1px solid var(--line); }
.faq-acc__heading { margin: 0; font: inherit; }
.faq-acc__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  min-height: 48px;
  padding: 16px 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  font: inherit;
}
.faq-acc__q {
  font-family: var(--display);
  font-size: clamp(18px, 1.8vw, 20px);
  line-height: 1.35;
  text-wrap: pretty;
}
.faq-acc__icon {
  flex: 0 0 auto;
  width: 10px; height: 10px;
  border-right: 1.5px solid var(--stone);
  border-bottom: 1.5px solid var(--stone);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 200ms var(--ease), border-color 200ms var(--ease);
}
.faq-acc__trigger[aria-expanded="true"] .faq-acc__icon {
  transform: rotate(-135deg) translate(-3px, -3px);
  border-color: var(--moss);
}
.faq-acc__trigger:hover .faq-acc__q { color: var(--moss); }
.faq-acc__trigger:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
.faq-acc__panel { padding-bottom: 18px; }
.faq-acc__panel[hidden] { display: none; }
.faq-acc__panel p {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.6;
  text-wrap: pretty;
  max-width: 52ch;
}
@media (prefers-reduced-motion: reduce) {
  .faq-acc__icon { transition: none; }
}


/* Inline conversion rail. This sits at the close of the featured-work gallery,
   where the page previously ran thousands of pixels of its most persuasive
   material with nothing to act on. */
.railcta {
  grid-column: 1 / -1;
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 18px clamp(24px, 4vw, 48px);
}
.railcta__p {
  flex: 1 1 22ch; margin: 0;
  font-family: var(--display); font-size: clamp(18px, 1.5vw, 20px);
  line-height: 1.4; color: var(--ink); text-wrap: pretty;
}
.railcta__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; }
@media (max-width: 620px) {
  .railcta__actions { width: 100%; }
  .railcta__actions .btn { flex: 1 1 100%; }
}

/* --------------------------------------------------------------------------
   Estimate — contained dark panel (luxury pass 2026-08-01)

   Was a full-bleed pine band. The Lawnx reference renders the consultation
   form as a rounded dark card sitting on a light section; translated here
   as an evergreen panel floating on paper with the shared --r radius, facts
   left, form right. build.py slices this section into every generated page,
   so the panel propagates site-wide. The colour story inside is unchanged:
   paper headings, sage links, the paper form card.
   -------------------------------------------------------------------------- */

.section--estimate { background: var(--paper); }
.estimate {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(36px, 6vw, 96px); align-items: start;
  background: var(--evergreen); color: var(--paper);
  border-radius: var(--r);
  padding: clamp(24px, 3.6vw, 48px);
  box-shadow:
    0 2px 4px -2px rgba(26, 24, 21, 0.16),
    0 36px 72px -40px rgba(26, 24, 21, 0.50);
}
.estimate h2 { color: var(--paper); }
.estimate__lede { margin-top: var(--sp-2); color: rgba(244, 241, 234, 0.8); max-width: 40ch; }
/* Mobile-only compact contact line — display:none at desktop widths, swapped
   in for .facts by the 900px query below. */
.estimate__alt { display: none; margin-top: 18px; font-size: 15px; line-height: 1.6; color: rgba(244, 241, 234, 0.8); max-width: 38ch; }
.estimate__alt a { color: var(--sage); font-weight: 600; }
.estimate__alt a:hover { color: var(--paper); }
.facts { margin-top: var(--sp-3); border-top: 1px solid var(--line-dark); }
.facts > div { padding: 15px 0; border-bottom: 1px solid var(--line-dark); display: grid; grid-template-columns: 90px 1fr; gap: 18px; align-items: baseline; }
.facts dt { font-family: var(--body); font-size: 13px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(244, 241, 234, 0.6); }
.facts dd { margin: 0; color: var(--paper); font-size: 16px; }
/* Mono is reserved for true numerals (luxury v3 sweep): the phone link gets
   it; the text-message and email links are words and addresses, so they
   stay in Geist like every other label. */
.facts a { color: var(--sage); }
.facts a[href^="tel:"] { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.facts a:hover { color: var(--paper); }

/* The form column runs taller than the facts column, so on desktop the phone
   number used to scroll away exactly while someone was deciding whether to
   type or just call. Pinned from 901px up; below that the column is dissolved
   with display:contents and reordered around the form, so it must not stick. */
@media (min-width: 901px) {
  .estimate > div:not(.form-card) {
    position: sticky;
    top: calc(var(--header-h) + var(--sp-3));
    align-self: start;
  }
}

/* The colour reset is load-bearing: .section--dark sets color:var(--paper) and this
   card sets background:var(--paper), so without it every heading inside the card —
   including the post-submit confirmation — inherits paper-on-paper at 1.00:1. */
.form-card { background: var(--paper); color: var(--ink); padding: clamp(24px, 2.9vw, 40px); border-radius: var(--r); }
.form-card h3 { font-family: var(--display); font-size: clamp(20px, 2vw, 25px); }
.form-success h3 { color: var(--moss); }
.form-card__note { margin-top: 8px; color: var(--stone); font-size: 15px; }
.field-grid { margin-top: var(--sp-3); display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field { display: flex; flex-direction: column; gap: 7px; }
.field__opt {
  font-weight: 400; text-transform: none; letter-spacing: 0;
  color: var(--stone); opacity: 0.8;
}
.field label { font-size: 12px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--stone); display: flex; gap: 5px; align-items: center; }
.req-dot { color: var(--error); font-size: 12px; }
/* Boxed soft-rect fields on the shared radius (luxury pass 2026-08-01) —
   the underline-only field died with the square era. The border is --line-3
   because the border of an interactive control falls under WCAG 1.4.11
   non-text contrast (3:1); it measures 3.14:1 on paper, 3.03:1 on the
   card surface. Focus adds a moss ring instead of changing border width,
   so nothing shifts layout. */
.field input, .field select, .field textarea {
  border: 1px solid var(--line-3); border-radius: var(--r);
  padding: 12px 14px; font-size: 16px; color: var(--ink);
  background: #FBFAF7; width: 100%;
  transition: border-color 180ms var(--ease), box-shadow 180ms var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--moss);
  box-shadow: 0 0 0 1px var(--moss);
}
.field textarea { resize: vertical; }
/* Placeholders used to inherit the field's own 16px ink, so "Marisol Etienne"
   read as a pre-filled value and users tabbed past required fields believing
   they were done. Lighter + italic marks them as examples; entered values keep
   full ink and roman. Opacity is set explicitly because Firefox otherwise
   applies its own 0.54 on top of the colour. */
.field input::placeholder,
.field textarea::placeholder {
  color: var(--stone);
  opacity: 0.62;
  font-style: italic;
}
/* Same problem in the optional "How did you hear about us?" select: its empty
   first option rendered in value ink. Only matches while the placeholder
   option is the selected one — a chosen answer keeps full colour. */
.field select:has(> option[value=""]:checked) {
  color: var(--stone);
  font-style: italic;
}
.field-error { font-size: 13px; color: var(--error); }
.field-error[hidden] { display: none; }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] {
  border-color: var(--error);
  box-shadow: 0 0 0 1px var(--error);
}

.needs { margin-top: var(--sp-3); }
.needs__label { font-size: 12px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--stone); }
.chips { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  border: 1px solid var(--line-3); border-radius: var(--r); background: transparent;
  color: var(--muted); padding: 11px 15px; min-height: 44px; font-size: 15px; cursor: pointer;
  transition: all 180ms var(--ease);
}
.chip[aria-pressed="true"] { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.chip[aria-pressed="true"]::before { content: '\2713\00a0'; }
.chip:active { transform: translateY(1px); }

.field--stack { margin-top: 26px; }

/* Two-step form — progressive enhancement only (2026-08-02).
   The markup ships BOTH steps visible and the step chrome [hidden], so a
   browser without JS renders and posts the complete form exactly as before.
   app.js adds .form--stepped, unhides the chrome, and hides step 2 until
   the phone number validates. Nothing here may become load-bearing. */
.fstep[hidden] { display: none; }
.fstep__nav { margin-top: var(--sp-4); }
.fstep__nav .btn { width: 100%; }
.fstep__nav[hidden] { display: none; }
.fstep__back {
  display: block; width: 100%;
  margin-top: 12px; padding: 12px 0; min-height: 44px;
  background: none; border: 0;
  font-size: 15px; font-weight: 600; color: var(--moss);
  cursor: pointer; text-align: center;
}
.fstep__back:hover { color: var(--ink); }
/* display:block above outranks the UA [hidden] rule, so this is load-bearing:
   without it the Back control sits under step 1 with nothing to go back to. */
.fstep__back[hidden] { display: none; }
.form-steps {
  margin-top: 10px;
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--stone);
}
.form-steps[hidden] { display: none; }

/* Photo upload. Dashed edge so it reads as a drop target rather than another
   text field; the button inside is the only control, so it carries the height. */
.field--file input[type="file"] {
  display: flex; align-items: center;
  min-height: 48px; width: 100%;
  padding: 8px 12px; font-size: 15px; color: var(--muted);
  background: #FBFAF7;
  border: 1px dashed var(--line-3); border-radius: var(--r);
  cursor: pointer;
}
.field--file input[type="file"]::file-selector-button {
  margin-right: 12px; padding: 10px 14px; min-height: 44px;
  border: 0; border-radius: var(--r);
  background: var(--paper-2); color: var(--ink-2);
  font-family: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer;
}
.field--file input[type="file"]:hover::file-selector-button { background: var(--paper-3); }
.field--file input[type="file"]:focus {
  border-color: var(--moss); border-style: solid;
  box-shadow: 0 0 0 1px var(--moss);
}
.field-help { font-size: 13px; color: var(--stone); }
.field-help[hidden] { display: none; }
.form-submit { margin-top: var(--sp-4); width: 100%; background: var(--moss); color: var(--paper); border: 0; border-radius: var(--r); padding: 19px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 200ms var(--ease); }
.form-submit:hover { background: var(--ink); }
.form-submit[aria-disabled="true"] { opacity: 0.6; cursor: progress; }
.form-trust {
  margin-top: var(--sp-3); padding: 12px 16px;
  /* Quiet tinted panel on the form card's paper surface — no directional
     border; the side-tab left rule fought the soft-rect card language.
     --muted measures 5.50 on paper-2 (AA). */
  background: var(--paper-2);
  border-radius: var(--r);
  font-size: 15px; line-height: 1.6; color: var(--muted); text-wrap: pretty;
}
.form-trust b { color: var(--ink); font-weight: 600; }
.form-foot { margin-top: 14px; font-size: 13px; color: var(--stone); }
.form-foot--error { color: var(--error); font-weight: 500; }

.form-success[hidden] { display: none; }
.form-success { min-height: 380px; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 14px; }
.form-success h3 { font-family: var(--display); font-size: 32px; }
.form-success p { color: var(--muted); max-width: 38ch; line-height: 1.65; }
.linkbtn { background: none; border: 0; padding: 0; font-size: 15px; font-weight: 600; color: var(--moss); cursor: pointer; }
.linkbtn:hover { color: var(--ink); }

/* --------------------------------------------------------------------------
   Footer — two groups, not a four-column link farm
   -------------------------------------------------------------------------- */

.footer { background: var(--evergreen); color: rgba(244, 241, 234, 0.72); padding: clamp(28px, 3vw, 40px) 0 0; }
/* auto-fit rather than fixed tracks: six children (a double-width brand and
   five link groups) must resolve to ONE row at 1280 or the footer doubles in
   height. minmax(140px) is the value that clears it — at 150px the row wrapped.
   Seven spans is the ceiling: the content column is 1165px, so an eighth span
   only fits by dropping the track floor to ~121px, at which point every
   service group wraps and the footer is taller than it started. Verified. */
.footer__top { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: clamp(16px, 2vw, 28px); padding-bottom: var(--sp-4); }
.footer__brand { grid-column: span 2; min-width: 0; }
@media (max-width: 620px) { .footer__brand { grid-column: span 1; } }
.footer__logo { height: 40px; width: auto; display: block; }
.footer__blurb { margin-top: var(--sp-2); max-width: 34ch; font-size: 15px; line-height: 1.6; }
.footer__tel { display: inline-block; margin-top: var(--sp-2); font-family: var(--mono); font-size: 20px; color: var(--paper); font-variant-numeric: tabular-nums; }
.footer__tel:hover { color: var(--sage); }
.footer__hours { display: block; margin-top: 6px; font-size: 14px; }
.footer__h { font-family: var(--body); font-size: 13px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(244, 241, 234, 0.5); margin: 0; }
.footer__links { margin-top: 16px; display: flex; flex-direction: column; }
.footer__links a { padding: 8px 0; font-size: 15px; color: rgba(244, 241, 234, 0.82); }
.footer__links a:hover { color: var(--sage); }

/* Footer groups ------------------------------------------------------------
   Measured at 360px before this: the footer ran 2240px — 3.1 full screens —
   as a single column of six blocks and 31 links. The columns collapse on
   phones using the same small-caps + chevron language as the mobile menu.
   They are <details open> in the markup, so desktop and no-JS keep every link
   visible; script only closes them below the breakpoint. */

.footer__group summary { list-style: none; cursor: default; }
.footer__group summary::-webkit-details-marker { display: none; }

@media (max-width: 620px) {
  .footer__top { gap: 0; padding-bottom: 22px; }
  .footer__brand { padding-bottom: 26px; }
  .footer__group:last-of-type { border-bottom: 1px solid var(--line-dark); }
  .footer__group { border-top: 1px solid var(--line-dark); }
  .footer__group summary {
    cursor: pointer; display: flex; align-items: center; justify-content: space-between;
    padding: 17px 0; min-height: 44px; margin: 0;
  }
  .footer__group summary::after {
    content: ''; width: 8px; height: 8px; flex: 0 0 auto; margin-left: 16px;
    border-right: 1.5px solid var(--sage); border-bottom: 1.5px solid var(--sage);
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform 200ms var(--ease);
  }
  .footer__group[open] summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
  .footer__group .footer__links { margin-top: 0; padding-bottom: 10px; }
  .footer__group .footer__links a { padding: 11px 0 11px 14px; }
  .footer__bar { flex-direction: column; gap: 14px; padding-top: 20px; }
  .footer__legal { gap: 6px 18px; }
}

.footer__bar { border-top: 1px solid var(--line-dark); padding: var(--sp-2) 0 var(--sp-4); display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; font-size: 13px; }
.footer__bar a { color: rgba(244, 241, 234, 0.72); }
.footer__bar a:hover { color: var(--sage); }
.footer__legal { display: flex; gap: 22px; flex-wrap: wrap; }

.callbar { display: none; }

/* Desktop sticky estimate — Bellantoni floating CTA behavior, Baudi square btn.
   Mobile keeps .callbar; this must not compete with it. */
.float-cta {
  display: none;
}
@media (min-width: 901px) {
  .float-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    right: 28px;
    bottom: 28px;
    z-index: 40;
    min-height: 52px;
    padding: 14px 26px;
    background: var(--moss);
    color: var(--paper);
    font-family: var(--body);
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0;
    border-radius: var(--r);
    box-shadow:
      0 2px 4px -2px rgba(26, 24, 21, 0.18),
      0 18px 36px -18px rgba(26, 24, 21, 0.45);
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    transition: opacity 240ms var(--ease), transform 240ms var(--ease),
                background 200ms var(--ease);
  }
  .float-cta:hover { background: var(--ink); color: var(--paper); }
  .float-cta[data-hidden="false"] {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
}
@media (prefers-reduced-motion: reduce) {
  .float-cta { transition: none; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 1280px) {
  .header__inner { gap: clamp(16px, 2.5vw, 32px); }
  .nav > a,
  .nav__btn { font-size: 14px; padding: 8px 10px; }
  .header .btn { font-size: 14px; padding: 11px 16px; }
  .header .btn--tel { padding: 11px 14px; }
}

/* Burger collapse moved 1120 -> 940 (2026-08-02). Between those widths the
   phone control and the estimate button were being hidden on a viewport with
   room for both — a laptop at 1024 got a hamburger and no visible number.
   The two links the nav can afford to lose in that band carry .nav--md-hide;
   Services, Service areas and Our work stay, so every route survives. */
@media (max-width: 1120px) and (min-width: 941px) {
  .nav--md-hide { display: none; }
}

@media (max-width: 940px) {
  .nav, .header__end { display: none; }
  .burger { display: flex; }
}

@media (max-width: 1100px) {
  .stats__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .head { grid-template-columns: 1fr; gap: 22px; }
  .head__intro { padding-bottom: 0; }

  .work__item,
  .work__item:nth-child(even) { grid-template-columns: 1fr; gap: 20px; margin: 0; }
  .work__item:nth-child(even) .work__figure { order: 0; }
  .work__item:nth-child(even) .work__meta  { order: 0; }

  .why { grid-template-columns: 1fr; gap: 40px; }
  .why__side { display: block; }
  .why__figure { max-width: 280px; }
  /* Back in flow on phones: the desktop stretch-to-column trick would leave an
     absolute image with nothing to measure against in a single-column grid. */
  .why__figure img { position: static; width: 100%; height: auto; aspect-ratio: 3 / 4; }
  .why__stamp { right: auto; left: 20px; bottom: -22px; }
  .reasons { margin-top: 36px; }

  .areas, .estimate, .faq-split { grid-template-columns: minmax(0, 1fr); }

  .areas__intro .d2,
  .faq-split__intro .d2 { max-width: none; }

  .faq-split__phone--desktop { display: none; }
  .faq-split__phone--mobile {
    display: block;
    margin-top: 24px;
    padding-bottom: 8px;
  }

  .faq:not(.faq--accordion) { columns: 1; }

  .stats__grid { gap: 22px 28px; }

  /* On phones the five-row facts list costs ~250px beside the form; the compact
     one-line alternative (.estimate__alt) carries phone, text and hours instead.
     Desktop keeps the full list and never sees the line. */
  .facts { display: none; }
  .estimate__alt { display: block; }

  /* Once the grid collapses, the copy column dissolves (display: contents) so
     its pieces can be ordered around the form card: label + heading + the
     reassurance lede first, THEN the form, then the compact contact line.
     Plain form-first buried "Twenty minutes on site, a written price" and the
     no-fee lede below the form, so the ask arrived with zero reassurance; the
     old copy-column-first order pushed the first input ~990px down. This keeps
     the pre-form cost to three short pieces while .facts stays hidden below.
     Row gap goes to 0 and the elements' own margins carry the vertical rhythm,
     because a grid gap would repeat between every dissolved child. */
  .estimate > div:not(.form-card) { display: contents; }
  .estimate > div:not(.form-card) > .label,
  .estimate > div:not(.form-card) > .d2,
  .estimate > div:not(.form-card) > .estimate__lede { order: -2; }
  .estimate { row-gap: 0; }
  .estimate .form-card { order: -1; margin-top: 28px; }
  .estimate__alt { margin-top: 24px; }

  /* Two across from here down. Three 300px cards put the one-line benefit on
     three lines. */
  .fams { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rgrid { grid-template-columns: minmax(0, 1fr); }

  /* The CTA photo becomes a band above the copy rather than a narrow column */
  .qcta { grid-template-columns: 1fr; }
  .qcta__figure { order: -1; aspect-ratio: 16 / 9; min-height: 0; }

  /* The before/after pair stays two-up: side by side IS the comparison, and
     stacking it turns one argument into two unrelated photographs. Squarer
     crop so each half still has something in it at half a phone's width. */
  .work__figure { gap: var(--sp-1); }
  .ba__frame { aspect-ratio: 1 / 1; }
}

@media (max-width: 620px) {
  body { font-size: 16px; }
  .header--solid { --header-h: 72px; }
  .header--solid .header__inner { min-height: 72px; }
  /* The two-of-two grid is for matching the photo's height on desktop; on a
     phone each column is ~150px and the copy runs one word per line. */
  .reasons { grid-template-columns: 1fr; }
  /* The lede now renders ABOVE the form (see the 900px block) as the section's
     reassurance copy — no fee, no sales visit — so it stays visible on phones. */
  .work__meta p { font-size: 16px; }
  .work__place { margin-bottom: 8px; }
  .stats__grid {
    grid-template-columns: 1fr 1fr;
    gap: 0 18px;
    padding-top: 8px; padding-bottom: 8px;
  }
  .stats__item {
    padding: 12px 0;
    border-top: 1px solid rgba(244, 241, 234, 0.10);
  }
  .stats__item:nth-child(-n+2) { border-top: 0; }
  .stats__num { font-size: 25px; }
  .stats__label { font-size: 13px; max-width: none; }

  /* At 360x732 the lede was clipped mid-sentence by the call bar and both
     buttons sat below the fold, so the highest-attention screen asked for
     nothing. Tighten the stack so the primary button clears the fold. */
  .hero__inner { padding-top: calc(var(--header-h) + clamp(24px, 6vh, 48px)); padding-bottom: clamp(40px, 7vh, 72px); }
  /* Was 11px/0.12em — the smallest text on the site, uppercase, tracked out, over a
     photographic scrim, and the very first line a visitor reads. Apple's 11pt floor
     is ~14.6px CSS; 13px tracked is the least this can defensibly be. */
  .hero__eyebrow { margin-bottom: 14px; font-size: 13px; letter-spacing: 0.12em; }
  .hero .d1 { font-size: clamp(36px, 9.5vw, 42px); line-height: 1.15; max-width: none; }
  .hero__lede { margin-top: 16px; font-size: 16px; line-height: 1.55; }
  .hero__actions { margin-top: 22px; gap: 10px; }
  .hero__actions .btn { flex: 1 1 100%; padding: 15px 22px; }

  /* Horizontal cards, not stacked ones. As full cards (photo + copy + four
     example links each) the three families ran ~2,000px on a phone; as rows
     they cost about a third of that. The example links drop off here — on a
     phone they live in the drawer and the footer, and the card's own name
     link still routes into the family. */
  .fams { grid-template-columns: 1fr; gap: 12px; }
  .fam { flex-direction: row; }
  .fam__media { flex: 0 0 108px; aspect-ratio: 1; }
  .fam__media img { height: 100%; }
  .fam__body { padding: 12px 14px; }
  .fam__name { font-size: 18px; }
  .fam__line { margin-top: 6px; font-size: 16px; line-height: 1.6; }
  .fam__list { display: none; }

  /* Shallower crops on a phone: three 4:3 frames cost ~240px more than three
     16:9 frames and the subjects (stone courses, the pool curve, the frontage
     line) all read horizontally anyway. */
  .work__figure img { aspect-ratio: 16 / 9; }

  .qcta__actions { gap: 10px; }
  .qcta__actions .btn { flex: 1 1 100%; padding: 15px 22px; }
  /* The crew photo is decorative support for the ask; on a phone it costs
     ~200px directly ahead of the two buttons. */
  .qcta__figure { display: none; }

  /* Steps render as the vertical timeline defined in the v90 block at the end
     of this file. (The old 2x2 grid lived here; its padding-left !important
     silently defeated the timeline's indent.) */

  /* Two-column town list on phones keeps the block compact. */
  .areas__list { column-gap: 12px; }
  .areas__link { padding: 14px 0; min-height: 48px; }

  .home #faq.section--tight { padding-bottom: clamp(56px, 10vw, 88px); }

  .rquote blockquote { font-size: 18px; }
  .form-card { padding: 22px 20px; }
  /* The panel's own padding plus the form card's would cost ~45px a side on a
     390px phone; trim the panel so the form keeps a workable measure. */
  /* row-gap stays 0 (set in the 900px block) — the dissolved copy column's own
     margins carry the vertical rhythm around the form card. */
  .estimate { padding: 16px 14px; column-gap: 26px; }
  .field-grid { grid-template-columns: 1fr; }
  .facts > div { grid-template-columns: 1fr; gap: 4px; }
  .footer__top { grid-template-columns: 1fr; }
  /* clears the fixed call bar (71px narrow / 73px wide). If viewport-fit=cover
     is ever added to the viewport meta, env(safe-area-inset-bottom) wakes up
     and the bar grows to ~107px — still clear, but raise this if it goes further. */
  .footer__bar { padding-bottom: 132px; }

  .callbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
    /* Not on screen at rest: it was covering the bottom of the hero on first
       paint. Starts translated out and slides in once past the hero. */
    transform: translateY(100%);
    transition: transform 260ms var(--ease);
    background: var(--evergreen); border-top: 1px solid var(--line-dark);
    /* Soft upward shadow so content sliding beneath reads as intentional
       layering instead of touching the bar. */
    box-shadow: 0 -10px 28px -14px rgba(26, 24, 21, 0.45);
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom)); gap: 10px;
  }
  .callbar[data-hidden="false"] { transform: none; }
  /* Three actions since the Text button landed: Call and Text sit as quiet
     outlined buttons; "Free estimate" carries the moss fill as the primary ask
     and takes the wider share of the row. */
  .callbar a { flex: 1 1 0; text-align: center; padding: 14px 10px; font-size: 15px; font-weight: 600; border-radius: var(--r); }
  .callbar a.callbar__quote { flex-grow: 1.5; background: var(--moss); color: var(--paper); }
  .callbar__call,
  .callbar__text { background: transparent; color: var(--paper); box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.45); }

  /* ---- Mobile type floor ----------------------------------------------------
     Measured on a 390px phone: 58 elements rendered their own text under 14px,
     the smallest at 11px. None of it is body copy — body is 16px and the service
     and card lines are 15px — it is labels, ordinal numbers, field meta and the
     footer's <summary> tap targets. That is a better starting position than the
     count suggests, but 12px tracked out at 0.12em is still not a phone size, and
     .field-error is the one string a user is *forced* to read. Floor: 13px for
     tracked labels and numbers, 14px for anything set as running text. */
  .label, .num, .needs__label,
  .field label, .req-dot,
  .facts dt, .footer__h,
  .towns__list li,
  .mnav__hours { font-size: 13px; }

  .footer__bar, .footer__legal a,
  .field-error, .form-foot, .why__stamp span { font-size: 14px; }
}

/* Touch targets. The editorial layout leans on inline links and thin rules,
   which read well but land under 44px on a phone. Pad them there only — the
   desktop rhythm is unchanged. */
@media (max-width: 900px) {
  .chip { padding: 12px 16px; }
  .field input, .field select, .field textarea { padding-top: 13px; padding-bottom: 13px; }
  .tlink { padding-top: 12px; padding-bottom: 12px; }
  .fam__list a { padding: 12px 0; }
  .footer__links a { padding: 13px 0; }
  .footer__legal { gap: 4px 22px; }
  .footer__legal a { display: inline-block; padding: 12px 0; }
    .logo { padding: 8px 0; }
  .footer__tel { padding: 8px 0; }
  .areas__link { padding: 14px 0; }
  .facts a { display: inline-block; padding: 10px 0; }
  .crumbs a, .crumbs [aria-current] { display: inline-block; padding: 13px 0; }
  .crumbs { margin-bottom: clamp(9px, 1.5vh, 21px); }
}

/* Narrow phones only. With three actions the labels are short (Call / Text /
   Free estimate — the full number lives in Call's aria-label and href); this
   just trims chrome so all three keep a full-word line at 320px. */
@media (max-width: 400px) {
  .callbar { padding-left: 10px; padding-right: 10px; gap: 8px; }
  .callbar a { padding-left: 6px; padding-right: 6px; font-size: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [hidden] { display: none; }
  /* app.js already bails out of the reveal observer under reduced motion, so
     nothing gets .reveal — but if it ever did, this guarantees it stays visible
     rather than being stuck at opacity 0 by the neutered transition above. */
  .reveal { opacity: 1 !important; transform: none !important; }
}

@media print {
  .header, .callbar, .form-card { display: none; }
  .section--paper::before, .section--band::before { display: none; }
  body { background: #fff; color: #000; }
}

/* --------------------------------------------------------------------------
   Primary nav menus
   Two dropdowns carry 14 service pages and 6 town pages that were previously
   only reachable from the footer. Panels are plain markup: with JS off they
   render as visible link lists (see the no-js rule at the end), so crawlers
   and no-JS visitors still reach every page.
   -------------------------------------------------------------------------- */

.nav__item { position: relative; display: flex; align-items: center; }
.nav__btn {
  display: inline-flex; align-items: center; gap: 8px;
  cursor: pointer;
}
.nav__btn[aria-expanded="true"] {
  color: var(--moss);
  background: rgba(26, 24, 21, 0.06);
}
.nav__caret {
  width: 6px; height: 6px; border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  opacity: 0.72;
  transition: transform 180ms var(--ease), opacity 160ms var(--ease);
}
.nav__btn:hover .nav__caret,
.nav__btn[aria-expanded="true"] .nav__caret { opacity: 1; }
.nav__btn[aria-expanded="true"] .nav__caret {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.menu {
  position: absolute; top: calc(100% + 12px); left: 0; z-index: var(--z-dropdown);
  background: var(--paper);
  border-radius: var(--r);
  /* Same plate recipe as .fam: contact shadow + low ambient + inset hairline.
     Replaces the old 1px --line border + 44px-blur shadow pair, which read as
     both flat and floaty at once. The hairline separates the panel from the
     paper header; the ambient does the lift. */
  box-shadow:
    0 2px 4px -2px rgba(26, 24, 21, 0.12),
    0 18px 40px -18px rgba(26, 24, 21, 0.30),
    inset 0 0 0 1px rgba(26, 24, 21, 0.07);
  padding: 22px 26px 18px;
  min-width: 280px;
}
/* Areas panel opens leftward so a right-clustered nav doesn't clip the viewport. */
.menu--areas { left: auto; right: 0; }
.menu[hidden] { display: none !important; }
/* Three columns, one per service family — matches the grouping the homepage
   services section, mobile drawer and footer all use. */
.menu__inner { display: grid; grid-template-columns: repeat(3, minmax(170px, 1fr)); gap: 22px 32px; }
.menu--narrow .menu__inner { grid-template-columns: repeat(2, minmax(160px, 1fr)); }
.menu--areas .menu__inner { grid-template-columns: repeat(3, minmax(148px, 1fr)); gap: 20px 26px; }
.menu--areas .menu__col a { padding: 5px 0; }
.menu__col { display: flex; flex-direction: column; }
.menu__head {
  font-family: var(--body); font-size: 12px; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--stone);
  margin-bottom: 8px;
}
.menu__col a {
  font-size: 15px; color: var(--ink-2); padding: 6px 0;
  border-bottom: 0; white-space: nowrap;
}
.menu__col a:hover { color: var(--moss); }
.menu__note { margin-top: 10px; font-size: 13px; line-height: 1.5; color: var(--muted); max-width: 24ch; }
.menu__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--line);
}
.menu__foot-cta {
  font-size: 14px; font-weight: 600; color: var(--moss) !important;
  padding: 0 !important; border-bottom: none !important;
}
.menu__foot-cta:hover { color: var(--ink) !important; }
.menu__foot-tel {
  font-family: var(--mono); font-size: 13px; color: var(--ink-2) !important;
  font-variant-numeric: tabular-nums; padding: 0 !important;
}
.menu__foot-tel:hover { color: var(--moss) !important; }

/* Mobile panel groups */

/* No JS: the panels are always visible rather than unreachable. */
.no-js .menu[hidden] { display: block; }
.no-js .menu { position: static; box-shadow: none; padding: 0; }
.no-js .nav__caret { display: none; }


/* Service / town page photography ------------------------------------------ */

.svc-hero--photo { position: relative; overflow: hidden; }
/* The homepage scrim was tuned for one deliberately dark photo. These job
   photos include bright stone and midday sky, and paper-coloured text over
   them was unreadable. This scrim is heavy enough to hold contrast on the
   lightest frame in the set while still showing the work. */
.svc-hero--photo .hero__scrim {
  background:
    linear-gradient(90deg,
      rgba(11, 18, 14, 0.90) 0%,
      rgba(11, 18, 14, 0.80) 48%,
      rgba(11, 18, 14, 0.58) 100%),
    linear-gradient(180deg,
      rgba(11, 18, 14, 0.55) 0%,
      rgba(11, 18, 14, 0.35) 45%,
      rgba(11, 18, 14, 0.55) 100%);
}

/* The 90deg ramp above assumes the desktop layout, where the text sits in the
   left third and the photo breathes in the right two-thirds. At phone widths
   there is no right two-thirds — the text spans the full column — so the two
   ramps compounded to 0.73-0.96 alpha across the whole frame and left the photo
   with about 12% of its tonal range. Measured: 28-30 levels on the service and
   town heroes against 185 on an unscrimmed in-body figure. Below 620px use one
   vertical ramp instead, dark where the type actually is. */
@media (max-width: 620px) {
  .svc-hero--photo .hero__scrim {
    background: linear-gradient(180deg,
      rgba(11, 18, 14, 0.84) 0%,
      rgba(11, 18, 14, 0.70) 45%,
      rgba(11, 18, 14, 0.46) 75%,
      rgba(11, 18, 14, 0.34) 100%);
  }
  /* keep the subject out from under the text block */
  .svc-hero__media { object-position: 50% 32%; }

  /* Same defect, same fix, on the homepage hero — it was never included above.
     Its 78deg ramp runs 0.86 -> 0.10 across the width, which is right when the
     type occupies the left third of a 1440px stage and wrong at 390px where the
     type spans everything. Compounded with the 180deg ramp the top-left corner
     reached ~0.97 alpha and the photograph kept a third of its tonal range:
     a wash, not a picture. One vertical ramp instead. */
  .hero .hero__scrim {
    /* Warmed and deepened with the desktop ramp for the sod-mulch-frontage
       frame (luxury v2): bright midday lawn needs more cover behind the lede
       than the old sod strip did. */
    background: linear-gradient(180deg,
      rgba(24, 21, 14, 0.84) 0%,
      rgba(24, 21, 14, 0.72) 45%,
      rgba(24, 21, 14, 0.62) 75%,
      rgba(24, 21, 14, 0.66) 100%);
  }
  /* Frontage: keep the wall line and the bed band mid-frame on phones. */
  .hero__media { object-position: 46% 55%; }
}

/* The band between a phone and a laptop was never given its own ramp, and it
   is where the hero fails hardest: the type block spans 55-75% of the stage
   while the 78deg ramp is tuned for it occupying the left third of 1440.
   Measured white-on-photo at 768 was 1.8:1 on the trust row. A vertical-led
   ramp with a deeper foot covers the copy without flattening the frame.
   Do not fold this back into the desktop rule: at 1440 it would darken two
   thirds of a photograph that already measures 3.8:1 or better. */
@media (min-width: 621px) and (max-width: 1100px) {
  .hero .hero__scrim {
    background:
      linear-gradient(180deg,
        rgba(24, 21, 14, 0.64) 0%,
        rgba(24, 21, 14, 0.50) 36%,
        rgba(24, 21, 14, 0.54) 70%,
        rgba(24, 21, 14, 0.66) 100%),
      linear-gradient(78deg,
        rgba(24, 21, 14, 0.46) 0%,
        rgba(24, 21, 14, 0.34) 55%,
        rgba(24, 21, 14, 0.12) 100%);
  }
}
.svc-hero--photo .wrap { position: relative; z-index: 2; }
.svc-hero__media { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* Page-level photo band: reuses homepage .work alternating rows. */
.section--figures {
  padding-top: clamp(36px, 5vw, 64px);
  padding-bottom: clamp(36px, 5vw, 64px);
}
.section--figures .work {
  margin-top: 0;
}

/* About / narrative sections: copy beside the photo, alternating sides. */
.svc-split {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(28px, 4vw, 56px);
  /* Centered, not start: the 5/6 figure is almost always taller than the
     copy block, and top-aligning left the copy stranded at the top of the
     row with a dead band of paper beneath it (about-us, masonry). Geo pages
     already centered their splits; this makes it the base behavior. */
  align-items: center;
}
.svc-split--flip .svc-split__copy { order: 2; }
.svc-split--flip .svc-split__figure { order: 1; }
.svc-split__figure { margin: 0; }
.svc-split__figure img {
  width: 100%;
  height: auto;
  display: block;
  /* Slightly taller than landscape work crops, shorter than a full portrait
     dump — keeps the copy column from floating beside empty air. */
  aspect-ratio: 5 / 6;
  object-fit: cover;
  object-position: center 35%;
  border-radius: var(--r);
}
.svc-split__figure figcaption {
  margin-top: 12px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
  max-width: 36ch;
}
.svc-split .head { margin-bottom: 0; }
.svc-split .prose {
  max-width: 36em;
  margin-top: 18px !important;
}
.svc-split .ticks__intro { margin-top: 22px; }
@media (max-width: 900px) {
  .svc-split,
  .svc-split--flip {
    grid-template-columns: 1fr;
    gap: 22px;
  }
  .svc-split--flip .svc-split__copy,
  .svc-split--flip .svc-split__figure { order: 0; }
  /* On mobile, photo first under the heading feels better than after a long copy block. */
  .svc-split__figure { order: -1; }
  .svc-split__figure img { aspect-ratio: 3 / 2; object-position: center; }
}

/* Legacy single-figure + inline proof photo (inside a prose section). */
.section--figure { padding-top: 0; }
.figure { margin: 0; }
.figure img {
  width: 100%; height: auto; display: block;
  aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: var(--r);
}
.figure figcaption {
  margin-top: 14px; font-size: 15px; line-height: 1.6;
  color: var(--muted); max-width: 54ch;
}

/* A figure sitting INSIDE a section rather than in the page-level gallery band.
   Service pages use this for the proof photo that has to appear third on the page,
   directly under the lede and CTA. Matches the section's own prose rhythm. */
.figure--inline { margin-top: clamp(28px, 4vw, 52px); }

/* --------------------------------------------------------------------------
   Service pages
   Composed almost entirely from the existing section / head / faq primitives.
   Only the pieces below had no equivalent on the homepage.
   -------------------------------------------------------------------------- */

.crumbs { font-size: 13px; margin-bottom: clamp(22px, 3vh, 34px); }
.crumbs a { color: rgba(244, 241, 234, 0.66); }
.crumbs a:hover { color: var(--paper); }
.crumbs span { color: rgba(244, 241, 234, 0.42); margin: 0 8px; }
.crumbs [aria-current] { color: var(--paper); }

.svc-hero__lede { margin-top: 22px; color: rgba(244, 241, 234, 0.82); max-width: 36em; }
.hero .hero__title, .hero .hero__lede,
.svc-hero--photo .page-hero__title, .svc-hero--photo .svc-hero__lede,
.svc-hero--photo .svc-hero__date, .svc-hero--photo .crumbs {
  text-shadow: 0 1px 2px rgba(11, 18, 14, 0.45), 0 2px 18px rgba(11, 18, 14, 0.55);
}
.svc-hero__date { margin-top: 14px; color: rgba(244, 241, 234, 0.62); }
.svc-hero__byline { color: inherit; text-decoration: none; }
.svc-hero__byline:hover { text-decoration: underline; }
.glance {
  background: var(--paper-2); border-left: 3px solid var(--moss);
  padding: 22px 26px; border-radius: var(--r);
}
.glance .label { margin-bottom: 10px; }
.glance__list { list-style: none; margin: 0; padding: 0; }
.glance__list li { padding: 5px 0 5px 22px; position: relative; }
.glance__list li::before {
  content: ""; position: absolute; left: 0; top: 13px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--moss); opacity: 0.75;
}
.tbl-wrap { overflow-x: auto; margin-top: 22px; position: relative; }
.cmp-table { width: 100%; border-collapse: collapse; font-size: 15px; min-width: 560px; }
.cmp-table caption { text-align: left; font-weight: 600; padding-bottom: 10px; }
.cmp-table th, .cmp-table td {
  text-align: left; vertical-align: top; padding: 10px 14px;
  border-bottom: 1px solid var(--line-2);
}
.cmp-table thead th { font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; }
.cmp-table tbody th { font-weight: 600; white-space: nowrap; }
@media (max-width: 620px) {
  /* Row labels stay pinned while the value columns swipe. */
  .cmp-table tbody th,
  .cmp-table thead th:first-child {
    position: sticky;
    left: 0;
    background: var(--paper);
    box-shadow: 14px 0 0 var(--paper);
    z-index: 1;
  }
  .section--band .cmp-table tbody th,
  .section--band .cmp-table thead th:first-child { background: var(--paper-2); }
  /* Right-edge fade says "there is more"; the caption says how to get it. */
  .tbl-wrap::after {
    content: '';
    position: sticky;
    left: 100%;
    top: 0;
    display: block;
    width: 28px;
    height: 100%;
    margin-left: -28px;
    float: right;
    background: linear-gradient(90deg, transparent, var(--paper));
    pointer-events: none;
  }
  .cmp-table caption::after {
    content: ' · Swipe to compare';
    font-weight: 400;
    font-size: 13px;
    color: var(--stone);
  }
}
.sec-link { margin-top: 14px; }
.pgrid__more { display: block; margin: 26px auto 0; }
.entry { border-top: 1px solid var(--line); padding-top: 18px; margin-top: 22px; }
.entry__label { font-family: var(--display); font-size: var(--t-title); margin: 0 0 8px; }
.entry p { color: var(--ink-2); line-height: 1.65; text-wrap: pretty; margin: 0; }
@media (max-width: 620px) {
  .section-heading { font-size: clamp(25px, 7.6vw, 32px); }
  .pgrid__type, .iprocess__num { font-size: 13px; }
  .pfilt__nav {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
  }
  .iprocess__step {
    display: grid;
    grid-template-columns: 32px 1fr;
    align-items: baseline;
    gap: 4px 10px;
    border-top: 1px solid var(--line-2);
    padding-top: 10px;
  }
  .iprocess__step:first-child { border-top: 2px solid var(--ink); }
  .iprocess__step .iprocess__text { grid-column: 2; }
  .steps h3 { hyphens: none; text-wrap: balance; }
  .svc-hero--split .svc-hero__photo { max-height: 46vh; }
}
@media (max-width: 900px) {
  /* Secondary tel/text links and inline read-more links measured 19-23px tall.
     Pad the hit area without moving the visual line. */
  a[href^="tel:"]:not(.btn):not(.callbar__call):not(.mnav__tel):not(.header__call),
  a[href^="sms:"]:not(.btn):not(.callbar__text),
  .work__meta p a,
  .fam__name a {
    display: inline-block;
    padding-top: 12px;
    padding-bottom: 12px;
    margin-top: -12px;
    margin-bottom: -12px;
  }
}
.svc-hero .hero__actions { margin-top: 34px; }

/* Photo-less hero. The honest-photo policy leaves some pages without imagery;
   this makes that state designed instead of a flat slab: a soft depth lift
   behind the copy and terrain contours holding the empty half. */
.svc-hero--field {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 150% at 10% -10%, var(--pine) 0%, var(--evergreen) 62%);
}
.svc-hero--field::before {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(58vw, 760px);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 900 640'%3E%3Cpath d='M490.0 320.0L492.8 324.5L494.5 329.2L494.7 334.1L493.2 338.9L490.1 343.1L485.7 346.8L480.5 349.8L474.7 352.2L468.9 354.2L463.3 355.9L457.8 357.4L452.4 358.9L446.9 360.2L441.4 361.2L435.7 361.8L430.0 361.8L424.4 361.3L418.9 360.2L413.7 358.7L408.7 357.1L403.7 355.4L398.7 353.7L393.4 352.1L388.0 350.3L382.4 348.1L377.2 345.4L372.7 342.1L369.3 338.1L367.3 333.7L366.9 329.0L367.9 324.4L370.0 320.0L372.7 315.9L375.4 312.2L377.9 308.6L379.9 305.0L381.5 301.3L383.0 297.4L384.8 293.3L387.2 289.2L390.4 285.3L394.6 281.9L399.7 279.2L405.4 277.2L411.5 276.0L417.7 275.4L423.9 275.3L430.0 275.4L436.0 275.8L442.0 276.5L447.9 277.5L453.6 279.0L458.8 281.2L463.4 284.0L467.2 287.4L470.1 291.1L472.3 295.0L474.0 298.8L475.7 302.4L477.6 305.8L480.1 309.1L483.2 312.4L486.6 316.0L490.0 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M549.4 320.0L550.1 328.5L547.9 336.9L543.2 344.7L536.7 351.8L529.2 358.2L521.3 363.9L513.5 369.4L505.9 374.6L498.2 379.8L490.1 384.8L481.4 389.3L472.0 393.1L461.9 395.8L451.4 397.3L440.6 397.8L430.0 397.4L419.5 396.5L409.2 395.4L398.7 394.2L388.0 393.0L377.0 391.5L365.8 389.2L354.9 385.9L345.1 381.2L336.9 375.0L331.2 367.5L328.0 359.2L327.3 350.6L328.5 342.2L330.8 334.2L333.2 326.9L334.9 320.0L335.7 313.3L335.4 306.5L334.7 299.2L334.2 291.4L334.7 283.3L336.8 275.2L341.0 267.4L347.2 260.4L355.2 254.4L364.5 249.4L374.6 245.3L385.2 242.1L396.0 239.4L407.1 237.2L418.5 235.7L430.0 235.0L441.5 235.6L452.8 237.6L463.2 241.1L472.5 246.1L480.4 252.1L486.9 258.7L492.4 265.2L497.5 271.4L502.9 276.9L509.0 282.0L516.1 286.9L524.1 291.9L532.3 297.6L539.9 304.3L545.9 311.8L549.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M598.1 320.0L592.1 331.5L584.7 342.1L576.7 352.0L569.0 361.4L561.6 370.7L554.4 379.9L546.8 389.0L538.1 397.9L528.1 406.1L516.5 413.2L503.5 419.1L489.6 423.6L475.2 427.2L460.4 430.1L445.4 432.8L430.0 435.4L413.9 437.9L396.9 439.8L379.2 440.5L361.4 439.3L344.2 435.5L328.9 429.0L316.3 419.8L307.1 408.5L301.4 396.0L298.7 383.1L298.0 370.8L297.9 359.4L297.3 349.0L295.5 339.3L292.3 329.8L288.1 320.0L283.9 309.6L280.7 298.6L279.7 287.2L281.4 275.7L286.0 264.6L293.3 254.2L302.7 244.8L313.5 236.1L325.3 228.1L337.8 220.6L351.0 213.6L365.1 207.2L380.2 201.9L396.4 198.3L413.1 196.8L430.0 197.8L446.2 201.2L461.3 206.7L474.9 213.4L487.2 220.6L498.6 227.6L510.0 233.8L522.0 239.3L535.0 244.4L549.1 249.6L563.4 255.8L577.0 263.4L588.5 272.7L596.8 283.6L601.2 295.5L601.4 307.8L598.1 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M634.3 320.0L628.1 334.0L622.2 347.5L616.5 360.7L610.2 373.8L602.8 386.5L593.4 398.6L582.0 409.8L568.6 419.8L553.8 428.6L538.2 436.6L522.2 444.1L505.8 451.8L488.9 459.9L471.0 468.3L451.4 476.4L430.0 483.3L407.0 487.8L383.3 488.9L360.1 485.9L338.7 478.6L320.4 467.7L305.6 454.1L294.3 439.1L285.6 424.0L278.4 409.6L271.2 396.4L263.2 384.2L254.0 372.5L243.9 360.6L234.0 348.1L225.7 334.5L220.0 320.0L218.1 305.0L220.0 289.9L225.6 275.4L233.9 261.5L244.1 248.4L255.4 236.0L267.4 223.9L280.2 212.2L294.2 200.9L309.9 190.5L327.4 181.8L346.8 175.4L367.6 171.9L388.9 171.2L409.9 173.1L430.0 176.8L449.0 181.2L467.2 185.3L485.4 188.6L504.3 190.8L524.6 192.5L546.4 194.6L569.0 198.1L591.2 204.0L611.4 212.8L627.9 224.8L639.7 239.3L646.2 255.5L647.7 272.4L645.3 289.2L640.4 305.1L634.3 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M682.4 320.0L680.7 337.8L676.8 355.3L669.9 372.4L659.8 388.5L646.7 403.4L631.6 417.0L615.3 429.5L598.7 441.5L582.3 453.6L565.9 466.4L548.8 480.0L529.9 493.7L508.6 506.7L484.5 517.4L458.0 524.5L430.0 526.8L402.1 524.1L375.7 516.6L351.9 505.4L331.1 491.9L312.8 477.9L296.0 464.4L279.4 452.1L262.0 441.0L243.1 430.4L223.3 419.5L203.5 407.2L185.4 393.0L170.6 376.7L160.4 358.6L155.3 339.5L155.1 320.0L159.0 300.8L165.7 282.2L174.4 264.2L184.3 246.7L195.5 229.7L208.3 213.3L223.5 198.0L241.6 184.3L262.6 173.2L286.2 165.1L311.4 160.3L337.0 158.3L361.9 158.3L385.5 159.0L408.0 159.1L430.0 157.7L452.6 154.6L476.9 150.1L503.5 145.6L532.1 142.5L561.9 142.4L591.0 146.5L617.7 155.3L640.2 168.6L657.5 185.6L669.4 204.8L676.6 225.1L680.4 245.3L682.0 265.0L682.7 283.8L682.9 302.1L682.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M740.0 320.0L737.2 341.8L729.8 362.9L718.9 383.1L705.8 402.2L691.7 420.7L677.5 439.1L663.3 457.9L648.4 477.3L631.6 496.9L611.7 515.8L587.6 532.4L559.5 545.1L528.0 552.6L494.7 554.3L461.5 550.5L430.0 542.2L401.1 531.1L374.9 519.3L350.6 508.4L326.8 499.3L302.2 492.2L275.8 486.2L247.5 480.1L218.3 472.4L189.6 462.0L163.4 448.3L141.2 431.1L124.1 411.2L112.2 389.4L104.9 366.6L101.4 343.3L100.6 320.0L102.0 296.7L105.7 273.6L112.4 250.6L123.0 228.4L138.5 207.8L159.0 189.6L184.3 174.8L213.0 163.8L243.3 156.2L273.5 151.3L302.1 147.7L328.6 143.8L353.4 138.2L377.6 130.3L402.7 120.4L430.0 109.7L460.1 99.9L492.7 93.0L526.6 90.8L560.0 94.1L591.0 103.1L618.2 117.2L641.0 134.9L659.6 154.7L675.0 175.2L688.3 195.7L700.6 215.9L712.3 235.8L723.1 256.0L732.2 276.7L738.2 298.1L740.0 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3Cpath d='M780.8 320.0L778.3 344.7L774.3 369.3L769.9 394.2L765.1 419.9L759.1 446.7L750.2 474.0L736.4 501.1L716.3 526.1L689.2 547.4L655.6 563.1L617.2 572.2L576.5 574.6L535.9 571.3L497.5 564.3L462.2 555.6L430.0 547.6L399.7 541.5L369.8 537.9L338.9 536.3L306.2 535.2L271.9 532.9L237.1 527.9L203.3 518.9L172.1 505.7L144.7 488.6L121.5 468.4L102.1 446.2L85.7 422.7L71.6 398.3L59.4 373.1L49.5 347.0L43.0 320.0L41.5 292.5L46.5 265.1L59.0 239.0L78.8 215.2L104.7 194.8L134.6 177.9L166.1 164.0L196.8 152.1L225.5 140.6L251.8 128.0L276.7 113.5L301.8 97.1L328.8 79.9L359.1 63.5L393.1 50.2L430.0 42.0L468.3 40.2L506.0 45.1L541.2 56.0L572.9 71.5L600.9 89.7L625.8 109.0L648.8 128.1L670.9 146.6L692.9 164.7L714.6 183.1L735.0 202.6L753.0 223.7L767.0 246.4L776.2 270.4L780.6 295.1L780.8 320.0Z' fill='none' stroke='rgba(169,192,141,0.1)' stroke-width='1.2'/%3E%3Cpath d='M821.4 320.0L831.2 348.4L840.4 378.8L846.7 411.0L847.3 444.5L839.7 477.7L822.1 508.6L794.2 535.2L757.5 555.8L714.6 569.7L668.8 577.3L623.1 580.1L579.6 580.1L539.4 579.7L502.0 580.5L466.0 583.3L430.0 587.8L392.7 592.6L353.8 596.0L313.7 596.1L273.7 591.7L235.4 582.1L200.0 567.9L168.0 549.8L139.4 529.3L113.2 507.2L88.7 484.2L65.4 460.3L43.5 435.3L24.4 408.6L9.8 380.2L1.8 350.4L1.9 320.0L10.5 290.2L26.7 262.2L48.4 236.7L72.9 213.5L97.5 192.0L120.2 171.0L140.5 149.0L159.1 125.0L178.1 99.0L199.7 71.8L226.1 45.4L258.6 22.1L297.0 4.4L340.0 -6.0L385.1 -8.3L430.0 -3.0L472.6 8.4L511.8 23.7L547.6 40.9L580.5 58.3L611.8 75.1L642.2 91.4L672.0 107.7L700.6 125.1L727.1 144.4L750.2 165.9L769.1 189.5L783.7 214.5L794.8 240.3L803.8 266.5L812.3 292.9L821.4 320.0Z' fill='none' stroke='rgba(169,192,141,0.07)' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right -120px center;
  background-size: 900px auto;
  pointer-events: none;
}
.svc-hero--field .wrap { position: relative; z-index: 1; }
@media (max-width: 900px) {
  .svc-hero--field::before {
    width: 100%;
    background-position: right -260px bottom -220px;
    background-size: 680px auto;
    opacity: 0.8;
  }
}


/* Split hero: text column + finished-project photo */
.svc-hero--split {
  padding: 0;
  background: var(--evergreen);
  color: var(--paper);
}
.svc-hero--split .svc-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: min(480px, 72vh);
}
.svc-hero--split .svc-hero__copy {
  display: flex;
  align-items: center;
  padding: clamp(28px, 5vw, 56px) 0;
  background: var(--evergreen);
  color: var(--paper);
}
.svc-hero--split .page-hero__title,
.svc-hero--split .svc-hero__lede,
.svc-hero--split .label--light { color: var(--paper); }
.svc-hero--split .label--light { opacity: 0.72; }
.svc-hero--split .svc-hero__photo {
  margin: 0;
  min-height: 280px;
  height: 100%;
}
.svc-hero--split .svc-hero__photo img {
  width: 100%;
  height: 100%;
  min-height: 280px;
  object-fit: cover;
  display: block;
}
.svc-hero--split .crumbs a { color: rgba(244, 241, 234, 0.72); }
.svc-hero--split .crumbs span { color: rgba(244, 241, 234, 0.55); }
.svc-hero--compact {
  padding: clamp(28px, 5vw, 48px) 0;
}
.svc-hero--compact .page-hero__title { max-width: none; }
.svc-hero--compact .svc-hero__lede { max-width: 42em; }

@media (max-width: 900px) {
  .svc-hero--split .svc-hero__grid {
    grid-template-columns: 1fr;
    min-height: 0;
  }
  .svc-hero--split .svc-hero__photo { order: -1; max-height: 42vh; }
  .svc-hero--split .svc-hero__photo img { min-height: 220px; max-height: 42vh; }
}

/* Reading column. Prose wants a measure, not the full 1280px wrap. */
.prose { max-width: 40em; }
.prose p { margin-top: 20px; color: var(--ink-2); font-size: 16px; line-height: 1.65; text-wrap: pretty; }
.prose p:first-child { margin-top: 0; }
.section--dark .prose p, .on-dark .prose p { color: rgba(244, 241, 234, 0.82); }

/* Marked list — the tick is drawn, not an icon font or an emoji */
.ticks { margin-top: 26px; max-width: 40em; }
.ticks li {
  position: relative; padding-left: 26px; margin-top: 12px;
  color: var(--muted); font-size: 16px; line-height: 1.65;
}
.ticks li::before {
  content: ''; position: absolute; left: 0; top: 8px;
  width: 9px; height: 5px; border-left: 1.5px solid var(--moss);
  border-bottom: 1.5px solid var(--moss); transform: rotate(-45deg);
}
.ticks__intro { margin-top: 20px; color: var(--ink-2); }

.related { display: flex; flex-wrap: wrap; gap: 10px 14px; margin-top: 28px; }
.related a {
  border: 1px solid var(--line-3); padding: 13px 18px;
  border-radius: var(--r);
  font-size: 15px; font-weight: 500; color: var(--ink-2);
}
.related a:hover { border-color: var(--ink); color: var(--ink); }

/* Town pages list all 14 services, so a wrapping row becomes a ragged mess.
   Grid keeps the columns honest and the tap targets even. */
.related--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
.related--grid a { text-align: left; }

@media (max-width: 900px) {
  .prose { max-width: none; }
  .ticks { max-width: none; }
}

/* --------------------------------------------------------------------------
   Internal pages — shared design system (body.page-internal)
   Smaller heroes, tighter rhythm, proof-led layouts.
   -------------------------------------------------------------------------- */

.page-internal .section { padding: clamp(32px, 5vw, 72px) 0; }
.page-internal .section--tight { padding: clamp(20px, 3.5vw, 48px) 0; }
.page-internal .svc-hero--split { padding: 0; }
.page-internal .header--solid { --header-h: 80px; }
.page-internal .page { padding-bottom: calc(var(--callbar-h, 0px) + env(safe-area-inset-bottom, 0px)); }

/* Single-column measure for internal copy blocks — no phantom 4fr grid track */
.page-internal .head--single,
.page-internal .head:not(:has(.head__intro)) {
  grid-template-columns: 1fr;
  gap: 14px;
  align-items: start;
  max-width: 42rem;
}
.page-internal .prose,
.page-internal .ticks,
.page-internal .ticks__intro {
  max-width: 42rem;
}
.page-internal .section--band {
  background: var(--paper);
}
.page-internal .section--band::before {
  display: none;
}

.page-hero__title {
  margin-top: clamp(14px, 2vh, 22px);
  font-family: var(--display);
  font-size: clamp(28px, 3.2vw, 42px);
  line-height: 1.15;
  letter-spacing: -0.015em;
  font-weight: 500;
  max-width: 22ch;
  text-wrap: balance;
  color: var(--paper);
}
.section-heading {
  font-family: var(--display);
  font-size: clamp(25px, 2.8vw, 32px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-weight: 500;
  text-wrap: balance;
}
.page-internal .head__lede {
  margin-top: 0;
  color: var(--muted);
  font-size: var(--t-intro);
  line-height: 1.65;
  text-wrap: pretty;
}
.page-internal .head + .prose,
.page-internal .head--single + .prose,
.page-internal .head + .ticks__intro,
.page-internal .head--single + .ticks__intro,
.page-internal .prose + .ticks__intro {
  margin-top: clamp(28px, 3.5vw, 48px);
}
.page-internal .head + .iprocess,
.page-internal .head--single + .pgrid,
.page-internal .head--single + .ifams,
.page-internal .pfilt__nav + .pgrid {
  margin-top: clamp(28px, 3.5vw, 48px);
}
.page-internal .prose { max-width: 42rem; }
.page-internal .prose p { font-size: 16px; line-height: 1.65; }

/* Proof strip under hero — full-width, evenly spaced, readable on evergreen */
.iproof {
  background: var(--evergreen);
  border-top: 1px solid var(--line-dark);
}
.iproof__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  padding: clamp(18px, 2.4vw, 26px) 0;
}
.iproof__item {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 clamp(14px, 2.5vw, 32px);
  border-right: 1px solid var(--line-dark);
}
.iproof__item:last-child { border-right: 0; }
.iproof__text {
  color: var(--paper);
  font-size: clamp(14px, 1.05vw, 15px);
  font-weight: 500;
  line-height: 1.55;
  letter-spacing: 0;
  text-wrap: balance;
}

/* Process steps */
.iprocess {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}
.iprocess__step {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 4px;
  border-top: 2px solid var(--ink);
}
.iprocess__num {
  /* Step labels wear the Geist label recipe like every other ordinal on the
     site (luxury v2) — mono is reserved for true tabular numerals. */
  font-family: var(--body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
}
.iprocess__text {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* Service families on location pages */
.ifams {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.ifams__h {
  font-family: var(--display);
  font-size: var(--t-title);
  line-height: 1.2;
  color: var(--ink);
  margin-bottom: 12px;
}
.ifams__links {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ifams__links li { margin-top: 8px; }
.ifams__links a {
  font-size: 15px;
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(74, 107, 79, 0.35);
}
.ifams__links a:hover { color: var(--ink); }

/* Portfolio grid */
.pfilt__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: clamp(24px, 3vw, 36px);
}
.pfilt {
  border: 1px solid var(--line-3);
  border-radius: var(--r);
  padding: 10px 14px;
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  background: transparent;
  cursor: pointer;
  text-decoration: none;
}
.pfilt.is-active,
.pfilt[aria-current="true"] {
  border-color: var(--ink);
  color: var(--ink);
  background: var(--paper);
}
@media (max-width: 620px) {
  .pfilt__nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    padding-bottom: 6px;
  }
  .pfilt {
    flex: 0 0 auto;
    scroll-snap-align: start;
    white-space: nowrap;
  }
}
.pgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.pgrid--proof { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pgrid__figure { margin: 0; }
.pgrid__figure img {
  width: 100%;
  height: auto;
  display: block;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--r);
}
.pgrid__meta { margin-top: 12px; }
.pgrid__type {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
  margin-bottom: 6px;
}
.pgrid__loc {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 6px;
}
.pgrid__title {
  font-family: var(--display);
  font-size: clamp(18px, 1.5vw, 20px);
  line-height: 1.2;
  color: var(--ink);
  margin-bottom: 6px;
}
.pgrid__meta p {
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}
.pgrid__item[hidden] { display: none; }

/* Case studies */
.icase {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr);
  gap: clamp(24px, 4vw, 48px);
  align-items: start;
  margin-top: clamp(32px, 4vw, 48px);
  padding-top: clamp(32px, 4vw, 48px);
  border-top: 1px solid var(--line);
}
.icase:first-of-type { margin-top: clamp(24px, 3vw, 36px); padding-top: 0; border-top: 0; }
.icase--flip .icase__copy { order: 2; }
.icase--flip .icase__figure { order: 1; }
.icase__title {
  font-family: var(--display);
  font-size: clamp(20px, 2.2vw, 25px);
  line-height: 1.15;
  color: var(--ink);
}
.icase__loc {
  display: block;
  font-family: var(--body);
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  margin-top: 6px;
}
.icase__facts {
  margin-top: 18px;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 16px;
  font-size: 15px;
  line-height: 1.55;
}
.icase__facts dt {
  font-weight: 600;
  color: var(--ink);
}
.icase__facts dd {
  margin: 0;
  color: var(--muted);
}
.icase__figure { margin: 0; }
.icase__figure img {
  width: 100%;
  height: auto;
  display: block;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--r);
}
.icase__figure figcaption {
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}

.qcta--dark {
  background: var(--pine);
  box-shadow:
    0 2px 4px -2px rgba(26, 24, 21, 0.20),
    0 20px 44px -24px rgba(26, 24, 21, 0.50);
}
.qcta--dark .qcta__p { color: rgba(244, 241, 234, 0.78); }
.qcta--dark .section-heading { color: var(--paper); }

@media (max-width: 1024px) {
  .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ifams { grid-template-columns: 1fr; }
  .iprocess { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .icase, .icase--flip { grid-template-columns: 1fr; }
  .icase--flip .icase__copy, .icase--flip .icase__figure { order: 0; }
  .icase__figure { order: -1; }
}
@media (max-width: 620px) {
  .pgrid, .pgrid--proof { grid-template-columns: 1fr; }
  .iprocess { grid-template-columns: 1fr; }
  .iproof__grid { grid-template-columns: 1fr; }
  .iproof__item {
    justify-content: flex-start;
    text-align: left;
    border-right: 0;
    border-bottom: 1px solid var(--line-dark);
    padding: 14px 0;
  }
  .iproof__item:last-child { border-bottom: 0; }
}

/* --------------------------------------------------------------------------
   Geo service pages
   Town pages need a stronger editorial rhythm than generic internal pages:
   wider compositions, alternating surfaces, shorter image crops, and compact
   information panels instead of long single-column islands.
   -------------------------------------------------------------------------- */
.page-geo .section--band {
  background: var(--paper-2);
}
.page-geo .section--band::before {
  display: block;
}

/* Separate the closing sequence into three readable chapters. Both the process
   and FAQ are generated as section--band, which otherwise makes them merge. */
.page-geo .section:has(.iprocess) {
  background: var(--paper-3);
  border-top: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
}
.page-geo #faq {
  background: var(--paper);
  border: 0;
}

/* Service families read as three intentional choices, not three loose lists. */
.page-geo .ifams {
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
.page-geo .ifams__group {
  min-width: 0;
  padding: clamp(22px, 2.6vw, 34px);
  background: var(--paper);
}
.page-geo .ifams__h {
  max-width: 15ch;
  font-size: clamp(20px, 2vw, 25px);
}
.page-geo .ifams__links li {
  margin-top: 0;
  border-top: 1px solid var(--line);
}
.page-geo .ifams__links a {
  display: block;
  padding: 11px 0;
  text-decoration: none;
}

/* A page commonly has one strong proof image — every generated "Recent work"
   section ships exactly one .pgrid__item. Present it as a feature rather than
   leaving a lonely half-width card in a two-column grid. Was scoped to
   .page-geo; service pages (masonry, landscaping-services, …) have the same
   single-proof section, so the treatment is internal-wide now. */
.page-internal .pgrid--proof:has(> .pgrid__item:only-child) {
  grid-template-columns: minmax(0, 1fr);
}
.page-internal .pgrid--proof > .pgrid__item:only-child {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.65fr);
  gap: clamp(24px, 4vw, 54px);
  align-items: center;
}
.page-internal .pgrid--proof > .pgrid__item:only-child .pgrid__figure img {
  aspect-ratio: 16 / 10;
}
.page-internal .pgrid--proof > .pgrid__item:only-child .pgrid__meta {
  margin-top: 0;
}

/* Town-condition stories: shorter photography and vertically centered copy
   eliminate the large blank tails caused by the previous portrait crop. */
.page-geo .svc-split {
  grid-template-columns: minmax(300px, 0.82fr) minmax(0, 1.18fr);
  gap: clamp(40px, 6vw, 88px);
  align-items: center;
}
.page-geo .svc-split--flip {
  grid-template-columns: minmax(0, 1.18fr) minmax(300px, 0.82fr);
}
.page-geo .svc-split__figure img {
  aspect-ratio: 4 / 3;
  object-position: center;
}
.page-geo .svc-split .prose {
  max-width: 34rem;
}

/* Tick-list sections become a balanced information panel: explanation on the
   left, scannable factors on the right. Was geo-only ("What affects the price
   in <town>"); service pages have the identical head + prose + ticks__intro +
   ticks composition ("What we install", "What affects the price", "How a job
   runs") and collapsed into the same stranded narrow column, so the panel is
   internal-wide now. A .figure--inline inside the section (paver-installation)
   drops to a full-width row beneath the panel instead of auto-placing into
   one column. */
@media (min-width: 901px) {
  .page-internal .section:has(> .wrap > .ticks) > .wrap {
    display: grid;
    grid-template-columns: minmax(280px, 0.8fr) minmax(420px, 1.2fr);
    grid-template-areas:
      "heading intro"
      "copy list";
    column-gap: clamp(52px, 7vw, 104px);
    row-gap: 20px;
    align-items: start;
    padding-top: clamp(28px, 3vw, 42px);
    padding-bottom: clamp(28px, 3vw, 42px);
    border-top: 1px solid var(--line-3);
    border-bottom: 1px solid var(--line-3);
  }
  .page-internal .section:has(> .wrap > .ticks) .head {
    grid-area: heading;
  }
  .page-internal .section:has(> .wrap > .ticks) .prose {
    grid-area: copy;
    margin-top: 0 !important;
  }
  .page-internal .section:has(> .wrap > .ticks) .ticks__intro {
    grid-area: intro;
    margin-top: 4px !important;
    font-weight: 600;
    color: var(--ink);
  }
  .page-internal .section:has(> .wrap > .ticks) .ticks {
    grid-area: list;
    margin-top: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 28px;
  }
  .page-internal .section:has(> .wrap > .ticks) .ticks li {
    margin-top: 0;
    padding-top: 13px;
    padding-bottom: 13px;
    border-top: 1px solid var(--line);
  }
  .page-internal .section:has(> .wrap > .ticks) > .wrap > .figure--inline {
    grid-column: 1 / -1;
    margin-top: clamp(20px, 3vw, 36px);
  }
}

@media (max-width: 900px) {
  .page-geo .ifams {
    grid-template-columns: 1fr;
  }
  .page-internal .pgrid--proof > .pgrid__item:only-child {
    grid-template-columns: 1fr;
    gap: 16px;
  }
  .page-geo .svc-split,
  .page-geo .svc-split--flip {
    grid-template-columns: 1fr;
    gap: 20px;
  }
}

/* --------------------------------------------------------------------------
   Mobile refinement pass — 2026-07-29
   Keeps the approved desktop composition intact while making the phone layout
   lead with the decision-making content instead of oversized media and CTAs.
   -------------------------------------------------------------------------- */
@media (max-width: 620px) {
  /* Homepage: preserve the editorial headline but keep it from becoming a
     five-line wall of type on narrow screens. */
  .home .hero .d1 {
    max-width: 15ch;
    /* 32 at 390px (a scale step): 34-35px wrapped the headline to five lines
       and pushed the CTAs and star row below the fold. */
    font-size: clamp(32px, 8.2vw, 42px);
    line-height: 1.15;
    text-wrap: pretty;
  }
  .home .hero__lede {
    max-width: 34ch;
    display: block;
    overflow: visible;
  }

  /* Internal pages: show the page name before the photograph. The photograph
     remains useful proof, but no longer delays the H1 by an entire viewport. */
  .page-internal .svc-hero--split .svc-hero__copy {
    order: -1;
    padding: 24px 0 26px;
  }
  .page-internal .svc-hero--split .svc-hero__photo {
    order: 0;
    height: 210px;
    min-height: 210px;
    max-height: 210px;
  }
  .page-internal .svc-hero--split .svc-hero__photo img {
    height: 210px;
    min-height: 210px;
    max-height: 210px;
  }
  .page-internal .page-hero__title {
    margin-top: 10px;
    font-size: clamp(32px, 9vw, 42px);
    line-height: 1.15;
    max-width: 18ch;
    text-wrap: pretty;
  }
  .page-internal .svc-hero__lede {
    margin-top: 14px;
    font-size: 16px;
    line-height: 1.55;
  }

  /* Both hero actions keep the pill system on phones; hierarchy comes from
     fill vs outline, not from demoting the second action to a text link. */
  .hero__actions {
    gap: 10px;
  }
  .hero__actions .btn:first-child,
  .hero__actions .btn.btn--ghost-light {
    flex: 1 1 100%;
    min-height: 48px;
  }

  /* A subtle clipped-edge cue makes the project filters read as a swipeable
     rail. A visible thin scrollbar is more honest than a cut-off label. */
  .pfilt__nav {
    margin-right: calc(var(--gut) * -1);
    padding-right: var(--gut);
    scrollbar-width: thin;
    scrollbar-color: var(--moss) rgba(74, 107, 79, 0.12);
    mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 32px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 32px), transparent 100%);
  }
  .pfilt__nav:focus-within,
  .pfilt__nav.is-at-end {
    mask-image: none;
    -webkit-mask-image: none;
  }

  /* The bar is supporting navigation, not a third hero. Reduce its footprint
     while retaining the three explicit conversion paths. */
  .callbar {
    padding: 7px 10px calc(7px + env(safe-area-inset-bottom));
    gap: 7px;
  }
  .callbar a {
    min-height: 44px;
    padding: 12px 7px;
    line-height: 1.2;
  }
  .footer__bar { padding-bottom: 92px; }
}

/* --------------------------------------------------------------------------
   Entrance reveal — DELIBERATELY LAST IN THE FILE

   The .reveal class is applied by app.js, never in the markup, so a browser
   without JS or without IntersectionObserver never has opacity:0 applied and
   simply shows the content.

   This block MUST stay at the end. .fam declares a `transition:` SHORTHAND,
   and a shorthand resets every longhand it does not mention — including
   transition-property and transition-delay. At equal specificity (one class
   each) source order decides, so when this rule sat earlier in the file the
   cards silently dropped the opacity transition and the stagger delay: they
   snapped from opacity 0 to 1 while everything else faded over 600ms, and the
   per-child --reveal-delay app.js was setting on exactly those elements was
   discarded. Verified in-browser before and after.

   Do not move this up, and do not "tidy" it into the components section.
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0; transform: translateY(24px);
  transition: opacity 640ms var(--ease), transform 640ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* Mobile pages are already interaction-dense and long. Entrance reveals can
   leave entire FAQ, CTA and card sections blank during fast scrolling, browser
   screenshots, or delayed observer callbacks. Keep the restrained motion on
   desktop; mobile content must be immediately present. */
@media (max-width: 900px) {
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    transition-delay: 0ms !important;
  }
}


/* --------------------------------------------------------------------------
   Proof block on generated pages
   Two verbatim Google reviews immediately above the estimate form. Deliberately
   quieter than the homepage reviews section — this is corroboration next to the
   ask, not a section in its own right.
   -------------------------------------------------------------------------- */
.pquotes {
  margin-top: clamp(24px, 3vw, 36px);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}
@media (max-width: 760px) { .pquotes { grid-template-columns: minmax(0, 1fr); } }

.pquote {
  /* flex + margin-top:auto on the caption so attributions sit on one baseline
     across the row whether the quote ran two lines or three. The cards are
     already equal height from the grid; without this the shorter one leaves its
     caption floating in the middle. */
  display: flex; flex-direction: column;
  margin: 0; padding: clamp(20px, 2.2vw, 28px);
  background: #FBFAF7;
  border-radius: var(--r);
  box-shadow:
    0 1px 2px -1px rgba(26, 24, 21, 0.08),
    0 10px 24px -16px rgba(26, 24, 21, 0.24),
    inset 0 0 0 1px rgba(26, 24, 21, 0.05);
}
/* Same decorative opening-quote glyph as .rquote (luxury v3). */
.pquote::before {
  content: '\201C';
  font-family: var(--display); font-weight: 500;
  font-size: 42px; line-height: 0.7;
  color: var(--paper-3);
  margin-bottom: 10px;
}
.pquote blockquote {
  margin: 0; font-family: var(--display); font-weight: 400;
  font-size: clamp(18px, 1.5vw, 20px); line-height: 1.4;
  color: var(--ink); text-wrap: pretty;
}
.pquote figcaption {
  margin-top: auto; padding-top: 16px; display: flex; flex-direction: column; gap: 2px;
}
.pquote figcaption b { font-size: 15px; font-weight: 600; color: var(--ink); }
.pquote figcaption span { font-size: 13px; color: var(--stone); }
.pquotes__foot { margin-top: clamp(20px, 2.4vw, 30px); }


/* --------------------------------------------------------------------------
   Internal-page whitespace pass — 2026-07-31
   (qa/BRIEF-internal-spacing-20260731.md)

   The section-padding half of this fix already landed with the internal-pages
   design-system block above (.page-internal .section at 56/48px, down from the
   shared 140/84 the generated pages inherited on staging). What remained was
   structural, and only on desktop:

   1. PROSE-ONLY SECTIONS (an h2 + one or two paragraphs, no figure, no list)
      rendered as a 42rem column hard against the left gutter with the entire
      right half of the wrap empty — four in a row on landscaping-services,
      two on every town page. At 56px section padding the float was vertical
      as well as horizontal: 140px of content in a 250px band of cream.
      The split below gives those sections the same head-left / copy-right
      composition the tick-list panel (now internal-wide, above) uses, so the
      full wrap carries the content. Measure on the right column resolves to
      ~60-70ch at 1440 — still a reading column, not a wall.

   2. A .qcta card that is the ONLY child of its section (our-work's dark
      "Have a similar project?" band, about-us's closing card) kept the
      margin-top it needs when it follows a grid on the homepage. Inside its
      own section that margin is an orphaned 44px above the card on top of
      the section padding, so the band reads top-heavy. Reset it.

   Mobile is untouched: both rules are desktop-only or neutral below 900px,
   keeping phone spacing within a couple of pixels of where it was.
   -------------------------------------------------------------------------- */
@media (min-width: 901px) {
  .page-internal .section:has(> .wrap > .head + .prose):not(:has(> .wrap > .ticks)) > .wrap {
    display: grid;
    grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
    column-gap: clamp(48px, 6vw, 96px);
    align-items: start;
  }
  .page-internal .section:has(> .wrap > .head + .prose):not(:has(> .wrap > .ticks)) > .wrap > .prose {
    margin-top: 0;
  }
  /* A proof photo inside a prose-only section (somers) spans the full row
     beneath head + copy instead of auto-placing into the left column. */
  .page-internal .section:has(> .wrap > .head + .prose):not(:has(> .wrap > .ticks)) > .wrap > .figure--inline {
    grid-column: 1 / -1;
  }
}

.page-internal .section > .wrap > .qcta:only-child { margin-top: 0; }

/* --------------------------------------------------------------------------
   Mobile ergonomics round (v90). Grouped here at the end so every rule wins
   its earlier same-specificity counterpart without !important.
   -------------------------------------------------------------------------- */

/* Compact-header call icon: hidden wherever the full header__end buttons are
   visible; appears alongside the burger. Sits in the grid column the nav
   vacates, so it never collides with the burger in column 3. */
.header__call { display: none; }
@media (max-width: 940px) {
  .header__call {
    display: inline-flex; align-items: center; justify-content: center;
    grid-column: 2; justify-self: end;
    width: 44px; height: 44px; border-radius: 50%;
    color: var(--ink);
    box-shadow: inset 0 0 0 1px var(--line-3);
  }
  .header--overlay:not(.is-scrolled) .header__call {
    color: var(--paper);
    box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.45);
  }
}

@media (max-width: 620px) {
  /* Stats: 2x2 squeezed both columns; four hairline-separated rows keep the
     numeral column aligned and each label on one comfortable line. */
  .stats__grid { grid-template-columns: 1fr; gap: 0; }
  .stats__item {
    display: grid; grid-template-columns: 96px minmax(0, 1fr);
    align-items: baseline; column-gap: 14px;
    padding: 11px 0;
  }
  .stats__item + .stats__item { border-top: 1px solid rgba(244, 241, 234, 0.14); }
  .stats__item .stats__label { margin-top: 0; }
  /* "Licensed & insured" cannot fit the 96px numeral column; that row stacks
     its label underneath instead of wrapping the claim mid-phrase. */
  .stats__item:first-child { grid-template-columns: 1fr; row-gap: 3px; }

  /* Section openers read left-aligned on phones: centered ragged paragraphs
     over three lines are hard to scan at 390px. */
  .head--center { text-align: left; justify-items: start; }
  .head--center .head__intro { margin-inline: 0; }
  .head--center .head__intro p { margin-inline: 0; }

  /* Body copy floor on phones is 16px. */
  .faq-acc__panel p { font-size: 16px; }

  /* Process: single-column timeline. The paper-backed numeral masks the
     connecting hairline as it passes behind. */
  .steps { grid-template-columns: 1fr; gap: 28px; position: relative; }
  .steps::before {
    content: ''; position: absolute; left: 21px; top: 10px; bottom: 10px;
    border-left: 1px solid var(--line-3);
  }
  .step { padding: 0 0 0 64px; position: relative; }
  .step__num {
    position: absolute; left: 0; top: -2px; width: 44px;
    text-align: center; background: var(--paper); padding: 4px 0;
  }
  .steps h3 { margin: 0 0 8px; padding-top: 6px; }
  .steps p { font-size: 16px; }

  /* Form controls: comfortable 52px height under a thumb. */
  .field input, .field select { min-height: 52px; }

  /* Before/after pairs stack full-width: side-by-side at ~44vw each, the
     transformation read as thumbnails. 5/4 holds each frame near 280px tall
     on a 390px viewport, so the before state is legible, not decorative. */
  .work__figure { grid-template-columns: 1fr; gap: 14px; }
  .ba__frame { aspect-ratio: 5 / 4; }

  /* Service cards stack with a full-width photo. The 108px thumb-row variant
     (defined earlier in this file) read as compressed desktop cards; the
     family links stay in the drawer and footer, so the list stays hidden. */
  .fam { flex-direction: column; }
  .fam__media { flex: none; aspect-ratio: 16 / 9; }
  .fam__body { padding: 18px 20px 20px; }
  .fam__name { font-size: 20px; }
  .fams { gap: 16px; }
}


/* --------------------------------------------------------------------------
   Callbar refinement (v93). Appended last so these rules win their earlier
   same-specificity counterparts without !important.

   The gap: env(safe-area-inset-bottom) only reports a real value once the
   viewport meta carries viewport-fit=cover, which it now does. Before that the
   bar stopped at the layout viewport and iOS painted the home-indicator strip
   below it. The bar now bleeds into that strip and pads its buttons back up
   out of it, so the fill runs to the physical bottom edge on every device.

   The look: three equal-weight buttons read as three equal asks. Call and Text
   become compact icon+label secondaries; "Free estimate" takes the remaining
   width as the single primary.
   -------------------------------------------------------------------------- */
@media (max-width: 940px) {
  .callbar {
    /* Fill through the home indicator, then pad the buttons back above it. */
    padding: 9px 12px calc(9px + env(safe-area-inset-bottom, 0px));
    gap: 8px;
    align-items: stretch;
    /* Cheap depth: a hairline highlight above the border reads as a raised
       surface rather than a flat slab pasted over the page. */
    border-top: 1px solid rgba(244, 241, 234, 0.14);
    box-shadow: 0 -12px 30px -18px rgba(26, 24, 21, 0.55);
  }

  .callbar a {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 7px;
    min-height: 46px;
    padding: 11px 12px;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.01em;
    border-radius: 12px;
    text-align: center;
    transition: background-color 160ms var(--ease), box-shadow 160ms var(--ease);
  }

  /* Secondaries: sized to their content so the primary keeps the width it
     needs. A faint fill lifts them off the evergreen instead of relying on a
     hairline outline alone, which nearly vanished at arm's length. */
  .callbar a.callbar__call,
  .callbar a.callbar__text {
    flex: 0 1 auto;
    padding-inline: 16px;
    background: rgba(244, 241, 234, 0.09);
    color: var(--paper);
    box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.22);
  }

  .callbar a.callbar__quote {
    flex: 1 1 auto;
    background: var(--moss);
    color: var(--paper);
    box-shadow: 0 1px 0 rgba(244, 241, 234, 0.12) inset;
  }

  /* Icons: inline SVG via mask so they inherit currentColor and add no
     requests. 0 0 auto stops them shrinking when the labels wrap. */
  .callbar a.callbar__call::before,
  .callbar a.callbar__text::before {
    content: '';
    flex: 0 0 auto;
    width: 16px; height: 16px;
    background: currentColor;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: 100% 100%;   mask-size: 100% 100%;
  }
  .callbar a.callbar__call::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.8 21 3 13.2 3 4c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.2.2 2.4.6 3.6.1.3 0 .7-.2 1l-2.3 2.2z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.8 21 3 13.2 3 4c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.2.2 2.4.6 3.6.1.3 0 .7-.2 1l-2.3 2.2z'/%3E%3C/svg%3E");
  }
  .callbar a.callbar__text::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 4h16c.6 0 1 .4 1 1v11c0 .6-.4 1-1 1H9l-4.3 3.4c-.4.3-1 0-1-.5V5c0-.6.4-1 1-1z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 4h16c.6 0 1 .4 1 1v11c0 .6-.4 1-1 1H9l-4.3 3.4c-.4.3-1 0-1-.5V5c0-.6.4-1 1-1z'/%3E%3C/svg%3E");
  }
}

/* Narrow phones: the labels are what give, not the tap targets. */
@media (max-width: 380px) {
  .callbar { padding-inline: 10px; gap: 7px; }
  .callbar a { font-size: 14px; padding-inline: 10px; }
  .callbar a.callbar__call,
  .callbar a.callbar__text { padding-inline: 13px; }
}

/* The footer must clear a bar that is now taller by the safe-area inset. */
@media (max-width: 940px) {
  .footer__bar {
    padding-bottom: calc(104px + env(safe-area-inset-bottom, 0px));
  }
}


/* --------------------------------------------------------------------------
   Stats strip mobile redesign (v94). Appended last so these win their earlier
   same-specificity counterparts inside the 620px block without !important.
   -------------------------------------------------------------------------- */
@media (max-width: 620px) {
  .stats__grid {
    grid-template-columns: 1fr;
    gap: 10px;
    padding-top: 20px;
    padding-bottom: 22px;
  }

  /* Reset the old table geometry before rebuilding: the earlier rule set a
     96px numeral column and hairline separators between rows. */
  .stats__item {
    display: grid;
    grid-template-columns: 38px minmax(0, 1fr);
    align-items: start;
    column-gap: 13px;
    row-gap: 1px;
    padding: 14px 14px;
    border-top: 0;
    border-radius: 13px;
    background: rgba(244, 241, 234, 0.045);
    box-shadow: inset 0 0 0 1px rgba(244, 241, 234, 0.07);
  }
  .stats__item + .stats__item { border-top: 0; }

  /* Icon well. ::before is the tinted disc, ::after the glyph, so the mask
     never has to composite against the disc fill. */
  .stats__item::before {
    content: '';
    grid-column: 1; grid-row: 1 / span 2;
    align-self: center; justify-self: center;
    width: 38px; height: 38px;
    border-radius: 50%;
    background: rgba(169, 192, 141, 0.13);
    box-shadow: inset 0 0 0 1px rgba(169, 192, 141, 0.20);
  }
  .stats__item::after {
    content: '';
    grid-column: 1; grid-row: 1 / span 2;
    width: 19px; height: 19px;
    align-self: center; justify-self: center;
    background: var(--sage);
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: contain;     mask-size: contain;
  }

  .stats__item .stats__num {
    grid-column: 2; grid-row: 1;
    align-self: end;
    font-size: 27px;
    line-height: 1.06;
    letter-spacing: -0.02em;
  }
  .stats__item .stats__label {
    grid-column: 2; grid-row: 2;
    align-self: start;
    margin-top: 3px;
    font-size: 13.5px;
    line-height: 1.45;
    color: rgba(244, 241, 234, 0.74);
  }

  /* ---- The credential is not a stat -------------------------------------
     Sage-tinted enclosure, full-width, sitting above the three numbers. Its
     "numeral" is a phrase, so it drops to a readable display size instead of
     competing with the figures below it. */
  .stats__item:first-child {
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: center;
    padding: 14px 15px;
    margin-bottom: 4px;
    background: rgba(169, 192, 141, 0.10);
    box-shadow: inset 0 0 0 1px rgba(169, 192, 141, 0.26);
  }
  .stats__item:first-child::before {
    width: 40px; height: 40px;
    background: rgba(169, 192, 141, 0.17);
    box-shadow: inset 0 0 0 1px rgba(169, 192, 141, 0.28);
  }
  .stats__item:first-child::after { width: 21px; height: 21px; }
  .stats__item:first-child .stats__num {
    align-self: end;
    font-size: 21px;
    line-height: 1.15;
    letter-spacing: -0.005em;
  }
  .stats__item:first-child .stats__label {
    margin-top: 1px;
    font-size: 13px;
    color: rgba(244, 241, 234, 0.68);
  }

  /* ---- Glyphs ------------------------------------------------------------
     1 shield-check (licensed & insured), 2 clock (24h turnaround),
     3 phone (a person answers), 4 crew (one crew per job). */
  .stats__item:nth-child(1)::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 4 5v6.1c0 4.9 3.4 9.5 8 10.9 4.6-1.4 8-6 8-10.9V5l-8-3zm-1.1 14.2L7.4 12.7l1.4-1.4 2.1 2.1 4.3-4.3 1.4 1.4-5.7 5.7z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 4 5v6.1c0 4.9 3.4 9.5 8 10.9 4.6-1.4 8-6 8-10.9V5l-8-3zm-1.1 14.2L7.4 12.7l1.4-1.4 2.1 2.1 4.3-4.3 1.4 1.4-5.7 5.7z'/%3E%3C/svg%3E");
  }
  .stats__item:nth-child(2)::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm.9-12.7h-1.8v5.3l4.4 2.7.9-1.5-3.5-2.1V7.3z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm.9-12.7h-1.8v5.3l4.4 2.7.9-1.5-3.5-2.1V7.3z'/%3E%3C/svg%3E");
  }
  .stats__item:nth-child(3)::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.8 21 3 13.2 3 4c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.2.2 2.4.6 3.6.1.3 0 .7-.2 1l-2.3 2.2z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8c1.4 2.8 3.8 5.1 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.8 21 3 13.2 3 4c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.2.2 2.4.6 3.6.1.3 0 .7-.2 1l-2.3 2.2z'/%3E%3C/svg%3E");
  }
  .stats__item:nth-child(4)::after {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm7.5.5a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM9 14c-3.3 0-6 1.8-6 4v2h12v-2c0-2.2-2.7-4-6-4zm7.5.5c-.9 0-1.7.1-2.4.4 1.2.9 1.9 2.1 1.9 3.4V20h6v-1.6c0-2-2.5-3.9-5.5-3.9z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm7.5.5a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM9 14c-3.3 0-6 1.8-6 4v2h12v-2c0-2.2-2.7-4-6-4zm7.5.5c-.9 0-1.7.1-2.4.4 1.2.9 1.9 2.1 1.9 3.4V20h6v-1.6c0-2-2.5-3.9-5.5-3.9z'/%3E%3C/svg%3E");
  }
}

/* Very narrow phones: trim the enclosure, never the glyph or the numeral. */
@media (max-width: 360px) {
  .stats__item { padding: 12px 11px; column-gap: 11px; }
  .stats__item .stats__num { font-size: 25px; }
  .stats__item .stats__label { font-size: 13px; }
}