/* ==========================================================================
   LH Roofing — site stylesheet
   Hand-written, no build step. Mobile-first; min-width queries only.
   Source of truth for values: "LH Roofing - Design Foundations.md".
   Order: fonts → tokens → base → layout → components → sections → utilities
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts

   TODO BEFORE LAUNCH: add the woff2 subsets to /fonts/ and UNCOMMENT the three
   blocks below, then delete the Google Fonts <link> from every page <head>.

   These are commented out deliberately. A stylesheet @font-face declared for a
   family+weight range that a later rule also declares WINS the match, and when
   its src 404s the browser does not retry the earlier rule — it falls through to
   the next family in the stack. So while /fonts/ is empty these blocks don't
   "fail safe", they actively suppress the Google-hosted faces and force
   Helvetica + Georgia everywhere.

@font-face {
  font-family: "Archivo";
  src: url("/fonts/archivo-variable.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Serif 4";
  src: url("/fonts/source-serif-4-variable.woff2") format("woff2-variations");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Serif 4";
  src: url("/fonts/source-serif-4-italic-variable.woff2") format("woff2-variations");
  font-weight: 400 600;
  font-style: italic;
  font-display: swap;
}
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* brand + neutrals */
  --red: #9f0107;
  --red-dark: #7c0105;
  --red-tint: #e4a0a3;          /* on dark only */
  --ink: #1b1714;
  --ink-2: #3c3631;
  --body: #514a45;
  --muted: #5f5751;
  --muted-2: #7a716a;
  --muted-3: #6f6862;           /* 5.3:1 on --paper; AA at 13px/600 */
  --paper: #fbf9f6;
  --paper-2: #f3eee7;
  --paper-3: #f1ece4;
  --white: #ffffff;
  --hair: #e4ded6;
  --hair-2: #ddd6cc;
  --frame: #d9d1c6;
  --field: #c9c1b6;
  --dark: #221d19;
  --dark-rule: #3a332e;
  --dark-rule-2: #4a423c;
  --on-dark: #bdb4ac;
  --on-dark-2: #ddd6ce;
  --on-dark-muted: #a49b93;     /* the --muted-3 role, on --dark: 6.4:1 */

  /* placeholder stripes — remove once real photography lands */
  --ph-a: #ede7de;
  --ph-b: #e5ddd2;

  /* type */
  --sans: "Archivo", "Helvetica Neue", Helvetica, sans-serif;
  --serif: "Source Serif 4", Georgia, serif;

  --h1: 2.0625rem;   /* 33px */
  --h2: 1.6875rem;   /* 27px */
  --h3: 1.0625rem;   /* 17px */
  --lead: 1.0625rem; /* 17px */
  --text: 1rem;      /* 16px */
  --small: 0.875rem; /* 14px */
  --meta: 0.8125rem; /* 13px */
  --eyebrow: 0.75rem;/* 12px */
  --legal: 0.75rem;

  /* spacing — 4px scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 48px; --s10: 56px;
  --s11: 64px; --s12: 72px;

  --gutter: var(--s4);
  --section-y: 28px;
  --section-y-end: 32px;
  --measure: 34rem;     /* body copy max — rem, never em: an em cap resolves
                           against the element's OWN size, so 34em is 578px on
                           17px copy but 646px on a 19px lead */
  --max: 1328px;        /* content cap */

  --tap: 44px;
  --callbar-h: 76px;    /* space the footer reserves for the sticky bar */
}

/* 900–1199 — desktop nav and split heroes, but an intermediate type scale:
   at this width a two-column hero leaves ~374px per column and a 60px H1
   wraps to six lines. Full display sizes wait for 1200. */
@media (min-width: 900px) {
  :root {
    --h1: 2.75rem;      /* 44px */
    --h2: 2rem;         /* 32px */
    --h3: 1.25rem;      /* 20px */
    --lead: 1.1875rem;  /* 19px */
    --text: 1.0625rem;  /* 17px */
    --small: 0.9375rem; /* 15px */
    --meta: 0.875rem;
    --eyebrow: 0.8125rem;
    --legal: 0.8125rem;
    --gutter: var(--s10);
    --section-y: var(--s11);
    --section-y-end: var(--s12);
  }
}

@media (min-width: 1200px) {
  :root {
    --h1: 3.75rem;      /* 60px */
    --h2: 2.625rem;     /* 42px */
    --h3: 1.3125rem;    /* 21px */
    --lead: 1.25rem;    /* 20px */
  }
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font: 400 var(--text) / 1.55 var(--serif);
  text-wrap: pretty;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.02em;
}
h1 { font-size: var(--h1); line-height: 1.08; letter-spacing: -0.025em; max-width: 11em; }
h2 { font-size: var(--h2); line-height: 1.1; max-width: 16em; }
/* em is deliberate on headings only: a display cap SHOULD scale with its own
   size. Every running-copy cap below is rem. */
h3 { font-size: var(--h3); line-height: 1.2; font-weight: 600; letter-spacing: 0; }

p { margin: 0; }
p + p { margin-top: var(--s4); }

a { color: var(--red); text-decoration: none; }
a:hover { color: var(--red-dark); text-decoration: underline; }

img, iframe { max-width: 100%; }
img { display: block; height: auto; }

button { font: inherit; cursor: pointer; }
input, textarea, select { font: inherit; }

:focus-visible { outline: 3px solid var(--red); outline-offset: 2px; }

::selection { background: var(--red); color: #fff; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
/* A skip link that can't be seen when focused is not a skip link. */
.visually-hidden:focus, .visually-hidden:focus-visible {
  position: fixed; top: var(--s2); left: var(--s2); z-index: 60;
  width: auto; height: auto; margin: 0; padding: var(--s3) var(--s4);
  overflow: visible; clip-path: none;
  background: var(--paper); border: 1px solid var(--ink); color: var(--ink);
  font: 600 1rem / 1 var(--sans); text-decoration: none;
}

/* --------------------------------------------------------------------------
   4. Layout
   -------------------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: calc(var(--max) + (var(--gutter) * 2));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-y) var(--section-y-end); }
/* :not(.cta) — the CTA declares its own 3px red top rule, and a compound
   sibling selector (0,2,0) would otherwise outrank .cta (0,1,0) on every page. */
.section + .section:not(.cta) { border-top: 1px solid var(--hair); }

/* Only two background colours per page, plus the one dark band. */
.section--tint { background: var(--paper-2); }
.section--dark { background: var(--dark); color: var(--on-dark); }
.section--dark h1, .section--dark h2, .section--dark h3 { color: var(--paper); }
.section--dark a { color: var(--red-tint); }
.section--dark + .section:not(.cta),
.section--tint + .section:not(.cta) { border-top: 1px solid var(--hair); }
/* For a section whose previous sibling ISN'T a .section — the first section
   under a sub-page hero, which sits in a bare .wrap. */
.section--ruled { border-top: 1px solid var(--hair); }

/* Asymmetric split: heading + aside left, prose or list right.
   Used by service prose, service FAQ, location intro. */
.split { display: grid; gap: var(--s5); }
.split__aside { max-width: 26rem; color: var(--muted-2); font-size: var(--small); }
.split__body { max-width: var(--measure); }

.pair { display: grid; gap: var(--s6); }        /* two equal columns on desktop */
.grid { display: grid; gap: var(--s3); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }

/* Card grids. TWO classes, because they carry different constraints:

   .grid--cards  service cards, which carry blurbs — ONE measure-driven track
                 shared by all three bands, so all ten cards are the same width
                 at every viewport. Track count comes from minmax(), not from the
                 item count: an incomplete last row in a card grid is just
                 whitespace. ("Column count must divide item count" is a
                 .link-grid rule — that's where empty cells paint as --hair-2.)
   .grid--work   photos with one-line captions and no blurb, so the blurb measure
                 doesn't govern — what governs is a MINIMUM USEFUL PHOTO WIDTH.
                 Same auto-fill mechanism as .grid--cards but a larger floor, so
                 the count rises one track at a time and cell width tapers
                 monotonically (396 → 349 → 317) instead of halving at a
                 breakpoint. Hardcoded per-breakpoint counts doubled the divisor
                 while the content box grew a third, bottoming out at 257px.

   Base declarations FIRST, media queries after — equal specificity, so source
   order decides. */
.grid--cards { grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
.grid--work {
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  /* Dense, because the work grid is the one grid whose item count is unknown at
     build time — the client sends what he has — and because before/after pairs
     span two tracks. Without it a spanning pair that doesn't fit the row leaves a
     hole; with all-equal items it's a no-op. */
  grid-auto-flow: dense;
}
/* Both grids taper by auto-fill at every width above the phone: 2-up on a small
   laptop (912px box → 2 × 446), 3-up at 1280, 4-up at the 1328 cap. Cards take
   the STACKED form from 900 up (see §8), so the column carries a full-width 3:2
   photo above the blurb — no 200px thumb eating the measure. Single column below
   900, where the card is a thumb row and a 4:3 photo two-up is unreadable. */
@media (max-width: 899px) {
  .grid--cards { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 599px) {
  .grid--work { grid-template-columns: minmax(0, 1fr); }
}

/* Fixed trio — the three job photos and three related-service cards on a
   service page. NOT auto-fill: 3 items only divide by 1 or 3, so every
   intermediate count leaves one item beside an empty half. Stacked below 900,
   3-up above (246px a cell at 900, 429px at the cap — clears the 32-char
   caption floor at both ends). */
.grid--trio { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .grid--trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
  .grid { gap: var(--s5); }
}
@media (min-width: 1200px) {
  /* All two-column layouts split here, not at 900: an 812px content box halves
     to 374px, which is 39 chars/line at 19px — below the ~45 floor. */
  .split { grid-template-columns: 1fr 1.35fr; gap: var(--s12); }
  .pair { grid-template-columns: 1fr 1fr; gap: var(--s11); }
}
/* Reviews carry the largest body font (18px) and are running prose, so the column
   has to stay wide: 2-up from 900 (446px box → 44 chars), 3-up from 1200 with the
   padding stepped back to --s5 to buy the measure back (~38 chars at 1200, ~43 at
   the 1328 cap). Below that, one per row. */
.grid--reviews { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .grid--reviews { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1200px) {
  .grid--reviews { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--reviews .review { padding: var(--s5); }
}

/* Section head: H2 + optional lead, with a tertiary link pushed right on desktop */
.section-head { display: flex; flex-direction: column; gap: var(--s3); }
/* Capped in rem, not em: 34em resolves against the lead's OWN 19px size (646px)
   and swallows the row the tertiary link has to sit in. */
.section-head__lead { max-width: 30rem; font-size: var(--lead); line-height: 1.5; }
@media (min-width: 900px) {
  .section-head {
    flex-direction: row; align-items: flex-end; justify-content: space-between;
    gap: var(--s8); flex-wrap: wrap;
  }
  .section-head > div { display: flex; flex-direction: column; gap: var(--s3); flex: 0 1 auto; min-width: 0; }
}

/* --------------------------------------------------------------------------
   5. Type helpers
   -------------------------------------------------------------------------- */
.eyebrow {
  font: 600 var(--eyebrow) / 1 var(--sans);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-3);
}
.eyebrow--red { color: var(--red); }
.eyebrow--tint { color: var(--red-tint); }
@media (min-width: 900px) { .eyebrow { letter-spacing: 0.16em; } }

/* Band label: eyebrow + rule, separating service groups */
.band-label { display: flex; align-items: center; gap: var(--s4); }
.band-label::after { content: ""; flex: 1; height: 1px; background: var(--hair-2); }

.lead { font-size: var(--lead); line-height: 1.5; color: var(--body); max-width: var(--measure); }
.prose { max-width: var(--measure); }.prose > p { font-size: var(--text); line-height: 1.62; }
.prose > p:first-child { color: var(--ink-2); }
.prose > p + p { margin-top: var(--s5); }
/* Subheads inside a post body. h3, not h2: the global h2 is 42px at desktop, which
   inside a 34rem measure reads as a new section of the page rather than a step in
   one argument — and the post's H1 is already the only display type it needs.
   The h3→p gap must be SMALLER than the 20px p→p gap, or the subhead floats
   between two paragraphs instead of belonging to the one underneath it. Both rules
   are needed: `p + p` doesn't match a paragraph that follows an h3. */
.prose > h3 { margin-top: var(--s7); margin-bottom: var(--s3); }
.prose > h3 + p { margin-top: 0; }
.prose > h3:first-child { margin-top: 0; }
@media (min-width: 900px) {
  .prose > p { font-size: 1.125rem; line-height: 1.65; }
  .prose > h3 { margin-top: var(--s8); }
}

/* Pull quote — the one device that breaks up the About page's 500 words, and the
   only thing in the type system that sits between H2 and body. Archivo, not the
   body serif: it's a heading-scale utterance, and an italic serif at this size
   reads as a testimonial, which this isn't. 3px red TOP rule, as the CTA and the
   job-book panel use — never a left-border accent bar.
   Its cap is in `em` because it is heading-scale and should grow with the display
   size; running prose stays in rem (see §3 of the foundations). */
.pull {
  margin: var(--s6) 0 var(--s6); padding-top: var(--s4);
  border-top: 3px solid var(--red);
  max-width: 22em;
  font: 600 1.375rem / 1.25 var(--sans);
  letter-spacing: -0.01em; color: var(--ink);
  text-wrap: pretty;
}
@media (min-width: 900px) {
  .pull { margin-block: var(--s8); font-size: 1.75rem; }
}

.meta { font: 500 var(--meta) / 1.35 var(--sans); color: var(--muted); }
.legal { font: 400 var(--legal) / 1.6 var(--sans); color: var(--muted-3); }

/* Fact strip: short claims separated by red squares */
.facts {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3);
  padding-block: var(--s4);
  border-block: 1px solid var(--hair);
}
.facts > li { list-style: none; font: 500 var(--meta) / 1.3 var(--sans); color: var(--ink-2); }
.facts { margin: 0; padding-inline: 0; }
.facts > li + li::before {
  content: ""; display: inline-block; width: 4px; height: 4px;
  margin-right: var(--s3); vertical-align: 0.15em; background: var(--red);
}
@media (min-width: 900px) {
  .facts { gap: var(--s3) 18px; }
  .facts > li { font-size: var(--small); }
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 52px; padding-inline: 26px;
  border: 1px solid transparent;
  font: 600 1.0625rem / 1 var(--sans);
  text-align: center; white-space: nowrap;   /* a phone number must never wrap */
}
.btn:hover { text-decoration: none; }
.btn--primary { background: var(--red); color: #fff; }
.btn--primary:hover { background: var(--red-dark); color: #fff; }
.btn--secondary { background: var(--white); border-color: var(--ink); color: var(--ink); }
.btn--secondary:hover { background: var(--paper-3); color: var(--ink); }
.btn--block { display: flex; width: 100%; }
@media (min-width: 900px) {
  .btn { min-height: 56px; font-size: 1.125rem; }
}

/* Tertiary link with trailing arrow. Two forms, and the distinction is
   STANDALONE vs IN-SENTENCE — not which block it sits in:
     .link-more--block  its own line/cell → 44px box (tap target)
     .link-more         inside running prose → stays inline, since boxing it
                        would break the line (inline text links are exempt)
   .section-head gets the block form automatically; anywhere else, add the
   modifier in the markup. */
.link-more { font: 600 var(--small) / 1 var(--sans); white-space: nowrap; }
.link-more--block,
.section-head .link-more { display: inline-flex; align-items: center; min-height: var(--tap); }
.link-more::after { content: " \2192"; }

.btn-row { display: flex; flex-direction: column; gap: var(--s3); }
@media (min-width: 600px) { .btn-row { flex-direction: row; flex-wrap: wrap; } }

/* Phone glyph — CSS-drawn, no icon font */
.i-phone {
  display: block; width: 15px; height: 15px; flex: 0 0 auto;
  border: 2px solid currentColor; border-radius: 50% 50% 50% 3px;
  transform: rotate(-45deg);
}
.i-phone--lg { width: 16px; height: 16px; }

/* --------------------------------------------------------------------------
   7. Photos — locked ratios, inset paper frame
   Any photo inside a flex or grid cell must top-align, or stretch alignment
   resolves its height from the sibling column and the ratio is lost.
   -------------------------------------------------------------------------- */
/* No global align-self here: it sets the CROSS axis, which is the width inside a
   column-flex card — collapsing thumbs to their text width. Top-alignment is
   declared per case instead, on the specific parents that need it.

   The paper frame is PADDING, not an inset box-shadow: an inset shadow paints
   behind the element's content, so it shows on a striped placeholder div and
   disappears the moment a real <img> fills the box. */
.photo {
  display: grid;               /* so a .ph or img child fills the ratio box */
  padding: 6px;
  background: var(--paper);
  border: 1px solid var(--frame);
  overflow: hidden;
}
.photo > * { min-width: 0; min-height: 0; }
.photo > .ph { height: 100%; }
.photo > img { width: 100%; height: 100%; object-fit: cover; }
.photo--16x9 { aspect-ratio: 16 / 9; }
.photo--4x3  { aspect-ratio: 4 / 3; }
.photo--3x2  { aspect-ratio: 3 / 2; }
.photo--4x5  { aspect-ratio: 4 / 5; }
.photo--flat { padding: 0; }         /* thumbs inside cards carry their own margin */
/* Home hero: portrait on phones because the subject is Leith himself and a
   person needs the height; landscape once there's width for the roof plane. */
.photo--hero-home { aspect-ratio: 4 / 5; }
@media (min-width: 600px) { .photo--hero-home { aspect-ratio: 16 / 9; } }

/* Home coverage map: landscape while the areas section is one column, portrait
   only when .pair actually splits — same breakpoint as the layout that motivates
   it, or full-width 4:5 resolves to 1015px of nothing.
   align-self:start because a stretched grid item takes its height from the row
   and the aspect-ratio is ignored. */
.photo--map-home { align-self: start; aspect-ratio: 16 / 9; }
@media (min-width: 1200px) { .photo--map-home { aspect-ratio: 4 / 5; } }
@media (min-width: 900px) {
  .photo { padding: 8px; }
  .photo--flat { padding: 0; }
  .photo--hero { padding: 10px; }
}

/* Striped placeholder — delete when photography lands */
.ph {
  display: flex; align-items: center; justify-content: center; padding: var(--s5);
  background: repeating-linear-gradient(128deg, var(--ph-a) 0 11px, var(--ph-b) 11px 22px);
  color: var(--muted-2);
  font: 0.625rem / 1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.05em; text-transform: uppercase; text-align: center;
}

/* Before/after pair — one figure, two shots, one caption. It spans TWO tracks of
   .grid--work so each shot keeps a useful width (374px at 1200); a pair squeezed
   into a single 317px cell would be two 152px photos, which proves nothing.
   The span is declared from 600 up ONLY: below that .grid--work is one column, and
   `span 2` in a single-track grid creates an implicit second column and overflows
   the page. Stacked before-over-after is the correct phone form anyway. */
/* gap: inherit, not a literal — the pair's inner gutter IS the parent grid's, or
   its two photos come out wider than every single photo and their inner edges
   stagger against the rows above and below. .grid steps 12px → 20px at 900 and a
   hardcoded var(--s3) only agreed below that; inherit tracks it at every width. */
.work-pair { display: grid; grid-template-columns: minmax(0, 1fr); gap: inherit; }
.work-pair__shot { display: grid; gap: 7px; align-content: start; }
.work-pair > figcaption { grid-column: 1 / -1; }
@media (min-width: 600px) {
  .grid--work > .work-pair { grid-column: span 2; }
  .work-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

figure { margin: 0; }
figcaption { margin-top: var(--s2); font: 500 var(--small) / 1.4 var(--sans); color: var(--ink-2); }
@media (min-width: 900px) { figcaption { margin-top: var(--s3); font-size: 1rem; } }

/* --------------------------------------------------------------------------
   8. Cards
   -------------------------------------------------------------------------- */
.card {
  display: flex; flex-direction: column;
  background: var(--white); border: 1px solid var(--hair);
}
a.card:hover { border-color: var(--red); text-decoration: none; }
.card__media { margin: var(--s2) var(--s2) 0; border: 1px solid #dcd4c9; }
.card__body { padding: var(--s4); }
.card__body > h3 { margin-bottom: 7px; }
.card__body > p { color: var(--muted); font-size: var(--small); line-height: 1.45; max-width: 34rem; }
@media (min-width: 900px) {
  .card__body > h3 { font-size: 1.1875rem; }
  .card__body > p { font-size: 1rem; line-height: 1.5; }
}

/* Horizontal thumb row — the mobile card form.
   align-items:flex-start is load-bearing: without it the 96px thumb stretches
   to the text column's height and .photo--3x2 is silently overridden. */
.card--row { flex-direction: row; align-items: flex-start; gap: var(--s3); padding: 10px; }
.card--row .card__media { flex: 0 0 96px; margin: 0; }
.card--row .card__body { padding: 0; }
.card--row .card__body > h3 { font-size: 1rem; margin-bottom: var(--s1); }
/* The 96px thumb can't hold a padded three-line caption; tighten the placeholder. */
.card--row .card__media .ph { padding: var(--s2); font-size: 0.5625rem; line-height: 1.45; }

/* Thumb row on mobile, vertical card on desktop — one markup, two forms.
   MUST come after the .card--row rules above: equal specificity, so source
   order decides which flex-direction wins. */
@media (min-width: 900px) {
  .card--responsive { flex-direction: column; align-items: stretch; gap: 0; padding: 0; }
  .card--responsive .card__media { flex: 0 0 auto; margin: var(--s2) var(--s2) 0; }
  .card--responsive .card__media .ph { padding: var(--s5); font-size: 0.625rem; line-height: 1.6; }
  .card--responsive .card__body { padding: var(--s4); }
  .card--responsive .card__body > h3 { margin-bottom: 7px; font-size: 1.1875rem; }
}

/* EVERY link list, in one place: a grid/flex container with `gap` gives its
   links no height of their own, so each one needs its own box. Add new link
   lists to this selector — don't write a fourth copy of it. */
.link-grid > a,
.drawer__sub > a,
.footer__links > a,
.dropdown__col > a { display: flex; align-items: center; min-height: var(--tap); }

/* Bordered link grid — services or towns. 1px gaps read as hairlines. */
.link-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  background: var(--hair-2); border: 1px solid var(--hair-2);
}
.link-grid > a {
  min-height: 46px; padding: var(--s3) 10px;
  background: var(--paper);
  font: 500 var(--small) / 1.2 var(--sans); color: var(--red);
}
.link-grid > a:hover { background: var(--white); color: var(--red-dark); text-decoration: none; }
@media (min-width: 900px) {
  .link-grid > a { min-height: 48px; padding: var(--s3) var(--s4); font-size: 1rem; }
}
@media (min-width: 1200px) {
  /* Column count must divide the item count, or trailing empty cells paint as
     bare --hair-2. minmax(0,1fr) keeps columns equal despite long town names. */
  .link-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .link-grid--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* Bulleted place list */
.places { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 10px var(--s3); }
.places > li { display: flex; align-items: baseline; gap: 9px; font: 500 var(--small) / 1.35 var(--sans); color: var(--ink-2); }
.places > li::before { content: ""; flex: 0 0 auto; width: 4px; height: 4px; margin-top: 7px; background: var(--red); }
@media (min-width: 900px) {
  .places { gap: var(--s3) var(--s6); }
  .places > li { font-size: 1.0625rem; }
  .places > li::before { width: 5px; height: 5px; margin-top: 8px; }
}

/* --------------------------------------------------------------------------
   9. Reviews
   -------------------------------------------------------------------------- */
.review {
  display: flex; flex-direction: column; margin: 0;
  padding: var(--s4); background: var(--white); border: 1px solid var(--hair);
}
.review__stars { font: var(--small) / 1 var(--sans); letter-spacing: 0.18em; color: var(--red); }
.review__text { margin-block: 10px var(--s3); flex: 1; max-width: 42rem; color: var(--ink-2); line-height: 1.5; }
.review__foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  font: 500 var(--meta) / 1.3 var(--sans); color: var(--muted);
}
.review__name { font-weight: 600; color: var(--ink); }
.review__foot > .dot { width: 3px; height: 3px; background: #b6ada2; }
.review__source {
  margin-left: auto; padding: 3px 7px; border: 1px solid #dcd4c9;
  font-size: 0.6875rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted-2);
}
@media (min-width: 900px) {
  .review { padding: var(--s6); }
  .review__stars { font-size: 1rem; letter-spacing: 0.2em; }
  .review__text { margin-block: var(--s4) var(--s5); font-size: 1.125rem; line-height: 1.55; }
  .review__foot { padding-top: var(--s4); border-top: 1px solid var(--ph-a); }
}
.review--inset { background: var(--paper-2); border-color: var(--hair); }

/* --------------------------------------------------------------------------
   9b. Reviews page — summary bar, job label, leave-a-review panel
   -------------------------------------------------------------------------- */
/* Summary bar. Four plain figures on the paper background, divided by rules — not
   four bordered cards, which would read as a stats grid and invite comparison
   between the numbers rather than just letting them be read.
   Dividers are inline borders, so they collapse correctly at 2 and 4 columns. */
.summary {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5) 0;
  padding-block: var(--s5);
  border-top: 3px solid var(--red); border-bottom: 1px solid var(--hair);
}
.summary > div { padding-inline: var(--s4); }
/* nth-child(odd), not :first-child: at two columns the left-hand cell of EACH
   row is the one that must lose its divider. */
.summary > div + div { border-left: 1px solid var(--hair-2); }
.summary > div:nth-child(odd) { border-left: 0; padding-inline-start: 0; }
.summary dt {
  font: 500 var(--eyebrow) / 1.2 var(--sans);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.summary dd {
  margin: 6px 0 0;
  font: 700 1.75rem / 1 var(--sans); letter-spacing: -0.02em; color: var(--ink);
}
.summary dd > small { display: block; margin-top: 6px; font: 400 var(--meta) / 1.35 var(--sans); color: var(--muted); }
@media (min-width: 900px) {
  .summary { grid-template-columns: repeat(4, minmax(0, 1fr)); padding-block: var(--s6); }
  .summary > div { padding-inline: var(--s6); }
  .summary > div:nth-child(odd) { border-left: 1px solid var(--hair-2); padding-inline-start: var(--s6); }
  .summary > div:first-child { border-left: 0; padding-inline-start: 0; }
  .summary dd { font-size: 2.25rem; }
}

/* The job a MyBuilder review was left against. On those cards and not on the
   Google ones, because MyBuilder reviews ARE per-job and Google's are not — an
   invented label on the Google cards would be worse than an uneven component. */
.review__job {
  margin: 0 0 var(--s3); padding-bottom: var(--s3);
  border-bottom: 1px solid var(--ph-a);
  font: 600 var(--meta) / 1.35 var(--sans); color: var(--ink);
}

/* Leave-a-review panel: box from .panel, parts from .panel__* (§16c). */

/* Panel parts — used by the leave-a-review prompt and the blog post's
   related-service box. Named for the panel, not for the first block that wanted
   them. */
.panel__lead { max-width: 34rem; font-size: var(--lead); line-height: 1.5; color: var(--ink-2); }
.panel__links { display: flex; flex-direction: column; gap: var(--s3); align-content: start; }
.panel__note { margin-top: var(--s4); font-size: var(--small); line-height: 1.5; color: var(--muted); }
/* Buttons side by side at 900—1199, where the panel is still one column and a
   stacked pair wastes the width; back to a stack at 1200 in the narrow column. */
@media (min-width: 900px) {
  .panel__links { flex-direction: row; flex-wrap: wrap; gap: var(--s4); }
}
@media (min-width: 1200px) {
  .panel__links { flex-direction: column; }
}

/* --------------------------------------------------------------------------
   9c. Blog index — the post list
   -------------------------------------------------------------------------- */
/* Hairline-divided rows, not a card grid. The site already uses this row
   vocabulary for every ordered list of things (.record, .steps, .accordion), and
   an index of posts is a list — cards would promise each post equal weight and
   force a photo to carry them.
   Degrades to any post count for the same reason .grid--work does: no row is
   positioned, so adding or deleting an <article> needs no other change. */
.postlist { display: grid; gap: 0; border-top: 1px solid var(--hair-2); }
.postlist > article {
  display: grid; grid-template-columns: 96px minmax(0, 1fr);
  /* column-gap only, never the `gap` shorthand: the date sits in row 2 below 1200,
     so any row-gap stacks on top of .postlist__date's own margin and the date ends
     up further from the summary it belongs to than from the next post's divider.
     The gutter here is the thumb-to-copy one; row spacing belongs to the date. */
  column-gap: var(--s4); row-gap: 0;
  align-items: start;
  padding-block: var(--s5);
  border-bottom: 1px solid var(--hair-2);
}
.postlist h3 { margin-top: 6px; }
/* The whole row's title is the link; the thumbnail is decorative and not a second
   tab stop to the same place. */
.postlist h3 > a { color: var(--ink); }
.postlist h3 > a:hover { color: var(--red); text-decoration: none; }
.postlist__sum {
  margin-top: var(--s2); max-width: var(--measure);
  font-size: var(--text); line-height: 1.5; color: var(--ink-2);
}
/* The date is the row's third child, so it MUST be placed at every width or it
   auto-flows into column 1 row 2 — under the thumbnail, detached from its post.
   Below 1200 it sits in the copy column under the summary; at 1200 it lifts into
   its own right-hand column.
   The thumbnail spans both rows below 1200, so row 1 takes its height from the
   COPY and not from the taller photo — otherwise the date floats a variable
   distance below its own summary (up to 90px on a short one) while staying a
   constant 25px above the next post's divider, and stops reading as belonging to
   the post above it. Same technique as .jobbook__media. */
.postlist__date { grid-column: 2; margin-top: var(--s3); }
.postlist > article > .photo { grid-row: 1 / span 2; }
@media (min-width: 900px) {
  .postlist > article { grid-template-columns: 200px minmax(0, 1fr); column-gap: var(--s6); row-gap: 0; padding-block: var(--s6); }
  .postlist h3 { margin-top: 8px; font-size: 1.5rem; letter-spacing: -0.01em; }
}
@media (min-width: 1200px) {
  /* Thumb, then copy, then the date pushed to its own right-hand column — the
     dates line up as a scannable column instead of trailing each summary. */
  .postlist > article { grid-template-columns: 240px minmax(0, 1fr) 8rem; column-gap: var(--s8); row-gap: 0; }
  .postlist h3 { font-size: 1.75rem; }
  /* Row 2 no longer exists here, so the span is released. */
  .postlist > article > .photo { grid-row: 1; }
  .postlist__date {
    grid-column: 3; grid-row: 1;
    margin-top: 0; padding-top: 10px;
    justify-self: end; text-align: right;
  }
}

/* --------------------------------------------------------------------------
   10. Numbered steps (service "What to expect")
   -------------------------------------------------------------------------- */
.steps { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; }
.steps > li {
  display: flex; gap: var(--s4); padding-block: var(--s4);
  border-top: 1px solid var(--hair-2);
}
.steps > li:last-child { border-bottom: 1px solid var(--hair-2); }
.steps__n { flex: 0 0 auto; font: 700 1.375rem / 1 var(--sans); letter-spacing: -0.02em; color: var(--red); }
.steps__body > h3 { margin-bottom: 5px; }
.steps__body > p { font-size: var(--text); line-height: 1.5; }
.steps__note { margin-top: var(--s3); font: 600 var(--small) / 1.45 var(--sans); color: var(--muted-2); }
/* 5-up steps need the full width — 5 items only divide by 1 or 5, and at 1200 a
   5-up row leaves a 195px step column (24 chars). Stacked rows until 1440. */
@media (min-width: 1440px) {
  .steps { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 28px; }
  .steps > li {
    flex-direction: column; gap: 0; padding-block: var(--s5) 0;
    border-top: 2px solid var(--field); border-bottom: 0;
  }
  .steps > li:last-child { border-top-color: var(--red); border-bottom: 0; }
  .steps__n { font-size: 2rem; letter-spacing: -0.03em; }
  .steps__body > h3 { margin-block: var(--s4) var(--s2); font-size: 1.25rem; }
}

/* --------------------------------------------------------------------------
   11. Record list — the location "job book" and the About facts block
   -------------------------------------------------------------------------- */
.record { margin: 0; }
.record > div {
  display: flex; gap: var(--s3); padding-block: 10px;
  border-top: 1px solid var(--ph-a);
}
.record > div:last-child { border-bottom: 1px solid var(--ph-a); }
.record dt {
  flex: 0 0 96px;
  font: 600 var(--eyebrow) / 1.4 var(--sans);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted-3);
}
.record dd { margin: 0; flex: 1; font-size: 0.9375rem; line-height: 1.5; color: var(--ink-2); }
/* Same rows on the dark band — the About page's practical-facts block. Colours
   only: the two roles are separate tokens, so --muted-3 becomes --on-dark-muted
   and --ink-2 becomes --on-dark-2 rather than being reused across backgrounds. */
.record--dark > div { border-top-color: var(--dark-rule-2); }
.record--dark > div:last-child { border-bottom-color: var(--dark-rule-2); }
.record--dark dt { color: var(--on-dark-muted); }
.record--dark dd { color: var(--on-dark-2); }
@media (min-width: 900px) {
  .record > div { gap: var(--s5); padding-block: var(--s3) var(--s3); }
  .record dt { flex-basis: 116px; letter-spacing: 0.1em; }
  .record dd { font-size: 1.0625rem; line-height: 1.55; }
}

/* Job-book panel: red top rule marks it as the page's local proof */
.jobbook { background: var(--white); border: 1px solid var(--hair); border-top: 3px solid var(--red); }
.jobbook__head { display: flex; align-items: center; gap: var(--s3); }
.jobbook__head::after { content: ""; order: 1; flex: 1; height: 1px; background: var(--hair); }
.jobbook__head > :last-child { order: 2; }
.jobbook__media { margin: var(--s2) var(--s2) 0; }
.jobbook__body { padding: var(--s4); }
.jobbook__body > h2 { font-size: 1.375rem; line-height: 1.18; }
/* Right-hand label in the job-book head: place + date. Lighter than an .eyebrow,
   which is the section-opening voice — this one is a caption. */
.jobbook__where {
  font: 500 var(--eyebrow) / 1 var(--sans);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted-3);
}
/* The customer's review, inside the job-book panel: a full-bleed band under the
   record on phones and tablets, a bordered box in the right-hand column once the
   panel splits. Only the top border survives while it is full-bleed. */
.jobbook__review { border-inline-width: 0; border-bottom-width: 0; }
@media (min-width: 900px) {
  .jobbook__body { padding: var(--s7); }
  .jobbook__body > h2 { font-size: 1.875rem; line-height: 1.14; }
}
@media (min-width: 1200px) {
  .jobbook { display: grid; grid-template-columns: 1.15fr 1fr; }
  /* Photo spans both rows so it stays beside the record AND the review; the
     review is placed explicitly, or auto-placement drops it under the photo. */
  .jobbook__media { align-self: start; grid-row: 1 / span 2; margin: var(--s2) 0 var(--s2) var(--s2); }
  .jobbook__where { font-size: var(--meta); letter-spacing: 0.08em; }
  .jobbook__review { grid-column: 2; margin: 0 var(--s7) var(--s7); border-width: 1px; }
}

/* --------------------------------------------------------------------------
   12. Accordion (service mini-FAQ and the FAQ page)
   -------------------------------------------------------------------------- */
.accordion { border-top: 1px solid var(--hair-2); }
.accordion__item { border-bottom: 1px solid var(--hair-2); }
.accordion__btn {
  display: flex; align-items: flex-start; gap: var(--s4);
  width: 100%; min-height: 48px; padding: var(--s4) 0;
  background: transparent; border: 0; text-align: left;
}
.accordion__q { flex: 1; font: 600 1.0625rem / 1.35 var(--sans); color: var(--ink); }
.accordion__sign {
  flex: 0 0 auto; margin-top: 3px;
  font: 400 1.25rem / 1 var(--sans); color: var(--red);
}
.accordion__btn[aria-expanded="true"] .accordion__sign::before { content: "\2212"; }
.accordion__btn[aria-expanded="false"] .accordion__sign::before { content: "+"; }
.accordion__a { padding-bottom: 18px; max-width: var(--measure); line-height: 1.6; }
.accordion__btn[aria-expanded="false"] + .accordion__a { display: none; }
@media (min-width: 900px) {
  .accordion { border-top-color: var(--field); }
  .accordion__item { border-bottom-color: var(--field); }
  .accordion__btn { gap: var(--s5); padding-block: 18px; }
  .accordion__q { font-size: 1.3125rem; line-height: 1.3; }
  .accordion__sign { font-size: 1.5rem; margin-top: 2px; }
  .accordion__a { padding-bottom: var(--s6); font-size: 1.125rem; line-height: 1.65; }
}

/* --------------------------------------------------------------------------
   13. Trust band (dark) — identical on every page that carries it
   -------------------------------------------------------------------------- */
.trust { display: grid; gap: var(--s5); }
.trust > li { list-style: none; display: flex; gap: var(--s4); }
.trust__icon { flex: 0 0 30px; padding-top: var(--s1); }
.trust h3 { margin-bottom: 5px; }
.trust p { font-size: 0.9375rem; line-height: 1.55; color: var(--on-dark); }
.trust { margin: 0; padding: 0; }
@media (min-width: 900px) {
  .trust { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px; }
  .trust > li { flex-direction: column; gap: 0; padding-top: var(--s5); border-top: 2px solid var(--dark-rule-2); }
  .trust__icon { padding: 0 0 var(--s5); }
  .trust h3 { margin-bottom: 9px; }
  .trust p { font-size: 1.0625rem; }
}
@media (min-width: 1440px) {
  .trust { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Guarantee band — service pages that carry a manufacturer guarantee. Sits
   inside .section--dark, reached over the pitch slope. The two-cell table names
   BOTH parties, and stays ONE column at every width: two tiny side-by-side
   cells on a phone force abbreviated copy, and abbreviated copy is exactly how
   the two guarantees start to blur. */
.guarantee { display: grid; gap: var(--s6); }
.guarantee__head { display: flex; align-items: center; gap: var(--s3); }
.guarantee__head > .eyebrow { letter-spacing: 0.14em; line-height: 1.3; }
.guarantee h2 { max-width: 18em; }
.guarantee__lead { margin-top: var(--s4); max-width: 32rem; font-size: var(--lead); line-height: 1.6; color: var(--on-dark); }
.guarantee__pair {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px;
  background: var(--dark-rule); border: 1px solid var(--dark-rule);
}
.guarantee__cell { padding: var(--s4); background: var(--dark); }
.guarantee__label { font: 600 0.6875rem / 1.3 var(--sans); letter-spacing: 0.1em; text-transform: uppercase; color: var(--on-dark-muted); }
.guarantee__who { margin-top: 6px; font: 600 1.0625rem / 1.3 var(--sans); color: var(--paper); }
.guarantee__cell > p:last-child { margin-top: var(--s2); font-size: 0.9375rem; line-height: 1.5; color: var(--on-dark); }
.guarantee__note { margin-top: var(--s4); font-size: var(--small); line-height: 1.55; color: var(--on-dark-muted); }
@media (min-width: 900px) {
  .guarantee__cell { padding: var(--s5); }
  .guarantee__who { font-size: 1.25rem; }
  .guarantee__cell > p:last-child { font-size: 1.0625rem; line-height: 1.55; }
}
@media (min-width: 1200px) {
  .guarantee { grid-template-columns: 1.15fr 1fr; gap: var(--s12); align-items: center; }
  .guarantee__cell { padding: var(--s6); }
}

/* CSS-drawn trust icons, 2px strokes in --red-tint */
.i-pitch { display: block; width: 22px; height: 22px; border-top: 2px solid var(--red-tint); border-right: 2px solid var(--red-tint); transform: rotate(-45deg); }
.i-dot { display: flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: 2px solid var(--red-tint); border-radius: 50%; }
.i-dot::before { content: ""; width: 6px; height: 6px; background: var(--red-tint); border-radius: 50%; }
.i-diamond { display: block; width: 18px; height: 18px; border: 2px solid var(--red-tint); transform: rotate(45deg); }
.i-pin { display: block; width: 22px; height: 22px; border: 2px solid var(--red-tint); border-radius: 50% 50% 50% 3px; transform: rotate(-45deg); }
@media (min-width: 900px) {
  .i-pitch, .i-dot, .i-pin { width: 28px; height: 28px; }
  .i-diamond { width: 24px; height: 24px; }
}

/* Pitch motif — the logo's lean, reused as a shallow slope into the dark band.
   Max two uses per page (logo + this). */
.pitch-slope { height: 34px; background: var(--dark); clip-path: polygon(0 0, 100% 100%, 0 100%); }
.pitch-slope + .section--dark { padding-top: 6px; border-top: 0; }
@media (min-width: 900px) {
  .pitch-slope { height: 64px; }
  .pitch-slope + .section--dark { padding-top: var(--s5); }
}

/* --------------------------------------------------------------------------
   14. Forms
   -------------------------------------------------------------------------- */
.form { display: flex; flex-direction: column; gap: var(--s3); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field > label { font: 500 var(--small) / 1 var(--sans); color: var(--ink-2); }
.field > input, .field > textarea, .field > select {
  min-height: 48px; padding: var(--s3);
  background: var(--white); border: 1px solid var(--field);
  font-size: 1rem; color: var(--ink);   /* 16px stops iOS zoom */
}
.field > textarea { resize: vertical; }
.form__note { font-size: var(--small); line-height: 1.5; color: var(--muted-2); }
@media (min-width: 900px) {
  .form {
    display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-content: start;
    max-width: 600px;   /* full-bleed fields make a 747px submit button */
    padding: var(--s7); background: var(--white); border: 1px solid var(--hair);
  }
  .field > input, .field > textarea { min-height: 50px; padding: 13px; background: var(--paper); }
  .field--wide, .form > .btn, .form__note { grid-column: 1 / -1; }
}
@media (min-width: 1200px) { .form { max-width: none; } }

/* --------------------------------------------------------------------------
   15. Header / nav
   -------------------------------------------------------------------------- */
.header {
  position: relative; z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: var(--s3) var(--gutter);
  background: var(--paper); border-bottom: 1px solid var(--hair);
}.header__logo { flex: 0 0 auto; }
.header__logo img { width: 121px; height: 36px; }
.header__actions { flex: 0 0 auto; display: flex; align-items: center; gap: var(--s2); }
/* One phone control at a time: icon square below 900, full number above. */
.header__actions > .btn { display: none; }
.icon-btn {
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; background: var(--red); color: #fff; border: 0;
}
.icon-btn--ghost { background: var(--white); border: 1px solid #d8d1c8; flex-direction: column; gap: 4px; padding-inline: var(--s3); }
.icon-btn--ghost > span { display: block; width: 100%; height: 2px; background: var(--ink); }

.nav { display: none; }
.nav a {
  display: flex; align-items: center; min-height: var(--tap); padding-inline: 14px;
  font: 600 0.9375rem / 1 var(--sans); color: var(--ink);
}
.nav__btn {
  display: flex; align-items: center; gap: 7px;
  min-height: var(--tap); padding-inline: 14px;
  background: transparent; border: 0;
  font: 600 0.9375rem / 1 var(--sans); color: var(--ink);
}
.nav__btn > .caret {
  display: block; width: 7px; height: 7px;
  border-right: 2px solid var(--red); border-bottom: 2px solid var(--red);
  transform: rotate(45deg) translateY(-2px);
}
.nav a:hover { color: var(--red); text-decoration: none; }
/* Attribute, not element: the Services item is a <button>, and on the ten
   service pages IT is the current one. */
.nav [aria-current="page"] { color: var(--red); box-shadow: inset 0 -2px 0 var(--red); }

@media (min-width: 900px) {
  .header { gap: var(--s4); padding: var(--s4) var(--gutter); }
  .header__logo img { width: 154px; height: 46px; }
  .header__actions > .icon-btn--ghost { display: none; }
  .nav { display: flex; align-items: center; gap: 0; margin-left: auto; min-width: 0; }
  .nav a, .nav__btn { padding-inline: 10px; white-space: nowrap; }
}

/* Only at 1200 is there room for the logo, six nav items and the full number. */
@media (min-width: 1200px) {
  .header { gap: var(--s7); }
  .header__actions > .btn { display: inline-flex; }
  .header__actions > .icon-btn { display: none; }
  .nav { gap: var(--s1); }
  .nav a, .nav__btn { padding-inline: 14px; }
}

/* Mobile drawer */
.drawer { position: fixed; inset: 0; z-index: 50; background: var(--paper); padding: var(--s4); overflow: auto; }
.drawer[hidden] { display: none; }
.drawer__top { display: flex; align-items: center; justify-content: space-between; }
.drawer__nav { display: flex; flex-direction: column; margin-top: 18px; border-top: 1px solid var(--hair); }
.drawer__nav > a {
  padding: 15px 2px; border-bottom: 1px solid var(--hair);
  font: 600 1.125rem / 1.2 var(--sans); color: var(--ink);
}
.drawer__sub {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px;
  padding: 14px 2px; border-bottom: 1px solid var(--hair);
  background: var(--paper-2); font: 500 var(--small) / 1.3 var(--sans);
}
/* The most-used nav on a mobile-first site: children sized by the shared
   link-list rule above; the container carries no row gap so 44px sets the rhythm. */
.drawer__sub > a { min-width: 0; }

/* Desktop services dropdown */
/* Desktop services dropdown — two columns at the intermediate step, four at 1200 */
.dropdown {
  position: absolute; top: 100%; left: 0; right: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s6) var(--s8);
  padding: var(--s7) var(--gutter) 36px;
  background: var(--white); border-bottom: 3px solid var(--red);
  box-shadow: 0 18px 40px rgb(27 23 20 / 0.14);
}
.dropdown[hidden] { display: none; }
.dropdown__col { display: flex; flex-direction: column; gap: 0; font: 500 1rem / 1.2 var(--sans); }
.dropdown__col > a { color: var(--ink); }
.dropdown__col > a:hover { color: var(--red); }
.dropdown__help { padding: var(--s5); background: var(--paper-2); border: 1px solid var(--hair); }
@media (min-width: 1200px) {
  .dropdown { grid-template-columns: 1fr 1fr 1fr auto; gap: var(--s8); }
  .dropdown__help { width: 270px; }
}

/* Breadcrumb */
.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; font: 500 var(--eyebrow) / 1.4 var(--sans); color: var(--muted-3); }
.crumbs a { color: var(--muted-3); }
.crumbs [aria-current] { color: var(--ink-2); }
@media (min-width: 900px) { .crumbs { gap: 9px; font-size: var(--meta); } }

/* --------------------------------------------------------------------------
   16. Hero
   -------------------------------------------------------------------------- */
.hero { display: grid; gap: var(--s5); padding-block: var(--s5) var(--s6); }
.hero__lead { margin-top: 14px; font-size: var(--lead); line-height: 1.5; max-width: 32rem; }
.hero .btn-row { margin-top: var(--s5); }
.hero .facts { margin-top: var(--s5); }
@media (min-width: 900px) {
  .hero { padding-block: var(--s11) var(--s10); }
  .hero__lead { margin-top: 22px; }
  .hero .btn-row, .hero .facts { margin-top: 30px; }
}
/* Two-column hero only at 1200: at 924 the column is 374px, a 44px H1 wraps to
   five lines, and a 16:9 photo can only be 210px tall — leaving 435px of void. */
@media (min-width: 1200px) {
  .hero { grid-template-columns: 1fr 1fr; gap: var(--s11); align-items: center; }
}

/* Sub-page hero (service, location): the breadcrumb above it carries the top
   space, so the hero's own top padding steps back. */
.crumbs--top { padding-top: var(--s4); }
.hero--sub { padding-block: var(--s4) var(--s6); }
@media (min-width: 900px) {
  .crumbs--top { padding-top: var(--s6); }
  .hero--sub { padding-block: var(--s6) var(--s10); }
}

/* Copy-only hero — FAQ, Reviews, Our Work. The base .hero splits 1fr 1fr at 1200,
   which on a page with no hero photo leaves half the fold empty, so this holds one
   column at every width. The H1's own 11em cap and .hero__lead's 32rem keep the
   copy off the 1328px content edge; no extra cap needed here.
   Declared as a shared modifier rather than per page: four pages need it. */
.hero--plain { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1200px) {
  .hero--plain { grid-template-columns: minmax(0, 1fr); }
}

/* About hero: the 4:5 portrait of Leith is the page's anchor image, but a 4:5 crop
   across a 608px hero column is 760px tall and dwarfs a 60px H1 — so it's capped
   and pushed to the outer edge of its column. Capped in px, not by a ratio switch:
   there is only one crop of this shot. */
.hero__portrait { max-width: 420px; }
@media (min-width: 1200px) {
  .hero__portrait { margin-inline-start: auto; }
}

/* Home hero: eyebrow, then photo, then copy on mobile; photo beside copy on desktop */
.hero--home { grid-template-areas: "eyebrow" "media" "copy"; }
.hero--home > .hero__eyebrow { grid-area: eyebrow; }
.hero--home > .hero__media { grid-area: media; }
.hero--home > .hero__copy { grid-area: copy; }
/* Copy leads once the photo is full-width: a 16:9 crop across 812px is 457px tall,
   which pushes the H1 and both CTAs below the fold. Must sit AFTER the base rule
   above — equal specificity, so source order decides. */
@media (min-width: 900px) {
  .hero--home { grid-template-areas: "eyebrow" "copy" "media"; }
}
@media (min-width: 1200px) {
  .hero--home {
    grid-template-areas: "eyebrow media" "copy media";
    grid-template-rows: auto auto;
    row-gap: 18px; column-gap: var(--s11);
    align-items: start;
  }
  .hero--home > .hero__media { align-self: start; }
}

/* Call panel — the box comes from .panel; the number type is what's its own.
   The brief for Contact is "phone first and visually dominant", so the number is
   set ABOVE --h1 at every width and the H1 above it is kept short. Nothing else
   on the site sets type larger than a heading; this is the one exception, and it
   exists because the number is the page's function, not its title. */
.callnow__num {
  display: block;
  /* 2.375rem = 38px. Archivo 700 sets "07811 286915" to ~245px, which clears the
     256px available inside the panel on a 320px viewport. Larger overflows. */
  font: 700 2.375rem / 1 var(--sans);
  letter-spacing: -0.035em; white-space: nowrap;
  color: var(--ink);
}
.callnow__num:hover { color: var(--red); text-decoration: none; }
/* The emergency line. A red top hairline and nothing else — it has to be findable
   by someone scanning in a panic, without becoming a second red button. */
.callnow__urgent {
  margin-top: var(--s6); padding-top: var(--s4);
  border-top: 1px solid var(--red);
  max-width: 34rem;
  font-size: var(--small); line-height: 1.5; color: var(--ink-2);
}
.callnow__urgent > strong { color: var(--ink); font-weight: 600; }
@media (min-width: 900px) {
  .callnow { padding: var(--s9); gap: var(--s8); }
  .callnow__num { font-size: 3.5rem; }
}
@media (min-width: 1200px) {
  .callnow__num { font-size: 4.25rem; }
}

/* --------------------------------------------------------------------------
   16c. Panel — the shared white-box recipe
   -------------------------------------------------------------------------- */
/* Three blocks wanted the same box (the Contact call panel, the leave-a-review
   prompt, the blog post's related-service box), so the box is one class and each
   keeps only what is genuinely its own. Same reasoning as the shared link-list
   selector in §8: when a fourth one turns up, add .panel to it — don't write a
   fourth copy of the border, padding and rule. */
.panel {
  display: grid; gap: var(--s6);
  padding: var(--s6);
  background: var(--white); border: 1px solid var(--frame);
  border-top: 3px solid var(--red);
}
@media (min-width: 900px) { .panel { padding: var(--s9); } }
/* The two-column variant splits at 1200 only, like every other two-column layout
   on the site (§6): an 812px content box halves to 374px, below the measure floor. */
@media (min-width: 1200px) {
  .panel--2col { grid-template-columns: 1.15fr 1fr; gap: var(--s12); align-items: start; }
}

/* --------------------------------------------------------------------------
   17. Map facade — the only third-party embed on the site, click-to-load
   -------------------------------------------------------------------------- */
.map { aspect-ratio: 4 / 3; border: 1px solid var(--frame); background: var(--ph-a); overflow: hidden; }
.map > iframe { display: block; width: 100%; height: 100%; border: 0; }
.map__facade {
  position: relative; aspect-ratio: 4 / 3;
  background: repeating-linear-gradient(128deg, #e8e2d8 0 11px, #e0d9ce 11px 22px);
  border: 1px solid var(--frame);
}
.map__inner {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: var(--s5); text-align: center;
}
.map__pin { width: 26px; height: 26px; border: 2px solid var(--red); border-radius: 50% 50% 50% 4px; transform: rotate(-45deg); }
/* The facade's own line — distance and drive time. The grey .map__note below it
   is the privacy caption, a step quieter. */
.map__lead { font-size: 0.9375rem; line-height: 1.5; color: var(--ink-2); max-width: 24rem; }
.map__note { font-size: var(--meta); line-height: 1.45; color: var(--muted-2); max-width: 22rem; }
.map__address { margin-top: 10px; font-size: var(--small); line-height: 1.5; color: var(--muted-2); }
@media (min-width: 900px) {
  .map__inner { gap: 18px; padding: var(--s7); }
  .map__pin { width: 32px; height: 32px; }
  .map__lead { font-size: var(--lead); line-height: 1.55; }
  .map__note { font-size: var(--small); }
}

/* --------------------------------------------------------------------------
   18. Shared CTA + footer + sticky call bar
   -------------------------------------------------------------------------- */
.cta { background: var(--paper); border-top: 3px solid var(--red); }
.cta__intro { margin-top: var(--s3); max-width: 32rem; color: var(--ink-2); font-size: var(--text); line-height: 1.55; }
.cta__who { display: flex; flex-direction: column; gap: var(--s5); margin-top: 36px; padding-top: var(--s6); border-top: 1px solid var(--hair); }
.cta__who .photo { flex: 0 0 auto; align-self: start; width: 128px; }
.cta__inner { display: grid; gap: var(--s5); }
/* Two columns only at 1200: a 265px left column leaves the portrait's paragraph
   ~117px, which sets to four words a line. */
@media (min-width: 1200px) {
  .cta__inner { grid-template-columns: 1fr 1.1fr; gap: var(--s12); align-items: start; }
  .cta__who { flex-direction: row; }
}

.footer { padding: 30px var(--gutter) var(--s6); background: var(--dark); color: var(--on-dark); }
.footer a { color: var(--on-dark-2); }
.footer a:hover { color: #fff; }
.footer__cols { display: grid; gap: var(--s6); margin-top: var(--s5); }
.footer h3 { margin-bottom: 10px; font: 600 var(--eyebrow) / 1 var(--sans); letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-dark-muted); }
.footer__links { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; font: 500 var(--small) / 1.3 var(--sans); }
.footer__text { font-size: 0.9375rem; line-height: 1.7; color: var(--on-dark-2); }
.footer__text a { color: var(--red-tint); }
.footer__links > a[aria-current="page"] { color: #fff; font-weight: 600; }
.footer__legal { margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--dark-rule); }
.footer .legal { color: var(--on-dark-muted); }
/* The link IS the .legal element here, not a descendant — target the row's child.
   The paragraph needs the cap too, or as a flex item it starves the link. */
.footer__legal > p { flex: 1 1 auto; min-width: 0; max-width: 56rem; }
.footer__legal > a {
  flex: 0 0 auto; display: inline-flex; align-items: center;
  min-height: var(--tap); white-space: nowrap;
}
/* Reserve room for the sticky call bar so it never covers the legal line. */
.footer { padding-bottom: calc(var(--s6) + var(--callbar-h)); }

@media (min-width: 900px) {
  .footer { padding: var(--s10) var(--gutter) var(--s7); }
  /* DOM order is Company · Services · Areas · Trust, so a 2-track grid pairs each
     ~470px link list against a ~130px text block twice, leaving ~680px of void.
     Reorder to pair like with like — the two short blocks, then the two lists —
     and let each row size to its own content. DOM order is untouched, so the
     ≥1200 four-up sequence still reads Company · Services · Areas · Trust. */
  .footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s9); margin-top: 0; align-items: start; }
  .footer__cols > div:nth-child(1) { order: 1; }   /* Company */
  .footer__cols > div:nth-child(4) { order: 2; }   /* Trust    */
  .footer__cols > div:nth-child(2) { order: 3; }   /* Services */
  .footer__cols > div:nth-child(3) { order: 4; }   /* Areas    */
  .footer__links { grid-template-columns: 1fr; gap: 0; font-size: 1rem; }
  .footer h3 { margin-bottom: var(--s4); letter-spacing: 0.16em; }
  .footer__legal { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s8); margin-top: var(--s9); padding-top: var(--s5); }
}
@media (min-width: 1200px) {
  .footer__cols { grid-template-columns: 1.1fr 1fr 1fr 1fr; }
  .footer__cols > div { order: 0; }
}

.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; align-items: center; gap: 10px;
  padding: var(--s2) var(--s3);
  background: var(--dark); border-top: 1px solid var(--dark-rule);
  box-shadow: 0 -6px 18px rgb(27 23 20 / 0.18);
}
.callbar p { flex: 1; margin: 0; font: 500 var(--meta) / 1.3 var(--sans); color: #efe9e1; }
.callbar strong { color: #fff; font-weight: 600; }
.callbar .btn { min-height: 48px; padding-inline: 18px; font: 700 var(--small) / 1 var(--sans); letter-spacing: 0.06em; }
@media (min-width: 900px) {
  .callbar { display: none; }
  .footer { padding-bottom: var(--s7); }
}

/* --------------------------------------------------------------------------
   19. Reduced motion — the site has no motion to speak of; belt and braces
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}