/* ============================================================
   Newton Pest Control — design system

   Concept: the field service report. Newton's edge is documentation,
   so the page borrows the language of their paperwork. Record fields,
   graded activity chips, a monospace utility face.

   Type: Archivo (variable 400-800) for everything structural, IBM Plex
   Mono for record fields and labels. Both self-hosted from
   assets/fonts/ so there is no third-party request and no FOUT gamble
   on a CDN. Archivo is a signage grotesque, which is the right register
   for a licensed trade.

   Light only. No dark theme. Colour comes from the logo: brand mint
   #10F5AA over a green-shifted near-black. Mint fails text contrast on
   white, so it only ever sits behind dark text or acts as a rule.

   ── On the warmth of the neutrals ───────────────────────────
   The palette used to be mint, near-black and a green-tinted off-white,
   every neutral cooled toward green. Precise, and correct for a licensed
   operator. It also read like a laboratory, and the business the reviews
   describe is not one: "two very kind gentleman", "Asad uncle", "very
   sincere and kind nature", "I felt like a valued customer, not just
   another number". That gap between a cold site and a warm company is
   worth closing.

   What changed, and what deliberately did not.

   THE NEUTRALS ARE NOW WARM. --paper went from #f7f8f5 to #faf8f4, the
   surfaces and rules with it. It is the largest area on the screen, so
   moving it is most of the effect for none of the risk, and it suits the
   concept better besides: a field service report is printed on warm paper,
   not on cool lab stock.

   THE MINT DID NOT CHANGE. It is straight off the client's logo and it is
   doing the licensed-operator job well. A second brand colour competing
   with it would weaken both.

   THERE IS NO NEW CHROMATIC ACCENT, and that is a decision rather than an
   omission. The obvious warm accent for a green brand is terracotta, and
   this design already spends that exact hue on something load-bearing:
   --lv3 #ab3f28 is "High" on the activity ramp that runs through the
   strips, the tiles, the report card and the seasonal band. A decorative
   rust next to a semantic rust teaches a visitor that rust means nothing,
   and the ramp is the most functional colour on the site. So warmth is
   carried by surfaces only: --sand, --sand-2 and the warmed rules, used as
   grounds, never as status.

   Photographs are the other half of this. Six real faces will warm the
   page more than any token can, which is what the --shot placeholders are
   holding space for.
   ============================================================ */

/* ── fonts ───────────────────────────────────────────────── */
@font-face {
  font-family: 'Archivo';
  src: url('../fonts/archivo-var-latin.woff2') format('woff2-variations');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
    U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('../fonts/plexmono-400-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('../fonts/plexmono-600-latin.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ── tokens ──────────────────────────────────────────────── */
:root {
  color-scheme: light;

  /* Dark surfaces stay green-shifted, so they hold hands with the mint. */
  --ink:      #14201b;
  --ink-2:    #3a4640;   /* 9.3:1 on --paper */
  --ink-3:    #5a655e;   /* 5.7:1 */
  --muted:    #63625a;   /* warm grey, 5.8:1 */

  /* Light surfaces are warm. See the note above. */
  --paper:    #faf8f4;
  --paper-2:  #f2ede4;
  --sand:     #f5efe4;   /* warm ground for the human sections */
  --sand-2:   #ece3d4;   /* its border */
  --card:     #ffffff;
  --rule:     #e0d8cc;
  --rule-2:   #efe9e0;

  --mint:     #10f5aa;
  --mint-2:   #0bd494;
  --mint-3:   #08b37c;
  --mint-wash:#e8fdf5;
  --pine:     #05593a;   /* 7.9:1 */
  --pine-2:   #033f28;

  /* Semantic activity ramp. Not the brand accent, and not shared with any
     decorative colour, so rust always means "High" and nothing else. */
  --lv0: #b3bfb9;
  --lv1: #2aa176;
  --lv2: #bd7d08;
  --lv3: #ab3f28;

  --fs: 'Archivo', ui-sans-serif, system-ui, 'Segoe UI', Roboto, Arial, sans-serif;
  --fm: 'Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;

  --wrap: 1160px;
  --r: 6px;
  --r-lg: 14px;
  --r-xl: 22px;
  --pill: 999px;

  /* Warmed to match the paper. A cool shadow on a warm ground reads grey. */
  --sh-sm: 0 1px 2px rgb(58 48 34 / .06);
  --sh: 0 2px 4px rgb(58 48 34 / .05), 0 10px 26px -14px rgb(58 48 34 / .18);
  --sh-lg: 0 3px 6px rgb(58 48 34 / .06), 0 28px 56px -28px rgb(58 48 34 / .32);

  /* Section rhythm. Three sizes, used deliberately, so the page does not
     scroll at one constant beat. */
  --pad-tight: clamp(34px, 4vw, 56px);
  --pad:       clamp(52px, 7vw, 96px);
  --pad-loose: clamp(72px, 10vw, 136px);
}

/* ── base ────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 clamp(1rem, .975rem + .12vw, 1.0625rem)/1.62 var(--fs);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, svg { max-width: 100%; height: auto; display: block; }
/* Reachable by a screen reader, invisible on screen. The month rail prints "J"
   for July and "J" for January and June, so each button carries its full name
   in here. `display: none` would take it out of the accessibility tree, which is
   the opposite of what is wanted. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
a { color: var(--pine); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--pine-2); }
:focus-visible { outline: 3px solid var(--mint-3); outline-offset: 2px; border-radius: 3px; }

/* Leading is set per level rather than once for all four.
   A single 1.05 was doing real damage. It is a display setting, correct for a
   3.5rem headline of two or three words and much too tight everywhere else: an
   h2 that wrapped to two lines closed up, and h3 at 1.32rem sat almost solid.
   The highlight sweep made it worse, because `.hl` paints a .26em bar at 92% of
   the line box, which at 1.05 lands in the descenders of the line above. Each
   level now gets leading proportional to how large it actually is. */
h1, h2, h3, h4 { font-weight: 800; text-wrap: balance; margin: 0; }
/* One clause of a heading, on its own line or lines. `text-wrap: balance` is
   inherited and applies per block, so each clause balances inside itself and
   no line ever starts halfway through a sentence. See `lines()` in layout.mjs
   for why this beats letting the browser choose, and why removing this one
   declaration breaks the home page headline rather than merely loosening it. */
.ln { display: block; }
/* Two stacked clauses want a little air between them that lines inside one
   clause do not. Without this the gap between "Other companies say 24/7." and
   "We actually turn up." is identical to the gap between wrapped lines, and the
   two sentences read as one block of four lines. */
.ln + .ln { margin-top: .14em; }
h1 { font-size: clamp(2.2rem, 1.5rem + 2.9vw, 3.5rem); letter-spacing: -.035em; line-height: 1.09; }
h2 { font-size: clamp(1.75rem, 1.3rem + 1.9vw, 2.7rem); letter-spacing: -.03em; line-height: 1.14; }
h3 { font-size: clamp(1.1rem, 1.02rem + .4vw, 1.32rem); letter-spacing: -.018em; line-height: 1.32; }
h4 { line-height: 1.32; }
p { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
strong { font-weight: 700; }

.mono { font-family: var(--fm); font-size: .84em; letter-spacing: 0; }
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 22px; }
.lede { font-size: clamp(1.08rem, 1rem + .45vw, 1.32rem); line-height: 1.5; color: var(--ink-2); }
.prose { max-width: 64ch; display: grid; gap: 1em; }
.prose p { color: var(--ink-2); }
.tight { max-width: 54ch; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 99;
  background: var(--mint); color: #06110d; padding: 12px 18px; font-weight: 700;
}
.skip:focus { left: 10px; top: 10px; }

.eyebrow {
  font-family: var(--fm); font-size: .715rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
  display: flex; align-items: center; gap: 10px; margin-bottom: 13px;
}
.eyebrow::before { content: ""; width: 20px; height: 2px; background: var(--mint-2); flex: none; }

/* ── scroll reveal ───────────────────────────────────────── */
[data-reveal] { opacity: 0; transform: translateY(18px); }
.reveal-ready [data-reveal] { transition: opacity .62s cubic-bezier(.2,.7,.3,1), transform .62s cubic-bezier(.2,.7,.3,1); }
[data-reveal].is-in { opacity: 1; transform: none; }
/* Stagger children of a revealed group. */
[data-reveal-group] > * { opacity: 0; transform: translateY(16px); }
.reveal-ready [data-reveal-group] > * { transition: opacity .55s cubic-bezier(.2,.7,.3,1), transform .55s cubic-bezier(.2,.7,.3,1); }
[data-reveal-group].is-in > * { opacity: 1; transform: none; }
[data-reveal-group].is-in > *:nth-child(2) { transition-delay: .07s; }
[data-reveal-group].is-in > *:nth-child(3) { transition-delay: .14s; }
[data-reveal-group].is-in > *:nth-child(4) { transition-delay: .21s; }
[data-reveal-group].is-in > *:nth-child(5) { transition-delay: .28s; }
[data-reveal-group].is-in > *:nth-child(6) { transition-delay: .35s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  [data-reveal], [data-reveal-group] > * { opacity: 1 !important; transform: none !important; }
}

/* ── buttons ─────────────────────────────────────────────── */
.btn {
  --bp: 15px 26px;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: var(--bp); border: 1px solid transparent; border-radius: var(--pill);
  font: 700 1rem/1 var(--fs); letter-spacing: -.012em;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: transform .16s cubic-bezier(.2,.7,.3,1), background-color .16s ease,
              border-color .16s ease, box-shadow .16s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn--sm { --bp: 11px 17px; font-size: .9rem; }
.btn--block { width: 100%; padding: 17px 26px; font-size: 1.04rem; }
.btn--primary { background: var(--mint); color: #06110d; box-shadow: 0 2px 0 var(--mint-3); }
.btn--primary:hover { background: var(--mint-2); color: #06110d; box-shadow: 0 5px 18px -6px var(--mint-3); }
.btn--call { background: var(--ink); color: #f2f6f3; }
.btn--call:hover { background: #1d332c; color: #fff; box-shadow: 0 6px 20px -8px rgb(18 33 28 / .5); }
.btn--callDark { background: var(--mint); color: #06110d; }
.btn--callDark:hover { background: var(--mint-2); }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--rule); }
.btn--ghost:hover { border-color: var(--ink-3); background: var(--card); }
.btn--ghostLight { background: transparent; color: #eef7f2; border-color: rgb(255 255 255 / .34); }
.btn--ghostLight:hover { border-color: #eef7f2; color: #fff; background: rgb(255 255 255 / .06); }
.ico { width: 18px; height: 18px; fill: currentColor; flex: none; }

/* ── promo strip ─────────────────────────────────────────── */
.promo {
  background: var(--ink); color: #dff0e8; font-size: .875rem;
  border-bottom: 1px solid rgb(16 245 170 / .2);
}
.promo__in { display: flex; align-items: center; gap: 12px; padding-block: 9px; flex-wrap: wrap; }
.promo__tag {
  background: var(--mint); color: #06110d; padding: 3px 9px; border-radius: var(--pill);
  font-family: var(--fm); font-weight: 600; text-transform: uppercase;
  letter-spacing: .1em; font-size: .66rem;
}
.promo a { color: var(--mint); font-weight: 600; }

/* ── header: floating, detached from the viewport edge ───── */
.hdr { position: sticky; top: 0; z-index: 40; padding-top: 12px; background: transparent; }
.hdr::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 100%;
  background: linear-gradient(var(--paper) 55%, transparent);
  pointer-events: none;
}
/* The header pill gets a wider container than the page grid. It is visually
   detached from the content anyway, and the nav grew by two items plus a
   rating badge, which no longer fits inside 1160. */
.hdr .wrap { max-width: 1320px; }
.hdr__in {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  min-height: 66px; padding: 9px 12px 9px 20px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid var(--rule);
  border-radius: var(--pill);
  box-shadow: var(--sh);
}
.brand { flex: none; line-height: 0; }
.brand img { width: 176px; height: auto; }
.nav { margin-left: auto; min-width: 0; }
.nav__list { display: flex; align-items: center; gap: 1px; }
.nav__list a {
  position: relative; display: block; padding: 9px 11px; border-radius: var(--pill);
  color: var(--ink-2); text-decoration: none; font-size: .91rem; font-weight: 600;
  white-space: nowrap; transition: color .15s ease, background-color .15s ease;
}
.nav__list a:hover { background: var(--paper-2); color: var(--ink); }
.nav__list a[aria-current="page"] { color: var(--ink); background: var(--mint-wash); }
.nav__btn { display: none; }
.hdr__acts { display: flex; gap: 8px; flex: none; }

/* ── hero: asymmetric, with an ambient wash ──────────────── */
.hero { position: relative; overflow: hidden; }
.hero::before {
  content: ""; position: absolute; z-index: 0;
  width: 900px; height: 900px; right: -280px; top: -420px;
  background: radial-gradient(circle at center,
    rgb(16 245 170 / .3) 0%, rgb(16 245 170 / .1) 42%, transparent 68%);
  pointer-events: none;
}
.hero::after {
  content: ""; position: absolute; z-index: 0;
  width: 620px; height: 620px; left: -300px; bottom: -340px;
  background: radial-gradient(circle at center, rgb(5 89 58 / .09) 0%, transparent 66%);
  pointer-events: none;
}
.hero__in {
  position: relative; z-index: 1;
  display: grid; gap: clamp(34px, 4.5vw, 60px);
  grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr);
  align-items: start; padding-block: clamp(40px, 5.5vw, 76px) var(--pad);
}
.hero__copy { display: grid; gap: 24px; }
/* 24ch, not 19ch, and the headline is now two declared clauses rather than one
   run of text. The old cap forced "Other companies say 24/7." to break mid
   sentence no matter what the wrap algorithm wanted. */
.hero h1 { max-width: 24ch; }
/* The highlight is a background sweep on an inline span. Inside a `.ln` block
   it must stay inline or the sweep spans the whole line box. */
.hero h1 .hl { display: inline; }
/* Highlight is a background sweep rather than a positioned ::after, because a
   negative z-index would drop behind the hero's own stacking context. */
.hl {
  background-image: linear-gradient(var(--mint), var(--mint));
  background-repeat: no-repeat;
  background-position: 0 92%;
  background-size: 0% .26em;
}
.reveal-ready .hl { transition: background-size .75s cubic-bezier(.2,.7,.3,1) .3s; }
.is-in .hl, .no-js .hl { background-size: 100% .26em; }
.hero__lede { max-width: 42ch; }
.hero__acts { display: flex; flex-wrap: wrap; gap: 12px; }
.hero__note {
  font-family: var(--fm); font-size: .78rem; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 6px 14px;
}
.hero__note b { color: var(--ink-2); font-weight: 600; }

/* ── the record document ─────────────────────────────────── */
.doc {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  box-shadow: var(--sh-lg); padding: clamp(20px, 2.4vw, 30px);
  display: grid; gap: 17px;
  background-image:
    linear-gradient(var(--rule-2) 1px, transparent 1px),
    linear-gradient(90deg, var(--rule-2) 1px, transparent 1px);
  background-size: 26px 26px, 26px 26px;
  background-position: -1px -1px;
}
.doc__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding-bottom: 15px; border-bottom: 2px solid var(--ink);
}
.doc__eyebrow {
  font-family: var(--fm); text-transform: uppercase; letter-spacing: .15em;
  color: var(--muted); margin-bottom: 6px; font-size: .66rem;
}
.doc__title { font-size: clamp(1.15rem, 1rem + .5vw, 1.45rem); }
.doc__stamp {
  border: 1px solid var(--mint-2); color: var(--pine); background: var(--mint-wash);
  padding: 5px 10px; border-radius: var(--pill); white-space: nowrap; font-weight: 600;
}
.doc__sub { color: var(--muted); font-size: .9rem; }
.doc__grid { display: grid; gap: 13px; }
.doc__grid > div { display: grid; gap: 3px; }
.doc__grid dt {
  font-family: var(--fm); font-size: .66rem; text-transform: uppercase;
  letter-spacing: .13em; color: var(--muted);
}
.doc__grid dd { color: var(--ink-2); font-size: .89rem; }
.doc__label {
  font-family: var(--fm); font-size: .66rem; text-transform: uppercase;
  letter-spacing: .13em; color: var(--muted); margin-bottom: 8px;
}
.acts { display: flex; flex-wrap: wrap; gap: 7px; }
.act {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--rule); background: var(--card);
  padding: 5px 11px; border-radius: var(--pill); font-size: .79rem; font-weight: 600;
  color: var(--ink-2);
}
.act i { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.act--0 i { background: var(--lv0); }
.act--1 i { background: var(--lv1); }
.act--2 i { background: var(--lv2); }
.act--3 i { background: var(--lv3); }
.act--3 { border-color: color-mix(in srgb, var(--lv3) 45%, var(--rule)); }
.doc__recs { display: grid; gap: 9px; border-top: 1px solid var(--rule); padding-top: 15px; }
.doc__recs li { position: relative; padding-left: 26px; font-size: .875rem; color: var(--ink-2); }
.doc__recs li::before {
  content: ""; position: absolute; left: 0; top: 3px;
  width: 15px; height: 15px; border: 1.5px solid var(--ink-3); border-radius: 3px;
  background:
    linear-gradient(45deg, transparent 44%, var(--pine) 44%, var(--pine) 56%, transparent 56%),
    linear-gradient(-45deg, transparent 44%, var(--pine) 44%, var(--pine) 56%, transparent 56%);
}

/* ── trust row ───────────────────────────────────────────── */
/* FLEX, NOT A FIXED COLUMN COUNT. This was repeat(4, 1fr), which silently left
   an empty fourth column the moment the row went from four cells to three
   (2026-09-01, when the applicator names and the free-inspection offer both
   came out). Flexing means the row follows however many cells trustBar()
   emits, so adding or dropping one is a change in one file instead of two.
   The 1px gap over a --rule background is what draws the dividers. */
.trust {
  display: flex; gap: 1px;
  background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r-lg);
  overflow: hidden;
}
.trust li {
  flex: 1 1 0; min-width: 0; background: var(--card);
  padding: 14px 16px; display: grid; gap: 3px;
}
.trust__k {
  font-family: var(--fm); font-size: .64rem; text-transform: uppercase;
  letter-spacing: .13em; color: var(--muted);
}
.trust__v { font-weight: 700; color: var(--ink); font-size: .95rem; }
.trust .mono { font-size: .85rem; font-weight: 600; }

/* ── sections ────────────────────────────────────────────── */
.sec { padding-block: var(--pad); }
.sec--tight { padding-block: var(--pad-tight); }
.sec--loose { padding-block: var(--pad-loose); }
.sec--alt { background: var(--paper-2); border-block: 1px solid var(--rule); }
.sec--ink { background: var(--ink); color: #dfeee7; }
.sec--ink h2 { color: #fff; }
.sec--ink .eyebrow { color: #8fab9f; }
.sec__head { max-width: 60ch; margin-bottom: clamp(26px, 3.4vw, 42px); display: grid; gap: 13px; }
.sec__head p { color: var(--ink-2); }
.sec--ink .sec__head p { color: #adc5b9; }
.sec__head--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 26px; max-width: none;
}
.sec__head--row > div { max-width: 58ch; display: grid; gap: 13px; }

.grid { display: grid; gap: 18px; }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Asymmetric: second column sits lower, so the row is not a flat band. */
.grid--offset > *:nth-child(even) { margin-top: clamp(0px, 3vw, 44px); }

.stack { display: grid; gap: clamp(22px, 3vw, 36px); align-content: start; }
.split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(28px, 4vw, 58px); align-items: start; }
.split--aside { grid-template-columns: minmax(0, 1.38fr) minmax(0, .62fr); }
/* Copy beside the example report, on /residential/ and /commercial/.
   NOT `split--aside`: at .62fr the card lands around 330px, which is narrow
   enough to break "Service / Inspection Report" over three lines and wrap the
   four activity chips onto two rows. It read as squeezed rather than as a
   document. The card is capped instead of filling, because past roughly 480px
   the mono field labels start to look stretched. */
.split--doc { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; }
.split--doc .doc { width: min(100%, 480px); margin-left: auto; }
.split--wide { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); }

.aside {
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  padding: 24px; display: grid; gap: 14px; position: sticky; top: 96px; box-shadow: var(--sh-sm);
}
.aside h2 { font-size: 1.15rem; }
.aside p { font-size: .92rem; color: var(--ink-2); }
.aside__or {
  font-family: var(--fm); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin-top: 2px;
}

/* ── coverage check ──────────────────────────────────────── */
/* Searches the neighbourhood names in data/areas.mjs. Ships hidden and is
   revealed by site.js, so with JS off the city pills above are the whole
   feature and nothing on screen is a dead input. */
.acheck { display: grid; gap: 8px; }
.acheck[hidden] { display: none; }
.acheck__lab {
  font-family: var(--fm); font-size: .66rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.acheck__in {
  width: 100%; font: 400 .95rem/1.4 var(--fs); color: var(--ink);
  padding: 12px 14px; background: var(--paper);
  border: 1px solid var(--rule); border-radius: var(--r);
  transition: border-color .16s ease, box-shadow .16s ease;
}
.acheck__in:focus {
  outline: none; border-color: var(--mint-2);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--mint) 22%, transparent);
}
.acheck__in::placeholder { color: var(--muted); }
/* Empty until something is typed, so it takes no space while it has nothing to
   say. Without :empty the aside jumps by the height of a blank box on load. */
.acheck__out:not(:empty) { margin-top: 2px; }
.acheck__hit {
  display: grid; gap: 3px; padding: 12px 14px; border-radius: var(--r);
  text-decoration: none; background: var(--mint-wash);
  border: 1px solid color-mix(in srgb, var(--mint-2) 45%, var(--rule));
  transition: border-color .16s ease, background-color .16s ease;
}
.acheck__hit:hover { border-color: var(--mint-2); background: color-mix(in srgb, var(--mint) 14%, var(--paper)); }
.acheck__yes {
  font-family: var(--fm); font-size: .64rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--pine);
}
.acheck__where { font-weight: 700; color: var(--ink); font-size: .95rem; letter-spacing: -.015em; }
.acheck__note { font-size: .84rem; color: var(--ink-2); }
.acheck__miss { font-size: .86rem; color: var(--ink-2); line-height: 1.55; }

/* ── cards ───────────────────────────────────────────────── */
.card {
  position: relative; background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: 22px; display: grid; gap: 10px; align-content: start;
  text-decoration: none; color: inherit; box-shadow: var(--sh-sm);
  transition: border-color .18s ease, transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease;
}
a.card:hover { border-color: var(--mint-2); transform: translateY(-3px); box-shadow: var(--sh); }
.card h3 { color: var(--ink); }
.card p { color: var(--ink-2); font-size: .92rem; }
.card__season {
  font-family: var(--fm); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .11em; color: var(--muted);
}
.card__more { color: var(--pine); font-weight: 700; font-size: .89rem; margin-top: 2px; }
a.card:hover .card__more { text-decoration: underline; text-underline-offset: 3px; }

/* dense pest tiles, five across, so it reads as an index not a 3-up */
/* ── pest marks ──────────────────────────────────────────── */
/* The clipart in src/data/pest-icons.mjs, drawn to match the seven icons the
   current newtonpestcontrol.ca already carries. Those icons are #10F5AA over a
   dark outline, which is this site's mint over this site's ink, so the two
   agree without anything being adjusted.

   COLOUR IS SET HERE, NOT IN THE MARKUP. Each mark is shape-only and picks up
   its fill and stroke from these three rules, which is what lets the same
   markup sit on --paper, on --sand and on a dark --ink section. Stroke uses
   --ink rather than the #0A1328 sampled off the originals: that navy is very
   slightly bluer than this palette, and matching the design system matters
   more here than matching a 2019 PNG to the hex.

   The stroke width is deliberately NOT scaled with the icon. These are drawn on
   a 64-unit grid at 3.2 units, which is heavy — at 28px in a tile it holds up
   as a mark, and at 96px on a pest page it reads as a deliberate flat style
   rather than a thin line drawing blown up. `vector-effect` is not used for
   the same reason: a hairline at 96px would look like a different icon set. */
.picon { display: block; width: 100%; height: 100%; overflow: visible; }
.picon__f {
  fill: var(--mint); stroke: var(--ink); stroke-width: 3.2;
  stroke-linejoin: round; stroke-linecap: round;
}
.picon__s {
  fill: none; stroke: var(--ink); stroke-width: 3.2;
  stroke-linecap: round; stroke-linejoin: round;
}
.picon__d { fill: var(--ink); stroke: none; }

.tiles { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.tile {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: 16px; display: grid; gap: 7px; align-content: start;
  text-decoration: none; color: inherit; box-shadow: var(--sh-sm);
  transition: border-color .18s ease, transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease;
}
.tile:hover { border-color: var(--mint-2); transform: translateY(-3px); box-shadow: var(--sh); }
/* The mark sits above the name rather than beside it. Five tiles across leaves
   each one about 150px wide, and an inline icon would take a third of the line
   the pest's name needs. It leans very slightly on hover, which is the only
   animation on the tile besides the lift. */
.tile__ico { width: 40px; height: 40px; margin-bottom: 3px; transition: transform .18s cubic-bezier(.2,.7,.3,1); }
.tile:hover .tile__ico { transform: rotate(-6deg) scale(1.06); }
.tile__n { font-weight: 800; font-size: 1.02rem; letter-spacing: -.02em; color: var(--ink); }
.tile__s { font-family: var(--fm); font-size: .64rem; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); }
.tile__bar { display: flex; gap: 2px; margin-top: 2px; }
.tile__bar i { height: 4px; flex: 1; border-radius: 2px; background: var(--rule-2); }
.tile__bar i.on1 { background: color-mix(in srgb, var(--lv1) 55%, transparent); }
.tile__bar i.on2 { background: color-mix(in srgb, var(--lv2) 65%, transparent); }
.tile__bar i.on3 { background: color-mix(in srgb, var(--lv3) 75%, transparent); }

/* ── service-area map ────────────────────────────────────── */
/* Geometry is in src/service-map.mjs and is projected from real coordinates.
   Everything below is only appearance.

   The restraint is the point. Water is a wash rather than a colour, the river
   and the coast are hairlines, and the spokes out of Newton are barely there —
   so what a visitor actually sees is seventeen labels and one emphasised dot.
   Mint is spent on exactly one thing, the home marker, because the map's job is
   to say "we are in the middle of this" and every other mint element would
   compete with that sentence. */
.smap {
  margin: 0; background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: 14px; box-shadow: var(--sh-sm);
}
.smap__svg { display: block; width: 100%; height: auto; }
.smap__water { fill: var(--paper-2); stroke: none; }
.smap__coast { fill: none; stroke: var(--rule); stroke-width: 1.6; }
.smap__inlet { fill: none; stroke: var(--rule); stroke-width: 1.6; }
.smap__river { fill: none; stroke: var(--rule); stroke-width: 2.4; stroke-linecap: round; }
.smap__border { stroke: var(--rule); stroke-width: 1.4; stroke-dasharray: 7 6; }
/* They carry "one yard, everywhere else from it" at a glance and then get out
   of the way. Nudged up on the client's instruction 2026-09-01, from 0.7 at
   .26 opacity, which was too close to invisible to read as anything. Both
   numbers moved rather than one, so the lines gained presence without becoming
   a starburst — seventeen lines at full weight out of a single point is a
   pattern that takes over whatever else is on the canvas. */
.smap__spoke { stroke: var(--mint-3); stroke-width: 1.1; opacity: .45; }
.smap__dot { fill: var(--ink); transition: r .14s ease, fill .14s ease; }

/* ── clickable cities ──
   The anchor is the interactive unit, not the dot: .smap__hit is a transparent
   rectangle spanning the dot and its label so the whole pairing is one target.
   Without it the hit area is a 4-unit circle, which is roughly 4 real pixels
   at the width this renders — unhittable with a mouse and hopeless on touch. */
.smap__city { cursor: pointer; }
.smap__hit { fill: transparent; }
.smap__city:hover .smap__dot { fill: var(--mint-3); r: 6; }
.smap__city:hover .smap__lab { fill: var(--ink); text-decoration: underline; }
.smap__city--home:hover .smap__dot { fill: var(--mint); r: 7; }
/* Focus is styled even though these are out of the tab order — see the comment
   in src/service-map.mjs. A browser or extension can still focus them, and a
   focused link with no visible ring is the worse failure. */
.smap__city:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.smap__city--home .smap__dot { fill: var(--mint); stroke: var(--ink); stroke-width: 2.2; }
.smap__halo { fill: var(--mint); opacity: .17; }
.smap__lab {
  font-family: var(--fs); font-size: 12.5px; font-weight: 600;
  fill: var(--ink-2); letter-spacing: -.01em;
}
.smap__city--home .smap__lab { fill: var(--ink); font-weight: 800; }
.smap__note {
  font-family: var(--fm); font-size: 10px; fill: var(--muted);
  text-transform: uppercase; letter-spacing: .09em;
}
.smap__note--water { fill: var(--ink-3); opacity: .7; }
@media (max-width: 620px) {
  /* Seventeen 12.5px labels do not fit a phone. The map keeps the shape and the
     dots, drops the names, and the pills underneath carry the list instead —
     they are the same seventeen and they are already links. */
  .smap__lab, .smap__note { display: none; }
  .smap__spoke { opacity: .4; }
}

/* ── "right now" seasonal band ───────────────────────────── */
.now { background: var(--ink); color: #dfeee7; overflow: hidden; }
.now__in {
  display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: clamp(26px, 4vw, 52px); align-items: center; padding-block: clamp(40px, 5vw, 64px);
}
.now__lab {
  font-family: var(--fm); font-size: .7rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--mint); display: flex; align-items: center; gap: 9px; margin-bottom: 14px;
}
.now__lab i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--mint); flex: none;
  box-shadow: 0 0 0 0 rgb(16 245 170 / .7); animation: ping 2.4s ease-out infinite;
}
@keyframes ping {
  0%   { box-shadow: 0 0 0 0 rgb(16 245 170 / .6); }
  70%  { box-shadow: 0 0 0 11px rgb(16 245 170 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(16 245 170 / 0); }
}
.now h2 { color: #fff; font-size: clamp(1.55rem, 1.2rem + 1.5vw, 2.3rem); }
.now__sub { color: #adc5b9; margin-top: 12px; max-width: 44ch; font-size: .98rem; }
.now__list { display: grid; gap: 10px; }
.now__item {
  display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: center;
  padding: 14px 18px; border-radius: var(--r-lg); text-decoration: none;
  background: rgb(255 255 255 / .05); border: 1px solid rgb(255 255 255 / .1);
  color: #eaf5ef; transition: background-color .18s ease, border-color .18s ease, transform .18s cubic-bezier(.2,.7,.3,1);
}
.now__item:hover {
  background: rgb(16 245 170 / .1); border-color: rgb(16 245 170 / .4);
  color: #fff; transform: translateX(4px);
}
.now__lvl {
  font-family: var(--fm); font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
  padding: 4px 9px; border-radius: var(--pill); font-weight: 600; white-space: nowrap;
}
.now__lvl--3 { background: color-mix(in srgb, var(--lv3) 82%, transparent); color: #fff; }
.now__lvl--2 { background: color-mix(in srgb, var(--lv2) 82%, transparent); color: #1a1200; }
.now__lvl--1 { background: color-mix(in srgb, var(--lv1) 78%, transparent); color: #04150e; }
.now__nm { font-weight: 700; letter-spacing: -.015em; }
.now__why { color: #9fbcae; font-size: .86rem; display: block; font-weight: 400; letter-spacing: 0; }
.now__go { color: var(--mint); font-size: 1.1rem; line-height: 1; }
/* Four of the twelve months have nothing at moderate or above. Saying so is a
   better answer than promoting the least-quiet pest to a headline. */
.now__quiet {
  color: #adc5b9; font-size: .95rem; line-height: 1.6;
  padding: 18px 20px; border-radius: var(--r-lg);
  background: rgb(255 255 255 / .04); border: 1px dashed rgb(255 255 255 / .16);
}

/* The month rail. Twelve buttons that scrub the list above from the pressure
   data already on the page. Ships hidden and is revealed by site.js, so with JS
   off there is no dead control. */
.months { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 20px; }
.months[hidden] { display: none; }
.months__b {
  font-family: var(--fm); font-size: .72rem; font-weight: 600; line-height: 1;
  width: 30px; height: 30px; display: grid; place-items: center;
  background: rgb(255 255 255 / .06); border: 1px solid rgb(255 255 255 / .12);
  border-radius: 8px; color: #b9d2c6; cursor: pointer;
  transition: background-color .16s ease, color .16s ease, border-color .16s ease;
}
.months__b:hover { background: rgb(16 245 170 / .16); color: #fff; border-color: rgb(16 245 170 / .45); }
.months__b.is-on { background: var(--mint); border-color: var(--mint); color: #06110d; }
.months__now {
  font-family: var(--fm); font-size: .66rem; letter-spacing: .08em; text-transform: uppercase;
  margin-left: 6px; padding: 7px 12px; border-radius: var(--pill); cursor: pointer;
  background: transparent; border: 1px solid rgb(255 255 255 / .28); color: #dff0e8;
}
.months__now[hidden] { display: none; }
.months__now:hover { background: rgb(255 255 255 / .08); border-color: #dff0e8; color: #fff; }

/* ── pest picker (interactive) ───────────────────────────── */
.picker {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-xl);
  padding: clamp(22px, 3vw, 36px); box-shadow: var(--sh-lg);
  display: grid; gap: 22px;
}
.picker__q { display: grid; gap: 14px; }
.picker__qh { font-family: var(--fm); font-size: .7rem; letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.picker__opts { display: flex; flex-wrap: wrap; gap: 9px; }
.chip {
  font: 600 .92rem/1 var(--fs); padding: 11px 16px; border-radius: var(--pill);
  border: 1px solid var(--rule); background-color: var(--paper); color: var(--ink-2);
  cursor: pointer;
  transition: background-color .16s ease, border-color .16s ease, color .16s ease;
}
.chip:hover { border-color: var(--mint-2); color: var(--pine); background-color: var(--mint-wash); }
/* Selected state. aria-pressed carries the meaning for assistive tech, .is-on
   carries the styling, so neither depends on the other. Listed after :hover so
   a selected chip stays dark while the pointer is over it. */
.chip.is-on,
.chip.is-on:hover {
  background-color: var(--ink); border-color: var(--ink); color: #f2f6f3;
}
.picker__out { border-top: 1px solid var(--rule); padding-top: 22px; min-height: 92px; }
.picker__empty { color: var(--muted); font-size: .95rem; }
.picker__res { display: grid; gap: 14px; }
.picker__resH { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.picker__resH h3 { font-size: 1.3rem; }
.picker__hits { display: grid; gap: 8px; }
.picker__hits li { position: relative; padding-left: 26px; font-size: .93rem; color: var(--ink-2); }
.picker__hits li::before {
  content: ""; position: absolute; left: 1px; top: .42em; width: 14px; height: 8px;
  border-left: 2.5px solid var(--mint-3); border-bottom: 2.5px solid var(--mint-3);
  transform: rotate(-45deg);
}
.picker__acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }

/* ── process: horizontal rail with a connecting line ─────── */
.rail { counter-reset: s; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 16px; }
.rail__step { counter-increment: s; position: relative; padding-top: 34px; }
.rail__step::before {
  content: ""; position: absolute; left: 0; right: 0; top: 11px; height: 2px;
  background: var(--rule);
}
.rail__step:first-child::before { left: 11px; border-radius: 2px 0 0 2px; }
.rail__step:last-child::before { right: calc(100% - 12px); }
.rail__step::after {
  content: ""; position: absolute; left: 5px; top: 6px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--paper); border: 2px solid var(--mint-3);
}
.rail__n {
  font-family: var(--fm); font-size: .66rem; letter-spacing: .12em; color: var(--muted);
  display: block; margin-bottom: 7px;
}
.rail__step h3 { font-size: 1rem; margin-bottom: 6px; }
.rail__step p { font-size: .875rem; color: var(--ink-2); }

/* vertical fallback / long-form steps */
.steps { counter-reset: s2; display: grid; gap: 0; }
.step {
  counter-increment: s2; display: grid; grid-template-columns: 52px 1fr; gap: 20px;
  padding: 20px 0; border-top: 1px solid var(--rule); align-items: start;
}
.step:last-child { border-bottom: 1px solid var(--rule); }
.step::before {
  content: counter(s2, decimal-leading-zero);
  font-family: var(--fm); font-size: 1.4rem; font-weight: 600; color: var(--mint-3);
  line-height: 1; padding-top: 3px;
}
.step__b { display: grid; gap: 6px; }
.step__b p { color: var(--ink-2); }

/* ── lists ───────────────────────────────────────────────── */
.checks { display: grid; gap: 11px; }
.checks li { position: relative; padding-left: 30px; color: var(--ink-2); }
.checks li::before {
  content: ""; position: absolute; left: 0; top: .42em; width: 16px; height: 9px;
  border-left: 2.5px solid var(--mint-3); border-bottom: 2.5px solid var(--mint-3);
  transform: rotate(-45deg);
}
.dots { display: grid; gap: 10px; }
.dots li { position: relative; padding-left: 22px; color: var(--ink-2); }
.dots li::before {
  content: ""; position: absolute; left: 4px; top: .6em; width: 7px; height: 7px;
  border-radius: 50%; background: var(--mint-2);
}
.sec--ink .checks li, .sec--ink .dots li { color: #c3d8cc; }

/* two-column fact list, replaces a wall of prose */
.facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--r-lg); overflow: hidden; }
.facts > div { background: var(--card); padding: 17px 19px; display: grid; gap: 5px; }
/* An odd number of facts otherwise leaves the grid's own background showing
   through as a stray coloured cell. The contact page has eleven. */
.facts > div:last-child:nth-child(odd) { grid-column: 1 / -1; }
.facts dt, .facts__k {
  font-family: var(--fm); font-size: .65rem; text-transform: uppercase;
  letter-spacing: .13em; color: var(--muted);
}
.facts dd, .facts__v { color: var(--ink-2); font-size: .93rem; }
.facts strong { color: var(--ink); }

.species { display: grid; }
.species > div {
  display: grid; grid-template-columns: minmax(130px, 200px) 1fr; gap: 8px 28px;
  padding: 17px 0; border-top: 1px solid var(--rule);
}
.species > div:last-child { border-bottom: 1px solid var(--rule); }
.species dt { font-weight: 700; color: var(--ink); }
.species dd { color: var(--ink-2); font-size: .94rem; }

/* ── seasonal strip ──────────────────────────────────────── */
.strip {
  margin: 0; border: 1px solid var(--rule); border-radius: var(--r-lg);
  background: var(--card); padding: 18px; display: grid; gap: 12px; box-shadow: var(--sh-sm);
}
.strip__cap {
  font-family: var(--fm); font-size: .66rem; text-transform: uppercase;
  letter-spacing: .13em; color: var(--muted);
}
.strip__row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 3px; }
.strip__cell {
  height: 48px; border-radius: 3px; display: flex; align-items: flex-end;
  justify-content: center; padding-bottom: 4px; position: relative;
  font: 600 .66rem/1 var(--fm);
}
.strip__cell--0 { background: color-mix(in srgb, var(--lv0) 24%, transparent); color: var(--muted); }
.strip__cell--1 { background: color-mix(in srgb, var(--lv1) 28%, transparent); color: var(--ink-2); }
.strip__cell--2 { background: color-mix(in srgb, var(--lv2) 38%, transparent); color: var(--ink-2); }
.strip__cell--3 { background: color-mix(in srgb, var(--lv3) 46%, transparent); color: var(--ink); }
/* the month you are actually reading this in */
.strip__cell.is-now { outline: 2px solid var(--ink); outline-offset: 1px; z-index: 1; }
.strip__cell.is-now::before {
  content: "NOW"; position: absolute; top: -9px; left: 50%; transform: translateX(-50%);
  font: 600 .52rem/1 var(--fm); letter-spacing: .08em;
  background: var(--ink); color: #fff; padding: 2px 5px; border-radius: 3px;
}
.strip__key { display: flex; flex-wrap: wrap; gap: 14px; }
.strip__keyItem { display: inline-flex; align-items: center; gap: 6px; font-family: var(--fm); font-size: .68rem; color: var(--muted); }
.sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.sw--0 { background: color-mix(in srgb, var(--lv0) 40%, transparent); }
.sw--1 { background: color-mix(in srgb, var(--lv1) 45%, transparent); }
.sw--2 { background: color-mix(in srgb, var(--lv2) 55%, transparent); }
.sw--3 { background: color-mix(in srgb, var(--lv3) 60%, transparent); }

/* ── stats with count-up ─────────────────────────────────── */
.stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
.stat {
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  padding: 22px; display: grid; gap: 7px; box-shadow: var(--sh-sm);
}
.stat__n {
  font: 800 clamp(1.8rem, 1.4rem + 1.4vw, 2.6rem)/1 var(--fs);
  letter-spacing: -.04em; color: var(--ink); font-variant-numeric: tabular-nums;
}
.stat__l { color: var(--ink-2); font-size: .9rem; }
/* On the dark section the cards are translucent, so they read as panels on the
   ground rather than white slabs punched through it. */
.sec--ink .stat {
  background: rgb(255 255 255 / .05); border-color: rgb(255 255 255 / .12); box-shadow: none;
}
.sec--ink .stat__n { color: #fff; }
.sec--ink .stat__l { color: #a8c2b5; }
.sec--ink .stat__l a { color: var(--mint); }

/* ── testimonials ────────────────────────────────────────── */
.stars { display: flex; gap: 2px; }
.star { width: 16px; height: 16px; fill: var(--lv2); flex: none; }
.star--off { fill: var(--rule); }

.quote {
  margin: 0; background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: 22px; display: grid; gap: 13px;
  align-content: start; box-shadow: var(--sh-sm);
  text-decoration: none; color: inherit;
  transition: border-color .18s ease, transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease;
}
/* the whole card is the link to the Google profile */
a.quote:hover { border-color: var(--mint-2); transform: translateY(-3px); box-shadow: var(--sh); }
a.quote:hover .quote__src { color: var(--pine); }
a.quote:hover .quote__go { transform: translateX(3px); }
.quote blockquote { margin: 0; display: grid; gap: .65em; }
.quote blockquote p { color: var(--ink-2); font-size: .96rem; }
/* Quote marks wrap the whole blockquote, not each paragraph of a
   multi-paragraph review. */
.quote blockquote p:first-child::before { content: "\201C"; }
.quote blockquote p:last-child::after { content: "\201D"; }
.quote figcaption { display: grid; gap: 6px; padding-top: 13px; border-top: 1px solid var(--rule); }
.quote__who { font-weight: 700; color: var(--ink); font-size: .92rem; }
.quote__src {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--fm); font-size: .68rem; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); transition: color .18s ease;
}
.quote__go { margin-left: auto; transition: transform .18s ease; color: var(--mint-3); }
.gico { width: 13px; height: 13px; fill: currentColor; flex: none; }

.sec--quote { background: var(--mint-wash); border-block: 1px solid var(--rule); }
.quote--feature {
  max-width: 700px; margin-inline: auto; padding: clamp(24px, 3vw, 38px);
  border-color: color-mix(in srgb, var(--mint-2) 40%, var(--rule));
}
.quote--feature .star { width: 19px; height: 19px; }
.quote--feature blockquote p { font-size: 1.08rem; line-height: 1.58; color: var(--ink); }

/* ── the reviews belt ────────────────────────────────────── */
/* Two rows of cards travelling in opposite directions. See `reviewBelt()` in
   layout.mjs for why this replaced the three-across grid.

   The seam. Each row prints its cards twice and slides exactly -50%, so when
   the animation restarts the second copy is sitting precisely where the first
   one was. `width: max-content` is what makes -50% mean "one full set" rather
   than "half the viewport", and it is the single declaration that breaks the
   loop if it is removed. */
.belt {
  display: grid; gap: clamp(14px, 1.6vw, 20px);
  /* Bleeds past .wrap on purpose: a belt that stops at the text column reads
     as a widget, one that runs off both edges reads as continuing. */
  margin-top: clamp(22px, 3vw, 34px);
  /* Fades both ends without needing to know the section's background colour,
     which is what lets the same belt sit on --paper and on --sand. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
/* Vertical padding, not just overflow:hidden. The cards lift 3px on hover and
   carry a shadow, and a row clipped tight to the card edge shears both off. */
.belt__row { overflow: hidden; padding-block: 7px; }
.belt__track {
  display: flex; width: max-content;
  /* Paused until the section reveals, so a belt further down the page is not
     already mid-travel by the time it is scrolled to. */
  animation: belt-a 64s linear infinite;
  animation-play-state: paused;
}
.is-in .belt__track,
/* With JS blocked nothing ever adds .is-in, and a permanently paused belt would
   sit there with its first card sliced by the edge mask. */
.no-js .belt__track { animation-play-state: running; }
.belt__row--b .belt__track { animation-name: belt-b; animation-duration: 78s; }
.belt__set { display: flex; gap: clamp(14px, 1.6vw, 20px); padding-right: clamp(14px, 1.6vw, 20px); }
/* Reading one means stopping it. Focus-within matters as much as hover: tabbing
   into a card must not drag it out from under the keyboard. */
.belt:hover .belt__track,
.belt:focus-within .belt__track { animation-play-state: paused; }
@keyframes belt-a { to { transform: translateX(-50%); } }
/* The second row starts already displaced and travels the other way, so the two
   rows never line up into one sliding block. */
@keyframes belt-b { from { transform: translateX(-50%); } to { transform: translateX(0); } }

/* Fixed width, and the row's default align-items:stretch gives every card in a
   row the same height, so the belt has a straight top and bottom edge. Reviews
   run from two lines to eight and a ragged belt reads as broken. */
.quote--belt { width: clamp(268px, 78vw, 350px); flex: none; }
.quote--belt blockquote p { font-size: .91rem; line-height: 1.55; }
.belt__foot { margin-top: 14px; }
.belt__foot p { font-size: .72rem; color: var(--muted); letter-spacing: .04em; }

/* Reduced motion: the belt stops and becomes two ordinary scrollers. Nothing is
   removed, it just waits to be pushed. Also the state a touch user gets, since
   there is no hover to pause with. */
@media (prefers-reduced-motion: reduce) {
  .belt { -webkit-mask-image: none; mask-image: none; }
  .belt__row { overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
  .belt__track { animation: none !important; transform: none !important; }
  /* The ghost copy exists only to hide the animation seam. With no animation it
     is 12 duplicate cards in a scroller, so it goes. */
  .belt__row .belt__set[aria-hidden="true"] { display: none; }
  .quote--belt { scroll-snap-align: start; }
  .belt__foot { display: none; }
}

.askreview {
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--card);
  padding: 22px; display: grid; gap: 12px; max-width: 58ch; box-shadow: var(--sh-sm);
}
.askreview h2 { font-size: 1.15rem; }
.askreview p { color: var(--ink-2); font-size: .94rem; }

/* ── FAQ ─────────────────────────────────────────────────── */
.faqs { display: grid; max-width: 72ch; align-content: start; }
/* Two independent columns, not one two-column grid. See `faqColumns()` in
   layout.mjs: shared grid rows mean opening an answer on the left pushes an
   unrelated question on the right down with it. */
.faqcols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 clamp(28px, 4vw, 60px); }
/* The 72ch cap is for a single centred list. In a column it would leave the
   rule short of the column edge. */
.faqcols .faqs { max-width: none; }
.faq { border-top: 1px solid var(--rule); }
.faq:last-child { border-bottom: 1px solid var(--rule); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 19px 0; cursor: pointer; font-weight: 700; color: var(--ink);
  list-style: none; font-size: 1.03rem; letter-spacing: -.018em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--pine); }
.faq__x { position: relative; width: 14px; height: 14px; flex: none; }
.faq__x::before, .faq__x::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  background: var(--mint-3); transition: transform .2s ease;
}
.faq__x::before { width: 14px; height: 2px; }
.faq__x::after { width: 2px; height: 14px; }
.faq[open] .faq__x::after { transform: scaleY(0); }
.faq__a { padding-bottom: 21px; }
.faq__a p { color: var(--ink-2); max-width: 66ch; }

/* ── forms ───────────────────────────────────────────────── */
.form {
  display: grid; gap: 15px; background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--r-xl); padding: clamp(20px, 2.6vw, 30px); box-shadow: var(--sh);
}
.form--compact { box-shadow: var(--sh-lg); }

/* ── home hero booking card ── */
/* The card is `.hbook`, not the form. The heading has to sit on the same panel
   as the inputs, and if `.form` kept its own background the eyebrow and title
   would float on the hero behind it, half over the photograph they overlap. So
   the wrapper carries the surface and the form inside it goes transparent.

   Tighter than `.form` on purpose: this one lands inside the first screen, next
   to a headline nobody has finished reading. */
.hbook {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-xl);
  box-shadow: var(--sh-lg); padding: clamp(18px, 2.1vw, 25px);
  display: grid; gap: 3px;
}
.hbook__k {
  font-family: var(--fm); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--mint-3);
}
.hbook__t { font-size: 1.16rem; font-weight: 800; letter-spacing: -.024em; color: var(--ink); }
.form--hero {
  background: transparent; border: 0; box-shadow: none; padding: 0;
  margin-top: 15px; gap: 13px;
}
/* Two inputs side by side inside a 430px card is about 175px each, which still
   holds a name and a phone number. Below the width where the hero stacks, the
   sitewide rule at the bottom of this file takes over and they go one per row. */
.form--hero .form__row { gap: 12px; }
.form--hero .form__fine { font-size: .76rem; }

.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.field { display: grid; gap: 6px; }
.field__l {
  font-family: var(--fm); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .12em; color: var(--muted); display: flex; gap: 7px; align-items: baseline;
}
.field__l i { font-style: normal; color: var(--lv3); font-size: 1.15em; line-height: 1; letter-spacing: 0; }
.field input, .field select, .field textarea {
  font: 400 1rem/1.5 var(--fs); color: var(--ink);
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 12px 13px; width: 100%; min-width: 0; transition: border-color .15s ease, background-color .15s ease;
}
.field textarea { resize: vertical; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--ink-3); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  border-color: var(--mint-3); background: var(--card);
}
.gotcha { position: absolute !important; left: -9999px !important; }
.form__fine { font-size: .82rem; color: var(--muted); }
.form__err {
  background: color-mix(in srgb, var(--lv3) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--lv3) 42%, transparent);
  color: var(--lv3); padding: 12px 14px; border-radius: var(--r);
  font-size: .89rem; font-weight: 600;
}
.form.is-sending .btn--block { opacity: .6; pointer-events: none; }

/* ── page head + crumbs ──────────────────────────────────── */
.crumbs { padding-top: 20px; }
.crumbs ol { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.crumbs li {
  font-family: var(--fm); font-size: .72rem; color: var(--muted);
  display: flex; align-items: center; gap: 6px;
}
.crumbs li + li::before { content: "/"; color: var(--rule); }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--pine); text-decoration: underline; }

.phead { position: relative; border-bottom: 1px solid var(--rule); overflow: hidden; }
.phead::before {
  content: ""; position: absolute; z-index: 0;
  width: 700px; height: 700px; right: -260px; top: -400px;
  background: radial-gradient(circle at center, rgb(16 245 170 / .2) 0%, transparent 66%);
  pointer-events: none;
}
.phead__in { position: relative; z-index: 1; padding-block: clamp(24px, 3.2vw, 44px) clamp(34px, 4.4vw, 58px); }
.phead__grid {
  display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr);
  gap: clamp(28px, 4vw, 54px); align-items: start;
}
.phead__copy { display: grid; gap: 20px; }
.phead h1 { max-width: 19ch; }
.phead__acts { display: flex; flex-wrap: wrap; gap: 12px; }

/* ── pills ───────────────────────────────────────────────── */
.pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pills a {
  display: inline-block; padding: 9px 15px; border: 1px solid var(--rule);
  border-radius: var(--pill); background: var(--card); color: var(--ink-2);
  text-decoration: none; font-size: .89rem; font-weight: 600;
  transition: all .16s ease;
}
.pills a:hover { border-color: var(--mint-2); color: var(--pine); background: var(--mint-wash); transform: translateY(-2px); }

/* ── area table ──────────────────────────────────────────── */
.areatable { width: 100%; border-collapse: collapse; font-size: .94rem; }
.areatable caption { text-align: left; color: var(--muted); font-size: .87rem; padding-bottom: 12px; }
.areatable th, .areatable td { text-align: left; padding: 13px 14px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.areatable th {
  font-family: var(--fm); font-size: .65rem; text-transform: uppercase;
  letter-spacing: .12em; color: var(--muted); font-weight: 400;
  border-bottom: 2px solid var(--ink);
}
.areatable td:first-child { font-weight: 700; }
.areatable td.mono { color: var(--muted); white-space: nowrap; }
.areatable tbody tr { transition: background-color .14s ease; }
.areatable tbody tr:hover td { background: var(--mint-wash); }
.tablewrap { overflow-x: auto; }

/* ── notes ───────────────────────────────────────────────── */
.note {
  border-left: 3px solid var(--mint-2); background: var(--mint-wash);
  padding: 16px 18px; border-radius: 0 var(--r-lg) var(--r-lg) 0; display: grid; gap: 7px;
}
.note h3, .note__t { font-size: 1rem; }
.note p { color: var(--ink-2); font-size: .92rem; }
.note--warn { border-left-color: var(--lv3); background: color-mix(in srgb, var(--lv3) 7%, transparent); }

/* ── footer ──────────────────────────────────────────────── */
.ftr { background: var(--ink); color: #b7ccc2; padding-top: clamp(44px, 5vw, 70px); }
.ftr__in { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1.4fr; gap: 36px; padding-bottom: 40px; }
.ftr__logo { width: 210px; }
.ftr__tag { margin-top: 14px; color: #90a99d; font-size: .92rem; max-width: 30ch; }
.ftr__nap { display: grid; gap: 5px; margin-top: 18px; font-style: normal; font-size: .94rem; }
.ftr__nap a { color: var(--mint); text-decoration: none; font-weight: 600; }
.ftr__nap a:hover { text-decoration: underline; }
.ftr__nap span { color: #90a99d; }
.ftr__lic { margin-top: 16px; color: #7b948a; }
.ftr__social { margin-top: 14px; }
.ftr__social a {
  display: inline-flex; align-items: center; gap: 8px; color: var(--mint);
  text-decoration: none; font-size: .89rem; font-weight: 600;
}
.ftr__social a:hover { text-decoration: underline; }
.ftr__col h2 {
  font-family: var(--fm); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .14em; color: #7b948a; font-weight: 400; margin-bottom: 14px;
}
.ftr__col ul { display: grid; gap: 9px; }
.ftr__col a { color: #cbe0d6; text-decoration: none; font-size: .92rem; }
.ftr__col a:hover { color: var(--mint); text-decoration: underline; }
.ftr__areas { grid-template-columns: 1fr 1fr; }
.ftr__base {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  flex-wrap: wrap; padding-block: 20px; border-top: 1px solid rgb(255 255 255 / .1);
  font-size: .84rem; color: #7b948a;
}
.ftr__base ul { display: flex; gap: 18px; }
.ftr__base a { color: #7b948a; text-decoration: none; }
.ftr__base a:hover { color: var(--mint); }

/* ── CTA band ────────────────────────────────────────────── */
.band { background: var(--ink); color: #eef7f2; position: relative; overflow: hidden; }
.band::before {
  content: ""; position: absolute; width: 620px; height: 620px; right: -240px; top: -280px;
  background: radial-gradient(circle at center, rgb(16 245 170 / .16) 0%, transparent 66%);
  pointer-events: none;
}
.band__in {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 32px; align-items: center; padding-block: clamp(40px, 4.8vw, 66px);
}
.band__t { color: #fff; max-width: 28ch; }
.band__b { color: #b6cec2; margin-top: 12px; max-width: 56ch; }
.band__promo { margin-top: 14px; color: var(--mint); border-left: 2px solid var(--mint); padding-left: 12px; }
.band__acts { display: flex; flex-direction: column; gap: 10px; min-width: 232px; }

.band__promise {
  margin-top: 12px; color: #dff0e8; display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid rgb(16 245 170 / .34); border-radius: var(--pill); padding: 5px 13px;
}

/* ── sticky mobile call bar ──────────────────────────────── */
.callbar { display: none; }

/* ══════════════════════════════════════════════════════════
   Everything below was added answering the July 2026 critique.
   ══════════════════════════════════════════════════════════ */

/* ── photography slots ───────────────────────────────────── */
/* Placeholders that reserve the exact box a real photo will fill, so dropping
   the image in later reflows nothing. Deliberately a plain, quiet panel and
   not a "coming soon" callout — that reading was right for the client walking
   through a draft and wrong for a real visitor, who has no reason to know a
   photo was ever planned there. See the note on `photo()` in layout.mjs. */
.shot { margin: 0; position: relative; }
.shot img {
  width: 100%; aspect-ratio: var(--ratio, 4/3); object-fit: cover;
  border-radius: var(--r-lg); background: var(--paper-2);
}
.shot--wide { --ratio: 16/7; }

/* ── header rating badge ─────────────────────────────────── */
.rating {
  display: inline-flex; align-items: center; gap: 9px; text-decoration: none;
  padding: 7px 13px; border-radius: var(--pill); border: 1px solid var(--rule);
  background: var(--card); color: var(--ink); white-space: nowrap;
  transition: border-color .16s ease, transform .16s cubic-bezier(.2,.7,.3,1);
}
.rating:hover { border-color: var(--mint-2); color: var(--ink); transform: translateY(-2px); }
.rating .star { width: 13px; height: 13px; }
.rating__t { font-size: .8rem; color: var(--ink-2); letter-spacing: -.01em; }
.rating__t b { color: var(--ink); font-weight: 700; }
/* In the header there is no room for the sentence. Stars, the score, the
   count, nothing else. The full wording is in the hero and the footer. */
.rating--hdr { flex: none; gap: 7px; padding: 7px 12px; }
.rating--hdr .rating__t { font-size: 0; }
.rating--hdr .rating__t b { font-size: .84rem; }
.rating--hdr .rating__t b:last-child::before { content: "("; font-weight: 400; color: var(--muted); }
.rating--hdr .rating__t b:last-child::after { content: ")"; font-weight: 400; color: var(--muted); }
.rating--hdr .rating__t b + b { margin-left: 4px; color: var(--ink-2); font-weight: 600; }

/* ── hero: photo with the record card overlapping ────────── */
.hero__media { position: relative; display: grid; }
.hero__shot { --ratio: 3/2; }
.hero__doc { position: relative; margin: -14% 0 0 auto; width: min(92%, 430px); z-index: 2; }
/* No photograph in this hero yet, so there is nothing above the card to overlap
   and the negative margin was dragging the form up into the headline. Emitted
   by pages.mjs when hasShot('team') is false; it disappears on its own the day
   the photo lands. */
.hero__media--solo .hero__doc { margin: 0; width: 100%; }
.hero__doc .doc { box-shadow: var(--sh-lg); }
.phead__media { display: grid; gap: 18px; }
/* The pest mark at the top of a pest page. Sized off the eyebrow that follows
   it rather than picked, so it stays in proportion when the type scale moves. */
.phead__ico { width: clamp(56px, 6vw, 76px); height: clamp(56px, 6vw, 76px); margin-bottom: 14px; }

/* ── hero proof strip ────────────────────────────────────── */
/* The claim and the customers it came from, so the claim is never more than a
   glance from its evidence.

   This was two columns, lead copy beside a single feature review. It became a
   head row over a three-up on 2026-09-01 when the band went from one spotlit
   review to three — see `heroProof` in src/pages.mjs for why three and not a
   second marquee. The head row is deliberately the same shape as
   .sec__head--row: copy left, one ghost button right, baseline-aligned. */
.proof { background: var(--sand); border-block: 1px solid var(--sand-2); padding-block: var(--pad-tight); }
.proof__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 26px; margin-bottom: clamp(22px, 2.6vw, 34px);
}
.proof__lead { display: grid; gap: 15px; justify-items: start; }
.proof__k {
  font-size: clamp(1.35rem, 1.05rem + 1.35vw, 2.05rem); font-weight: 800;
  line-height: 1.12; letter-spacing: -.028em; color: var(--ink); max-width: 24ch;
  text-wrap: balance;
}
.proof__quote { background: var(--card); }
/* The three picks run long, medium, short on purpose. Stretching them to a
   shared height would put a block of dead card under the short one, so they
   sit top-aligned at their natural heights instead. */
.proof__grid { align-items: start; }

/* ── low-friction email capture ──────────────────────────── */
.capture {
  display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, .75fr);
  gap: clamp(24px, 4vw, 54px); align-items: center;
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-xl);
  padding: clamp(24px, 4vw, 46px); box-shadow: var(--sh);
}
.capture__copy { display: grid; gap: 13px; }
.capture__copy p { color: var(--ink-2); }
.capture__fine { font-size: .85rem; color: var(--muted); }
.capture__form { display: grid; gap: 13px; align-content: start; }
.capture__ok {
  font-size: .9rem; color: var(--pine); background: var(--mint-wash);
  border-left: 3px solid var(--mint-2); padding: 11px 14px; border-radius: 0 var(--r) var(--r) 0;
}

/* ── map facade ──────────────────────────────────────────── */
/* Nothing loads from Google until the visitor asks. See mapPanel(). */
.map { border: 1px solid var(--rule); border-radius: var(--r-lg); overflow: hidden; background: var(--card); }
.map__face { position: relative; min-height: 320px; display: grid; align-content: center; padding: clamp(22px, 3.4vw, 38px); }
.map__grid {
  position: absolute; inset: 0; opacity: .5;
  background:
    linear-gradient(var(--rule-2) 1px, transparent 1px) 0 0/100% 34px,
    linear-gradient(90deg, var(--rule-2) 1px, transparent 1px) 0 0/34px 100%,
    var(--paper);
}
.map__grid::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(115deg, transparent 46%, var(--sand-2) 46% 49%, transparent 49%),
    linear-gradient(24deg, transparent 62%, var(--sand-2) 62% 64%, transparent 64%);
}
.map__pin { position: absolute; left: 50%; top: 26%; transform: translate(-50%, -50%); }
.map__pin i {
  display: block; width: 18px; height: 18px; border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg); background: var(--mint); border: 2px solid var(--ink);
  box-shadow: 0 0 0 7px rgb(16 245 170 / .22);
}
.map__body {
  position: relative; z-index: 1; background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: clamp(18px, 2.6vw, 26px); display: grid; gap: 11px;
  box-shadow: var(--sh); max-width: 46ch; margin-top: 62px;
}
.map__addr { font-weight: 700; font-size: 1.02rem; }
.map__note { color: var(--ink-2); font-size: .92rem; }
.map__acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 3px; }
.map__fine { font-size: .68rem; color: var(--muted); }
.map iframe { display: block; width: 100%; height: 380px; border: 0; }

/* ── consent banner ──────────────────────────────────────── */
.consent {
  position: fixed; left: 14px; right: 14px; bottom: 14px; z-index: 60;
  background: var(--ink); color: #dff0e8; border-radius: var(--r-lg);
  box-shadow: 0 20px 50px -18px rgb(10 20 16 / .6); max-width: 460px;
}
.consent[hidden] { display: none; }
.consent__in { padding: 20px 22px; display: grid; gap: 9px; }
.consent__t { font-weight: 700; color: #fff; }
.consent__b { font-size: .88rem; color: #b6cec2; line-height: 1.55; }
.consent__b a { color: var(--mint); }
.consent__acts { display: flex; gap: 9px; margin-top: 5px; }
.consent .btn--ghost { color: #dff0e8; border-color: rgb(255 255 255 / .3); }
.consent .btn--ghost:hover { background: rgb(255 255 255 / .07); color: #fff; border-color: #dff0e8; }

/* ── scroll CTA dock ─────────────────────────────────────── */
/* Bottom right rather than a full-width bar. A bar takes a strip off every
   page for the entire visit; a card of this size covers nothing that is being
   read and can be shrugged off with one click. Dark ground so it reads as
   chrome instead of as another section of the page.

   Sits below the consent banner's z-index on purpose: consent is a legal
   choice and has to win if both are ever up at once. */
/* Expanded: pinned to the middle of the right edge, wide enough to carry the
   rating and what the visit involves. Minimised (.is-min): the small
   bottom-right card this used to be, three buttons and nothing else.

   Both states are the same element. `right` and `width` are the only geometry
   that differs, plus which children are displayed, so the transition between
   them is a real animation rather than one card swapped for another. */
.dock {
  position: fixed; right: 20px; top: 50%; bottom: auto; z-index: 44;
  width: min(360px, calc(100vw - 40px));
  max-height: calc(100vh - 40px); overflow: auto; overscroll-behavior: contain;
  background: var(--ink); color: #eef7f2;
  border: 1px solid rgb(16 245 170 / .22); border-radius: var(--r-lg);
  box-shadow: 0 26px 62px -22px rgb(10 20 16 / .66);
  padding: 20px 20px 18px; display: grid; gap: 3px;
  /* CENTRING AND ANIMATION ARE DELIBERATELY SEPARATE PROPERTIES.
     `translate` holds the -50% that centres the panel on the right edge and
     nothing else ever touches it. `transform` is only ever the show/hide
     movement. Doing both in one `transform` means every state that needs to
     move the panel has to restate the -50% it does not care about, and the
     first rule that forgets — a reduced-motion override, a short-viewport
     override — drops the panel half off the top of the window. Two properties
     compose without either knowing about the other. */
  translate: 0 -50%;
  /* Enters from the right, because that is the edge it lives on, so it reads as
     sliding in rather than as an element that was always there. */
  opacity: 0; transform: translateX(16px) scale(.98);
  transition: opacity .34s cubic-bezier(.2,.7,.3,1), transform .34s cubic-bezier(.2,.7,.3,1),
              translate .3s cubic-bezier(.2,.7,.3,1), width .3s cubic-bezier(.2,.7,.3,1),
              padding .3s ease;
  pointer-events: none;
}
.dock[hidden] { display: none; }
.dock.is-in { opacity: 1; transform: none; pointer-events: auto; }
/* Retreats, rather than disappearing, while a form or the CTA band holds the
   screen. Coming back is then the same motion as arriving. */
.dock.is-away { opacity: 0; transform: translateX(16px) scale(.98); pointer-events: none; }

/* ── minimised ── */
/* Back to the bottom-right corner and back to the old width. Dropping the
   centring is one property, and the show/hide transform is untouched. */
.dock.is-min {
  top: auto; bottom: 16px; right: 16px; translate: 0;
  width: min(324px, calc(100vw - 32px));
  padding: 17px 18px 18px; max-height: none; overflow: visible;
}
.dock.is-min .dock__full,
.dock.is-min .dock__fine { display: none; }
/* The two corner buttons are hidden/shown further down, AFTER the rule that
   gives them their shared `display: grid`. Putting the `display: none` up here
   with the rest of the minimised state looks tidier and does not work: same
   specificity, later rule wins, and both buttons end up stacked in the same
   corner with the × painting over the minimise. */

.dock__k {
  font-family: var(--fm); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--mint);
  /* Room for whichever corner button is showing. */
  padding-right: 34px;
}
.dock__t { font-weight: 800; font-size: 1.22rem; letter-spacing: -.024em; color: #fff; }
.dock.is-min .dock__t { font-size: 1.06rem; letter-spacing: -.022em; }

.dock__full { display: grid; gap: 12px; margin-top: 13px; }
/* The rating, linked to the profile so it can be checked. */
.dock__rate {
  display: flex; align-items: center; gap: 8px; text-decoration: none;
  padding: 9px 11px; border-radius: var(--r-sm);
  background: rgb(255 255 255 / .06); border: 1px solid rgb(255 255 255 / .1);
  color: #eef7f2; transition: background-color .16s ease, border-color .16s ease;
}
.dock__rate:hover { background: rgb(255 255 255 / .11); border-color: rgb(255 255 255 / .2); }
.dock__rate .gico { width: 16px; height: 16px; flex: none; }
.dock__rate b { font-size: 1.06rem; font-weight: 800; color: #fff; letter-spacing: -.02em; }
.dock__rateN { font-size: .78rem; color: #adc5b9; }
.dock__list { list-style: none; display: grid; gap: 7px; margin: 0; padding: 0; }
.dock__list li {
  position: relative; padding-left: 20px; font-size: .84rem; line-height: 1.42; color: #cfe2d9;
}
.dock__list li::before {
  content: ""; position: absolute; left: 2px; top: .42em; width: 8px; height: 8px;
  border-radius: 50%; background: var(--mint); opacity: .85;
}
.dock__fine { margin-top: 11px; font-size: .68rem; color: #8fab9f; letter-spacing: .04em; }

/* Book and text on the top row, the phone number spanning the bottom. Three
   across cannot hold a real phone number at this width, and widening the card
   to fit one would cover content. */
.dock__acts { display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-top: 14px; }
.dock.is-min .dock__acts { margin-top: 11px; }
/* Spans both columns. With `sms: null` the text button is not rendered at all,
   and this still holds: book takes the 1fr, the number takes the row below. */
.dock__acts .btn--call { grid-column: 1 / -1; background: rgb(255 255 255 / .09); color: #eef7f2; }
.dock__acts .btn--call:hover { background: rgb(255 255 255 / .16); color: #fff; }
.dock__acts .btn--ghost { color: #dff0e8; border-color: rgb(255 255 255 / .28); }
.dock__acts .btn--ghost:hover { background: rgb(255 255 255 / .07); color: #fff; border-color: #dff0e8; }

.dock__x, .dock__min {
  position: absolute; top: 8px; right: 8px; width: 30px; height: 30px;
  display: grid; place-items: center; padding: 0; cursor: pointer;
  background: transparent; border: 0; border-radius: 50%; color: #8fab9f;
  transition: background-color .16s ease, color .16s ease;
}
.dock__x:hover, .dock__min:hover { background: rgb(255 255 255 / .09); color: #fff; }
.dock__x svg { width: 13px; height: 13px; fill: currentColor; }
.dock__min svg { width: 17px; height: 17px; fill: currentColor; }
/* One corner button at a time, and this has to come after the shared rule above
   or it loses to it. Expanded offers minimise; minimised offers close. See the
   note on scrollCta in layout.mjs for why they are not the same button. */
.dock__x { display: none; }
.dock.is-min .dock__min { display: none; }
.dock.is-min .dock__x { display: grid; }

/* Short viewports. A centred panel taller than the window would sit half off
   the top, so here it stops centring, spans top to bottom and scrolls inside
   itself. One property, and the show/hide transform still works. */
@media (max-height: 620px) {
  .dock:not(.is-min) { top: 20px; bottom: 20px; translate: 0; }
}
/* Nothing moves; it appears and disappears. The panel still centres, because
   that is `translate`, not `transform`. */
@media (prefers-reduced-motion: reduce) {
  .dock { transition: opacity .01s linear; }
  .dock, .dock.is-in, .dock.is-away { transform: none; }
}

/* ── reviews page ────────────────────────────────────────── */
.bd { background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg); padding: 20px; display: grid; gap: 9px; }
.bd__head { display: flex; align-items: center; gap: 14px; margin-bottom: 5px; }
.bd__avg { font-size: 2.9rem; font-weight: 800; letter-spacing: -.04em; line-height: 1; }
.bd__meta { color: var(--muted); font-size: .72rem; margin-top: 5px; }
.bd__row { display: grid; grid-template-columns: 26px 1fr 30px; gap: 10px; align-items: center; }
.bd__n { font-size: .72rem; color: var(--muted); }
.bd__c { font-size: .72rem; color: var(--muted); text-align: right; }
.bd__track { height: 7px; border-radius: 4px; background: var(--rule-2); overflow: hidden; }
.bd__track i { display: block; height: 100%; background: var(--lv2); border-radius: 4px; }
.bd__fine { font-size: .72rem; color: var(--muted); line-height: 1.5; margin-top: 6px; }

.rfilter { display: grid; gap: 12px; margin-bottom: clamp(22px, 3vw, 34px); }
.rfilter[hidden] { display: none; }
.rfilter__l { font-family: var(--fm); font-size: .7rem; letter-spacing: .13em; text-transform: uppercase; color: var(--muted); }
.rfilter__row { display: flex; flex-wrap: wrap; gap: 8px; }
.rfilter__row--pests { padding-top: 2px; }
.chip--sm { padding: 7px 13px; font-size: .82rem; }
.chip__n { color: var(--muted); font-weight: 400; font-size: .9em; margin-left: 3px; }
.chip.is-on .chip__n { color: inherit; opacity: .7; }
.rfilter__count { font-size: .82rem; color: var(--muted); }

/* Masonry-ish columns so 145 reviews of wildly different lengths do not
   leave ragged whitespace across a rigid grid. */
.rgrid { columns: 3; column-gap: 18px; }
.rgrid__i { break-inside: avoid; margin-bottom: 18px; }
.rgrid__i[hidden] { display: none; }
.rgrid .quote { height: auto; }
.rnote { margin-top: 26px; color: var(--muted); font-size: .88rem; max-width: 68ch; }

/* ── blog ────────────────────────────────────────────────── */
.pcard {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: 24px; display: grid; gap: 10px; align-content: start; text-decoration: none;
  transition: border-color .16s ease, transform .16s cubic-bezier(.2,.7,.3,1), box-shadow .16s ease;
}
.pcard:hover { border-color: var(--mint-2); transform: translateY(-3px); box-shadow: var(--sh); color: var(--ink); }
.pcard h3 { color: var(--ink); }
.pcard p { color: var(--ink-2); font-size: .92rem; }
.pcard__d { color: var(--muted); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; }

.phead--post h1 { max-width: 22ch; }
.post { max-width: 68ch; }
.post h2 { font-size: clamp(1.3rem, 1.1rem + .8vw, 1.7rem); margin-top: .75em; }
.post h3 { margin-top: .5em; }
.post li { position: relative; padding-left: 24px; color: var(--ink-2); }
.post li::before {
  content: ""; position: absolute; left: 4px; top: .62em; width: 6px; height: 6px;
  border-radius: 50%; background: var(--mint-3);
}
.post__archive {
  margin-top: 2em; padding: 13px 16px; background: var(--sand); border-left: 3px solid var(--sand-2);
  color: var(--muted); font-size: .78rem; line-height: 1.6; border-radius: 0 var(--r) var(--r) 0;
}

/* ── form: the optional group ────────────────────────────── */
/* Three fields are asked for. These are offered, and the summary says so
   out loud, so nobody feels stopped by a dropdown they are unsure of. */
.form__more { border: 1px dashed var(--rule); border-radius: var(--r-lg); background: var(--paper); }
.form__more summary {
  list-style: none; cursor: pointer; padding: 13px 16px; display: grid;
  grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 3px 12px;
}
.form__more summary::-webkit-details-marker { display: none; }
.form__more summary:hover .form__moreT { color: var(--pine); }
.form__moreT { grid-area: 1 / 1; font-weight: 700; font-size: .92rem; }
.form__moreS { grid-area: 2 / 1; font-size: .8rem; color: var(--muted); }
.form__more .faq__x { grid-area: 1 / 2 / span 2 / 2; align-self: center; }
.form__more[open] .faq__x::after { transform: scaleY(0); }
.form__moreIn { padding: 2px 16px 17px; display: grid; gap: 15px; }
.field__l em {
  font-style: normal; font-weight: 400; color: var(--muted); text-transform: none;
  letter-spacing: 0; font-size: .95em;
}

/* ── responsive ──────────────────────────────────────────── */
@media (max-width: 1240px) {
  /* The rating badge is the first thing to go from the header. The phone
     number outranks it, and the badge is repeated in the hero anyway. */
  .rating--hdr { display: none; }
}
/* The tablet band tightens the nav before it gives up on it, which keeps the
   full menu visible on a small laptop. */
@media (min-width: 1021px) and (max-width: 1140px) {
  .brand img { width: 156px; }
  .hdr__in { gap: 10px; padding-left: 16px; }
  .nav__list a { padding: 9px 8px; font-size: .88rem; }
}
@media (max-width: 1080px) {
  .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .rail { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 28px; }
  .rail__step:nth-child(3)::before { right: calc(100% - 12px); }
  .rail__step:nth-child(4)::before { left: 11px; }
  .rgrid { columns: 2; }
}
@media (max-width: 1020px) {
  .hero__in, .phead__grid, .split, .split--aside, .split--wide, .split--doc, .now__in { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the report card centres instead of hugging the right edge, which
     is where `margin-left: auto` would leave it once there is no second column
     for it to sit against. */
  .split--doc .doc { margin-inline: auto; }
  .aside { position: static; }
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--offset > *:nth-child(even) { margin-top: 0; }
  .ftr__in { grid-template-columns: 1fr 1fr; }
  .band__in { grid-template-columns: minmax(0, 1fr); }
  .band__acts { flex-direction: row; flex-wrap: wrap; min-width: 0; }
  .capture { grid-template-columns: minmax(0, 1fr); }
  /* The head row stacks: the "read all" button drops under the copy rather
     than being squeezed against it. */
  .proof__head { flex-direction: column; align-items: flex-start; gap: 18px; }
  .hero__doc { margin: -12% auto 0; width: min(100%, 480px); }

  /* ── the nav collapses here, not at 860 ──────────────────
     A bug that predates the scroll dock. `.hdr__acts` is flex: none and every
     nav link is white-space: nowrap, so nothing wrapped and nothing shrank:
     the eight nav items simply ran underneath the call button, and the phone
     number sat on top of "About" and "Contact". The hamburger used to take
     over at 860px, which left a live overlap across roughly 160px of viewport
     width that nobody had looked at.
     1020px is the right place for it, because it is where the page already
     drops to a single column. The chrome and the layout now go mobile
     together, and the call button keeps its number the whole way down. */
  .nav { margin-left: auto; order: 3; }
  .nav__btn {
    display: inline-flex; align-items: center; gap: 9px;
    background: transparent; border: 1px solid var(--rule); border-radius: var(--pill);
    padding: 10px 14px; font: 700 .88rem/1 var(--fs); color: var(--ink); cursor: pointer;
  }
  .nav__bars { position: relative; width: 15px; height: 2px; background: currentColor; }
  .nav__bars::before, .nav__bars::after {
    content: ""; position: absolute; left: 0; width: 15px; height: 2px; background: currentColor;
    transition: transform .2s ease;
  }
  .nav__bars::before { top: -5px; }
  .nav__bars::after { top: 5px; }
  .nav__list {
    display: none; position: absolute; left: 0; right: 0; top: calc(100% + 8px);
    flex-direction: column; align-items: stretch; gap: 2px;
    background: var(--card); border: 1px solid var(--rule); border-radius: var(--r-xl);
    box-shadow: var(--sh-lg); padding: 12px;
  }
  .nav__list.is-open { display: flex; }
  .nav__list a { padding: 13px 14px; border-radius: var(--r); }

  /* The sticky call bar moves with the nav, so mobile chrome arrives all at
     once rather than the menu collapsing 160px before the bottom bar shows
     up. It also keeps exactly one persistent CTA on screen at any width: the
     dock hides here, and the bar offers the same three actions. */
  .consent { max-width: none; bottom: 84px; }
  .callbar {
    display: grid; grid-template-columns: 1.45fr auto 1.1fr; gap: 7px;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    background: color-mix(in srgb, var(--ink) 94%, transparent);
    backdrop-filter: blur(10px);
    padding: 9px 12px; padding-bottom: max(9px, env(safe-area-inset-bottom));
    box-shadow: 0 -8px 26px -12px rgb(0 0 0 / .5);
  }
  .callbar a {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 14px 6px; border-radius: var(--pill); text-decoration: none;
    font: 700 .87rem/1 var(--fs); text-align: center; white-space: nowrap;
    letter-spacing: -.02em;
  }
  .callbar a:first-child { background: var(--mint); color: #06110d; }
  .callbar a:not(:first-child) { background: transparent; color: #eef7f2; border: 1px solid rgb(255 255 255 / .3); }
  .callbar a[data-track="sms"] { padding-inline: 18px; }
  body { padding-bottom: 76px; }
  .dock { display: none !important; }
}
/* Content grids stay at 860, where they always were. Only the header chrome
   and the sticky bar moved up to 1020. */
@media (max-width: 860px) {
  .hdr__in { min-height: 58px; gap: 10px; padding-left: 16px; }
  .brand img { width: 148px; }
  /* Two per line, still flex: wrap and let each cell take half. */
  .trust { flex-wrap: wrap; }
  .trust li { flex: 1 1 calc(50% - 1px); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Back to one list. Two columns of questions at this width gives each answer
     about 30 characters a line. */
  .faqcols { grid-template-columns: minmax(0, 1fr); }
  .stats { grid-template-columns: 1fr; }
  .species > div { grid-template-columns: minmax(0, 1fr); gap: 5px; }
  .step { grid-template-columns: 38px 1fr; gap: 14px; }
  .sec__head--row { flex-direction: column; align-items: flex-start; }
  .facts { grid-template-columns: minmax(0, 1fr); }
  .map__body { margin-top: 46px; }
}
@media (max-width: 620px) {
  .hdr__acts .btn--call { padding: 11px 13px; }
  .hdr__acts .btn--call span { display: none; }
  .nav__btn span:last-child { display: none; }
  .nav__btn { padding: 11px 13px; }
  .form__row { grid-template-columns: minmax(0, 1fr); }
  .grid--3, .grid--4, .grid--2 { grid-template-columns: minmax(0, 1fr); }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .trust li { flex: 1 1 100%; }
  .ftr__in { grid-template-columns: 1fr; gap: 30px; }
  .doc__head { flex-direction: column; }
  .hero__acts .btn, .phead__acts .btn { flex: 1 1 100%; }
  .now__item { grid-template-columns: auto 1fr; }
  .now__go { display: none; }
  .rail { grid-template-columns: minmax(0, 1fr); row-gap: 22px; }
  .rail__step { padding-top: 0; padding-left: 30px; }
  .rail__step::before { left: 5px !important; right: auto !important; top: 0; bottom: -22px; width: 2px; height: auto; }
  .rail__step:last-child::before { display: none; }
  .rail__step::after { left: 0; top: 4px; }
  .strip__cell { height: 40px; }
  .strip__cell.is-now::before { display: none; }
  .rgrid { columns: 1; }
  .hero__doc { margin-top: -8%; }
  .capture { padding: 22px; }
  .map__face { min-height: 0; padding: 16px; }
  .map__body { margin-top: 30px; }
  .bd__avg { font-size: 2.3rem; }
}

/* ── print ───────────────────────────────────────────────── */
@media print {
  .hdr, .promo, .callbar, .band, .form, .nav, .picker, .now,
  .consent, .capture, .rfilter, .map, .dock { display: none !important; }
  body { padding: 0; color: #000; background: #fff; }
  [data-reveal], [data-reveal-group] > * { opacity: 1 !important; transform: none !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; }
}
