/* diakite-css-build 678bfbed */
/* Diakite Roofing & Restoration - Bluebird wireframe, roofing/restoration brand.
   Accent amber #FBB204 is sampled from the client's own logo (recovered off their live site and
   cropped to the wordmark; assets/img/logo-header.png is the light-ground version, logo-footer.png
   the white-ink version for the navy footer). Charcoal #17181C is the logo's ink.
   Token names are kept from the base build; only the VALUES are ever re-pointed, so --orange
   now carries the brand amber.
   Montserrat headings + Inter body. */

:root {
  --navy: #17181c;   /* charcoal - headings, dark sections, footer */
  --navy-2: #26282f;
  --orange: #fbb204; /* brand amber, sampled from the logo - CTAs, kickers, icons, accents */
  --orange-2: #e09f02;
  --orange-ink: #8a5e00; /* darkened amber for small TEXT on light grounds (contrast) */
  --sky: #fbb204;    /* brand amber on dark backgrounds */
  --amber-lite: #ffd873; /* same hue, lighter: small TEXT over a scrimmed photo, where #fbb204
                            would need a scrim heavy enough to hide the photo. See the scrim note. */
  --ink: #1e2126;
  --muted: #626b74;
  --line: #e6e8ec;
  --tint: #f6f5f3;   /* warm neutral tint */
  --white: #fff;
  --wrap-inner: 1440px;  /* was 1180: 46% of a 2560 screen read as boxed */
  --measure: 730px;      /* the ONE reading measure. Fixed px on purpose: ch resolves per
                            font-size, so headings and body could never share a left edge. */
  --gut: 22px;
  --radius: 14px;
  --shadow: 0 10px 30px rgba(23, 24, 28, 0.12);
  --shadow-sm: 0 4px 14px rgba(23, 24, 28, 0.09);
  --font-head: "Montserrat", system-ui, sans-serif;
  --font-body: "Inter", system-ui, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { font-family: var(--font-body); color: var(--ink); line-height: 1.65; background: var(--white); font-size: 16.5px; }
img, svg, iframe { max-width: 100%; }
a { color: var(--navy-2); }

h1, h2, h3, .btn, .logo { font-family: var(--font-head); }
h1 { font-size: clamp(2rem, 4.2vw, 3.1rem); line-height: 1.12; font-weight: 800; color: var(--navy); letter-spacing: -0.5px; }
h2 { font-size: clamp(1.5rem, 2.8vw, 2.15rem); line-height: 1.18; font-weight: 800; color: var(--navy); letter-spacing: -0.3px; margin-bottom: 0.6em; }
h3 { font-size: 1.12rem; font-weight: 700; color: var(--navy); }

.wrap { max-width: var(--wrap-inner); margin: 0 auto; padding: 0 var(--gut); }
/* .wrap-narrow no longer sets its own width: a second wrapper width was a second prose left
   edge. Prose is constrained by --measure instead, which every band shares. */
.wrap-narrow { max-width: var(--wrap-inner); }
.prose h2 { margin-top: 1.5em; }
.prose h2:first-child, .prose .kicker + h2 { margin-top: 0; }
.prose p { margin-bottom: 1.05em; color: var(--ink); }
.prose p:last-child { margin-bottom: 0; }
.section { padding: 78px 0; }
.section-tint { background: var(--tint); }
.section-dark { background: linear-gradient(150deg, var(--navy) 0%, var(--navy-2) 100%); }
.section-dark h2, .section-dark p { color: var(--white); }
.muted { color: var(--muted); }
.skip { position: absolute; left: -999px; }
.skip:focus { left: 12px; top: 12px; background: var(--white); padding: 8px 14px; z-index: 99; }

.kicker {
  display: inline-block; font-family: var(--font-head); font-weight: 700; font-size: 0.78rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--orange-ink);
  margin-bottom: 10px;
}
.section-dark .kicker { color: var(--sky); }

/* icons */
.ic { width: 22px; height: 22px; flex: 0 0 auto; }

/* buttons */
.btn {
  display: inline-flex; align-items: center; gap: 9px; font-weight: 700; font-size: 0.95rem;
  padding: 13px 24px; border-radius: 999px; text-decoration: none; border: 2px solid transparent;
  cursor: pointer; transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
/* Amber is a light accent: CTA text is the logo ink, never white, or contrast fails. */
.btn-cta { background: var(--orange); color: var(--navy); }
.btn-cta:hover { background: var(--orange-2); color: var(--navy); }
.btn-ghost { border-color: var(--navy); color: var(--navy); background: transparent; }
.hero .btn-ghost, .page-head .btn-ghost { border-color: rgba(255, 255, 255, 0.65); color: var(--white); }
.btn-light { background: var(--white); color: var(--navy); }
.btn-lg { padding: 16px 30px; font-size: 1.05rem; }
.btn-block { width: 100%; justify-content: center; }
.header-cta { flex: 0 0 auto; white-space: nowrap; }
.header-in .logo { flex: 0 0 auto; }

/* ---------------- header ---------------- */
.site-header {
  position: sticky; top: 0; z-index: 50; background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--line);
}
.header-in { display: flex; align-items: center; gap: 26px; height: 76px; }
.logo { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; font-size: 1.3rem; font-weight: 800; color: var(--navy); }
.logo b { color: var(--orange-ink); font-weight: 800; }
.logo-ic { width: 30px; height: 30px; color: var(--orange); }
.logo-img { display: block; height: 48px; width: auto; }
.logo-chip { background: var(--white); padding: 8px 12px; border-radius: 10px; }
.logo-chip .logo-img { height: 42px; }
/* Real logo files (wordmark crop, 1260x321). The footer uses the white-ink version straight on
   the navy ground, so it must NOT sit inside the white .logo-chip. */
.logo-header { height: 44px; }
.f-brand .logo-chip .logo-footer { height: 46px; }
.f-brand .logo-chip { background: none; padding: 0; }

/* Typographic wordmark. Stands in for the logo while data.js brand.logo is null; set brand.logo
   to a real file and build.js swaps an <img> back in with no CSS change needed. */
.wordmark { display: flex; flex-direction: column; line-height: 1.04; }
.wordmark-name { font-family: var(--font-head); font-weight: 800; font-size: 1.42rem; letter-spacing: -0.015em; color: var(--navy); }
.wordmark-sub {
  font-family: var(--font-body); font-weight: 600; font-size: 0.6rem; margin-top: 4px;
  text-transform: uppercase; letter-spacing: 0.15em; color: var(--orange-ink); white-space: nowrap;
}
.wordmark-footer .wordmark-name { font-size: 1.24rem; }
.wordmark-footer .wordmark-sub { font-size: 0.55rem; }
@media (max-width: 640px) {
  .wordmark-name { font-size: 1.14rem; }
  .wordmark-sub { font-size: 0.5rem; letter-spacing: 0.11em; }
}

.nav { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.nav ul { display: flex; gap: 4px; list-style: none; }
.nav a { display: block; padding: 10px 13px; text-decoration: none; font-weight: 600; font-size: 0.94rem; color: var(--navy); border-radius: 8px; white-space: nowrap; }
.nav a:hover { background: var(--tint); color: var(--orange-ink); }
.has-sub { position: relative; }
.has-sub > a::after { content: " \25BE"; font-size: 0.7em; }
.sub {
  position: absolute; top: 100%; left: 0; min-width: 240px; background: var(--white);
  border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
  padding: 8px; display: none !important; flex-direction: column; gap: 0;
}
.has-sub:hover .sub, .has-sub:focus-within .sub { display: flex !important; }
.sub a { padding: 9px 12px; font-weight: 500; }

.header-phone { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; white-space: nowrap; font-weight: 700; color: var(--navy); text-decoration: none; font-size: 0.95rem; }
.header-phone .ic { color: var(--orange); }
.nav-toggle { display: none; background: none; border: 0; cursor: pointer; }
.nav-toggle span { display: block; width: 24px; height: 3px; background: var(--navy); margin: 5px 0; border-radius: 2px; transition: transform 0.2s ease, opacity 0.2s ease; }
body.nav-open .nav-toggle span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
body.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
body.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* ---------------- hero ---------------- */
/* The gradient stays as the FALLBACK ground: build.js only emits the hero photo when
   assets/img/hero-roof-replacement-columbia-md.jpg exists, so deleting that file leaves the
   original gradient hero rather than a blank band. position:relative is what the absolutely
   positioned .bg-photo / .bg-scrim layers are placed against. */
.hero {
  position: relative;
  background:
    radial-gradient(900px 420px at 85% -10%, rgba(251, 178, 4, 0.26), transparent 60%),
    linear-gradient(160deg, var(--navy) 0%, var(--navy-2) 62%, #3a3319 100%);
  padding: 84px 0 92px;
}
.hero-grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 56px; align-items: center; }
.hero-copy h1 { color: var(--white); margin: 6px 0 18px; }
.hero-sub { font-size: 1.1rem; color: rgba(255, 255, 255, 0.85); max-width: 56ch; }
.page-head .hero-sub, .page-head h1 { color: inherit; }
.hero-ctas { display: flex; flex-wrap: wrap; gap: 14px; margin: 26px 0 30px; }
.badge-row { display: flex; flex-wrap: wrap; gap: 10px 22px; list-style: none; }
.badge-row li { display: inline-flex; align-items: center; gap: 7px; font-size: 0.88rem; font-weight: 600; color: rgba(255, 255, 255, 0.9); }
.badge-row .ic { width: 17px; height: 17px; color: var(--sky); }
.badge-dark li { color: var(--navy); }
.badge-dark .ic { color: var(--orange); }

/* estimate form card */
.form-card {
  background: var(--white); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 30px 28px 26px; border-top: 5px solid var(--orange);
}
.form-kicker { font-family: var(--font-head); font-weight: 700; font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--orange-ink); }
.form-title { font-size: 1.45rem; margin: 4px 0 16px; }
.form-rating { display: flex; align-items: center; gap: 8px; margin: -8px 0 14px; font-weight: 600; font-size: 0.92rem; }
.form-rating b { font-size: 1rem; }
.form-rating .stars { display: inline-flex; gap: 2px; }
.form-rating .stars .ic { width: 17px; height: 17px; color: #b07a00; fill: #b07a00; }
.form-card label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--muted); margin-bottom: 12px; }
.form-card input, .form-card textarea {
  display: block; width: 100%; margin-top: 5px; padding: 11px 13px; font: inherit;
  border: 1.5px solid var(--line); border-radius: 9px; background: #fbfdfe;
}
.form-card input:focus, .form-card textarea:focus { outline: 2px solid var(--sky); border-color: var(--sky); }
.form-note { font-size: 0.8rem; color: var(--muted); text-align: center; margin-top: 10px; }

/* ---------------- page head (interior heroes) ---------------- */
.page-head {
  position: relative;
  background:
    radial-gradient(700px 300px at 90% -20%, rgba(251, 178, 4, 0.22), transparent 60%),
    linear-gradient(160deg, var(--navy), var(--navy-2));
  padding: 72px 0;
}
.page-head h1 { color: var(--white); margin: 6px 0 14px; }
.page-head .hero-sub { color: rgba(255, 255, 255, 0.85); }

/* breadcrumbs */
/* Breadcrumbs are a full-bleed band with their own tint: as a bare top-level .wrap they were
   the one element narrower than the viewport, and being untinted they merged with the white
   section under them. */
.crumbs-band { background: var(--tint); border-bottom: 1px solid var(--line); }
.crumbs { font-size: 0.85rem; color: var(--muted); padding-top: 14px; padding-bottom: 14px; }
.crumbs a { color: var(--muted); }

/* ---------------- cards ---------------- */
.card-grid { display: grid; gap: 22px; }
.cols-2 { grid-template-columns: repeat(2, 1fr); }
.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cols-4 { grid-template-columns: repeat(4, 1fr); }

.svc-card, .benefit-card, .offer-card, .team-card {
  background: var(--white); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px 24px; box-shadow: var(--shadow-sm); text-decoration: none;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.svc-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: var(--sky); }
.svc-card .ic, .benefit-card .ic { width: 40px; height: 40px; color: var(--orange); margin-bottom: 14px; }
.svc-card h3, .benefit-card h3 { margin-bottom: 8px; }
.svc-card p, .benefit-card p, .offer-card p { font-size: 0.92rem; color: var(--muted); }
.link-more { display: inline-block; margin-top: 12px; font-weight: 700; font-size: 0.88rem; color: var(--orange-ink); }

/* ---------------- why / two-col ---------------- */
.why-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 52px; align-items: center; }
.why-list { list-style: none; display: grid; gap: 20px; margin-top: 22px; }
.why-list li { display: flex; gap: 14px; }
.why-list .ic { color: var(--orange); margin-top: 4px; }
.why-list p { color: var(--muted); font-size: 0.94rem; }

.check-list { list-style: none; display: grid; gap: 12px; margin: 18px 0; }
.check-list li { display: flex; gap: 11px; align-items: flex-start; font-weight: 500; }
.check-list .ic { color: var(--orange); margin-top: 3px; width: 19px; height: 19px; }

/* ---------------- photo & widget placeholders ---------------- */
.photo-ph, .map-ph {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  background:
    repeating-linear-gradient(45deg, rgba(251, 178, 4, 0.07) 0 14px, rgba(251, 178, 4, 0.14) 14px 28px),
    linear-gradient(160deg, #f2efec, #e6e2de);
  border: 2px dashed rgba(23, 24, 28, 0.25); border-radius: var(--radius);
  color: var(--navy); font-weight: 700; font-family: var(--font-head); text-align: center;
  min-height: 300px; padding: 24px;
}
.photo-ph small, .map-ph small { font-family: var(--font-body); font-weight: 500; color: var(--muted); }
.ph-ic { width: 42px; height: 42px; opacity: 0.55; }
.map-ph { min-height: 260px; }
.why-photo { min-height: 360px; }

/* real photos */
.media-img { display: block; width: 100%; border-radius: var(--radius); box-shadow: var(--shadow); object-fit: cover; }
.media-img.why-photo { min-height: 360px; height: 100%; max-height: 460px; }

/* ================= SWIPEABLE PHOTO CAROUSEL =================
   Client feedback, relayed 2026-07-31: the photo sets read as "a straight line" and the pages are
   "a lot of scrolling down". Both photo grids used to be `repeat(3, 1fr)` collapsing to ONE column
   under 560px, so on a phone every photo cost a full screen of vertical scroll and /gallery/ ran
   to 18,061px. These tracks are horizontal now.

   Mechanism, in three declarations and no JavaScript: overflow-x on the track, scroll-snap-type on
   the track, scroll-snap-align on the item. Touch swipe is then the platform's, not ours. The
   buttons in .carousel-nav are for mouse and keyboard, and the script that drives them is progressive
   - with JS off this is still a scrollable, snapping, keyboard-focusable strip.

   --cv IS THE WHOLE DESIGN DECISION: it is how many items fit across, and it is deliberately
   FRACTIONAL at every breakpoint. A track showing exactly 3 of 3 looks like a finished row and
   nobody swipes it; showing 3.4 puts a slice of the next photo against the edge, which is the only
   affordance that reliably reads as "there is more this way".

   HORIZONTAL OVERFLOW: the items overflow the TRACK, and the track clips them. The document's
   scrollWidth is untouched, which is what keeps the format standard's "page never scrolls
   horizontally" rule intact. Verified at 2560/1280/375, zero elements past the viewport edge. */
.carousel { position: relative; }
.carousel-track {
  --cv: 3.4;            /* items across, fractional so the next one always peeks */
  --cgap: 18px;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--cv) - 1) * var(--cgap)) / var(--cv));
  gap: var(--cgap);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;   /* a swipe that runs out does not navigate the page back */
  scroll-snap-type: x mandatory;
  /* room for the item shadow and for the focus ring, which would otherwise be clipped by
     overflow-x and leave keyboard users with no visible focus at all */
  padding: 4px 4px 10px;
  margin: -4px -4px -10px;
  scrollbar-width: none;            /* hidden visually only - scrolling is never disabled */
  -ms-overflow-style: none;
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-track > * { scroll-snap-align: start; }
.carousel-track:focus-visible { outline: 3px solid var(--orange-2); outline-offset: 2px; border-radius: var(--radius); }
/* Smooth paging, but only for people who have not asked the OS for less motion. */
@media (prefers-reduced-motion: no-preference) { .carousel-track { scroll-behavior: smooth; } }
.carousel-nav { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
/* The buttons exist for mouse and keyboard. On a touch-only device the swipe IS the control, and
   the button row is 60px of the vertical scroll the client asked us to cut, on every photo strip
   on the site. Hidden only where there is no hover AND the pointer is coarse, so a laptop with a
   touchscreen (fine pointer available) keeps them. The track itself stays tabindex-focusable
   everywhere, so keyboard access never depends on this query. */
@media (hover: none) and (pointer: coarse) { .carousel-nav { display: none; } }
.carousel-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;                     /* 44px is the touch-target floor, not a look */
  border-radius: 999px; border: 1.5px solid var(--navy); background: var(--white);
  color: var(--navy); cursor: pointer; transition: background 0.15s ease, opacity 0.15s ease;
}
.carousel-btn:hover:not(:disabled) { background: var(--navy); color: var(--white); }
.carousel-btn:focus-visible { outline: 3px solid var(--orange-2); outline-offset: 2px; }
/* Disabled at an end. Kept in the DOM and kept announced; a control that vanishes mid-interaction
   is worse than one that reports it has nothing left to do. */
.carousel-btn:disabled { opacity: 0.32; cursor: default; }
.carousel-btn .ic { width: 20px; height: 20px; }
.section-tint .carousel-btn { background: var(--white); }

/* gallery + work items (the things inside a track) */
.gallery-item { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-sm); background: var(--tint); }
.gallery-item img { display: block; width: 100%; height: 280px; object-fit: cover; transition: transform 0.3s ease; }
.gallery-item:hover img { transform: scale(1.04); }
@media (max-width: 900px) { .carousel-track { --cv: 2.3; } }
@media (max-width: 560px) {
  .carousel-track { --cv: 1.15; --cgap: 12px; }
  .carousel-nav { justify-content: center; }
}

/* ---------------- reviews (real testimonials) ---------------- */
.review-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; margin-top: 10px; }
.review-card {
  background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 90, 104, 0.3);
  border-radius: var(--radius); padding: 26px 24px;
}
.review-card .stars { display: inline-flex; gap: 3px; margin-bottom: 12px; }
.review-card .stars .ic { color: #f5b301; fill: #f5b301; width: 20px; height: 20px; }
.review-card blockquote { color: rgba(255, 255, 255, 0.92); font-size: 1rem; line-height: 1.6; }
.review-card figcaption { margin-top: 14px; font-family: var(--font-head); font-weight: 700; color: var(--white); font-size: 0.92rem; }
.review-card figcaption .muted { color: rgba(255, 255, 255, 0.55); font-weight: 500; }
.review-cta { text-align: center; margin-top: 30px; }

/* ---------------- areas ---------------- */
.areas-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 40px; align-items: stretch; margin-top: 10px; }
.area-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; align-content: start; }
.area-card {
  display: flex; align-items: center; gap: 10px; padding: 15px 17px; background: var(--white);
  border: 1px solid var(--line); border-radius: 11px; text-decoration: none; font-weight: 700;
  color: var(--navy); box-shadow: var(--shadow-sm);
}
.area-card:hover { border-color: var(--orange); color: var(--orange-ink); }
.area-card .ic { color: var(--orange); }
.area-map iframe { width: 100%; height: 100%; min-height: 320px; border: 0; border-radius: var(--radius); }
.areas-grid .media-img { height: 100%; min-height: 320px; object-fit: cover; }
.contact-map iframe { width: 100%; min-height: 300px; border: 0; border-radius: var(--radius); }
/* Local-routes cards (town pages) - 3-up desktop, stacked on mobile. The canon's .dir-card grid,
   carrying corridor facts and a neighbour area map instead of a turn-by-turn directions link:
   this client's address is private, so there is no destination to route to. The cards previously
   had no chrome at all (they inherited the band background), which read as three loose stacks;
   they are now real cards so the 3-up row registers as a block. */
/* Grid default `stretch` on purpose: a card with two route facts instead of three would otherwise
   end short and leave the row ragged. Cards fill the row height and `margin-top:auto` on the
   button pins all three CTAs to the same baseline. */
.dir-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 22px; }
.dir-card {
  display: flex; flex-direction: column; background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px 20px 22px; box-shadow: var(--shadow-sm);
}
.dir-card h3 { font-size: 1.05rem; margin-bottom: 12px; }
.dir-card .area-map iframe { min-height: 190px; }
.dir-card .btn { margin-top: auto; }
/* Corridor facts: a definition list, so a term reads as a label and its value as data. */
.route-facts { margin: 14px 0 16px; display: grid; gap: 9px; }
.route-facts dt {
  font-family: var(--font-head); font-weight: 700; font-size: 0.7rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--orange-ink);
}
.route-facts dd { font-size: 0.95rem; line-height: 1.45; color: var(--ink); margin-top: -3px; }
/* Straight-line distances are honest but not the point of the block, so the caveat sits small. */
.route-note { margin-top: 26px; font-size: 0.85rem; color: var(--muted); }
@media (max-width: 860px) { .dir-grid { grid-template-columns: 1fr; gap: 20px; } }
/* Homepage teaser track: shorter frames than the full gallery and more of them across, so the
   strip reads as a teaser rather than a second gallery. --cv is re-declared per breakpoint on
   purpose - a single unconditional value here would out-order the .carousel-track media queries
   above (same specificity, later in the file) and the teaser would try to fit 4.4 frames on a
   375px phone. */
.gallery-preview { --cv: 4.4; }
@media (max-width: 900px) { .gallery-preview { --cv: 3.3; } }
@media (max-width: 560px) { .gallery-preview { --cv: 2.2; } }
.gallery-preview .gallery-item img { height: 210px; }

/* ---------------- process steps ---------------- */
.steps { list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; counter-reset: step; margin-top: 12px; }
.steps li { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 22px; box-shadow: var(--shadow-sm); }
.step-no { display: inline-block; font-family: var(--font-head); font-weight: 800; font-size: 1.9rem; color: var(--sky); margin-bottom: 8px; }
.steps h3 { margin-bottom: 7px; }
.steps p { font-size: 0.92rem; color: var(--muted); }

/* ---------------- financing: the Enhancify partner card (/financing/ only) ----------------
   REPLACED, 2026-07-31: this used to be `.enh-banner iframe`, the provider's supplied 930x180
   embed. It served a 292 KB PNG from another origin, so it could not be sized, themed or made
   responsive from this stylesheet at all, and it was the heaviest thing on the page by an order
   of magnitude against the ~6 KB logo that now stands in its place.

   TWO RULES THIS CARD IS BUILT AROUND, both of them about somebody else's property:

   1. The mark is not ours to restyle. `.enh-logo` sets a HEIGHT and leaves width auto, so the
      aspect ratio cannot be squashed by a future layout change; there is no filter, no
      opacity, no mix-blend-mode, no background plate and no border-radius on it. Nothing here
      recolours it and nothing crops it. The margins around it are real clear space, comfortably
      more than half the mark's own height on every side, so it never crowds the eyebrow above
      or the sentence below.
   2. The card ground is WHITE, not the band's tint and not navy, because that is the ground the
      full-colour supplied asset is drawn for. Their gray one-colour variant
      (assets/img/enhancify-logo.svg, also on disk) is the correct asset if this card ever moves
      to a dark ground. Swap the FILE, never the artwork: on white the gray variant's rendered ink
      averages 2.83:1 against a 3:1 graphic floor, which is why the colour PNG is what ships.

   The card is capped at --measure and centred so its left edge IS the page's one prose edge; it
   is furniture, but there is no reason for furniture to invent a second edge. */
.enh-card {
  max-width: var(--measure);
  margin: 28px auto 22px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px 36px 34px;
}
/* Label, not a heading: an <h3> here would sit under the band's <h2> and read as a content
   section in the outline, which this is not. */
.enh-card-eyebrow {
  font-family: var(--font-head); font-weight: 700; font-size: 0.72rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 20px;
}
/* height + width:auto = the supplied proportions, always. Never set both. */
.enh-logo { display: block; height: 32px; width: auto; margin-bottom: 28px; }
.enh-card-line { font-size: 1.02rem; color: var(--ink); margin-bottom: 28px; max-width: 54ch; }
.enh-card-cta { text-decoration: none; }
/* Third-party disclosure. Small, but never muted below the 4.5:1 body-text floor: --muted is
   #626b74 on the --tint band, which measures 5.3:1. */
.enh-note { font-size: 0.9rem; color: var(--muted); }
@media (max-width: 560px) {
  .enh-card { padding: 26px 22px 28px; }
  .enh-logo { height: 26px; margin-bottom: 22px; }
  .enh-card-cta { width: 100%; justify-content: center; }
}

/* ---------------- FAQ ---------------- */
.faq-item { border: 1px solid var(--line); border-radius: 12px; background: var(--white); margin-bottom: 12px; box-shadow: var(--shadow-sm); overflow: hidden; }
.faq-item summary {
  cursor: pointer; list-style: none; padding: 18px 22px; font-family: var(--font-head);
  font-weight: 700; color: var(--navy); position: relative; padding-right: 46px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); font-size: 1.4rem; color: var(--orange-ink); font-weight: 700; }
.faq-item[open] summary::after { content: "\2013"; }
.faq-item p { padding: 0 22px 20px; color: var(--muted); }

/* ---------------- CTA band ---------------- */
.cta-band { background: linear-gradient(135deg, var(--orange) 0%, var(--orange-2) 100%); padding: 54px 0; }
.cta-in { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 24px; }
.cta-band h2, .cta-band p { color: var(--navy); }
.cta-band p { opacity: 0.86; margin-top: 6px; }
.cta-actions { display: flex; flex-wrap: wrap; gap: 14px; }
.cta-band .btn-cta { background: var(--navy); color: var(--white); }
.cta-band .btn-cta:hover { background: var(--navy-2); color: var(--white); }

/* ---------------- footer ---------------- */
.site-footer { background: var(--navy); color: rgba(255, 255, 255, 0.8); padding: 66px 0 0; }
.footer-grid { display: grid; grid-template-columns: 1.7fr 1fr 1.2fr 1fr 0.9fr; gap: 36px; padding-bottom: 46px; }
.f-col .f-head { font-size: 0.95rem; color: var(--sky); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 14px; font-family: var(--font-head); font-weight: 800; line-height: 1.18; }
.f-col ul { list-style: none; display: grid; gap: 8px; }
.f-col a { color: rgba(255, 255, 255, 0.82); text-decoration: none; font-size: 0.93rem; }
.f-col a:hover { color: var(--sky); }
.f-brand p { font-size: 0.93rem; margin-top: 12px; }
.nap { line-height: 1.8; }
/* Sitewide GBP listing card (CID embed), in the footer brand column under the NAP. */
.f-brand h2 { margin-top: 20px; }
.footer-map { margin-top: 4px; max-width: 320px; }
.footer-map iframe { width: 100%; height: 190px; border: 0; border-radius: 12px; background: rgba(255, 255, 255, 0.06); }
.license { font-size: 0.8rem; color: rgba(255, 255, 255, 0.5); }
.footer-bottom {
  display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  border-top: 1px solid rgba(255, 255, 255, 0.12); padding: 20px 22px; font-size: 0.83rem;
  color: rgba(255, 255, 255, 0.55);
}
.footer-bottom a { color: rgba(255, 255, 255, 0.7); }
/* AI/GEO #8: the visible last-updated stamp. Same weight as the rest of the footer bottom row -
   it is a freshness signal for readers and assistants, not a badge to draw the eye. */
.f-updated { margin: 0; }
.f-updated time { color: rgba(255, 255, 255, 0.7); }

/* mobile sticky call bar */
.mobile-call {
  display: none; position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 60;
  align-items: center; justify-content: center; gap: 10px;
  background: var(--orange); color: var(--navy); font-family: var(--font-head); font-weight: 700;
  padding: 15px; border-radius: 999px; text-decoration: none; box-shadow: 0 8px 24px rgba(11, 36, 50, 0.35);
}

/* ---------------- responsive ---------------- */
@media (max-width: 1020px) {
  .cols-4 { grid-template-columns: repeat(2, 1fr); }
  .footer-grid { grid-template-columns: repeat(2, 1fr); }
  .steps { grid-template-columns: repeat(2, 1fr); }
  .header-phone { display: none; }
}
@media (max-width: 860px) {
  .hero-grid, .why-grid, .contact-grid, .areas-grid { grid-template-columns: 1fr; gap: 36px; }
  .cols-2, .cols-3, .review-grid { grid-template-columns: 1fr; }
  /* backdrop-filter makes the header a containing block for the fixed nav,
     which collapses the mobile overlay - drop it and use a solid header. */
  .site-header { backdrop-filter: none; -webkit-backdrop-filter: none; background: #fff; }
  .nav { position: fixed; inset: 76px 0 0 0; background: var(--white); padding: 18px 22px 110px; display: none; overflow-y: auto; z-index: 55; }
  body.nav-open .nav { display: block; }
  body.nav-open { overflow: hidden; }
  .nav ul { flex-direction: column; }
  .nav { display: none; }
  body.nav-open .nav { display: block; }
  .sub { position: static; display: flex !important; box-shadow: none; border: 0; padding-left: 16px; }
  .nav-toggle { display: block; margin-left: auto; }
  .header-cta { display: none; }
  .mobile-call { display: flex; }
  .site-footer { padding-bottom: 76px; }
  .section { padding: 56px 0; }
  .hero { padding: 56px 0 64px; }
}
@media (max-width: 560px) {
  /* .cols-4, .footer-grid and .steps used to drop to ONE column here. They are now two across -
     see the "2-ACROSS SERVICE CARDS ON A PHONE" block near the end of this file, which owns the
     mobile layout for all three together with the card density that makes it readable. Same
     reasoning as .area-cards below, applied to the three biggest stacked blocks on a phone. */
  /* .area-cards deliberately NOT dropped to one column: they are two-word pill links, they fit
     two across at 375, and 12 of them in a single column was 12 rows of pure vertical scroll. */
  .area-cards { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .area-card { padding: 12px 12px; font-size: 0.9rem; }
  /* The home teaser is a TEASER. As a 3-up grid it was 96px tall; a carousel row must not be
     what makes the homepage taller, so it stays close to that on a phone. */
  .gallery-preview .gallery-item img { height: 110px; }
}

/* ---- Answer-first block (AI/GEO layer, 2026 launch standard) ----
   Sits directly under the H1 inside .hero / .page-head (both dark surfaces).
   UNBOXED on Dylan's call (2026-07-29): the grey callout with the amber left bar read as a
   bolted-on notice. The element and its one-per-page build gate stay because AI assistants
   extract it; only the box treatment is gone, so it now reads as the page's lead paragraph.
   Do NOT reintroduce background / border-left / padding here. */
.answer-first {
  margin: 14px 0 18px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
}
.answer-first p { color: rgba(255, 255, 255, 0.92); font-size: 1.05rem; line-height: 1.65; }
.answer-first .page-meta {
  margin-top: 8px;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.6);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}


/* ================= SOCIAL PROOF LAYER (2026-07-29) =================
   Real figures only, read from brand.ratingValue/brand.reviewCount in data.js (never typed
   into markup or copy), and the three real profiles from
   data.js brand.social. Accent is the brand amber #FBB204 sampled off the logo. */

/* --- social profile links (header nav + footer) --- */
.social-row, .site-header .social-row { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
/* Geometry is pinned and padding is zeroed on purpose: this link class used to live inside
   <nav>, where `.nav a { display:block; padding:10px 13px }` (0,1,1) outranks `.social-link`
   (0,1,0) and turned each icon into a block box at the nav's full width, stacked and
   overflowing the header. The row is now a sibling of <nav>, and these selectors are scoped so
   nothing in the header can outrank them again. */
.social-link, .site-header .social-link, .site-footer .social-link {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; min-width: 34px; padding: 0; flex: 0 0 auto;
  box-sizing: border-box; border-radius: 999px; color: var(--navy);
  background: var(--tint); border: 1px solid var(--line);
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.social-link .ic, .site-header .social-link .ic { width: 17px; height: 17px; flex: 0 0 auto; }
.social-link:hover { background: var(--orange); color: var(--navy); transform: translateY(-1px); }
.site-footer .social-link {
  background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.16); color: var(--white);
}
.site-footer .social-link:hover { background: var(--orange); color: var(--navy); border-color: var(--orange); }
/* Deliberate decision: below 1361px the top bar has no room for the icon row (logo + 6 nav
   items + phone + CTA already fill it), so the header row is hidden and the FOOTER row is the
   only one. It is not duplicated into the mobile menu. */
@media (max-width: 1360px) { .social-header, .site-header .social-header { display: none; } }

/* --- rating proof: stars + "<rating> from <n> Google reviews" (figures from data.js) --- */
/* Base class = geometry ONLY. Colour comes from the ground class below, so an instance on a
   white band can never inherit the dark-ground colour, and neither can fall through to the
   base `a { color: var(--navy-2) }` and paint near-black on the dark hero. */
.rating-proof {
  display: inline-flex; align-items: center; gap: 10px; text-decoration: none;
  font-size: 0.95rem; font-weight: 600;
}
.rating-proof .stars { display: inline-flex; gap: 2px; }
.rating-proof .stars .ic { width: 18px; height: 18px; }
.rating-proof b { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; }
.rating-proof:hover span { text-decoration: underline; }
/* dark ground (hero, photo bands): white text, brand amber stars and figure */
a.rating-on-dark, .rating-on-dark { color: #ffffff; }
a.rating-on-dark b, .rating-on-dark b { color: var(--orange); }
.rating-on-dark .stars .ic { color: var(--orange); fill: var(--orange); }
/* light ground (white / tint bands): ink text, darkened amber so the star shape clears 3:1 */
a.rating-on-light, .rating-on-light { color: var(--navy); }
a.rating-on-light b, .rating-on-light b { color: var(--orange-ink); }
.rating-on-light .stars .ic { color: #b07a00; fill: #b07a00; }
/* Above the fold, directly over the H1. The chip is a near-opaque dark fill rather than an 8%
   white wash: the hero paints an opaque navy gradient PLUS a 26%-amber radial, so a translucent
   chip made the pill's contrast depend on where the radial happens to fall. A solid chip pins
   white text and amber stars against a known backdrop at every viewport. */
.rating-hero {
  margin: 2px 0 14px; padding: 7px 14px 7px 12px; border-radius: 999px;
  background: rgba(23, 24, 28, 0.78); border: 1px solid rgba(251, 178, 4, 0.55);
}
/* Sits bare on the hero gradient with no chip behind it, so the figure is white: amber on the
   lightest gradient stop measures 3.89:1, which clears 3:1 for the star graphics but not the
   4.5:1 that 15px bold text needs. Stars keep the amber. */
.rating-cta.rating-on-dark b { color: #ffffff; }
/* beside the primary CTA */
.cta-with-proof { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; }
.rating-cta { font-size: 0.88rem; padding-left: 2px; }
.rating-cta .stars .ic { width: 15px; height: 15px; }
.rating-cta b { font-size: 0.95rem; }

/* the same line inside the dark review-proof section */
.rating-line { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 22px; font-weight: 600; color: var(--white); }
.rating-line .stars { display: inline-flex; gap: 2px; }
.rating-line .stars .ic { width: 20px; height: 20px; color: var(--orange); fill: var(--orange); }
.rating-line b { font-family: var(--font-head); font-weight: 800; font-size: 1.15rem; color: var(--orange); }

/* --- trust strip: verified credentials, directly under every hero --- */
.trust-strip { background: var(--navy); border-top: 3px solid var(--orange); padding: 16px 0; }
.trust-in { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px 30px; }
.trust-item {
  display: inline-flex; align-items: center; gap: 8px; color: rgba(255, 255, 255, 0.92);
  font-family: var(--font-head); font-weight: 700; font-size: 0.82rem;
  letter-spacing: 0.05em; text-transform: uppercase;
}
.trust-item .ic { width: 17px; height: 17px; color: var(--orange); }
@media (max-width: 640px) {
  .trust-in { justify-content: flex-start; gap: 10px 18px; }
  .trust-item { font-size: 0.72rem; letter-spacing: 0.03em; }
  .rating-hero { font-size: 0.88rem; }
  .rating-hero b { font-size: 1rem; }
}


/* ================= FORMAT STANDARD: ONE PROSE LEFT EDGE =================
   page-format-standard.md section 2. Every band-level heading and paragraph is constrained to
   the SAME fixed measure and centred, so the left edge is identical in every band regardless of
   which wrapper it sits in: (viewport - measure) / 2. Cards, grids, forms, figures and the hero
   are furniture and are deliberately exempt. */
.section > .wrap > h2,
.section > .wrap > h3,
.section > .wrap > p,
.section > .wrap > .kicker,
.section > .wrap > ul,
.section > .wrap > ol,
.section > .wrap > details,
.prose > h2,
.prose > h3,
.prose > p,
.prose > ul,
.prose > ol,
.wrap-narrow > .kicker,
.lf-sec > .wrap > .kicker,
.lf-sec > .wrap > h2,
.faq .wrap > .kicker,
.faq .wrap > h2,
.faq .wrap > .faq-item {
  max-width: var(--measure);
  margin-inline: auto;
}
/* the page-head/hero answer block and sub share the same measure so the top of the page lines
   up with the body below it */
.page-head .answer-first, .page-head .hero-sub { max-width: var(--measure); }
.answer-first { max-width: var(--measure); }
.hero-sub { max-width: var(--measure); }
.section > .wrap > .kicker,
.prose > .kicker,
.wrap-narrow > .kicker {
  /* .kicker is inline-block by default, and auto margins cannot centre an inline-block, so it
     kept the container's left edge (582 on a 2560 screen) while every other band-level element
     sat on the shared measure edge (915). Blocking it puts the eyebrow on the same edge. */
  display: block;
  max-width: var(--measure);
  margin-inline: auto;
}

/* the page-head band (hubs, about, gallery, contact, 404) is a single-column band, so its own
   heading, answer block and sub-line share the body measure and therefore the body left edge */
.page-head > .wrap > h1,
.page-head > .wrap > .kicker,
.page-head > .wrap > .hero-sub,
.page-head > .wrap > .answer-first,
.page-head > .wrap > .hero-ctas {
  max-width: var(--measure);
  margin-inline: auto;
}
.page-head > .wrap > .kicker { display: block; }

/* prose paragraphs are never centred: only headings and short furniture rows are */
.prose p, .section > .wrap > p, .lf-text p { text-align: left; }

/* ================= FORMAT STANDARD: SAME-TINT BAND SEPARATORS =================
   Two adjacent same-tint bands read as one slab. A hairline plus extra top padding makes a new
   section announce itself. This shipped as a 174-page regression on Four Seasons. */
.section:not(.section-tint):not(.section-dark) + .section:not(.section-tint):not(.section-dark),
.section-tint + .section-tint,
.section-dark + .section-dark {
  border-top: 1px solid var(--line);
  padding-top: 100px;
}
.section-tint + .section-tint { border-top-color: rgba(23, 24, 28, 0.1); }
.section-dark + .section-dark { border-top-color: rgba(255, 255, 255, 0.14); }

/* ================= PORTED FROM CLEAR EXTERIORS =================
   Zigzag image+text rows, stat ribbon, real-review pull-quotes, photo bands and work strips.
   Same generator architecture, live at clearexteriorsllc.com. */

/* --- zigzag long-form rows --- */
.lf-sec .lf-head { max-width: var(--measure); }
.lf-row { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: center; margin: 44px 0; }
.lf-row-rev .lf-text { order: 2; }
.lf-text h3 { font-size: 1.4rem; line-height: 1.22; margin-bottom: 0.55em; letter-spacing: -0.2px; }
.lf-text p { color: var(--ink); font-size: 1.02rem; margin-bottom: 1em; }
.lf-text p:last-child { margin-bottom: 0; }
.lf-media { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: 1px solid var(--line); }
.lf-media img { display: block; width: 100%; height: 100%; aspect-ratio: 3 / 2; object-fit: cover; }

/* --- real-review pull-quote --- */
.lf-quote { max-width: 900px; margin: 52px auto; text-align: center; padding: 0 10px; }
.lf-quote .stars { display: inline-flex; gap: 4px; margin-bottom: 14px; }
.lf-quote .stars .ic { width: 22px; height: 22px; color: var(--orange); fill: var(--orange); }
.lf-quote blockquote { font-family: var(--font-head); font-weight: 700; font-size: clamp(1.2rem, 2.4vw, 1.6rem); line-height: 1.34; color: var(--navy); letter-spacing: -0.3px; }
.lf-quote blockquote::before { content: "\201C"; }
.lf-quote blockquote::after { content: "\201D"; }
.lf-quote figcaption { margin-top: 16px; font-family: var(--font-head); font-weight: 700; font-size: 0.92rem; color: var(--navy); }
.lf-quote figcaption .muted { font-weight: 500; color: var(--muted); }

/* --- stat ribbon (verified facts only) --- */
.stat-ribbon {
  display: grid; grid-template-columns: repeat(4, 1fr); margin: 6px auto 8px; max-width: 1100px;
  background: var(--white); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); overflow: hidden;
}
.stat-cell { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 3px; padding: 24px 16px; border-right: 1px solid var(--line); }
.stat-cell:last-child { border-right: 0; }
.stat-cell .ic { width: 26px; height: 26px; color: var(--orange); margin-bottom: 5px; }
.stat-cell b { font-family: var(--font-head); font-size: 1.4rem; line-height: 1.1; color: var(--navy); }
.stat-cell span { font-size: 0.8rem; color: var(--muted); }

/* --- full-bleed photo band --- */
/* The photo is a real <img class="bg-photo band-photo"> now, not a background-image (see the note
   over photoBand() in build.js: a background cannot carry srcset, and the role="img" that used to
   sit on this section hid its own H2 and CTA from assistive tech). The navy background-color is
   the fallback ground if the image ever fails to load. */
.photo-band { position: relative; padding: 92px 0; background-color: var(--navy); text-align: center; }
.photo-band-in { max-width: 900px; }
/* max-width must be the shared --measure, not none: with `none` this h2 centred at its own
   intrinsic 856px and landed at left:852 on a 2560 viewport, giving the page a SECOND prose
   left edge (915 everywhere else). One-edge-per-page is the format standard's assertion. */
.photo-band h2 { color: var(--white); font-size: clamp(1.7rem, 3.4vw, 2.4rem); max-width: var(--measure); margin-inline: auto; }
.photo-band p { color: rgba(255, 255, 255, 0.9); max-width: var(--measure); margin: 14px auto 28px; font-size: 1.05rem; text-align: center; }
.band-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px 48px; margin: 0 auto 30px; }
.band-stat { display: flex; flex-direction: column; align-items: center; }
.band-stat b { font-family: var(--font-head); font-size: 2rem; line-height: 1; color: var(--orange); }
.band-stat span { font-size: 0.76rem; letter-spacing: 0.06em; color: rgba(255, 255, 255, 0.75); margin-top: 7px; text-transform: uppercase; }

/* --- work strip --- */
.work-item { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-sm); background: var(--tint); }
.work-item img { display: block; width: 100%; height: 260px; object-fit: cover; transition: transform 0.3s ease; }
.work-item:hover img { transform: scale(1.04); }
.work-note { text-align: center; margin-top: 22px; font-weight: 600; }

@media (max-width: 900px) {
  .lf-row, .lf-row-rev { grid-template-columns: 1fr; gap: 22px; margin: 34px 0; }
  .lf-row-rev .lf-text { order: 0; }
  .lf-media img { aspect-ratio: 16 / 9; }
  .lf-quote { margin: 40px auto; }
  .stat-ribbon { grid-template-columns: repeat(2, 1fr); }
  .stat-cell:nth-child(2) { border-right: 0; }
  .stat-cell:nth-child(1), .stat-cell:nth-child(2) { border-bottom: 1px solid var(--line); }
  .work-item img { height: 200px; }
  .photo-band { padding: 64px 0; }
  .band-stat b { font-size: 1.55rem; }
  .band-stats { gap: 16px 30px; }
}
@media (max-width: 560px) {
  .section:not(.section-tint):not(.section-dark) + .section:not(.section-tint):not(.section-dark),
  .section-tint + .section-tint,
  .section-dark + .section-dark { padding-top: 72px; }
}

/* --- closing lead-form band (pages without a hero form) --- */
.closing-in { display: grid; grid-template-columns: 1fr 0.9fr; gap: 48px; align-items: center; }
.closing-copy h2 { max-width: none; }
.closing-copy p { color: var(--ink); }
.rating-closing { margin-top: 8px; }
@media (max-width: 900px) { .closing-in { grid-template-columns: 1fr; gap: 30px; } }


/* ================= BACKGROUND PHOTO LAYER (client request, 2026-07-30) =================
   The hero, the interior page-head and the mid-page photo bands all paint a real job photo behind
   their copy. Three stacked layers inside one position:relative band:
     0  .bg-photo   the <img>, object-fit:cover, absolutely filling the band
     1  .bg-scrim   the dark wash that makes white text legible
     2  > .wrap     the copy, lifted above both
   The <img> is deliberately NOT a CSS background: only a real element can carry srcset/sizes (a
   full-bleed background pulled the 1600px file onto a 375px phone) and fetchpriority (the hero
   photo is the LCP element on every page and must not be lazy).

   CONTRAST IS THE GATE, AND THE SCRIM IS WHAT PASSES IT.
   Hero text is white, and it sits over a photograph containing sky and pale trim, so the backdrop
   cannot be reasoned about from a background-color lookup - there is no background-color under this
   text at all. Every alpha below was derived from the arithmetic and then VERIFIED by sampling the
   photo's own painted pixels in the browser and compositing:
     backdrop = a * scrim + (1 - a) * photoPixel
   Worst case for light text is the BRIGHTEST pixel a text box covers. The measured brightest pixel
   behind hero text in this frame is rgb(245,242,241), but every alpha here is set against pure
   rgb(255,255,255) so a future photo swap cannot quietly break it.

   THE FLOOR IS a = 0.70 ANYWHERE A TEXT BOX REACHES. At 0.70 over a white pixel the backdrop is
   rgb(86,86,88), L = 0.093, which gives:
     #ffffff                    7.3:1   (h1, buttons, rating lines)
     85% white                  5.9:1   (.hero-sub)
     #ffd873 (--amber-lite)     5.4:1   (the eyebrow - see below)
     #fbb204 star/check graphic 4.0:1   (needs 3:1)
   Below 0.70 the amber graphics are the first thing to fail, at roughly a = 0.62.

   WHY THE SCRIM IS NOT HEAVIER. It was 0.94/0.91 first, which passed at 8.8:1 minimum and rendered
   the hero as a flat black rectangle - the photograph was measurably there and visually absent,
   which fails the actual request. Every 0.01 of alpha is contrast bought with the client's picture,
   so the alphas sit just above the floor rather than comfortably above it.

   WHY THE EYEBROW IS A LIGHTER AMBER. Brand amber #fbb204 as 12.5px TEXT needs a = 0.76 to clear
   4.5:1, and 0.76 is dark enough to bury the photo again. #ffd873 is the same hue lighter, clears
   4.5:1 at a = 0.67, and keeps the amber eyebrow the rest of the site uses. Star and check icons
   stay brand amber, because a graphic only owes 3:1.

   The right-hand fade to 0.44 is behind the OPAQUE white form card on every viewport that keeps the
   two-column hero (hero copy never passes 55% of the width at 2560, 1280 or 900), and it is
   replaced by a uniform scrim the moment the hero stacks. */
.bg-photo {
  position: absolute; inset: 0; z-index: 0;
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 42%;
  border-radius: 0; box-shadow: none;
}
.bg-scrim { position: absolute; inset: 0; z-index: 1; display: block; }
.hero > .wrap, .page-head > .wrap, .photo-band > .wrap { position: relative; z-index: 2; }

/* Hero: heaviest under the copy column, easing off toward the far right, which is behind the
   opaque form card on every viewport wide enough to keep two columns. */
.scrim-hero {
  background: linear-gradient(96deg,
    rgba(13, 14, 17, 0.78) 0%,
    rgba(13, 14, 17, 0.74) 42%,
    rgba(13, 14, 17, 0.7) 58%,
    rgba(13, 14, 17, 0.44) 100%);
}
/* Interior page-head copy is centred on the shared measure, so its scrim has to be uniform: a
   horizontal fade would put centred text over the lightest part of it. */
.scrim-head {
  background: linear-gradient(180deg, rgba(13, 14, 17, 0.74) 0%, rgba(13, 14, 17, 0.7) 100%);
}
/* Photo band copy is centred too, and it includes 0.76rem uppercase labels at 75% white, which are
   the tightest text on any photo band. It was 0.66-0.82 before this pass; at 0.66 those labels
   measured 4.06:1 over a white pixel, under the 4.5:1 floor. */
.scrim-band {
  background: linear-gradient(180deg, rgba(23, 24, 28, 0.72) 0%, rgba(23, 24, 28, 0.78) 100%);
}
/* Once the hero stacks, the copy spans the full width and the horizontal fade would put text over
   the lightest part of the scrim. Uniform below the stacking breakpoint. */
@media (max-width: 860px) {
  .scrim-hero {
    background: linear-gradient(180deg, rgba(13, 14, 17, 0.76) 0%, rgba(13, 14, 17, 0.72) 55%, rgba(13, 14, 17, 0.8) 100%);
  }
  .bg-photo { object-position: 50% 38%; }
}

/* PRE-EXISTING DEFECT, found by measuring rather than by looking. The eyebrow on a dark hero was
   inheriting the light-ground colour: .kicker is --orange-ink (#8a5e00, a darkened amber chosen for
   small text on WHITE) and only `.section-dark .kicker` ever overrode it. Neither .hero nor
   .page-head is .section-dark, so every hero and page-head eyebrow on all 27 pages was painting
   #8a5e00 on near-black - 2.4:1 against the old pure gradient, 2.3:1 against the scrim. Same class
   of defect as the invisible rating line, and it predates the photo. --amber-lite measures 5.4:1
   against the worst-case backdrop. */
.hero .kicker, .page-head .kicker { color: var(--amber-lite); }


/* ================= SERVICE CARD PHOTOS (client request, 2026-07-30) =================
   Every service photo existed in assets/img and none of them appeared on a service card.
   The card is now photo-first: image, then icon + name + blurb + link. One renderer in build.js
   (svcCard) feeds the homepage grid, the /services/ hub, the related-services block and the
   town-page service grid, so these rules cover every placement.
   Padding moves from the card to .svc-body, and overflow:hidden is what lets the photo sit flush
   inside the card's rounded corners. */
.svc-card { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.svc-media { display: block; background: var(--tint); }
.svc-media img { display: block; width: 100%; height: 178px; object-fit: cover; transition: transform 0.3s ease; }
.svc-card:hover .svc-media img { transform: scale(1.05); }
.svc-body { display: block; padding: 22px 24px 24px; }
/* A 40px icon under a photo is too much weight; scoped to the card so .benefit-card keeps its own. */
.svc-card .svc-body .ic { width: 30px; height: 30px; margin-bottom: 9px; }
@media (max-width: 1020px) { .svc-media img { height: 200px; } }

/* ================= 2-ACROSS SERVICE CARDS ON A PHONE (client request, 2026-07-31) =================
   "Reduce the scrolling down as much as possible." The measured lever, not a guess: the service
   grid was 3,990px of the homepage's 20,768px at 375, because .cols-4 collapsed to ONE card per row
   under 560px and each card carries a photo. It is the single biggest block on the page.

   NOT a carousel. Every tile in this grid is a primary hub link, and putting six of them off-screen
   behind a swipe hides the site's own service architecture from a crawler and from anyone who does
   not think to swipe. Two across halves the row count while every link stays rendered and in the
   document, which is the whole difference between shortening a page and hiding it.

   The card has to get denser to survive a ~160px slot, so the photo, the icon, the heading and the
   blurb all step down together. Nothing is hidden: the blurb still renders in full, just smaller.
   The `sizes` attribute in build.js (SIZES.card4) has the matching mobile rung - change one and the
   other is wrong. */
@media (max-width: 560px) {
  .cols-4 { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .cols-4 .svc-media img { height: 104px; }
  .cols-4 .svc-body { padding: 13px 13px 15px; }
  .cols-4 .svc-card .svc-body .ic { width: 21px; height: 21px; margin-bottom: 6px; }
  .cols-4 .svc-card h3 { font-size: 0.92rem; line-height: 1.22; margin-bottom: 5px; }
  .cols-4 .svc-card p { font-size: 0.78rem; line-height: 1.5; }
  .cols-4 .svc-card .link-more { font-size: 0.78rem; }
  /* Anything else that lands in a .cols-4 grid (there is nothing today, but the grid class is
     shared) keeps a sane inner padding at the narrower slot. */
  .cols-4 .benefit-card, .cols-4 .offer-card, .cols-4 .team-card { padding: 15px; }
}
@media (max-width: 560px) { .svc-media img { height: 190px; } }

/* Same reasoning, same "no content removed" rule, applied to the two other blocks that were pure
   stacked furniture on a phone. Measured at 375 on the homepage before the change: the 4-step
   process list was 1,079px in one column, and the 5-column footer was 1,821px. Both are lists of
   short items, not prose, so two across costs nothing in readability. */
@media (max-width: 560px) {
  .steps { grid-template-columns: repeat(2, 1fr); gap: 14px; }
  .steps li { padding: 16px 14px; }
  .steps h3 { font-size: 0.95rem; }
  .steps p { font-size: 0.82rem; line-height: 1.5; }
  .footer-grid { grid-template-columns: repeat(2, 1fr); gap: 26px 18px; }
  /* the brand column carries the NAP and the Google listing embed - it needs the full width */
  .f-brand { grid-column: 1 / -1; }
}

/* ================= BEFORE / AFTER WIPE SLIDER (client request, 2026-07-31) =================
   Two photographs of the same view stacked in one box, the top one clipped by a draggable divider.
   The pairs, and why two of the three candidates were rejected, are documented at BEFORE_AFTER in
   build.js.

   The control is a real <input type="range"> laid transparently over the picture, which is what
   buys mouse drag, touch drag, arrow keys and a screen-reader-announced slider from one element.
   It paints nothing: transparent track, transparent 2px thumb. The divider and handle you actually
   see are drawn separately at --ba-pos with pointer-events:none, so the handle can be finger-sized
   without the half-thumb-width offset that would push it out of step with the clip edge.

   No transition on the clip or the divider, deliberately: a drag has to track the pointer exactly,
   and a transition that smooths a keyboard step also lags a drag. The one thing that does animate
   is the handle's hover state, and that is what prefers-reduced-motion turns off below. */
.ba { max-width: var(--measure); margin: 0 auto; }
.ba-lede { margin-bottom: 26px; }
.ba-stage {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--radius);
  border: 1px solid var(--line); box-shadow: var(--shadow); background: var(--tint);
}
.ba-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.ba-after-img { z-index: 0; }
/* The reveal itself. These two 50% values are the NO-JS state and the starting position; the script
   overwrites clip-path and left directly as the range moves. inset() clips from the RIGHT, so 0%
   shows all "after" and 100% all "before". */
.ba-before-img { z-index: 1; clip-path: inset(0 50% 0 0); }
.ba-divider {
  position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; margin-left: -1.5px;
  background: var(--white); box-shadow: 0 0 0 1px rgba(23, 24, 28, 0.4); z-index: 2;
  pointer-events: none;
}
.ba-handle {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 999px; background: var(--white); color: var(--navy);
  box-shadow: 0 4px 16px rgba(23, 24, 28, 0.4); transition: transform 0.15s ease;
}
.ba-chev { width: 17px; height: 17px; margin: 0 -2px; }
.ba-stage:hover .ba-handle { transform: translate(-50%, -50%) scale(1.07); }
.ba-range {
  position: absolute; inset: 0; z-index: 3; width: 100%; height: 100%; margin: 0; padding: 0;
  -webkit-appearance: none; appearance: none; background: transparent; border: 0;
  cursor: ew-resize; touch-action: pan-y; /* vertical page scroll still passes through */
}
.ba-range::-webkit-slider-runnable-track { height: 100%; background: transparent; border: 0; }
.ba-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 2px; height: 100%; background: transparent; border: 0; }
.ba-range::-moz-range-track { height: 100%; background: transparent; border: 0; }
.ba-range::-moz-range-thumb { width: 2px; height: 100%; background: transparent; border: 0; border-radius: 0; }
.ba-range:focus { outline: none; }
/* Keyboard focus has to be obvious on a control that paints nothing, so it lights up twice: a ring
   inside the frame, and the handle itself. */
.ba-range:focus-visible { outline: 3px solid var(--orange); outline-offset: -3px; }
.ba-range:focus-visible ~ .ba-divider .ba-handle { box-shadow: 0 0 0 4px var(--orange), 0 4px 16px rgba(23, 24, 28, 0.4); }
.ba-tag {
  position: absolute; top: 12px; z-index: 2; pointer-events: none;
  font-family: var(--font-head); font-weight: 700; font-size: 0.7rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--white); background: rgba(23, 24, 28, 0.74);
  padding: 6px 12px; border-radius: 999px;
}
.ba-tag-before { left: 12px; }
.ba-tag-after { right: 12px; }
.ba-cap { margin-top: 16px; color: var(--muted); font-size: 0.95rem; text-align: left; }
@media (prefers-reduced-motion: reduce) {
  .ba-handle { transition: none; }
  .ba-stage:hover .ba-handle { transform: translate(-50%, -50%); }
}
@media (max-width: 560px) {
  .ba-handle { width: 42px; height: 42px; }
  .ba-tag { font-size: 0.64rem; padding: 5px 10px; top: 10px; }
  .ba-tag-before { left: 10px; }
  .ba-tag-after { right: 10px; }
  .ba-cap { font-size: 0.9rem; }
}

/* ================= LABELLED PROGRESS PAIR (batch 2, 2026-08-01) =================
   Two photographs side by side with a label under each. Used where two frames document the same
   job at two stages but were NOT shot from the same position, which is exactly the case the wipe
   slider above cannot honestly serve - drag one across the other and the subject appears to move.
   The pairs and the measurements that ruled out a wipe are documented at PROGRESS_PAIRS in
   build.js.

   Capped at --measure like every other band-level block, so the eyebrow, the H2, the lede, the
   grid and the note all share the page's single prose left edge.

   NO fixed aspect box and no object-fit here, on purpose. The two pairs are different shapes - the
   commercial crops are 3:4 portrait and the aerial frames are 16:9 - so any single ratio would
   either crop one pair or letterbox the other. Each image renders at its own natural aspect
   instead, and the two cells still line up exactly, because build.js refuses a pair whose crops
   differ in aspect by more than 2 percent. Equal column width plus equal aspect is equal height,
   with nothing reframed to get there. */
.pp-sec .pp-lede { margin-bottom: 24px; }
.pp-grid {
  max-width: var(--measure); margin: 0 auto;
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start;
}
.pp-cell { margin: 0; }
.pp-img {
  display: block; width: 100%; height: auto;
  border-radius: var(--radius); border: 1px solid var(--line); box-shadow: var(--shadow);
}
.pp-label {
  margin-top: 10px; text-align: center;
  font-family: var(--font-head); font-weight: 700; font-size: 0.72rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink);
}
.pp-note {
  max-width: var(--measure); margin: 18px auto 0;
  color: var(--muted); font-size: 0.92rem; text-align: left;
}
@media (max-width: 619px) {
  /* One per row on a phone. Stacked, the two frames are read one after the other rather than
     compared at a glance, so the labels do more of the work and get a size bump. */
  .pp-grid { grid-template-columns: 1fr; gap: 16px; }
  .pp-label { font-size: 0.76rem; }
  .pp-note { font-size: 0.88rem; }
}

/* Honeypot: a real field, visually and programmatically removed. Bots that parse the DOM and
   fill every input trip it; nobody using a browser or a screen reader ever meets it. Not
   `visibility:hidden` or an off-screen shift, both of which some form-fillers still populate. */
.hp{display:none !important;}

/* ---------- comparison table (guide pages, 2026-08-06) ----------
   Nothing in this generator rendered a <table> before the guide pages, so this is the whole
   table system: one class, all of it used. Scrolls inside .table-scroll on a phone so a wide
   table can never push the page body sideways. Row headers are <th scope="row">, which is what
   makes the first column announce correctly in a screen reader. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 22px 0 14px; border-radius: var(--radius); box-shadow: var(--shadow-sm); background: var(--white); }
table.cmp { width: 100%; border-collapse: collapse; font-family: var(--font-body); font-size: 15.5px; min-width: 520px; }
table.cmp thead th { background: var(--navy); color: var(--white); font-family: var(--font-head); font-weight: 700; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; text-align: left; padding: 14px 18px; }
table.cmp tbody th, table.cmp tbody td { padding: 14px 18px; border-top: 1px solid var(--line); vertical-align: top; text-align: left; }
table.cmp tbody th { font-weight: 600; color: var(--ink); font-family: var(--font-body); }
table.cmp tbody td { color: var(--muted); }
table.cmp tbody tr:nth-child(even) { background: var(--tint); }
.table-note { font-size: 14.5px; color: var(--muted); line-height: 1.65; max-width: var(--measure); }

/* CITY PAGES #10 town-framed offer card. Navy ground so it reads as the one merchandised thing on
   the page without a second colour entering the system. */
.offer-sec { padding-top: 28px; }
.offer-card {
  max-width: var(--measure); background: var(--navy); color: var(--white);
  border-radius: var(--radius); padding: 30px 32px; box-shadow: var(--shadow);
}
.offer-card .kicker { color: var(--sky); margin-bottom: 10px; }
.offer-card h2 { color: var(--white); margin: 0 0 12px; }
.offer-card p { color: rgba(255, 255, 255, 0.86); margin: 0 0 14px; }
.offer-card .offer-note { font-size: 14.5px; color: rgba(255, 255, 255, 0.62); }
@media (max-width: 560px) { .offer-card { padding: 24px 20px; } }

/* A2.1 short-answer block (ON-PAGE #6). Reuses the existing card idiom rather than inventing a
   second one: same radius, same shadow, same tint, same --measure left edge as every other prose
   band, so it reads as part of the system and not as the bolted-on notice the hero version was.
   Deliberately quiet - it is a summary a reader can skip, not a callout competing with the H1. */
.short-answer-sec { padding-top: 34px; padding-bottom: 6px; }
.short-answer {
  max-width: var(--measure); background: var(--tint); border: 1px solid var(--line);
  border-left: 4px solid var(--orange); border-radius: var(--radius); padding: 24px 26px;
}
.short-answer .kicker { margin-bottom: 8px; }
.sa-lead { margin: 0; font-size: 17px; line-height: 1.7; color: var(--ink); }
.sa-points { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 11px; }
.sa-points li { font-size: 15.5px; line-height: 1.65; color: var(--muted); padding-left: 17px; position: relative; }
.sa-points li::before { content: ""; position: absolute; left: 0; top: 9px; width: 7px; height: 7px; border-radius: 50%; background: var(--orange); }
.sa-points b { color: var(--ink); font-weight: 600; }
/* AI/GEO #6 E-E-A-T byline. No date by design - the visible last-updated stamp was removed on
   client request and the build gate still hard-fails on it. */
.byline { margin: 16px 0 0; padding-top: 13px; border-top: 1px solid var(--line); font-size: 14.5px; line-height: 1.6; color: var(--muted); }
.byline-name { color: var(--ink); font-weight: 600; }
/* ON-PAGE #19 / A2.5 entity block (service pages) */
.entity-sec { padding-top: 10px; }
.entity-list { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 13px; }
.entity-list li { font-size: 16px; line-height: 1.7; color: var(--muted); padding-left: 17px; position: relative; }
.entity-list li::before { content: ""; position: absolute; left: 0; top: 11px; width: 7px; height: 7px; border-radius: 50%; background: var(--orange); }
.entity-list b { color: var(--ink); font-weight: 600; }
/* VISUAL #15 photo captions - derived from the alt corpus, never a place name */
.fig-cap { margin-top: 8px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.gallery-item .fig-cap, .work-item .fig-cap { padding: 0 2px 2px; }
@media (max-width: 560px) { .short-answer { padding: 20px 18px; } .sa-lead { font-size: 16px; } }
@media (max-width: 560px) { table.cmp { font-size: 14.5px; } table.cmp thead th, table.cmp tbody th, table.cmp tbody td { padding: 12px 14px; } }

/* ---- MOBILE HERO ORDER (2026-08-08, VISUAL #1) ----
   Measured live at 375x812: .hero-sub runs ~290px tall, which pushed the primary call CTA to
   document y=654 - outside the first ~600px the checklist requires for the dominant CTA +
   tap-to-call. On a phone the CTA row now renders directly under the H1 (and answer block where
   one exists) and the sub paragraph follows it. CSS order only - the DOM order, the copy and
   the desktop layout are untouched, so screen readers and desktop read exactly as before. */
@media (max-width: 560px) {
  .hero-copy { display: flex; flex-direction: column; }
  .hero-copy .hero-sub { order: 1; margin-top: 22px; }
  .hero-copy .badge-row { order: 2; }
}

/* ============================================================================
   2026-08-29 · Jeff manual site-scan fixes (#cust-diakite-roofing 2026-08-28)
   Appended at the end of the sheet so each rule wins on ORDER without editing
   the rules above it. One block per Jeff line item, quoting his wording.
   ============================================================================ */

/* [J2] "increase h2 font size, as well as the 'our services' words and other
   similar (sitewide)". h2 was clamp(1.5rem,2.8vw,2.15rem) -> 34.4px at 1440.
   Every eyebrow (.kicker: "Our Services", "Simple & Transparent", ...) was
   0.78rem -> 12.48px, measured live. */
h2 { font-size: clamp(1.7rem, 3.1vw, 2.6rem); }
.kicker { font-size: 0.92rem; letter-spacing: 0.12em; }

/* [J1] "'The Short Answer' section needs to be implemented better sitewide,
   make it fit in the whole page". The card was pinned to the 730px reading
   measure inside a 1440px band, leaving ~690px dead to its right. It now takes
   the full band and lays its Q&A points two-up so the width is earned rather
   than just stretching one long line of prose. */
.short-answer { max-width: none; padding: 30px 34px; }
.sa-points { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 38px; }
@media (max-width: 900px) { .sa-points { grid-template-columns: 1fr; } }

/* [J3][J8] "Included on a Full Replacement section needs be centered in the
   homepage" + "... in service pages needs to be centered". Same .offer-card on
   both. It had max-width:var(--measure) with NO auto margin, so it sat hard
   left at x=22 in a 1440px band. */
.offer-card { margin-inline: auto; }

/* [J4] "How It Works section needs to be bigger along with its boxes".
   .steps is an <ol> directly under .wrap, so it was captured by the prose rule
   `.section > .wrap > ol { max-width: var(--measure) }` -- the same rule whose
   own comment says grids are "deliberately exempt". The exemption was by
   intent, not by selector. Freed, the four boxes get the whole band. */
.section > .wrap > ol.steps { max-width: none; }
.steps { gap: 26px; }
.steps li { padding: 34px 30px; }
.steps h3 { font-size: 1.24rem; }
.steps p { font-size: 1rem; }
.step-no { font-size: 2.2rem; }

/* [J6] "Read all 17 reviews on Google CTA needs to be centered (sitewide)".
   .review-cta{text-align:center} already existed at line 337 but lost the
   cascade to `.section > .wrap > p{text-align:left}` (0-1-0 vs 0-2-1), so the
   declaration was present in the file and inert in the browser. Matching that
   specificity is the whole fix. */
.section > .wrap > p.review-cta, .review-cta { text-align: center; }

/* [J10] "Towns We Serve section in service areas needs to be implemented
   better, like buttons not just text". It was a run of plain <p> paragraphs.
   Now a card grid that keeps every town's one-line local sentence (that copy is
   the page's unique local content -- turning it into bare name buttons would
   have deleted it) while giving each town the button affordance Jeff asked for.
   Sits on the tint band so the white cards read as raised. */
.towns-sec .town-cards {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px; margin: 24px 0 26px;
}
.town-card {
  display: flex; flex-direction: column; gap: 5px; padding: 18px 20px;
  background: var(--white); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow-sm); text-decoration: none;
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.town-card:hover, .town-card:focus-visible {
  transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--orange);
}
.town-card-name { font-family: var(--font-head); font-weight: 800; font-size: 1.06rem; color: var(--navy); }
.town-card-county {
  font-family: var(--font-head); font-weight: 700; font-size: 0.72rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--orange-ink);
}
.town-card-note { font-size: 0.9rem; line-height: 1.55; color: var(--muted); }
.towns-note { max-width: var(--measure); margin-inline: auto; color: var(--muted); }
@media (max-width: 1000px) { .towns-sec .town-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .towns-sec .town-cards { grid-template-columns: 1fr; } }

/* [J7] "Service area tab in nav needs to have a dropdown summary". Services
   already had .has-sub; Service Areas did not. 12 towns in a single flex
   column would be a 450px-tall menu, so this variant lays them two-up as a
   summary panel and closes with an "All service areas" link. */
.sub-areas { min-width: 460px; }
.has-sub:hover .sub-areas, .has-sub:focus-within .sub-areas {
  display: grid !important; grid-template-columns: 1fr 1fr; gap: 0 6px;
}
.sub-areas .sub-all { grid-column: 1 / -1; border-top: 1px solid var(--line); margin-top: 6px; padding-top: 4px; }
.sub-areas .sub-all a { font-weight: 700; color: var(--orange-ink); }
@media (max-width: 900px) {
  .sub-areas { min-width: 0; }
  .has-sub:hover .sub-areas, .has-sub:focus-within .sub-areas,
  .sub-areas { display: flex !important; grid-template-columns: none; }
}

/* [J12] "reformat contact page, form needs to be at the top of the page or in
   the hero". Root cause was not ordering: .contact-grid had NO base rule at
   all -- its only declaration lives inside `@media (max-width:860px)` (line
   506), so on desktop it stayed display:block and the info column + map (844px
   tall) stacked ABOVE the form, pushing the form to y=1695. Declaring the grid
   at >=861px puts the form beside the details, at the top of the band.
   Scoped to min-width so the 860px stack rule keeps working on mobile. */
@media (min-width: 861px) {
  .contact-grid {
    display: grid; grid-template-columns: 0.92fr 1.08fr;
    gap: 40px; align-items: start;
  }
  .contact-grid .estimate-card, .contact-grid form { margin-top: 0; }
}

/* [J9] "How It Runs section in financing needs to be smaller and adjust width,
   its too long". The width half is covered by the [J4] .steps fix above (it was
   capped at the 730px measure, now the full band). This tightens the remaining
   height: the band's 78px top/bottom padding plus equal-height stretch left a
   lot of dead space under the shorter cards. Applies to the homepage
   "How It Works" band too, which is the same component. */
.process-sec { padding-top: 58px; padding-bottom: 58px; }
.steps li { padding: 28px 26px; }
