/* =====================================================================
   Devine Builders — shared stylesheet
   Phil Devine · Connah's Quay, Flintshire, North Wales
   Mobile-first, vanilla CSS. No build tools, no external CDNs.
   Works fully offline with a robust system font stack.
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. Custom properties (design tokens)
   --------------------------------------------------------------------- */
:root {
  /* Brand / palette ------------------------------------------------- */
  --color-accent:        #2563eb;  /* blue-600 — primary brand blue / industrial accent */
  --color-accent-dark:   #1d4ed8;  /* blue-700 — hover / active accent (AA ~5.9:1 on white) */
  --color-accent-darker: #1e40af;  /* blue-800 — pressed / deep accent */
  --color-accent-soft:   #eff6ff;  /* blue-50 — faint blue wash for bands */

  --color-ink:        #1e293b;  /* slate-800 — primary text (AA on white) */
  --color-ink-soft:   #475569;  /* slate-600 — secondary text */
  --color-ink-faint:  #64748b;  /* slate-500 — muted / meta */

  --color-bg:         #ffffff;  /* page background */
  --color-surface:    #f8fafc;  /* slate-50 — cards / sections */
  --color-surface-2:  #f1f5f9;  /* slate-100 — alt band */
  --color-border:     #e2e8f0;  /* slate-200 — hairline borders */
  --color-border-2:   #cbd5e1;  /* slate-300 — stronger borders */

  --color-dark:       #0f172a;  /* slate-900 — footer / dark band bg */
  --color-on-dark:    #e2e8f0;  /* text on dark */
  --color-on-dark-mut:#94a3b8;  /* muted text on dark */

  --color-danger:     #b91c1c;  /* red-700 — error text (AA on white) */
  --color-danger-bg:  #fef2f2;  /* error field wash */
  --color-success:    #15803d;  /* green-700 — success messaging */

  --color-focus:      #1e3a8a;  /* blue-900 — focus ring (high contrast on white AND on blue buttons) */

  /* Typography ------------------------------------------------------ */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
               Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
               "Segoe UI Emoji";
  --fs-300: 0.875rem;   /* 14px — meta / small */
  --fs-400: 1rem;       /* 16px — body base */
  --fs-500: 1.125rem;   /* 18px — lead / large body */
  --fs-600: 1.375rem;   /* 22px — h3 */
  --fs-700: 1.75rem;    /* 28px — h2 */
  --lh-tight: 1.12;
  --lh-snug:  1.28;
  --lh-base:  1.65;

  /* Fluid type — smooth scaling between the existing static steps so big
     headings grow with the viewport without sudden jumps. Min/max are kept
     inside the established scale so nothing gets larger than before. */
  --fs-fluid-600: clamp(1.25rem, 1.13rem + 0.6vw, 1.375rem);  /* h3 */
  --fs-fluid-700: clamp(1.5rem, 1.28rem + 1.1vw, 1.75rem);    /* h2 / section titles */
  --fs-fluid-800: clamp(2rem, 1.55rem + 2.25vw, 2.5rem);      /* hero h1 (mobile→mid) */
  --fs-fluid-900: clamp(2.25rem, 1.55rem + 3.4vw, 3.25rem);   /* hero h1 (desktop) */

  /* Spacing scale --------------------------------------------------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  --space-9: 6rem;

  /* Radius / shadow / layout --------------------------------------- */
  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Elevation system — soft, neutral, consistent. A subtle hairline ring is
     baked into the larger steps for crisp card edges on light surfaces. */
  --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05);
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05),
               0 1px 3px rgba(15, 23, 42, 0.07);
  --shadow:    0 2px 4px -1px rgba(15, 23, 42, 0.07),
               0 6px 12px -4px rgba(15, 23, 42, 0.09);
  --shadow-md: 0 4px 8px -2px rgba(15, 23, 42, 0.08),
               0 10px 20px -6px rgba(15, 23, 42, 0.12);
  --shadow-lg: 0 8px 16px -6px rgba(15, 23, 42, 0.12),
               0 18px 32px -12px rgba(15, 23, 42, 0.18);
  --shadow-xl: 0 12px 24px -8px rgba(15, 23, 42, 0.16),
               0 28px 48px -16px rgba(15, 23, 42, 0.22);

  /* Accent-tinted shadow for the primary CTA — gives the button a confident,
     grounded lift without looking garish. Blue-toned, not heavy. */
  --shadow-accent:    0 2px 4px rgba(29, 78, 216, 0.18),
                      0 8px 16px -4px rgba(29, 78, 216, 0.30);
  --shadow-accent-lg: 0 4px 8px rgba(29, 78, 216, 0.22),
                      0 14px 26px -6px rgba(29, 78, 216, 0.38);

  --container: 72rem;       /* 1152px max content width */
  --container-narrow: 48rem;/* 768px for prose / forms */
  --header-h: 4.5rem;       /* sticky header height (used for scroll offset) */

  /* Motion — a single base duration plus a refined, confident easing curve
     (gentle overshoot-free ease-out) used across hover lifts and reveals. */
  --ease: cubic-bezier(0.2, 0.6, 0.25, 1);
  --transition: 180ms var(--ease);
  --transition-fast: 120ms var(--ease);

  /* ---- Craft-elevation layer (ADDITIVE — changes no value above) ----
     "Trowel & Slate" premium pass: surfaces lit from above, hairline edges,
     a running-bond brick motif, and a consistent eyebrow/numeral voice. */
  --shadow-craft: inset 0 1px 0 rgba(255, 255, 255, 0.7),
                  0 1px 2px rgba(15, 23, 42, 0.06),
                  0 10px 24px -10px rgba(15, 23, 42, 0.16);
  --shadow-craft-hover: inset 0 1px 0 rgba(255, 255, 255, 0.8),
                  0 4px 10px -2px rgba(15, 23, 42, 0.10),
                  0 22px 40px -16px rgba(15, 23, 42, 0.24);
  --hairline:        1px solid rgba(15, 23, 42, 0.08);
  --hairline-strong: 1px solid rgba(15, 23, 42, 0.12);
  --surface-grad:    linear-gradient(180deg, #ffffff 0%, var(--color-surface) 100%);
  --accent-tile-grad: linear-gradient(160deg, #eff6ff, #dbeafe);
  --accent-ring:     0 0 0 4px rgba(37, 99, 235, 0.12);
  --tracking-eyebrow: 0.14em;
  --tracking-tight:  -0.025em;
  --fs-fluid-500: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
}

/* ---------------------------------------------------------------------
   2. Reset / normalize basics
   --------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--space-4));
}

body {
  font-family: var(--font-sans);
  font-size: var(--fs-400);
  line-height: var(--lh-base);
  color: var(--color-ink);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100vh;
  overflow-x: hidden; /* belt-and-braces against accidental overflow */
}

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

input,
button,
textarea,
select { font: inherit; color: inherit; }

button { cursor: pointer; }

a {
  color: var(--color-accent-dark);
  text-decoration-color: rgba(37, 99, 235, 0.4);
  text-underline-offset: 2px;
  transition: color var(--transition), text-decoration-color var(--transition);
}
a:hover { text-decoration-color: currentColor; color: var(--color-accent-darker); }

/* Animated underline for in-prose / in-copy links. Scoped to text content so
   it never affects buttons, nav, footer, chips, or icon links. The underline
   is drawn as a background gradient that grows from 0 → full width on hover/
   focus. Falls back gracefully: the native underline still shows. */
.section__lead a,
.section__head a:not(.btn),
.prose a,
.about__body a,
.faq__a a,
.service-detail__block p a,
.ba-grid__note a {
  position: relative;
  text-decoration: none;
  font-weight: 600;
  background-image: linear-gradient(currentColor, currentColor);
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 100% 1.5px;       /* visible underline by default (no-JS / reduced-motion safe) */
  padding-bottom: 1px;
}
@media (prefers-reduced-motion: no-preference) {
  .section__lead a,
  .section__head a:not(.btn),
  .prose a,
  .about__body a,
  .faq__a a,
  .service-detail__block p a,
  .ba-grid__note a {
    background-size: 0% 1.5px;        /* collapse, then animate open on hover */
    transition: background-size var(--transition), color var(--transition);
  }
  .section__lead a:hover,
  .section__lead a:focus-visible,
  .section__head a:not(.btn):hover,
  .section__head a:not(.btn):focus-visible,
  .prose a:hover,
  .prose a:focus-visible,
  .about__body a:hover,
  .about__body a:focus-visible,
  .faq__a a:hover,
  .faq__a a:focus-visible,
  .service-detail__block p a:hover,
  .service-detail__block p a:focus-visible,
  .ba-grid__note a:hover,
  .ba-grid__note a:focus-visible {
    background-size: 100% 1.5px;
  }
}

h1, h2, h3, h4 {
  line-height: var(--lh-snug);
  color: var(--color-ink);
  font-weight: 700;
  text-wrap: balance;
  letter-spacing: -0.011em;
}

p { text-wrap: pretty; }

/* Comfortable reading measure for body prose */
.about__body p,
.prose p,
.service-detail__block p { max-width: 68ch; }

ul[class],
ol[class] { list-style: none; padding: 0; }

:target { scroll-margin-top: calc(var(--header-h) + var(--space-4)); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------
   3. Layout helpers
   --------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: max(var(--space-4), env(safe-area-inset-left, 0px)) max(var(--space-4), env(safe-area-inset-right, 0px));
}
@media (min-width: 40em) {
  .container { padding-inline: max(var(--space-5), env(safe-area-inset-left, 0px)) max(var(--space-5), env(safe-area-inset-right, 0px)); }
}

.section { padding-block: var(--space-7); }
@media (min-width: 48em) {
  .section { padding-block: var(--space-8); }
}
.section--tight { padding-block: var(--space-6); }
.section--surface { background: var(--color-surface); }

.section__head {
  max-width: var(--container-narrow);
  margin-bottom: var(--space-7);
}
.section__head--center {
  margin-inline: auto;
  text-align: center;
}
.section__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-300);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent-dark);
  margin-bottom: var(--space-3);
}
/* Tasteful accent rule preceding the eyebrow label */
.section__eyebrow::before {
  content: "";
  width: 1.75rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-darker));
}
/* On centred heads the rule reads better balanced on both sides */
.section__head--center .section__eyebrow::after {
  content: "";
  width: 1.75rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--color-accent-darker), var(--color-accent));
}
.section__title {
  font-size: var(--fs-fluid-700);
  letter-spacing: -0.02em;
  line-height: var(--lh-tight);
}
.section__lead {
  margin-top: var(--space-4);
  font-size: var(--fs-500);
  color: var(--color-ink-soft);
  line-height: var(--lh-base);
}
.section__head--center .section__lead { margin-inline: auto; max-width: 60ch; }
/* Extra breathing room above a closing CTA lead (gallery) */
.section__lead--push { margin-top: var(--space-7); }

/* Visually hidden but available to assistive tech */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------
   4. Accessibility — skip link & focus
   --------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  top: 0;
  left: var(--space-3);
  transform: translateY(-120%);
  z-index: 1000;
  padding: var(--space-2) var(--space-4);
  background: var(--color-accent);
  color: #fff;
  font-weight: 700;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  box-shadow: var(--shadow);
  transition: transform var(--transition);
}
.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--color-ink);
  outline-offset: 2px;
}

/* Global, consistent focus indicator on every interactive element */
:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Keep mouse users clean while supporting keyboard focus */
:focus:not(:focus-visible) { outline: none; }

/* On dark bands (#0f172a) the default #1e3a8a ring is only ~1.7:1 — below the
   WCAG 1.4.11 3:1 floor for focus indicators. Give controls that render over a
   dark background a lighter blue ring instead (#93c5fd on #0f172a = 8.1:1, and
   still 3.4:1 across the offset gap over the blue button fill). Light pages keep
   the default --color-focus, where it already passes. */
.call-bar :focus-visible,
.home .hero__actions .btn--secondary:focus-visible,
.home .hero__phone a:focus-visible,
.breadcrumb a:focus-visible,
.btn--ghost-light:focus-visible,
.site-footer a:focus-visible,
.contact-card a:focus-visible,
.lightbox__control:focus-visible,
.hero .btn:focus-visible,
.hero--inner a:not(.btn):focus-visible,
.service-area .btn:focus-visible {
  outline-color: #93c5fd;
}

/* ---------------------------------------------------------------------
   5. Buttons
   --------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.75rem 1.5rem;
  min-height: 2.875rem;        /* ~46px touch target */
  font-size: var(--fs-400);
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 1.1;
  text-align: center;
  text-decoration: none;
  border: 2px solid transparent;
  border-radius: var(--radius);
  transition: background var(--transition), color var(--transition),
              border-color var(--transition), transform var(--transition-fast),
              box-shadow var(--transition);
}
.btn:active { transform: translateY(1px); }
/* Motion-gated press feedback (adds a subtle scale on top of the press) */
@media (prefers-reduced-motion: no-preference) {
  .btn:active { transform: translateY(1px) scale(0.99); }
}

.btn--primary {
  /* SOLID colour first as a guaranteed fallback: Chrome can drop a
     linear-gradient background-IMAGE when the button sits inside a composited
     .reveal section, which left the fill transparent (white label on a white
     section — an unreadable CTA). The background-COLOUR always paints, so the
     button is never invisible; the gradient just layers on top where it renders.
     accent-dark (#1d4ed8) gives ~5.9:1 against #fff (WCAG AA). */
  background-color: var(--color-accent-dark);
  background-image: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  color: #ffffff;
  border-color: var(--color-accent-dark);
  box-shadow: var(--shadow-accent);
}
.btn--primary:hover {
  background-color: var(--color-accent-darker);
  background-image: linear-gradient(180deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%);
  border-color: var(--color-accent-darker);
  color: #fff;
  box-shadow: var(--shadow-accent-lg);
}
@media (prefers-reduced-motion: no-preference) {
  .btn--primary:hover { transform: translateY(-2px); }
}
.btn--primary:active {
  background: var(--color-accent-darker);
  box-shadow: var(--shadow-accent);
  transform: translateY(1px);
}
@media (prefers-reduced-motion: no-preference) {
  .btn--primary:active { transform: translateY(1px) scale(0.99); }
}

.btn--secondary {
  background: #ffffff;
  color: var(--color-ink);
  border-color: var(--color-border-2);
  box-shadow: var(--shadow-xs);
}
.btn--secondary:hover {
  border-color: var(--color-accent-dark);
  background: var(--color-accent-soft);
  color: var(--color-accent-darker);
  box-shadow: var(--shadow-sm);
}
@media (prefers-reduced-motion: no-preference) {
  .btn--secondary:hover { transform: translateY(-2px); }
}

/* Inverted button for dark bands */
.btn--ghost-light {
  background: rgba(255, 255, 255, 0.06);
  color: #ffffff;
  border-color: rgba(255, 255, 255, 0.6);
}
.btn--ghost-light:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: #fff;
  color: #fff;
}
@media (prefers-reduced-motion: no-preference) {
  .btn--ghost-light:hover { transform: translateY(-2px); }
}

.btn--block { width: 100%; }
.btn--lg { padding: 0.95rem 2rem; font-size: var(--fs-500); min-height: 3.25rem; }

/* ---------------------------------------------------------------------
   6. Site header + navigation
   No-JS responsive nav: links wrap below the brand on small screens.
   --------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #ffffff;   /* solid (no backdrop-filter) — cheap to repaint while scrolling */
  border-bottom: 1px solid var(--color-border);
  box-shadow: 0 1px 0 rgba(15, 23, 42, 0.02), 0 4px 16px -10px rgba(15, 23, 42, 0.18);
}
.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
  min-height: var(--header-h);
  padding-block: var(--space-2);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--color-ink);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.05;
}
.brand:hover { color: var(--color-ink); }
/* Real logo monogram (transparent PNG) — replaces the text "DB" mark. */
.brand__logo {
  flex: none;
  height: 2.5rem;
  width: auto;
  display: block;
}
.brand__name { font-size: var(--fs-500); }
.brand__tagline {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-ink-faint);
}

.primary-nav { margin-left: auto; }
.primary-nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.primary-nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;            /* 44px guaranteed tap target on mobile */
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-ink-soft);
  font-weight: 600;
  text-decoration: none;
  transition: color var(--transition), background var(--transition);
}
/* Animated underline cue on desktop nav links */
.primary-nav__link::after {
  content: "";
  position: absolute;
  left: var(--space-3);
  right: var(--space-3);
  bottom: 0.45rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--transition);
}
@media (prefers-reduced-motion: reduce) {
  .primary-nav__link::after { transition: none; }
}
.primary-nav__link:hover {
  color: var(--color-ink);
  background: var(--color-surface-2);
}
.primary-nav__link:hover::after { transform: scaleX(1); }
.primary-nav__link[aria-current="page"] {
  color: var(--color-accent-dark);
  background: var(--color-accent-soft);
}
.primary-nav__link[aria-current="page"]::after { transform: scaleX(1); }
/* In the mobile dropdown panel the underline cue would clutter — drop it */
@media (max-width: 47.99em) {
  .primary-nav__link::after { display: none; }
}
/* Nav CTA gets accent treatment */
.primary-nav__cta {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;            /* 44px guaranteed tap target on mobile */
  margin-left: var(--space-1);
  padding: var(--space-2) var(--space-4);
  /* accent-dark (#1d4ed8) gives ~5.9:1 against #fff — passes WCAG AA for normal text */
  background: var(--color-accent-dark);
  color: #fff;
  border-radius: var(--radius-sm);
  font-weight: 700;
  text-decoration: none;
  box-shadow: var(--shadow-accent);
  transition: background var(--transition), box-shadow var(--transition),
              transform var(--transition-fast);
}
.primary-nav__cta:hover { background: var(--color-accent-darker); color: #fff; box-shadow: var(--shadow-accent-lg); }
@media (prefers-reduced-motion: no-preference) {
  .primary-nav__cta:hover { transform: translateY(-1px); }
}
.primary-nav__cta[aria-current="page"] { background: var(--color-accent-darker); color: #fff; }

@media (max-width: 36em) {
  .primary-nav { width: 100%; margin-left: 0; }
  .primary-nav__list { justify-content: center; }
}

/* ---------------------------------------------------------------------
   7. Hero (home)
   --------------------------------------------------------------------- */
.hero {
  position: relative;
  background:
    radial-gradient(120% 120% at 100% 0%, rgba(37, 99, 235, 0.10), transparent 55%),
    linear-gradient(180deg, var(--color-surface) 0%, var(--color-bg) 100%);
  border-bottom: 1px solid var(--color-border);
  overflow: hidden;
}
.hero__inner {
  display: grid;
  gap: var(--space-6);
  padding-block: var(--space-8);
}
@media (min-width: 56em) {
  .hero__inner {
    grid-template-columns: 1.1fr 0.9fr;
    align-items: center;
    padding-block: var(--space-9);
  }
}
.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-300);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-accent-dark);
  background: var(--color-accent-soft);
  border: 1px solid #bfdbfe;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
}
.hero__title {
  margin-top: var(--space-4);
  font-size: var(--fs-fluid-800);
  letter-spacing: -0.025em;
  line-height: var(--lh-tight);
}
@media (min-width: 56em) {
  .hero__title { font-size: var(--fs-fluid-900); }
}
.hero__lead {
  margin-top: var(--space-4);
  font-size: var(--fs-500);
  color: var(--color-ink-soft);
  max-width: 40ch;
}
.hero__actions {
  margin-top: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
/* Centre the action buttons inside a centred section head (e.g. the services
   hero) or immediately after one (e.g. the thank-you / closing CTAs). The home
   hero keeps left-aligned actions — they sit in the left-aligned hero content,
   not in/after a .section__head--center. (justify-content centres the flex row;
   text-align:center on the parent doesn't reach flex items.) */
.section__head--center .hero__actions,
.section__head--center + .hero__actions {
  justify-content: center;
}
/* Phone: stack the hero CTAs as clean full-width buttons instead of wrapped pills. */
@media (max-width: 36em) {
  .hero__actions { gap: var(--space-2); }
  .hero__actions .btn { width: 100%; }
}
.hero__trust {
  margin-top: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
  font-weight: 600;
}
.hero__trust li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.hero__trust li::before {
  content: "";
  flex: none;
  width: 0.5rem; height: 0.5rem;
  border-radius: 50%;
  background: var(--color-accent);
}
/* (The hero__media photo-plate rules were removed with the plate itself —
   restore markup + CSS together from git history if it ever returns.) */

/* ---------------------------------------------------------------------
   8. Services grid (home — 3 cards)
   --------------------------------------------------------------------- */
.services-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;   /* full rows fill edge-to-edge; only a partial last row centres (no lopsided gap) */
  gap: var(--space-4);
}
.services-grid > * { flex: 0 1 100%; }                                      /* mobile: 1 per row */
@media (min-width: 40em) {
  .services-grid > * { flex-basis: calc((100% - var(--space-4)) / 2); }     /* 2 per row */
}
@media (min-width: 60em) {
  .services-grid > * { flex-basis: calc((100% - 2 * var(--space-4)) / 3); } /* 3 per row */
}
.service-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5) var(--space-5);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}
/* Accent cue: a top edge that fills with brand colour on hover/focus-within */
.service-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-darker));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--transition);
}
@media (prefers-reduced-motion: reduce) {
  .service-card::before { transition: none; }
}
.service-card:hover,
.service-card:focus-within {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: #bfdbfe;
}
@media (prefers-reduced-motion: reduce) {
  .service-card:hover,
  .service-card:focus-within { transform: none; }
}
.service-card:hover::before,
.service-card:focus-within::before { transform: scaleX(1); }
.service-card__icon {
  display: grid;
  place-items: center;
  width: 3rem; height: 3rem;
  border-radius: var(--radius);
  background: var(--color-accent-soft);
  color: var(--color-accent-dark);
  border: 1px solid #bfdbfe;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
  transition: background var(--transition), color var(--transition),
              transform var(--transition);
}
.service-card:hover .service-card__icon {
  background: var(--color-accent-dark);
  color: #fff;
  border-color: var(--color-accent-dark);
}
@media (prefers-reduced-motion: no-preference) {
  .service-card:hover .service-card__icon { transform: scale(1.04); }
}
.service-card__icon svg { width: 1.6rem; height: 1.6rem; }
.service-card__title { font-size: var(--fs-fluid-600); letter-spacing: -0.015em; }
.service-card__text { color: var(--color-ink-soft); }
.service-card__list {
  margin-top: auto;
  padding-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
}
.service-card__list li {
  position: relative;
  padding-left: 1.35rem;
}
.service-card__list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.5rem;
  width: 0.65rem; height: 0.65rem;
  background: var(--color-accent);
  -webkit-mask: var(--check-mask) center / contain no-repeat;
  mask: var(--check-mask) center / contain no-repeat;
}
:root {
  --check-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M6.2 11.4 2.8 8l1.1-1.1 2.3 2.3 5-5L12.3 5.3z"/></svg>');
}

/* ---------------------------------------------------------------------
   9. About (home)
   --------------------------------------------------------------------- */
.about {
  display: grid;
  gap: var(--space-6);
}
@media (min-width: 56em) {
  .about { grid-template-columns: 1fr 1fr; align-items: center; }
}
.about__body p + p { margin-top: var(--space-4); }
.about__body { color: var(--color-ink-soft); font-size: var(--fs-500); }
.about__stats {
  display: grid;
  grid-template-columns: 1fr;   /* phone: stack the three stats cleanly */
  gap: var(--space-4);
}
@media (min-width: 40em) {
  /* a clean, even 3-up row on tablet and up (no more 2+1 orphan) */
  .about__stats { grid-template-columns: repeat(3, 1fr); }
}
.stat {
  padding: var(--space-5);
  background: linear-gradient(180deg, #ffffff, var(--color-surface));
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  text-align: center;
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}
.stat:hover {
  box-shadow: var(--shadow);
  border-color: #bfdbfe;
}
@media (prefers-reduced-motion: no-preference) {
  .stat:hover { transform: translateY(-3px); }
}
.stat__num {
  font-size: var(--fs-700);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--color-accent-dark);
  line-height: 1;
}
.stat__label {
  margin-top: var(--space-2);
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
  font-weight: 600;
}

/* ---------------------------------------------------------------------
   10. Service-area band
   --------------------------------------------------------------------- */
.service-area {
  background: var(--color-dark);
  color: var(--color-on-dark);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 48em) {
  .service-area {
    grid-template-columns: 1fr auto;
    align-items: center;
    padding: var(--space-7);
  }
}
.service-area__title { color: #fff; font-size: var(--fs-fluid-700); letter-spacing: -0.02em; line-height: var(--lh-tight); }
.service-area__text { color: var(--color-on-dark); margin-top: var(--space-3); }
.service-area__places {
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.service-area__chip {
  font-size: var(--fs-300);
  font-weight: 600;
  color: var(--color-on-dark);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
}

/* ---------------------------------------------------------------------
   11. Gallery grid
   1 col mobile → 2 col small tablet → 3 col desktop; a partial last row centres
   --------------------------------------------------------------------- */
.gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;   /* full rows fill; a partial last row centres (no lopsided gap) */
  gap: var(--space-4);
}
.gallery__item { flex: 0 1 100%; }                                          /* mobile: 1 per row */
@media (min-width: 36em) {
  .gallery__item { flex-basis: calc((100% - var(--space-4)) / 2); }         /* 2 per row */
}
@media (min-width: 60em) {
  .gallery__item { flex-basis: calc((100% - 2 * var(--space-4)) / 3); }     /* 3 per row */
}

.gallery__item { margin: 0; }
/* The clickable thumbnail is a <button> for full keyboard support */
.gallery__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface-2);
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}
.gallery__btn:hover,
.gallery__btn:focus-visible {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: #bfdbfe;
}
@media (prefers-reduced-motion: reduce) {
  .gallery__btn:hover,
  .gallery__btn:focus-visible { transform: none; }
}
.gallery__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--color-surface-2);
  transition: transform 360ms var(--ease);
}
@media (prefers-reduced-motion: no-preference) {
  .gallery__btn:hover .gallery__img,
  .gallery__btn:focus-visible .gallery__img { transform: scale(1.05); }
}
.gallery__caption {
  display: block; /* the caption is a <span> (buttons only allow phrasing content) */
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
  text-align: left;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
}
/* Subtle hint that a thumb is interactive */
.gallery__btn::after {
  content: "View";
  position: absolute;
}
.gallery__item { position: relative; }
.gallery__btn::after {
  top: var(--space-2);
  right: var(--space-2);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(15, 23, 42, 0.72);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--transition), transform var(--transition);
  pointer-events: none;
}
.gallery__btn:hover::after,
.gallery__btn:focus-visible::after { opacity: 1; transform: translateY(0); }

/* ---------------------------------------------------------------------
   12. Lightbox / modal
   Hidden by default. gallery.js toggles [hidden] + .is-open.
   --------------------------------------------------------------------- */
.lightbox[hidden] { display: none; }
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: rgba(15, 23, 42, 0.92);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  padding: clamp(0.75rem, 3vw, 2rem);
  opacity: 0;
  transition: opacity var(--transition);
}
.lightbox.is-open { opacity: 1; }

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  color: var(--color-on-dark);
}
.lightbox__counter {
  font-size: var(--fs-300);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--color-on-dark);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
}

.lightbox__stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0; /* allow image to shrink within grid row */
}
.lightbox__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  max-width: 100%;
  max-height: 100%;
}
.lightbox__img {
  max-width: min(100%, 64rem);
  max-height: 70vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  background: #0b1220;
}
.lightbox__caption {
  color: var(--color-on-dark);
  font-size: var(--fs-400);
  text-align: center;
  max-width: 60ch;
}

/* Control buttons (close / prev / next) */
.lightbox__control {
  display: inline-grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  transition: background var(--transition), border-color var(--transition),
              transform var(--transition);
}
.lightbox__control:hover {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.5);
}
@media (prefers-reduced-motion: no-preference) {
  .lightbox__control:hover { transform: scale(1.06); }
}
.lightbox__control svg { width: 1.4rem; height: 1.4rem; }

/* Prev/next float over the stage on larger screens */
.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
}
.lightbox__nav--prev { left: 0; }
.lightbox__nav--next { right: 0; }
@media (min-width: 48em) {
  .lightbox__nav--prev { left: var(--space-3); }
  .lightbox__nav--next { right: var(--space-3); }
  .lightbox__control { width: 3.25rem; height: 3.25rem; }
}

/* On small screens, prev/next sit static beneath the image (no float) */
@media (max-width: 47.99em) {
  .lightbox__nav { position: static; transform: none; }
}

/* Body scroll-lock helper toggled by JS.
   `overflow: hidden !important` on html/body already prevents page scroll, so
   we deliberately avoid `touch-action: none` here: that would propagate to
   descendants and block touch-scrolling inside an open, overflowing element
   (e.g. the mobile nav panel's `overflow: auto`). The lightbox content is
   centred, not scrolled, so it is unaffected. */
.no-scroll {
  overflow: hidden !important;
}

/* ---------------------------------------------------------------------
   13. Quote / contact form
   --------------------------------------------------------------------- */
.quote-layout {
  display: grid;
  gap: var(--space-6);
}
@media (min-width: 56em) {
  .quote-layout {
    grid-template-columns: 1.4fr 1fr;
    align-items: start;
    gap: var(--space-7);
  }
  /* Keep the contact card in view while scrolling the long form (2-column only,
     so it never overlaps the stacked layout on phone/portrait tablet). */
  .contact-aside {
    position: sticky;
    top: calc(var(--header-h, 4.5rem) + var(--space-4));
  }
}

.quote-form {
  display: grid;
  gap: var(--space-5);            /* consistent vertical rhythm for ALL field groups */
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-5);
}
@media (min-width: 40em) { .quote-form { padding: var(--space-6); } }

.form-row {
  display: grid;
  gap: var(--space-2);
}
.form-row--split {
  gap: var(--space-4);
}
@media (min-width: 40em) {
  .form-row--split { grid-template-columns: 1fr 1fr; }
}

.field {
  display: grid;
  gap: var(--space-2);
  /* When JS focuses the first invalid field, keep it clear of the sticky
     header (top) and the mobile call bar (bottom). */
  scroll-margin-top: calc(var(--header-h, 4.5rem) + var(--space-3));
  scroll-margin-bottom: 5rem;
}

.label {
  font-weight: 700;
  font-size: var(--fs-400);
  color: var(--color-ink);
}
.label .req { color: var(--color-danger); font-weight: 700; }
.field__hint { font-size: 0.9375rem; line-height: 1.5; color: var(--color-ink-faint); }

/* Preferred contact method (estimate.html). A <fieldset>, so the legend is
   announced with each radio; the browser default fieldset chrome is removed
   because .field already supplies the grid + spacing. The options are real
   radios (not restyled boxes) so focus, keyboard and forced-colours modes
   all behave, wrapped in a card that gives a 44px+ touch target. */
.contact-pref {
  border: 0;
  padding: 0;
  margin: 0;
  min-inline-size: 0;   /* Chrome/Safari fieldsets refuse to shrink otherwise */
}
.contact-pref__legend { padding: 0; }
.contact-pref__options {
  display: grid;
  gap: var(--space-2);
}
@media (min-width: 30em) {
  .contact-pref__options { grid-template-columns: 1fr 1fr; }
}
.contact-pref__option {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: 2.75rem;
  padding: 0.625rem 0.9rem;
  font-size: var(--fs-400);
  color: var(--color-ink);
  background: var(--color-bg);
  border: 1.5px solid var(--color-border-2);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}
.contact-pref__option:hover { border-color: var(--color-accent); }
.contact-pref__option input {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--color-accent);
  flex: 0 0 auto;
}
/* :has() lifts the whole card on selection; browsers without it simply keep
   the plain card, and the radio dot still shows the state. */
.contact-pref__option:has(input:checked) {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
  font-weight: 600;
}
.contact-pref__option:has(input:focus-visible) {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

.input,
.select,
.textarea {
  width: 100%;
  padding: 0.75rem 0.9rem;
  font-size: var(--fs-400);
  color: var(--color-ink);
  background: var(--color-bg);
  border: 1.5px solid var(--color-border-2);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--transition), box-shadow var(--transition),
              background var(--transition);
}
.textarea { min-height: 8rem; resize: vertical; line-height: var(--lh-base); }
.select {
  appearance: none;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="%23475569"><path d="M5.5 7.5 10 12l4.5-4.5z"/></svg>');
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 1.25rem;
  padding-right: 2.5rem;
}
.input::placeholder,
.textarea::placeholder { color: var(--color-ink-faint); }

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--color-ink-faint); }

/* Accent ring on focus (mouse + keyboard). Keyboard users additionally get the
   high-contrast :focus-visible outline below for WCAG 2.4.7 / 1.4.11. */
.input:focus,
.select:focus,
.textarea:focus {
  outline: none;
  border-color: var(--color-accent-dark);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18);
  background: #fff;
}
.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 1px;
  border-color: var(--color-focus);
}

/* Invalid state (set by estimate.js via aria-invalid + .has-error) */
.field.has-error .input,
.field.has-error .select,
.field.has-error .textarea,
.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--color-danger);
  background: var(--color-danger-bg);
}
.field.has-error .input:focus,
.field.has-error .select:focus,
.field.has-error .textarea:focus,
.input[aria-invalid="true"]:focus,
.select[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus {
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px rgba(185, 28, 28, 0.18);
  background: var(--color-danger-bg);
}

/* Inline error message — hidden until populated/visible */
.error {
  display: none;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--fs-300);
  font-weight: 600;
  color: var(--color-danger);
}
.error.is-visible,
.field.has-error .error { display: flex; }
.error::before {
  content: "!";
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 1.1rem; height: 1.1rem;
  margin-top: 0.05rem;
  font-size: 0.75rem;
  font-weight: 800;
  color: #fff;
  background: var(--color-danger);
  border-radius: 50%;
}

/* Form-level status region (success / submit feedback) */
.form-status {
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.form-status:empty { display: none; }
.form-status.is-success {
  color: var(--color-success);
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
}
.form-status.is-error {
  color: var(--color-danger);
  background: var(--color-danger-bg);
  border: 1px solid #fecaca;
}

/* Persistent success panel rendered by estimate.js after a valid submit.
   Visually matches the form's success state but lives in a NON-live
   container, so its links aren't announced as transient status text. */
.form-success {
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--color-success);
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
}
.form-success[hidden] { display: none; }
.form-success__heading {
  display: block;
  font-weight: 600;
}
.form-success__heading:focus-visible {
  outline: 2px solid var(--color-success);
  outline-offset: 3px;
}
.form-success p { margin-top: var(--space-2); }
.form-success__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin-top: var(--space-4);
}
.form-success__retry { font-weight: 600; }

.form-actions { margin-top: var(--space-2); }

/* Direct-contact block (sidebar) */
.contact-aside {
  display: grid;
  gap: var(--space-4);
}
.contact-card {
  position: relative;
  background:
    radial-gradient(120% 120% at 100% 0%, rgba(37, 99, 235, 0.22), transparent 55%),
    var(--color-dark);
  color: var(--color-on-dark);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-lg);
}
.contact-card__title { color: #fff; font-size: var(--fs-fluid-600); letter-spacing: -0.015em; }
.contact-card__text { color: var(--color-on-dark-mut); margin-top: var(--space-2); }
.contact-list {
  margin-top: var(--space-5);
  display: grid;
  gap: var(--space-3);
}
.contact-list a,
.contact-list .contact-item__value { color: #fff; text-decoration: none; }
/* Breathing room between the contact list (Facebook row) and the
   "Save Phil's number" button so they don't sit flush together. */
.contact-card > .btn { margin-top: var(--space-5); }
.contact-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.contact-item__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: var(--radius);
  background: rgba(37, 99, 235, 0.18);
  color: #93c5fd;
  border: 1px solid rgba(37, 99, 235, 0.35);
  transition: background var(--transition), color var(--transition),
              border-color var(--transition);
}
.contact-item:hover .contact-item__icon {
  background: rgba(37, 99, 235, 0.32);
  color: #dbeafe;
  border-color: rgba(96, 165, 250, 0.6);
}
.contact-item__icon svg { width: 1.25rem; height: 1.25rem; }
.contact-item__label {
  display: block;
  font-size: var(--fs-300);
  color: var(--color-on-dark-mut);
  font-weight: 600;
}
.contact-item__value { font-size: var(--fs-500); font-weight: 700; }
.contact-item a.contact-item__value:hover { text-decoration: underline; }
/* Long, unbreakable tokens (e.g. phildevine24@icloud.com) must wrap inside the
   dark contact card / footer rather than overflow and get clipped by
   body{overflow-x:hidden}. Let the bold email/value break, and let the flex
   text column shrink below its min-content width. No motion, static layout. */
.contact-item__value,
.footer-list a,
.footer-contact-item a { overflow-wrap: anywhere; }
.contact-item > span:last-child { min-width: 0; flex: 1 1 auto; }
.footer-contact-item { min-width: 0; }

.contact-note {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius);
  padding: var(--space-5);
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
  box-shadow: var(--shadow-xs);
}

/* ---------------------------------------------------------------------
   14. Footer
   --------------------------------------------------------------------- */
.site-footer {
  background: var(--color-dark);
  color: var(--color-on-dark-mut);
  margin-top: var(--space-8);
}
.site-footer__inner {
  display: grid;
  gap: var(--space-6);
  padding-block: var(--space-7);
}
@media (min-width: 48em) {
  .site-footer__inner { grid-template-columns: 1.4fr 1fr 1fr; }
}
.footer-brand { color: #fff; }
/* Real logo monogram (white variant) on the dark footer. */
.footer-brand__logo { height: 2.75rem; width: auto; display: block; margin-bottom: var(--space-3); }
.footer-brand__name {
  font-size: var(--fs-500);
  font-weight: 800;
  color: #fff;
}
.footer-brand__text {
  margin-top: var(--space-3);
  max-width: 38ch;
  color: var(--color-on-dark-mut);
}
.footer-col__title {
  font-size: var(--fs-300);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-on-dark);
  margin-bottom: var(--space-3);
}
.footer-col__title::after {
  content: "";
  display: block;
  width: 1.75rem;
  height: 2px;
  margin-top: var(--space-2);
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--color-accent), transparent);
}
.footer-list { display: grid; gap: var(--space-2); }
.footer-list a,
.footer-list span { color: var(--color-on-dark-mut); text-decoration: none; }
.footer-list a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;          /* >=44px touch target (phone/tablet); fine on desktop too */
  min-width: 0;                 /* let long email/URL wrap instead of overflowing */
  padding-block: var(--space-2);
  border-radius: var(--radius-sm);
  transition: color var(--transition), transform var(--transition);
}
.footer-list a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
@media (prefers-reduced-motion: no-preference) {
  .footer-list a:hover { transform: translateX(2px); }
}
.footer-contact-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;          /* comfortable touch row for the tel:/mailto: links */
}
.footer-contact-item svg { width: 1.1rem; height: 1.1rem; flex: none; color: #93c5fd; }

.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-bottom__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-4);
  font-size: var(--fs-300);
}
.footer-bottom a { color: var(--color-on-dark-mut); }
.footer-bottom a:hover { color: #fff; }

/* Generic content prose width helper (legal text etc.) */
.prose { max-width: var(--container-narrow); }
.prose p + p { margin-top: var(--space-4); }

/* =====================================================================
   15. Bricklaying interactive background  (paired with site.js)
   A <canvas class="bg-canvas"> paints a dark running-bond brick wall with
   a blue, cursor-reactive work-light. site.js moves the canvas INSIDE
   the hero and positions it absolutely there, so the wall is confined to
   the hero box only. The rest of the page is solid white and the band/
   footer are solid dark — there is no full-page dark layer behind the
   content, so fast scrolling never reveals a dark flash.
   ===================================================================== */

/* The page background stays SOLID WHITE everywhere except the hero (which
   hosts the canvas) and the dark accent band/footer. A white body means fast
   scrolling can never reveal a dark layer behind the content — the brick wall
   is now scoped to the hero box, not painted behind the whole page. */
body {
  background: var(--color-bg);
}

/* Decorative hero-scoped canvas — always behind the hero content, inert.
   z-index:0 keeps the canvas behind the hero content (.hero__inner is
   z-index:1) within the hero's own isolated stacking context (isolation:isolate
   on .home .hero). The hero clips it via overflow:hidden, so it never escapes
   the hero box, and the header (z-index:100) and lightbox (z-index:200) are
   unaffected. */
.bg-canvas {
  position: absolute;    /* scoped to its positioned parent (the hero), NOT the viewport */
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;         /* shown by site.js only once it is placed inside the hero */
  z-index: 0;            /* behind the hero's content (.hero__inner is z-index:1) */
  pointer-events: none;  /* never intercepts clicks / focus */
  user-select: none;
}

/* ---- HERO: full-viewport photo band (home) ------------------------ */
/* ONE block owns the home hero's geometry: a single-column band that
   fills the first screen (viewport minus the sticky header — min-height
   only ever GROWS it, so short/landscape viewports keep the content's
   natural height; svh avoids the iOS address-bar jump, vh is the
   fallback) over the interactive kitchen backdrop (.hero-bg, §24).
   The solid dark background is the no-image / no-JS fallback. */
.home .hero {
  position: relative;            /* positioning context for the backdrop */
  overflow: hidden;              /* clip the backdrop layers to the band */
  isolation: isolate;
  background: var(--color-dark); /* solid fallback under the photo */
  border-bottom-color: rgba(255, 255, 255, 0.08);
  display: grid;
  align-items: center;           /* content vertically centred */
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
}
/* Hero content sits ABOVE the backdrop, as one extended column (the
   higher-specificity single-column rule overrides the base two-column
   grid that still serves a future hero__media plate — see git history). */
.home .hero__inner {
  position: relative;
  z-index: 1;
  grid-template-columns: 1fr;
}
/* The photo (and its cursor glow) can brighten the area behind the hero
   text. Give the text column its own dark scrim so contrast is guaranteed
   independent of the backdrop (AA floor holds over the brightest pixel). */
.home .hero__content {
  max-width: 50rem;
  background: linear-gradient(180deg, rgba(7, 11, 20, 0.84), rgba(7, 11, 20, 0.68));
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: var(--shadow-xl),
              inset 0 1px 0 rgba(255, 255, 255, 0.08);
  -webkit-backdrop-filter: saturate(120%);
  backdrop-filter: saturate(120%);
}
.home .hero__eyebrow {
  /* light blue chip on a translucent dark pill — AA on the scrim */
  color: #dbeafe;
  background: rgba(37, 99, 235, 0.22);
  border-color: rgba(96, 165, 250, 0.5);
}
/* Light copy over the dark scrim. The text-shadow is belt-and-braces in case
   the scrim is brightened at the edges by the work-light. */
.home .hero__title,
.home .hero__lead,
.home .hero__trust {
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.home .hero__title { color: #f8fafc; }            /* >=7.6:1 even over the brightest lit pixel */
.home .hero__lead { color: #e2e8f0; max-width: 60ch; } /* --color-on-dark; >=6.4:1; capped reading measure */
.home .hero__trust { color: var(--color-on-dark); } /* bumped from #cbd5e1 for AA headroom (>=6.4:1) */
.home .hero__trust li::before {
  background: #93c5fd;                              /* brighter blue dot */
}
/* Honesty note: the kitchen backdrop is licensed stock, so say so. */
.home .hero__note {
  font-size: 0.75rem;
  color: rgba(226, 232, 240, 0.72);
  margin-top: var(--space-3);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
/* Keep the blue primary button; switch the secondary to a light ghost
   that reads on the dark wall. */
/* Light "ghost on dark" secondary button — shared by the home hero and every
   inner-hero band (.hero--inner) so the treatment can't drift between them. */
.home .hero__actions .btn--secondary,
.hero--inner .btn--secondary {
  background: rgba(255, 255, 255, 0.06);
  color: #ffffff;
  border-width: 2px;             /* match .btn--ghost-light (Trowel & Slate hairline shrinks it) */
  border-color: rgba(255, 255, 255, 0.6);
  box-shadow: none;              /* no inset hairline on the dark band */
}
.home .hero__actions .btn--secondary:hover,
.hero--inner .btn--secondary:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: #ffffff;
  color: #ffffff;
}
/* ---- Subtle, accessible scroll-down cue --------------------------------
   Decorative only (a pseudo-element, never announced to assistive tech and
   pointer-events:none so it can't intercept clicks). Drawn above the
   backdrop (z-index:1) at the bottom-centre of the hero. Hidden on
   short/small viewports to avoid crowding, and any motion is fully gated. */
@media (min-width: 56em) {
  .home .hero::after {
    content: "";
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: var(--space-5);
    width: 1.5rem;
    height: 1.5rem;
    margin-left: -0.75rem;
    border-right: 2px solid rgba(255, 255, 255, 0.7);
    border-bottom: 2px solid rgba(255, 255, 255, 0.7);
    transform: rotate(45deg);
    opacity: 0.8;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
  }
}
@media (min-width: 56em) and (prefers-reduced-motion: no-preference) {
  .home .hero::after {
    animation: hero-scroll-cue 2.2s var(--ease) infinite;
  }
}
@keyframes hero-scroll-cue {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: 0.55; }
  50%      { transform: rotate(45deg) translate(4px, 4px); opacity: 0.9; }
}

/* ---------------------------------------------------------------------
   Inner-page hero band — brings the animated brick wall to every page.
   A single-column, centred dark band that hosts the SAME .bg-canvas wall
   as the home hero. site.js finds it via the generic `.hero` selector
   (so it paints the wall) and excludes any `.hero` + its contents from
   scroll-reveal (so the band is visible above the fold, no flash). The
   wall stays scoped to this band, so the rest of each page is solid white
   and can never flash dark on a fast scroll — identical to the home hero.
   --------------------------------------------------------------------- */
.hero--inner {
  display: flex;
  align-items: center;
  min-height: clamp(15rem, 26vh, 22rem);
  position: relative;            /* positioning context for the .bg-canvas */
  overflow: hidden;              /* clip the brick wall to the band */
  isolation: isolate;
  background: var(--color-dark); /* solid base: readable before/without JS; canvas paints over it */
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.hero--inner > .container {
  position: relative;            /* sit ABOVE the canvas */
  z-index: 1;
  width: 100%;
  padding-block: var(--space-8);
}
@media (min-width: 48em) {
  .hero--inner > .container { padding-block: var(--space-9); }
}
/* The animated wall + blue work-light can brighten the area behind the
   copy, collapsing contrast for light text. Give the intro block its own
   dark scrim so contrast is guaranteed independent of the wall's lit state
   (AA floor holds even under the brightest lit pixel) — mirrors the home
   hero's .hero__content treatment. */
.hero--inner .section__head,
.hero--inner .error-page {
  background: linear-gradient(180deg, rgba(7, 11, 20, 0.84), rgba(7, 11, 20, 0.68));
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: var(--shadow-xl), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  margin-bottom: 0;
}
/* Light copy over the dark scrim. The text-shadow is belt-and-braces in
   case the scrim edge is brightened by the work-light. */
.hero--inner .section__title,
.hero--inner .section__lead,
.hero--inner .error-page__title,
.hero--inner .error-page__text {
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.hero--inner .section__title,
.hero--inner .error-page__title { color: #f8fafc; }  /* AA even over the brightest lit pixel */
.hero--inner .section__lead,
.hero--inner .error-page__text { color: #e2e8f0; }   /* --color-on-dark; AA over the scrim */
.hero--inner .section__eyebrow,
.hero--inner .error-page__code { color: #dbeafe; }   /* light-blue accent on the dark wall */
/* Inline links inside the band (e.g. the 404 phone/email) read light. */
.hero--inner a:not(.btn) {
  color: #bfdbfe;
}
/* Secondary buttons in an inner-hero band get the same "ghost on dark"
   treatment as the home hero — see `.home .hero__actions .btn--secondary` above. */

/* ---- Dark accent bands let the wall show through ----------------- */
.service-area {
  /* deep slate with a soft blue work-light glow in the top corner + crisp edge */
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(37, 99, 235, 0.20), transparent 55%),
    var(--color-dark);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.site-footer {
  background: var(--color-dark);   /* solid dark accent — no canvas behind it, no flash */
  /* gradient accent rule along the top edge for a premium, layered finish */
  border-top: 3px solid transparent;
  border-image: linear-gradient(90deg, var(--color-accent), var(--color-accent-darker)) 1;
}

/* ---- Keep light content sections OPAQUE & readable --------------- */
/* These sections must never sit on the busy wall. The surface/white
   sections are already opaque via their own backgrounds; we make the
   plain (transparent) .section bands explicitly opaque too. */
.section { background: var(--color-bg); }
.section--surface { background: var(--color-surface); }
/* The header's translucent white already covers the wall; ensure the
   default body text sections read crisp. */

/* The skip-link, header and lightbox already carry their own z-index;
   nothing here lowers them. */

@media (prefers-reduced-motion: reduce) {
  /* Canvas renders a single static frame from JS; nothing extra needed,
     but make sure no decorative transition sneaks in. */
  .bg-canvas { transition: none !important; }
}

/* =====================================================================
   16. Mobile hamburger nav  (paired with site.js)
   Desktop: inline nav shown, toggle hidden.
   Mobile (< 48em): inline list hidden, toggle shown, list becomes an
   animated dropdown panel under the header.
   ===================================================================== */

/* The toggle button — hidden on desktop. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;          /* 44px tap target */
  height: 2.75rem;
  margin-left: auto;
  padding: 0;
  background: transparent;
  border: 1px solid var(--color-border-2);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
}
.nav-toggle {
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.nav-toggle:hover { background: var(--color-accent-soft); border-color: #bfdbfe; color: var(--color-accent-darker); }

/* Hamburger -> X icon, built from 3 bars. */
.nav-toggle__box {
  position: relative;
  display: block;
  width: 1.375rem;
  height: 1rem;
}
.nav-toggle__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform var(--transition), opacity var(--transition),
              top var(--transition);
}
.nav-toggle__bar:nth-child(1) { top: 0; }
.nav-toggle__bar:nth-child(2) { top: 50%; transform: translateY(-50%); }
.nav-toggle__bar:nth-child(3) { top: 100%; transform: translateY(-100%); }

/* Open state (driven by aria-expanded) morphs the bars into an X. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) {
  top: 50%;
  transform: translateY(-50%) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
  top: 50%;
  transform: translateY(-50%) rotate(-45deg);
}

/* ---- Mobile layout: below the desktop breakpoint ----------------- */
@media (max-width: 47.99em) {
  .nav-toggle { display: inline-flex; }

  /* The inline list becomes a dropdown panel anchored under the header. */
  .primary-nav {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    margin-left: 0;
    background: rgba(255, 255, 255, 0.98);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-border);
    box-shadow: var(--shadow-lg);

    /* Closed by default: collapsed + non-interactive, animated open. */
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    transition: max-height var(--transition), opacity var(--transition),
                transform var(--transition), visibility var(--transition);
  }
  .primary-nav.is-open {
    max-height: 80vh;
    overflow: auto;
    overscroll-behavior: contain;
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }

  .primary-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4) var(--space-4);
  }
  .primary-nav__link,
  .primary-nav__cta {
    width: 100%;
    min-height: 2.875rem;     /* >= 44px tap target */
    justify-content: flex-start;
    margin-left: 0;
  }
  .primary-nav__cta {
    justify-content: center;
    margin-top: var(--space-2);
  }

  /* The mobile panel needs the header to be a positioning context. */
  .site-header__inner { position: relative; }
}

/* On desktop the JS may have left aria-expanded around; ensure the
   inline nav is always visible and the panel styles never apply. */
@media (min-width: 48em) {
  .nav-toggle { display: none; }
  .primary-nav { position: static; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle__bar,
  .primary-nav { transition: none !important; }
}

/* =====================================================================
   17. Conversion / SEO upgrade sections
   New conversion + SEO markup: trust strip, process steps, FAQ
   accordion, scroll-reveal, sticky mobile call bar, print styles, and
   404 page. Each NEW top-level section uses
   .section / .section--surface, which are forced opaque in section 15,
   so the brick canvas never bleeds through these. Reuses brand tokens
   only; nothing here is transparent.
   ===================================================================== */

/* ---------------------------------------------------------------------
   17.1 Trust strip (Why-choose)
   --------------------------------------------------------------------- */
.trust-strip {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr;
}
@media (min-width: 40em) {
  .trust-strip { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 64em) {
  .trust-strip { grid-template-columns: repeat(4, 1fr); }
  /* Home "Our promise" strip has exactly three cards — fill the row evenly */
  .trust-strip--three { grid-template-columns: repeat(3, 1fr); }
}
.trust-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  height: 100%;
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}
.trust-item:hover,
.trust-item:focus-within {
  box-shadow: var(--shadow-md);
  border-color: #bfdbfe;
}
@media (prefers-reduced-motion: no-preference) {
  .trust-item:hover,
  .trust-item:focus-within { transform: translateY(-4px); }
}
.trust-item__icon {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius);
  background: var(--color-accent-soft);
  color: var(--color-accent-dark);
  border: 1px solid #bfdbfe;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.6);
  transition: background var(--transition), color var(--transition);
}
.trust-item:hover .trust-item__icon {
  background: var(--color-accent-dark);
  color: #fff;
  border-color: var(--color-accent-dark);
}
.trust-item__icon svg { width: 1.5rem; height: 1.5rem; }
.trust-item__title { font-size: var(--fs-fluid-600); letter-spacing: -0.015em; }
.trust-item__text { color: var(--color-ink-soft); font-size: var(--fs-400); }

/* ---------------------------------------------------------------------
   PROMISE CAROUSEL (home "Our promise") — progressive enhancement.
   With NO JavaScript the three cards render as the normal grid above.
   site.js initPromiseCarousel() adds `.is-carousel` + controls to turn
   them into an auto-rotating carousel: one card at a time, swapping every
   8s, with a countdown progress line that fills to the next swap. Auto-
   rotation is skipped entirely under prefers-reduced-motion (the static
   grid stays); it pauses on hover / keyboard focus / hidden tab and has a
   play-pause toggle plus dot controls (so the motion is always stoppable
   — WCAG 2.2.2 — and every card is reachable). The card content is never
   hidden from assistive tech: all three stay in the DOM and in reading
   order, so a screen-reader user gets the whole section regardless. */
.promise-carousel { max-width: 44rem; margin-inline: auto; }
.promise-carousel__viewport {
  overflow: hidden;
  /* let the card hover-lift shadow breathe without being clipped */
  padding: var(--space-2) var(--space-1);
  margin: calc(var(--space-2) * -1) calc(var(--space-1) * -1);
}
/* The grid becomes a single-file horizontal track; each card fills 100%. */
.trust-strip.is-carousel {
  display: flex;
  grid-template-columns: none;
  gap: 0;
  transform: translateX(calc(var(--active, 0) * -100%));
  transition: transform 520ms var(--ease);
}
.trust-strip.is-carousel > .trust-item {
  flex: 0 0 100%;
  /* Opt out of the home "db-rise-in" scroll animation: the clipped viewport
     becomes these cards' view() scroll container and would freeze it. The
     carousel is their motion now. (The reveal fade is transition-based and
     unaffected, so cards still fade in on first scroll.) */
  animation: none;
}
@media (prefers-reduced-motion: reduce) {
  .trust-strip.is-carousel { transition: none; }
}

/* Countdown progress line — a CSS animation drives the fill so it pauses
   in perfect sync with the rotation (both freeze via animation-play-state,
   and the JS advance is triggered by this bar's animation loop). */
.promise-carousel__progress {
  margin-top: var(--space-4);
  height: 4px;
  border-radius: 999px;
  background: var(--color-border);
  overflow: hidden;
}
.promise-carousel__bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--color-accent);
  animation: promise-fill 8s linear infinite;
}
.promise-carousel.is-paused .promise-carousel__bar { animation-play-state: paused; }
@keyframes promise-fill { from { width: 0; } to { width: 100%; } }

.promise-carousel__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
}
.promise-carousel__dots { display: inline-flex; align-items: center; gap: 0.6rem; }
.promise-carousel__dot {
  position: relative;
  width: 0.7rem;
  height: 0.7rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--color-border);
  cursor: pointer;
  transition: background var(--transition), transform var(--transition);
}
/* Keep the visible dot small but the tap/click target ≥ 24px (WCAG 2.5.8). */
.promise-carousel__dot::before { content: ""; position: absolute; inset: -0.55rem; }
.promise-carousel__dot:hover { background: var(--color-accent-dark); }
.promise-carousel__dot.is-current { background: var(--color-accent); transform: scale(1.3); }
.promise-carousel__dot:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 3px; }
.promise-carousel__play {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-bg);
  color: var(--color-accent-dark);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.promise-carousel__play:hover { background: var(--color-accent-soft); border-color: #bfdbfe; }
.promise-carousel__play:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 2px; }
.promise-carousel__play svg { width: 1.05rem; height: 1.05rem; display: block; }
/* Screen-reader announcement of the active card on manual navigation. */
.promise-carousel__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------
   17.2 Process steps (How we work)
   --------------------------------------------------------------------- */
.process-steps {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
  counter-reset: none; /* numbers are rendered explicitly via __num */
}
@media (min-width: 52em) {
  .process-steps { grid-template-columns: repeat(3, 1fr); }
}
.process-step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5) var(--space-5);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}
.process-step::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-darker));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--transition);
}
@media (prefers-reduced-motion: reduce) {
  .process-step::before { transition: none; }
}
.process-step:hover {
  box-shadow: var(--shadow-md);
  border-color: #bfdbfe;
}
@media (prefers-reduced-motion: no-preference) {
  .process-step:hover { transform: translateY(-4px); }
}
.process-step:hover::before { transform: scaleX(1); }
.process-step__num {
  display: grid;
  place-items: center;
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  /* Solid colour fallback under the gradient (same Chrome composited-.reveal
     gradient-drop bug as .btn--primary) so the numbered badges never go blank. */
  background-color: var(--color-accent-dark);
  background-image: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-darker) 100%);
  color: #fff;
  font-weight: 800;
  font-size: var(--fs-500);
  line-height: 1;
  box-shadow: var(--shadow-accent);
  border: 2px solid #fff;
}
.process-step__title { font-size: var(--fs-fluid-600); letter-spacing: -0.015em; }
.process-step__text { color: var(--color-ink-soft); }

/* ---------------------------------------------------------------------
   17.4 FAQ accordion (native <details>/<summary>)
   --------------------------------------------------------------------- */
.faq {
  max-width: var(--container-narrow);
  margin-inline: auto;
  display: grid;
  gap: var(--space-3);
}
.faq__item {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: box-shadow var(--transition), border-color var(--transition);
}
.faq__item:hover { box-shadow: var(--shadow); }
.faq__item[open] {
  border-color: #bfdbfe;
  box-shadow: var(--shadow);
}
.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  font-weight: 700;
  font-size: var(--fs-500);
  color: var(--color-ink);
  cursor: pointer;
  list-style: none;
  min-height: 3rem; /* tap target */
  transition: background var(--transition), color var(--transition);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::marker { content: ""; }
.faq__q:hover { background: var(--color-accent-soft); color: var(--color-accent-darker); }
.faq__item[open] .faq__q { color: var(--color-accent-darker); }
.faq__chevron {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--color-accent-soft);
  border: 1px solid #bfdbfe;
  color: var(--color-accent-dark);
  transition: transform var(--transition), background var(--transition),
              color var(--transition);
}
.faq__q:hover .faq__chevron { background: var(--color-accent-dark); color: #fff; border-color: var(--color-accent-dark); }
.faq__chevron svg { width: 1.1rem; height: 1.1rem; }
.faq__item[open] .faq__chevron {
  transform: rotate(180deg);
  background: var(--color-accent-dark);
  color: #fff;
  border-color: var(--color-accent-dark);
}
.faq__a {
  padding: 0 var(--space-5) var(--space-4);
  color: var(--color-ink-soft);
}
.faq__a p { margin: 0; }
.faq__a a { color: var(--color-accent-dark); }
/* Under prefers-reduced-motion the chevron transition is neutralised by
   the global reduced-motion block in section 2. */

/* ---------------------------------------------------------------------
   17.5 Scroll-reveal (no-JS / reduced-motion SAFE)
   Default: content is FULLY VISIBLE. The .reveal class alone does nothing
   so the page is perfect with JS off or animation disabled. site.js adds
   the flag class `js-reveal` to <html> ONLY when JS runs AND
   prefers-reduced-motion is "no-preference".
   --------------------------------------------------------------------- */
.reveal { /* no visual change by itself */ }
/* Refined, cohesive fade-up. One rule set deliberately covers BOTH the few
   sections that ship with class="reveal" in the HTML AND every section that
   site.js auto-tags with the SAME `.reveal` class at runtime — so the whole
   site shares one consistent entrance. Uses the project easing curve and a
   short, premium duration. Transform/opacity ONLY (compositor-friendly, zero
   layout shift): the element's box is fully reserved while hidden. */
.js-reveal .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
  will-change: opacity, transform;
}
.js-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;   /* release the hint once settled */
}
/* Belt-and-braces: if reduced motion is on, never hide content even if the
   flag class somehow got added. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    will-change: auto !important;
  }
}
/* Print: every revealed section must be solid ink on paper. */
@media print {
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------------
   17.6 Sticky mobile call bar (injected by site.js)
   Container created by JS as <div class="call-bar" id="call-bar"> with
   two <a> children. Mobile-only, fixed bottom; below header (100) and
   lightbox (200) but above page content.
   --------------------------------------------------------------------- */
.call-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
  padding-left: calc(var(--space-3) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--space-3) + env(safe-area-inset-right, 0px));
  background: var(--color-dark);   /* solid (no backdrop-filter) — this bar is fixed during scroll */
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 -4px 12px rgba(15, 23, 42, 0.25);
}
.call-bar__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 2.875rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  font-weight: 700;
  font-size: var(--fs-400);
  text-decoration: none;
  border: 2px solid transparent;
}
.call-bar__btn svg { width: 1.15rem; height: 1.15rem; flex: none; }
.call-bar__btn {
  transition: background var(--transition), border-color var(--transition),
              box-shadow var(--transition);
}
.call-bar__btn--call {
  background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  color: #fff;
  border-color: var(--color-accent-dark);
  box-shadow: var(--shadow-accent);
}
.call-bar__btn--call:hover {
  background: linear-gradient(180deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%);
  border-color: var(--color-accent-darker);
  color: #fff;
}
.call-bar__btn--quote {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.5);
}
.call-bar__btn--quote:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: #fff;
  color: #fff;
}
/* VISIBILITY: hidden on desktop entirely; shown only below nav breakpoint. */
.call-bar { display: none; }
@media (max-width: 47.99em) {
  .call-bar { display: grid; }
  /* When the mobile nav panel is open, JS toggles .is-hidden so the bar
     doesn't clash with the dropdown / focus order. */
  .call-bar.is-hidden { display: none; }
}
body.has-call-bar { /* only meaningful with the mobile media query below */ }
@media (max-width: 47.99em) {
  /* Reserve space so the fixed bar never covers the footer's last line.
     4.25rem ~= bar height (btn 2.875rem + padding); the bar's own
     padding-bottom also adds env(safe-area-inset-bottom), so match it
     here too or the bar can overlap the footer on notched devices. */
  body.has-call-bar { padding-bottom: calc(4.25rem + env(safe-area-inset-bottom, 0px)); }
}
/* When a lightbox is open, gallery.js sets the call-bar inert + aria-hidden
   automatically (sibling of #lightbox); no extra CSS needed. */

/* ---------------------------------------------------------------------
   17.6b Back-to-top button polish
   site.js injects a <style id="to-top-styles"> AFTER styles.css and a
   <button id="to-top" class="to-top">. That injected sheet wins over any
   equal-specificity `.to-top` rule here, so we use the ID selector
   (#to-top, specificity 1,0,0 > the JS `.to-top` 0,1,0) to refine the look
   without touching JS. We only override visual polish — never the show/hide
   logic, position, or z-index that the JS owns.
   --------------------------------------------------------------------- */
#to-top {
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-darker) 100%);
  box-shadow: var(--shadow-accent-lg);
  border: 1px solid rgba(255, 255, 255, 0.18);
}
#to-top:hover {
  background: linear-gradient(160deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%);
  box-shadow: var(--shadow-accent-lg), 0 0 0 4px rgba(37, 99, 235, 0.16);
}
@media (prefers-reduced-motion: no-preference) {
  #to-top.is-visible:hover { transform: translateY(-2px); }
}

/* ---------------------------------------------------------------------
   17.7 Print styles
   --------------------------------------------------------------------- */
@media print {
  .bg-canvas,
  .call-bar,
  .nav-toggle,
  .skip-link,
  .site-header { display: none !important; }
  .reveal { opacity: 1 !important; transform: none !important; }
  .faq__item { break-inside: avoid; }
  body { background: #fff !important; color: #000; }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
    word-break: break-all;
  }
  .service-area,
  .site-footer { background: #fff !important; color: #000 !important; }
  #to-top,
  .scroll-progress,
  .hero-bg,
  .fx { display: none !important; }
  .home .hero,
  .hero--inner { background: #fff !important; min-height: 0 !important; border-bottom: 1px solid #e2e8f0 !important; }
  .home .hero__content,
  .hero--inner .section__head,
  .hero--inner .error-page { background: none !important; border: 0 !important; box-shadow: none !important; }
  .home .hero__title,
  .home .hero__lead,
  .home .hero__trust,
  .home .hero__phone,
  .hero__phone a,
  .hero--inner .section__title,
  .hero--inner .section__lead,
  .hero--inner .error-page__title,
  .hero--inner .error-page__text,
  .breadcrumb,
  .hero--inner a:not(.btn) { color: #000 !important; text-shadow: none !important; }
  .hero--inner .section__eyebrow,
  .hero--inner .error-page__code { color: #1d4ed8 !important; }
  .home .hero__actions .btn--secondary,
  .hero--inner .btn--secondary,
  .btn--ghost-light { color: #000 !important; border-color: #cbd5e1 !important; }
}

/* Forced-colors (Windows High Contrast): icons drawn with backgrounds are
   stripped by the UA — repaint them with system colours, which are exempt. */
@media (forced-colors: active) {
  .nav-toggle__bar { background: ButtonText; }
  .service-card__list li::before,
  .service-detail__list li::before,
  .hero__trust li::before,
  .areas-list__chip::before { background: CanvasText; }
  .scroll-progress { background: Highlight; }
}

/* ---------------------------------------------------------------------
   17.8 404 page (used by 404.html)
   404 content sits in a <main class="section"> which is already opaque.
   --------------------------------------------------------------------- */
.error-page {
  text-align: center;
  max-width: var(--container-narrow);
  margin-inline: auto;
  padding-block: var(--space-7);
}
.error-page__code {
  font-size: var(--fs-fluid-900);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--color-accent-dark);
}
.error-page__title {
  font-size: var(--fs-fluid-700);
  letter-spacing: -0.02em;
  margin-top: var(--space-3);
}
.error-page__text {
  color: var(--color-ink-soft);
  font-size: var(--fs-500);
  margin-top: var(--space-3);
}
.error-page__actions {
  margin-top: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
}

/* =====================================================================
   18. Services page + Areas chips + Before/After slider
   New markup for services.html (per-service detail blocks + jump links),
   the home "Areas we cover" light chip list, and a no-JS-safe Before/After
   image slider (clip/reveal driven by a range input). Blue/slate/white only,
   reusing the existing tokens. Light bands keep the light palette; the
   slider keeps a 4/3 aspect-ratio so it scales with no horizontal overflow.
   ===================================================================== */

/* ---------------------------------------------------------------------
   18.1 Services page — quick-jump pills
   --------------------------------------------------------------------- */
.services-jump {
  margin-top: var(--space-5);
  display: flex;
  justify-content: center;
}
.services-jump__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;                /* a touch more room so wrapped rows don't crowd on phone */
  justify-content: center;
  list-style: none;
  padding: 0;
  margin: 0;
}
.services-jump__link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;          /* >=44px touch target */
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: var(--fs-300);
  font-weight: 700;
  text-decoration: none;
  color: var(--color-accent-dark);
  background: var(--color-accent-soft);
  border: 1px solid #bfdbfe;
  transition: background var(--transition), color var(--transition),
              border-color var(--transition);
}
.services-jump__link {
  box-shadow: var(--shadow-xs);
}
.services-jump__link:hover {
  background: #dbeafe;
  border-color: #93c5fd;
  color: var(--color-accent-darker);
  box-shadow: var(--shadow-sm);
}
@media (prefers-reduced-motion: no-preference) {
  .services-jump__link { transition: background var(--transition), color var(--transition),
              border-color var(--transition), box-shadow var(--transition), transform var(--transition-fast); }
  .services-jump__link:hover { transform: translateY(-1px); }
}

/* ---------------------------------------------------------------------
   18.2 Services page — per-service detail blocks
   Sit inside the existing .section / .section--surface containers, which
   alternate white / surface. The global :target rule (section 2) already
   provides the sticky-header scroll offset for the #id anchors.
   --------------------------------------------------------------------- */
.service-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.service-detail__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.service-detail__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: var(--radius-sm);
  background: var(--color-accent-soft);
  color: var(--color-accent-dark);
  border: 1px solid #bfdbfe;
}
.service-detail__icon svg { width: 1.75rem; height: 1.75rem; }
.service-detail__title {
  font-size: var(--fs-fluid-700);
  letter-spacing: -0.02em;
  line-height: var(--lh-tight);
}
.service-detail__lead {
  margin-top: var(--space-2);
  font-size: var(--fs-500);
  color: var(--color-ink-soft);
  max-width: 52ch;
}
.service-detail__grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}
@media (min-width: 60em) {
  .service-detail__grid {
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
  }
}
.service-detail__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* When a block is also .prose, let it fill the grid column instead of being
   capped by the global .prose max-width. */
.service-detail__block.prose { max-width: none; }
.service-detail__subtitle {
  font-size: var(--fs-fluid-600);
  color: var(--color-ink);
}
.service-detail__block p { color: var(--color-ink-soft); }
.service-detail__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--fs-400);
  color: var(--color-ink-soft);
}
/* Reuse the accent check-mask bullet pattern, vertically centred. */
.service-detail__list li {
  position: relative;
  padding-left: 1.6rem;
}
.service-detail__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45rem;
  width: 0.7rem;
  height: 0.7rem;
  background: var(--color-accent);
  -webkit-mask: var(--check-mask) center / contain no-repeat;
  mask: var(--check-mask) center / contain no-repeat;
}

/* ---------------------------------------------------------------------
   18.3 Areas we cover (home) — light-theme chip list
   This band is white/surface (NOT the dark .service-area band), so the
   chips use the light palette, unlike .service-area__chip.
   --------------------------------------------------------------------- */
.areas-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  justify-content: center;
  max-width: var(--container-narrow);
  margin-inline: auto;
  list-style: none;
  padding: 0;
}
.areas-list__chip {
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-300);
  font-weight: 600;
  color: var(--color-ink-soft);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-xs);
  transition: color var(--transition), background var(--transition),
              border-color var(--transition), box-shadow var(--transition);
}
.areas-list__chip:hover {
  color: var(--color-accent-darker);
  background: var(--color-accent-soft);
  border-color: #bfdbfe;
  box-shadow: var(--shadow-sm);
}
.areas-list__chip::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--color-accent);
  margin-right: var(--space-2);
  flex: none;
}

/* ---------------------------------------------------------------------
   18.4 Before / After slider
   No-JS safe: the after layer defaults to a 50% reveal, so even with JS off
   both images are visible (after on the left, before on the right) and the
   BEFORE/AFTER labels make the orientation explicit. The clip update is
   instantaneous — NO transition on the reveal so dragging tracks 1:1 and
   reduced-motion users get no animation.
   --------------------------------------------------------------------- */
.ba-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
  margin: 0;
}
@media (min-width: 52em) {
  .ba-grid { grid-template-columns: repeat(2, 1fr); }
}
.ba-grid__note {
  margin-top: var(--space-5);
  text-align: center;
  font-size: var(--fs-300);
  font-style: italic;
  color: var(--color-ink-soft);
}

.ba-slider {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
}
.ba-slider__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-sm);
  background: var(--color-surface-2);
  isolation: isolate;
  user-select: none;
}
.ba-slider__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.ba-slider__img--before { z-index: 0; }

/* Reveal wrapper: clip the after layer from the left edge to --ba-pos using
   clip-path so the after image stays full-size (no squashing). */
.ba-slider__after {
  position: absolute;
  inset: 0;
  z-index: 1;
  clip-path: inset(0 calc(100% - var(--ba-pos, 50%)) 0 0);
}
.ba-slider__after .ba-slider__img--after {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ba-slider__label {
  position: absolute;
  top: var(--space-2);
  z-index: 2;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(15, 23, 42, 0.72);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  pointer-events: none;
}
.ba-slider__label--before { left: var(--space-2); }
.ba-slider__label--after { right: var(--space-2); }

.ba-slider__divider {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
  left: var(--ba-pos, 50%);
  width: 2px;
  transform: translateX(-1px);
  background: rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.25);
  pointer-events: none;
}
/* Decorative handle knob; the real control is the range input below. */
.ba-slider__divider::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--color-accent-dark);
  box-shadow: var(--shadow);
}

/* Visible-but-subtle caption so no-JS users see what the control does. */
.ba-slider__range-label {
  display: block;
  font-size: var(--fs-300);
  font-weight: 600;
  color: var(--color-ink-soft);
}

.ba-slider__range {
  width: 100%;
  margin: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  /* 44px touch target: 6px track + symmetric padding-block. */
  min-height: 2.75rem;
  padding-block: calc((2.75rem - 6px) / 2);
}
/* Track (flat blue/slate). */
.ba-slider__range::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-border-2);
}
.ba-slider__range::-moz-range-track {
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-border-2);
}
/* Thumb. */
.ba-slider__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: -0.5rem; /* centre on the 6px track */
  border-radius: 50%;
  background: var(--color-accent-dark);
  border: 2px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.ba-slider__range::-moz-range-thumb {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--color-accent-dark);
  border: 2px solid #fff;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
/* The global 3px var(--color-focus) outline (section 4) already applies to
   inputs; we don't clip the range, so the offset ring stays visible. Add an
   explicit thumb ring for extra clarity on keyboard focus. */
.ba-slider__range:focus-visible::-webkit-slider-thumb {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}
.ba-slider__range:focus-visible::-moz-range-thumb {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

/* Print: drop the interactive control + divider so a printed page just shows
   both images side-by-clip. */
@media print {
  .ba-slider__range,
  .ba-slider__divider { display: none !important; }
}

/* =====================================================================
   19. Cohesive front-end enhancements
   A restrained, professional polish layer that ENHANCES the existing
   design without restructuring it. Five tightly-scoped additions:
     19.1  Scroll-aware header (.is-scrolled compact/elevated state)
     19.2  Hero staggered fade-up entrance
     19.3  Connected process timeline (decorative connector + nodes)
     19.4  Reveal stagger for grid children
     19.5  Cohesive micro-accents
   GUARANTEES (every rule below honours these):
     - Animates ONLY transform / opacity / box-shadow / colour — never any
       property that reflows (no animated width/height/top/margin), so there
       is ZERO layout shift and the Lighthouse 100s are preserved.
     - ALL motion is gated behind @media (prefers-reduced-motion:no-preference)
       and is degrade-safe: with JS off or reduced motion on, nothing is ever
       left hidden, shifted, or stuck at opacity:0.
     - :focus-visible indicators and the blue/white theme are untouched.
   ===================================================================== */

/* ---------------------------------------------------------------------
   19.1 Scroll-aware header — ".is-scrolled" compact / elevated state
   site.js toggles `.is-scrolled` on `.site-header` via an IntersectionObserver
   watching a 1px sentinel at the top of <body> (no scroll-event loop). With JS
   off the class is never added, so the header simply keeps its default look —
   fully no-JS safe. The header stays solid white and legible in both states.
   Transitions are gated; under reduced motion the state flips instantly.
   --------------------------------------------------------------------- */
.site-header {
  /* Only the shadow + hairline colour animate (compositor/paint-cheap, no
     reflow of page content). The header's reserved flow-box height NEVER
     changes between states (see .is-scrolled below), so toggling the class
     cannot move any in-flow content and there is provably zero layout-shift
     cost — the bar is position:sticky and a sticky box reserves its size in
     normal flow, so compacting that box would shift everything after it. */
  transition: box-shadow var(--transition), border-color var(--transition);
}
/* Scrolled state: a crisper hairline + lifted shadow so the bar reads as
   "floating" over content once you leave the very top. The compaction is
   expressed PURELY through shadow + border-colour (both already transitioned,
   both layout-free) — the header keeps its resting height so nothing below it
   reflows. No min-height / padding change here: that would shrink the sticky
   element's reserved flow box and shift subsequent content (a scroll-triggered
   shift, which counts toward CLS). */
.site-header.is-scrolled {
  border-bottom-color: var(--color-border-2);
  box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04),
              0 8px 24px -12px rgba(15, 23, 42, 0.28);
}
/* Reduced motion → the state change is instant (no easing). */
@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}

/* ---------------------------------------------------------------------
   19.2 Hero entrance — subtle staggered fade-up
   The opacity:0 start AND the animation BOTH live inside the
   (prefers-reduced-motion: no-preference) query, so reduced-motion users and
   no-CSS-animation engines always see the hero at full opacity from frame one.
   Order: eyebrow -> title -> lead -> actions -> trust. Transform/opacity only,
   short durations, runs once on load — no CLS, no scroll dependency.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .home .hero__eyebrow,
  .home .hero__title,
  .home .hero__lead,
  .home .hero__actions,
  .home .hero__phone,
  .home .hero__trust,
  .home .hero__note {
    opacity: 0;
    animation: hero-fade-up 620ms var(--ease) both;
  }
  .home .hero__eyebrow { animation-delay: 80ms; }
  .home .hero__title   { animation-delay: 180ms; }
  .home .hero__lead    { animation-delay: 280ms; }
  .home .hero__actions { animation-delay: 380ms; }
  .home .hero__phone   { animation-delay: 430ms; }
  .home .hero__trust   { animation-delay: 480ms; }
  .home .hero__note    { animation-delay: 530ms; }
}
@keyframes hero-fade-up {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* ---------------------------------------------------------------------
   19.3 Process timeline — connected visual "How we work"
   A subtle connector line links the numbered .process-step__num badges so the
   three steps read as nodes on a single path: HORIZONTAL on desktop, a VERTICAL
   stack on mobile. Purely decorative pseudo-elements on .process-steps — the
   <ol>/<li> semantics, the numbers, and all content stay exactly as authored.
   Nothing animates; nothing can overflow (connectors are inset within padding).
   --------------------------------------------------------------------- */
/* The badge reads as a "node": a white halo rings it so the connector appears
   to tuck neatly behind each node. (Shadow only — no layout change.) */
.process-step__num {
  position: relative;
  box-shadow: var(--shadow-accent),
              0 0 0 4px var(--color-bg);
}

/* MOBILE (vertical stack) ------------------------------------------------
   Each non-last badge drops a short VERTICAL line beneath itself, drawn on the
   BADGE's own ::after so it is anchored exactly to the badge (immune to text
   length) and stays INSIDE the card (so the card's overflow:hidden never clips
   it). It runs from just under the badge down through the card body, reading as
   the continuing "spine" of the timeline. Decorative, transform-free, no CLS. */
.process-step:not(:last-child) .process-step__num::after {
  content: "";
  position: absolute;
  top: calc(100% + 4px);                 /* clear the badge's white halo */
  left: 50%;
  width: 2px;
  height: var(--space-6);                /* fixed, subtle drop — never reflows */
  transform: translateX(-50%);
  background: linear-gradient(180deg,
              var(--color-accent), var(--color-accent-darker));
  border-radius: var(--radius-pill);
  opacity: 0.45;
  pointer-events: none;
}

/* DESKTOP (3-up row) -----------------------------------------------------
   The connector becomes HORIZONTAL. It is drawn on the GRID CONTAINER
   (.process-steps, which has NO overflow clipping) as its two pseudo-elements,
   each filling exactly ONE inter-card gap at badge-centre height — so the three
   badges read as nodes on one continuous line. In a grid row all cells share a
   height, so the badge centre is a fixed offset from the row top: the position
   is exact regardless of content length. The badge halos mask the join. */
.process-steps { position: relative; }
@media (min-width: 52em) {
  .process-steps { --ps-gap: var(--space-5); }
  /* hide the mobile vertical drop on desktop */
  .process-step:not(:last-child) .process-step__num::after { display: none; }

  .process-steps::before,
  .process-steps::after {
    content: "";
    position: absolute;
    z-index: 0;                          /* behind the badge nodes' halos */
    top: calc(var(--space-6) + 1.4375rem - 1px);   /* badge vertical centre */
    width: var(--ps-gap);
    height: 2px;
    background: linear-gradient(90deg,
                var(--color-accent), var(--color-accent-darker));
    border-radius: var(--radius-pill);
    opacity: 0.5;
    pointer-events: none;
  }
  /* col width = (100% - 2*gap)/3. Gap 1 starts at the end of column 1; gap 2
     starts at the end of column 2 plus the first gap. Both stay inside the row,
     so there is no overflow at any width above the breakpoint. */
  .process-steps::before {
    left: calc((100% - 2 * var(--ps-gap)) / 3);
  }
  .process-steps::after {
    left: calc(2 * (100% - 2 * var(--ps-gap)) / 3 + var(--ps-gap));
  }
}

/* ---------------------------------------------------------------------
   19.4 Reveal stagger — grid children ease in one after another
   The site uses `.reveal` on <section> wrappers and `.is-visible` (added by
   site.js) to fade the whole section up. Here we add a SMALL per-child
   transition-delay so the grid items inside a revealed section land in a gentle
   cascade rather than all at once. Scoped under `.js-reveal` (only present when
   JS runs AND reduced motion is off) and only takes effect once `.is-visible`
   is set — so with JS off or reduced motion on, every child is fully visible
   with no delay. Transform/opacity only; the section's existing reveal owns the
   actual animation, we only stagger its grid children.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  /* Grid containers that live inside a revealed section. Their direct children
     inherit the section's reveal transform/opacity but with a staggered delay.
     Children start in the section's hidden state and settle in sequence. */
  .js-reveal .reveal .trust-strip > *,
  .js-reveal .reveal .process-steps > *,
  .js-reveal .reveal .areas-list > *,
  .js-reveal .reveal .service-detail__grid > * {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 460ms var(--ease), transform 460ms var(--ease);
  }
  .js-reveal .reveal.is-visible .trust-strip > *,
  .js-reveal .reveal.is-visible .process-steps > *,
  .js-reveal .reveal.is-visible .areas-list > *,
  .js-reveal .reveal.is-visible .service-detail__grid > * {
    opacity: 1;
    transform: none;
  }
  /* Cascade: each subsequent child waits a little longer. Capped at the 8th so
     long lists (e.g. the areas chips) finish promptly and never feel sluggish. */
  .js-reveal .reveal.is-visible .trust-strip > *:nth-child(1),
  .js-reveal .reveal.is-visible .process-steps > *:nth-child(1),
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(1),
  .js-reveal .reveal.is-visible .service-detail__grid > *:nth-child(1) { transition-delay: 60ms; }
  .js-reveal .reveal.is-visible .trust-strip > *:nth-child(2),
  .js-reveal .reveal.is-visible .process-steps > *:nth-child(2),
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(2),
  .js-reveal .reveal.is-visible .service-detail__grid > *:nth-child(2) { transition-delay: 120ms; }
  .js-reveal .reveal.is-visible .trust-strip > *:nth-child(3),
  .js-reveal .reveal.is-visible .process-steps > *:nth-child(3),
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(3),
  .js-reveal .reveal.is-visible .service-detail__grid > *:nth-child(3) { transition-delay: 180ms; }
  .js-reveal .reveal.is-visible .trust-strip > *:nth-child(4),
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(4) { transition-delay: 240ms; }
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(5) { transition-delay: 300ms; }
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(6) { transition-delay: 360ms; }
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(7) { transition-delay: 420ms; }
  .js-reveal .reveal.is-visible .areas-list > *:nth-child(n+8) { transition-delay: 480ms; }
}
/* Belt-and-braces: never leave a staggered child hidden under reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .reveal .trust-strip > *,
  .js-reveal .reveal .process-steps > *,
  .js-reveal .reveal .areas-list > *,
  .js-reveal .reveal .service-detail__grid > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
/* Print: flatten any staggered children too. */
@media print {
  .reveal .trust-strip > *,
  .reveal .process-steps > *,
  .reveal .areas-list > *,
  .reveal .service-detail__grid > * {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ---------------------------------------------------------------------
   19.5 Cohesive micro-accents
   A few restrained touches that tie the set together. Nothing flashy.
   --------------------------------------------------------------------- */
/* (b) Consistent hover-lift easing on interactive cards: the cards already lift
       on hover via translateY; ensure they all share the same refined --ease
       curve and a matching focus-visible affordance so keyboard users get the
       same crisp accent edge as mouse users (the ::before edge already fills on
       :focus-within for cards that have it; here we add a clean ring for the
       whole card on keyboard focus-within without shifting layout). */
.service-card:focus-within,
.trust-item:focus-within,
.process-step:focus-within {
  outline: none;   /* the inner control keeps its own :focus-visible ring */
}

/* (c) Tasteful node-badge accent on the process timeline: gently scale the
       badge on card hover so the "node" feels alive. Transform only, gated. */
@media (prefers-reduced-motion: no-preference) {
  .process-step__num { transition: transform var(--transition), box-shadow var(--transition); }
  .process-step:hover .process-step__num,
  .process-step:focus-within .process-step__num {
    transform: scale(1.06);
  }
}

/* (d) Unify the trust-item icon with the service-card icon micro-interaction:
       a subtle scale on hover, motion-gated, to match the existing card feel. */
@media (prefers-reduced-motion: no-preference) {
  .trust-item__icon { transition: background var(--transition), color var(--transition), transform var(--transition); }
  .trust-item:hover .trust-item__icon,
  .trust-item:focus-within .trust-item__icon { transform: scale(1.04); }
}

/* =====================================================================
   20. Cross-document View Transitions + cohesive entrance/interaction polish
   Pure progressive enhancement. Browsers without the View Transitions API
   simply navigate normally; everything below is transform/opacity only and
   fully neutralised under prefers-reduced-motion, so the perfect Lighthouse
   scores and CLS 0 are preserved.
   ===================================================================== */

/* ---------------------------------------------------------------------
   20.1 Cross-document View Transitions (page-to-page crossfade)
   `navigation: auto` opts every same-origin navigation into a smooth
   default crossfade. The persistent header (and footer) get a STABLE
   view-transition-name so the browser MORPHS them across pages instead of
   cross-fading — the chrome stays put while the content swaps beneath it.
   The API already disables itself under prefers-reduced-motion; we also
   guard our custom names + animations in an explicit reduced-motion reset.
   --------------------------------------------------------------------- */
@view-transition {
  navigation: auto;
}

/* Keep the default crossfade subtle and fast. The root snapshot owns the
   page-content fade; the named header/footer morph independently. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(root) {
    animation-duration: 320ms;
    animation-timing-function: var(--ease);
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 320ms;
    animation-timing-function: var(--ease);
  }

  /* Persistent chrome morphs in place rather than flashing. Because these
     regions are visually identical across pages, the morph reads as the
     header/footer simply "staying" while the body content transitions. */
  .site-header { view-transition-name: site-header; }
  .site-footer { view-transition-name: site-footer; }

  /* Give the morphing chrome a snappy, near-instant settle so it never
     drifts or smears during the swap. */
  ::view-transition-group(site-header),
  ::view-transition-group(site-footer) {
    animation-duration: 240ms;
    animation-timing-function: var(--ease);
  }
}

/* Reduced motion: no view-transition animation, no custom names, no morph.
   Belt-and-braces on top of the API's own reduced-motion handling. */
@media (prefers-reduced-motion: reduce) {
  .site-header,
  .site-footer { view-transition-name: none; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ---------------------------------------------------------------------
   20.2 Cohesive reveal nuance by element type
   The single `.reveal` rule (17.5) gives every section a harmonious fade-up.
   Here we vary the DISTANCE/feel subtly by element kind so the entrance reads
   as intentional rather than uniform — still transform/opacity only, still
   gated, still box-reserving. Dark bands and cards settle from a gentler
   offset so heavy surfaces don't feel like they "drop". The head deliberately
   has no separate entrance — it rides the parent fade as one cohesive move.
   Never applied to anything but already-revealed boxes.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  /* The section head intentionally has NO separate entrance: the parent
     `.reveal` already carries it as part of one cohesive fade-up. A second
     opacity:0 layer here would compound multiplicatively with the parent
     (parentOpacity × childOpacity) and stack transforms, making the heading
     read as a laggy "fade within a fade" that desyncs from its own section.
     One owner, one entrance. */

  /* Heavy dark surfaces (service-area band, contact card) ease from a calmer,
     shorter offset so they feel grounded, not bouncy. */
  .js-reveal .reveal .service-area,
  .js-reveal .reveal .contact-card {
    transform: translateY(10px);
    transition: opacity 560ms var(--ease), transform 560ms var(--ease);
  }
}
/* Reduced motion: flatten the per-type nuance too. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .reveal .service-area,
  .js-reveal .reveal .contact-card {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
@media print {
  .reveal .service-area,
  .reveal .contact-card { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------------
   20.3 Restrained interaction touches
   A few small "alive" details that match the existing hover language. All
   transform/opacity, all motion-gated, none affecting layout or focus rings.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  /* (a) The hover "sheen sweep" on primary CTAs was REMOVED. Giving
         .btn--primary position:relative + overflow:hidden + a pseudo-element
         caused Chrome to DROP the button's gradient fill whenever the button
         sat inside a composited .reveal section — leaving white label text on a
         white fill (an unreadable call-to-action). The buttons keep their solid
         gradient fill + hover lift, which is clean and rock-solid; the flourish
         wasn't worth the rendering risk. */

  /* (b) Footer link arrow nudge already exists; mirror the same crisp feel on
         contact-card rows so the dark surfaces share one interaction grammar.
         The base icon already transitions colour/border — we layer transform on
         top here (gated) so the nudge eases rather than snaps. */
  .contact-item__icon {
    transition: background var(--transition), color var(--transition),
                border-color var(--transition), transform var(--transition);
  }
  .contact-item:hover .contact-item__icon { transform: translateY(-1px); }
}

/* Reduced motion: kill the contact-icon nudge. */
@media (prefers-reduced-motion: reduce) {
  .contact-item:hover .contact-item__icon { transform: none; }
}

/* =====================================================================
   Interactive features (progressive enhancement — all no-JS safe)
   Scroll-progress bar, animated stat count-up, services live filter,
   and the quote-form smart helper. Vanilla CSS, on-brand, offline.
   ===================================================================== */

/* ---- Scroll progress bar (injected by site.js on every page) ------- */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-darker));
  z-index: 150;            /* above the header (100), below the lightbox (200) */
  pointer-events: none;
  will-change: transform;
}

/* ---- Services live filter (services.html capabilities section) ----- */
.service-filter {
  max-width: 34rem;
  margin: 0 auto var(--space-6);
}
.service-filter__field { position: relative; }
.service-filter__icon {
  position: absolute;
  left: var(--space-4);
  top: 50%;
  transform: translateY(-50%);
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-ink-faint);
  pointer-events: none;
}
.service-filter__icon svg { width: 100%; height: 100%; display: block; }
.service-filter__input {
  width: 100%;
  font: inherit;
  font-size: var(--fs-400);
  color: var(--color-ink);
  background: var(--color-bg);
  border: 1px solid var(--color-border-2);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-xs);
  padding: 0.7rem 3rem 0.7rem calc(var(--space-4) * 2 + 1.1rem);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.service-filter__input::placeholder { color: var(--color-ink-faint); }
.service-filter__input:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 1px;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-soft);
}
/* Touch-reachable clear (x) button — shown by site.js only when there's text. */
.service-filter__clear {
  position: absolute;
  right: 0.3rem;
  top: 50%;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
  display: inline-grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--color-ink-faint);
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.service-filter__clear svg { width: 1.05rem; height: 1.05rem; }
.service-filter__clear:hover { color: var(--color-ink); }
.service-filter__clear:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: -2px;
  color: var(--color-accent);
  box-shadow: none;
}
.service-filter__clear[hidden] { display: none; }
.service-filter__count {
  margin: var(--space-2) 0 0;
  text-align: center;
  font-size: var(--fs-300);
  color: var(--color-ink-soft);
  min-height: 1.25rem;
}
.services-empty {
  text-align: center;
  color: var(--color-ink-soft);
  font-size: var(--fs-500);
  padding: var(--space-6) 0;
}
/* A filtered-out card: `.service-card` sets display:flex (an author rule that
   out-ranks the UA [hidden] rule), so hiding needs this explicit override. */
.service-card[hidden] { display: none; }

/* ---- Quote-form smart helper (estimate.html) -------------------------- */
.field-tip {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-300);
  line-height: var(--lh-base);
  color: var(--color-ink-soft);
  background: var(--color-accent-soft);
  border: 1px solid #bfdbfe;
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius);
}
.field-tip[hidden] { display: none; }
/* The tip stays in the accessibility tree (live region) and is driven by
   text content only — collapse it fully whenever it is empty */
.field-tip:empty { display: none; }

/* =====================================================================
   "TROWEL & SLATE" PREMIUM ELEVATION
   Appended so it layers over the base rules by source order. Additive
   polish only: NO brand colour value changed, NO JS behaviour touched,
   hero <canvas> untouched. Lit card surfaces, a hairline "kicker" eyebrow
   voice, oversized accent numerals, section depth + a dark CTA plinth.
   ===================================================================== */

/* ---- Eyebrow "kicker": one crafted type voice across hero + sections - */
.section__eyebrow { letter-spacing: var(--tracking-eyebrow); }
.section__eyebrow::before { background: linear-gradient(90deg, var(--color-accent), transparent); }
.section__head--center .section__eyebrow::after { background: linear-gradient(90deg, transparent, var(--color-accent)); }

/* ---- Lit, physical card surfaces (key-light + hairline + hover ring) - */
.service-card,
.trust-item,
.process-step,
.stat {
  border: var(--hairline);
  box-shadow: var(--shadow-craft);
}
.service-card:hover,
.service-card:focus-within,
.trust-item:hover,
.process-step:hover,
.stat:hover {
  border-color: #bfdbfe;
  box-shadow: var(--shadow-craft-hover), var(--accent-ring);
}
.service-card::before { height: 4px; }               /* thicker signature accent top-edge */

/* Icon tiles: dimensional resting state; hover flip to solid stays. */
.service-card__icon,
.trust-item__icon {
  background: var(--accent-tile-grad);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
}

/* ---- Precise, oversized numerals (spec-sheet feel) ------------------ */
.stat__num {
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.5rem, 1.9rem + 2.6vw, 3.25rem);
  line-height: 1;
}
.process-step__num { font-variant-numeric: tabular-nums; }

/* ---- Section depth: lit surface bands + premium dark CTA plinth ----- */
.section--surface {
  background: var(--surface-grad);
  border-top: var(--hairline);
}
.service-area {
  border-radius: var(--radius-lg);
  /* Match the primary-button fill so the CTA band reads as the SAME blue as the
     rest of the site (was flat --color-accent #2563eb, which looked brighter).
     Solid colour first as a guaranteed fallback, gradient layered on top. */
  background-color: var(--color-accent-dark);
  background-image: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  box-shadow: var(--shadow-accent-lg), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.service-area__text { color: #fff; }          /* white body copy keeps AA on the solid blue */
.service-area__chip { color: #fff; background: rgba(15, 23, 42, 0.25); }

/* ---- Component detailing: buttons, secondary, form fields ----------- */
.btn--primary { box-shadow: var(--shadow-accent), inset 0 1px 0 rgba(255, 255, 255, 0.25); }
.btn--primary:hover { box-shadow: var(--shadow-accent-lg), inset 0 1px 0 rgba(255, 255, 255, 0.28); }
.btn--secondary { border: var(--hairline-strong); box-shadow: var(--shadow-craft); }
.input,
.select,
.textarea {
  border: var(--hairline-strong);
  box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.05);
}

/* ---- Areas chips: refined lit pill ---------------------------------- */
.areas-list__chip { box-shadow: var(--shadow-xs), inset 0 1px 0 rgba(255, 255, 255, 0.7); }

/* ---- Hero: two-tone accent clause --------------------------------- */
.home .hero__title {
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}
.hero__title-accent { color: #93c5fd; }              /* light blue, AA over the dark scrim */
/* (The "blueprint plate" hero__media/.media-note styling left with the
   plate — restore from git history alongside the markup if it returns.) */

/* ---- Bigger lead measure/scale (kept inside the existing cap) ------- */
.hero__lead,
.section__lead { font-size: var(--fs-fluid-500); }

/* =====================================================================
   18b. AI CHAT ASSISTANT  (chatbot.js — shared, every page)
   A free, on-site assistant. Blue/white theme, reuses the design tokens.
   Stacking: launcher/panel sit above the header (100) and call-bar (90)
   but BELOW the lightbox (200), so gallery.js still covers them. The
   back-to-top (#to-top, z-index 85) is nudged up to stack above the
   launcher using ID specificity (1,0,0) which wins over the JS-injected
   `.to-top` (0,1,0) — same documented technique used at §17.6b.
   ===================================================================== */

/* ---- Launcher (FAB) ------------------------------------------------- */
.db-chat__launcher {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 150;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  height: 3.5rem;
  padding: 0 1.15rem 0 1rem;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-pill);
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-darker) 100%);
  color: #fff;
  font-weight: 700;
  font-size: var(--fs-400);
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-accent-lg);
  transition: transform var(--transition), box-shadow var(--transition),
              background var(--transition-fast);
}
.db-chat__launcher:hover {
  background: linear-gradient(160deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%);
  box-shadow: var(--shadow-accent-lg), 0 0 0 4px rgba(37, 99, 235, 0.16);
}
.db-chat__launcher:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}
.db-chat__launcher-icon { width: 1.4rem; height: 1.4rem; flex: none; }
.db-chat__launcher-label { white-space: nowrap; }

/* A gentle, finite attention pulse (rests after a few cycles; off when the
   panel is open or the user prefers reduced motion). */
.db-chat__launcher::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.45);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .db-chat:not(.is-open) .db-chat__launcher::after {
    animation: db-chat-pulse 2.4s var(--ease) 1.2s 3;
  }
}
@keyframes db-chat-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.40); }
  70%  { box-shadow: 0 0 0 12px rgba(37, 99, 235, 0); }
  100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0); }
}
@media (prefers-reduced-motion: no-preference) {
  .db-chat__launcher:hover { transform: translateY(-2px); }
}

/* ---- Panel ---------------------------------------------------------- */
.db-chat__panel {
  position: fixed;
  right: 1.25rem;
  bottom: 5.5rem;              /* clears the 3.5rem launcher + gap */
  z-index: 160;
  display: flex;
  flex-direction: column;
  width: min(23.75rem, calc(100vw - 2.5rem));
  height: min(35rem, calc(100vh - 8rem));
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
  transform-origin: bottom right;
  /* hidden until opened */
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px) scale(0.98);
  transition: opacity var(--transition), transform var(--transition),
              visibility var(--transition);
}
.db-chat.is-open .db-chat__panel {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* ---- Header --------------------------------------------------------- */
.db-chat__header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.85rem 1rem;
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-darker) 100%);
  color: #fff;
}
.db-chat__avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.30);
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
}
.db-chat__header-text { flex: 1 1 auto; min-width: 0; }
.db-chat__title { margin: 0; font-weight: 800; font-size: var(--fs-500); line-height: 1.15; }
.db-chat__subtitle {
  margin: 0;
  font-size: var(--fs-300);
  color: rgba(255, 255, 255, 0.92);   /* AA on the lighter end of the header gradient */
  line-height: 1.2;
}

/* Visually-hidden (screen-reader only) — used by the typing announcement. */
.db-chat__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.db-chat__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.db-chat__close:hover { background: rgba(255, 255, 255, 0.24); }
.db-chat__close:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.db-chat__close-icon { width: 1.15rem; height: 1.15rem; }

/* ---- Message log ---------------------------------------------------- */
.db-chat__log {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  background: var(--color-surface);
  scroll-behavior: smooth;
  overscroll-behavior: contain;
}
.db-chat__msg { display: flex; }
.db-chat__msg--bot { justify-content: flex-start; }
.db-chat__msg--user { justify-content: flex-end; }
.db-chat__bubble {
  max-width: 85%;
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  font-size: var(--fs-400);
  line-height: 1.5;
  box-shadow: var(--shadow-xs);
}
.db-chat__msg--bot .db-chat__bubble {
  background: var(--color-bg);
  color: var(--color-ink);
  border: 1px solid var(--color-border);
  border-bottom-left-radius: var(--radius-sm);
}
.db-chat__msg--user .db-chat__bubble {
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  color: #fff;
  border-bottom-right-radius: var(--radius-sm);
}
.db-chat__p { margin: 0; }
.db-chat__p + .db-chat__p { margin-top: 0.5rem; }

/* Action links inside a bot bubble */
.db-chat__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
}
.db-chat__action {
  display: inline-flex;
  align-items: center;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-300);
  font-weight: 700;
  text-decoration: none;
  border: 1px solid var(--color-border-2);
  background: var(--color-bg);
  color: var(--color-accent-dark);
  transition: background var(--transition-fast), border-color var(--transition-fast),
              color var(--transition-fast);
}
.db-chat__action:hover {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
  color: var(--color-accent-darker);
}
.db-chat__action--primary {
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  border-color: var(--color-accent-dark);
  color: #fff;
  box-shadow: var(--shadow-accent);
}
.db-chat__action--primary:hover {
  background: linear-gradient(160deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%);
  border-color: var(--color-accent-darker);
  color: #fff;
}
.db-chat__action:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

/* ---- Quick-reply chips --------------------------------------------- */
.db-chat__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.15rem;
}
.db-chat__chip {
  display: inline-flex;
  align-items: center;
  padding: 0.45rem 0.8rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-300);
  font-weight: 600;
  font-family: inherit;
  text-decoration: none;
  color: var(--color-accent-dark);
  background: var(--color-bg);
  border: 1px solid var(--color-border-2);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast),
              color var(--transition-fast);
}
.db-chat__chip:hover {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
  color: var(--color-accent-darker);
}
.db-chat__chip:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

/* ---- Typing indicator ---------------------------------------------- */
.db-chat__bubble--typing {
  display: inline-flex;
  gap: 0.28rem;
  align-items: center;
  padding: 0.7rem 0.85rem;
}
.db-chat__dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--color-ink-faint);
  opacity: 0.5;
}
@media (prefers-reduced-motion: no-preference) {
  .db-chat__dot { animation: db-chat-bounce 1.2s var(--ease) infinite; }
  .db-chat__dot:nth-child(2) { animation-delay: 0.15s; }
  .db-chat__dot:nth-child(3) { animation-delay: 0.30s; }
}
@keyframes db-chat-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-3px); opacity: 0.9; }
}

/* ---- Composer ------------------------------------------------------- */
.db-chat__composer {
  flex: none;
  padding: 0.6rem 0.7rem 0.7rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-bg);
}
.db-chat__input-wrap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.db-chat__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 2.75rem;
  padding: 0 0.9rem;
  font-size: var(--fs-400);          /* 16px — no iOS zoom on focus */
  color: var(--color-ink);
  background: var(--color-surface);
  border: var(--hairline-strong);
  border-radius: var(--radius-pill);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.db-chat__input::placeholder { color: var(--color-ink-faint); }
.db-chat__input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--accent-ring);
  background: var(--color-bg);
}
.db-chat__input:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 1px;
  border-color: var(--color-focus);
}
.db-chat__send {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--color-accent-dark);
  border-radius: var(--radius-pill);
  background: linear-gradient(160deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
  color: #fff;
  cursor: pointer;
  box-shadow: var(--shadow-accent);
  transition: background var(--transition-fast);
}
.db-chat__send:hover { background: linear-gradient(160deg, var(--color-accent-dark) 0%, var(--color-accent-darker) 100%); }
.db-chat__send:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 2px; }
.db-chat__send-icon { width: 1.2rem; height: 1.2rem; }
.db-chat__note {
  margin: 0.5rem 0.25rem 0;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--color-ink-faint);
  text-align: center;
}

/* ---- Scrollbar (subtle) -------------------------------------------- */
.db-chat__log { scrollbar-width: thin; scrollbar-color: var(--color-border-2) transparent; }
.db-chat__log::-webkit-scrollbar { width: 8px; }
.db-chat__log::-webkit-scrollbar-thumb {
  background: var(--color-border-2);
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-surface);
}

/* ---- Hide while the mobile nav menu is open (mirrors call-bar) ------ */
.db-chat.db-chat--nav-open { display: none; }

/* ---- Stack the back-to-top button ABOVE the launcher (desktop) ----- *
   #to-top (ID, 1,0,0) beats the JS-injected `.to-top` (0,1,0). We only
   adjust position to avoid overlap — never the show/hide logic. */
#to-top { bottom: 5.5rem; right: 1.25rem; }

/* ---- While the chat panel is open, get the other FABs out of the way */
body.db-chat-active #to-top { display: none !important; }

/* ---- Mobile / tablet (below the nav breakpoint) -------------------- */
@media (max-width: 47.99em) {
  /* Launcher sits above the fixed mobile call-bar (~4.25rem + safe area) */
  .db-chat__launcher {
    bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
    right: 1rem;
    height: 3.25rem;
    padding: 0;
    width: 3.25rem;
    justify-content: center;
  }
  .db-chat__launcher-label { display: none; }   /* circular FAB on phones */

  /* The open bottom sheet covers the launcher — take it out of the tab order */
  .db-chat.is-open .db-chat__launcher { display: none; }

  /* Back-to-top stacks above the launcher on mobile too */
  #to-top { bottom: calc(8.75rem + env(safe-area-inset-bottom, 0px)); }

  /* Landscape phones: three stacked FABs would cover half the short viewport —
     drop the least essential one (scrolling back up is cheap at this height).
     Call bar + chat launcher stay. */
  @media (max-width: 47.99em) and (max-height: 30em) { #to-top { display: none; } }

  /* Panel becomes a near-full-height bottom sheet */
  .db-chat__panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    height: min(86vh, 40rem);
    height: min(86svh, 40rem);   /* small-viewport unit: header stays visible with the iOS toolbar expanded (vh line above is the fallback) */
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: 0;
    transform-origin: bottom center;
    transform: translateY(16px);
  }
  .db-chat.is-open .db-chat__panel { transform: none; }

  /* Bottom sheet sits at bottom:0 (call bar hidden) — keep the send row
     clear of the iOS home indicator */
  .db-chat__composer { padding-bottom: calc(0.7rem + env(safe-area-inset-bottom, 0px)); }

  /* With the sheet open, hide the call-bar so it can't peek through */
  body.db-chat-active .call-bar { display: none !important; }
}

/* ---- Reduced motion: no slides, pulses or bounce ------------------- */
@media (prefers-reduced-motion: reduce) {
  .db-chat__panel { transition: opacity 1ms linear, visibility 1ms linear; transform: none; }
  .db-chat.is-open .db-chat__panel { transform: none; }
  .db-chat__launcher { transition: none; }
}

/* ---- Print: never show the assistant ------------------------------- */
@media print {
  .db-chat { display: none !important; }
}

/* =====================================================================
   19b. SCROLL-DRIVEN MOTION  (home page only)
   A pure-CSS progressive enhancement layered on top of the existing
   .reveal fade-up. Compositor-driven (no scroll handlers), so it costs
   nothing when idle. Gated three ways so it can only ever ADD polish:
     • @supports (animation-timeline) — older browsers get today's behaviour
     • prefers-reduced-motion: no-preference — motion-safe users only
     • scoped to .home — never touches the other pages
   IMPORTANT: animates the individual `translate`/`scale` properties (NOT
   `transform`), leaving `transform` free for the existing hover lifts so a
   finished scroll animation can't pin a card flat and break its :hover.
   ===================================================================== */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* NOTE: the hero parallax that used to live here (scroll(root) on
       .hero__content/.hero__media) moved to the JS engine in site.js
       (initParallax) so it works in EVERY browser, Firefox included.
       Keeping both would double-move the hero — don't re-add it here. */

    /* --- Cards & blocks rise + settle into place as they scroll into view.
       Items sharing a row enter together, so rows stagger naturally. The
       .reveal section still owns the fade; this adds the motion. --- */
    .home .services-grid > li,
    .home .trust-strip > li,
    .home .process-steps > li,
    .home .workstrip > li {
      animation: db-rise-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 92%;
    }

    /* The big CTA band gets a gentler pop. */
    .home .service-area {
      animation: db-rise-soft linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 88%;
    }
  }
}

@keyframes db-rise-in {
  from { translate: 0 48px; scale: 0.965; }
  to   { translate: 0 0;    scale: 1; }
}
@keyframes db-rise-soft {
  from { translate: 0 34px; scale: 0.985; }
  to   { translate: 0 0;    scale: 1; }
}

/* =====================================================================
   20b. QUOTE-FORM UPGRADES  (photo upload, area checker, next-steps)
   ===================================================================== */
.field__optional { font-weight: 600; color: var(--color-ink-faint); font-size: var(--fs-300); }

.input--file {
  padding: 0.55rem 0.7rem;
  background: var(--color-surface);
  cursor: pointer;
}
.input--file::file-selector-button {
  margin-right: 0.75rem;
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--color-border-2);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--color-accent-dark);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.input--file::file-selector-button:hover {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
}

/* Advisory "do we cover your area?" reassurance under the postcode field */
.field__area {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-300);
  font-weight: 600;
  padding: 0.4rem 0.65rem;
  border-radius: var(--radius-sm);
}
.field__area:empty { display: none; }
.field__area.is-yes   { color: var(--color-success);    background: #f0fdf4; border: 1px solid #bbf7d0; }
.field__area.is-maybe { color: var(--color-accent-dark); background: var(--color-accent-soft); border: 1px solid #bfdbfe; }

/* "What happens next" aid in the quote-page contact aside */
.next-steps {
  margin-top: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
}
.next-steps__title { font-size: var(--fs-500); font-weight: 800; color: var(--color-ink); margin: 0 0 var(--space-2); }
.next-steps__list {
  margin: 0;
  padding-left: 1.2rem;
  color: var(--color-ink-soft);
  display: grid;
  gap: var(--space-2);
  line-height: var(--lh-snug);
}
.next-steps__list span { font-weight: 700; color: var(--color-ink); }

/* =====================================================================
   21. GALLERY "ILLUSTRATIVE" BADGE  (honesty: these are stock examples,
   not photos of Phil's own completed jobs)
   ===================================================================== */
.gallery__btn { position: relative; }
.gallery__tag,
.ba-slider__tag {
  position: absolute;
  top: 0.55rem;
  left: 0.55rem;
  z-index: 3;
  padding: 0.2rem 0.55rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(15, 23, 42, 0.80);
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: var(--radius-pill);
  pointer-events: none;
}
.ba-slider__tag { z-index: 4; }

/* =====================================================================
   22. SITE REHAUL — parallax layers, work strip, tilt, page transitions
   All motion is triple-gated (reduced-motion, pointer type, @supports)
   and every piece degrades to the previous design when unsupported.
   ===================================================================== */

/* ---- Decorative parallax floaters (home) ----------------------------
   Absolutely-positioned, aria-hidden, pointer-events:none "blueprint"
   depth layers that initParallax (site.js) drifts at their own speeds.
   Desktop-only; the host section clips them so they can never cause
   horizontal overflow. Nearly transparent by design. */
.section.has-fx {
  position: relative;
  overflow: clip;
}
.fx {
  position: absolute;
  z-index: 0;
  display: none;
  pointer-events: none;
  border-radius: var(--radius-pill);
}
@media (min-width: 60em) {
  .fx { display: block; }
}
.fx--glow-a {
  width: 30rem; height: 30rem;
  top: -8rem; right: -9rem;
  background: radial-gradient(circle at 40% 40%, rgba(37, 99, 235, 0.11), transparent 64%);
}
.fx--glow-b {
  width: 24rem; height: 24rem;
  bottom: -7rem; left: -8rem;
  background: radial-gradient(circle at 55% 45%, rgba(37, 99, 235, 0.10), transparent 64%);
}
.fx--grid {
  width: 22rem; height: 16rem;
  top: 3rem; left: -6rem;
  border-radius: var(--radius-lg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='28' viewBox='0 0 56 28'%3E%3Cg fill='none' stroke='%232563eb' stroke-opacity='0.55' stroke-width='1'%3E%3Cpath d='M0 .5h56M0 14.5h56M0 27.5h56M.5 0v14M28.5 0v14M14.5 14v14M42.5 14v14'/%3E%3C/g%3E%3C/svg%3E");
  opacity: 0.16;
  -webkit-mask-image: radial-gradient(ellipse at center, #000 30%, transparent 72%);
  mask-image: radial-gradient(ellipse at center, #000 30%, transparent 72%);
}

/* ---- "The kind of work we do" photo strip (home) -------------------- */
.workstrip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 48em) {
  .workstrip { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); }
}
.workstrip__item { position: relative; }
.workstrip__link {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  border: var(--hairline);
  box-shadow: var(--shadow-craft);
  background: var(--color-dark);
  transition: box-shadow var(--transition);
}
.workstrip__link:hover { box-shadow: var(--shadow-craft-hover), var(--accent-ring); }
.workstrip__link:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 3px; }
.workstrip__link img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  display: block;
  transition: transform 400ms var(--ease);
}
@media (prefers-reduced-motion: no-preference) {
  .workstrip__link:hover img { transform: scale(1.045); }
}
.workstrip__label {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 2rem var(--space-4) var(--space-3);
  color: #fff;
  font-weight: 700;
  background: linear-gradient(180deg, transparent, rgba(7, 11, 20, 0.85));
}
.workstrip__cta { text-align: center; margin: var(--space-6) 0 0; }

/* ---- 3D card tilt helper (JS sets the transform) --------------------
   NOTE: this shorthand overrides the cards' own transition lists (equal
   specificity, later in the file), so it must carry every property those
   lists ease — including border-color — or hover tints snap. */
.has-tilt {
  transition: transform 180ms var(--ease), box-shadow var(--transition),
              border-color var(--transition);
}

/* ---- Services jump-pill scrollspy active state ----------------------- */
.services-jump__link.is-active {
  background: var(--color-accent);
  border-color: var(--color-accent-dark);
  color: #fff;
  box-shadow: var(--shadow-accent);
}
.services-jump__link.is-active:hover { color: #fff; background: var(--color-accent-dark); }

/* ---- NO primary-CTA sheen, NO second view-transition block here ------
   Both were added in an early §22 draft and REMOVED after review:
   • the sheen (position:relative + overflow:hidden + ::after on
     .btn--primary) re-introduced the exact pattern §20.3(a) documents as
     causing Chrome to drop the button's gradient inside composited .reveal
     sections (white-on-white CTA). §20.3(a)'s decision stands.
   • cross-document view transitions are ALREADY implemented in §20.1
     (header + footer morph, reduced-motion resets). One owner only. */

/* ---- Smooth FAQ open/close (progressive; Chrome 129+) ---------------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (interpolate-size: allow-keywords) {
    :root { interpolate-size: allow-keywords; }
    .faq__item::details-content {
      block-size: 0;
      overflow: clip;
      transition: block-size 280ms var(--ease), content-visibility 280ms allow-discrete;
    }
    .faq__item[open]::details-content { block-size: auto; }
  }
}

/* =====================================================================
   23. CONVERSION POLISH — header phone, hero CTA hierarchy, contextual
   service links & per-service quote CTAs. Honesty-safe copy throughout.
   ===================================================================== */

/* Always-visible phone in the header (desktop row + inside the mobile
   menu). accent-dark on white = ~5.9:1 AA. */
.primary-nav__phone {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 800;
  color: var(--color-accent-dark);
  white-space: nowrap;
}
.primary-nav__phone svg { width: 1.05rem; height: 1.05rem; flex: none; }
/* Icon inside a hero CTA button (quote hero call button) */
.hero__actions .btn svg { width: 1.05rem; height: 1.05rem; flex: none; }
.primary-nav__phone:hover { color: var(--color-accent-darker); }
/* On tight desktop widths drop the phone from the row so the nav never
   wraps — the mobile menu (<48em) and wide desktops (>=64em) keep it. */
@media (min-width: 48em) and (max-width: 63.99em) {
  .primary-nav__phone-item { display: none; }
}

/* Hero: quiet call-Phil line under the (now two) CTA buttons. #93c5fd on
   the dark scrim is the established hero-accent pairing (AA). */
.hero__phone {
  margin-top: var(--space-4);
  color: var(--color-on-dark);
  font-weight: 600;
}
.hero__phone a {
  color: #93c5fd;
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.hero__phone a:hover { color: #dbeafe; }

/* Homepage service cards: contextual "More about X" link. */
.service-card__more {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: var(--space-3);
  font-size: var(--fs-300);
  font-weight: 700;
  color: var(--color-accent-dark);
  text-decoration: none;
}
.service-card__more::after { content: "→" / ""; }
.service-card__more:hover,
.service-card__more:focus-visible { color: var(--color-accent-darker); text-decoration: underline; }
@media (prefers-reduced-motion: no-preference) {
  .service-card__more::after { transition: translate var(--transition-fast); }
  .service-card__more:hover::after { translate: 3px 0; }
}

/* Services page: per-service quote CTA row at the end of each detail. */
.service-detail__cta {
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
}
.service-detail__cta-note { color: var(--color-ink-faint); font-size: var(--fs-300); }

/* =====================================================================
   24. PHOTO HERO BACKDROP  (home page — replaces the brick canvas there)
   A dimmed kitchen photo with three composed motion layers (see the
   index.html comment / initHeroBg). The hero keeps its solid dark
   background as the no-image fallback, and the content card keeps its
   own scrim, so text contrast never depends on the photo.
   ===================================================================== */
.hero-bg {
  position: absolute;
  left: 0;
  right: 0;
  top: -64px;                 /* slack so the scroll-parallax drift (max 52px) */
  bottom: -64px;              /* never reveals an edge inside the hero        */
  z-index: 0;                 /* behind .hero__inner (z-index: 1) */
  overflow: hidden;
  pointer-events: none;
}
.hero-bg__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 60%;
  /* Zoom slack for the cursor pan (±22px) — composes with the JS-written
     `translate` and the keyframed `scale` below (individual properties). */
  scale: 1.12;
}
/* Slow ambient "Ken Burns" zoom — desktop fine-pointer + motion-allowed only
   (an infinite animation is cheap on the compositor but phones stay static,
   matching the brick canvas's battery policy). */
@media (prefers-reduced-motion: no-preference) and (hover: hover) and (pointer: fine) {
  .hero-bg__img { animation: hero-kb 26s var(--ease) infinite alternate; }
}
@keyframes hero-kb {
  from { scale: 1.12; }
  to   { scale: 1.17; }
}
/* The cursor "work light" — a soft blue glow that follows the pointer
   (initHeroBg drives --gx/--gy). At rest it idles up-right of centre. */
.hero-bg__glow {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    46rem circle at var(--gx, 60%) var(--gy, 40%),
    rgba(147, 197, 253, 0.18),
    transparent 65%
  );
  mix-blend-mode: screen;
}
/* Contrast dim: heavier over the text column (left) and the base, lighter
   where the photo should read through. AA is still owned by the scrim. */
.hero-bg__dim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(7, 11, 20, 0.62) 0%, rgba(7, 11, 20, 0.30) 55%, rgba(7, 11, 20, 0.45) 100%),
    linear-gradient(0deg, rgba(7, 11, 20, 0.55) 0%, rgba(7, 11, 20, 0.10) 40%);
}

/* ---- Hover edge-trace ("blueprint" reveal) ---------------------------
   A pre-computed white edge-map of the kitchen photo sits above the dim,
   hidden until the pointer is over the hero (.is-lit). A radial mask
   following the cursor (--gx/--gy, written on .hero-bg by initHeroBg)
   reveals the contours around the mouse — the kitchen's outlines light
   up white as you trace them. Shares .hero-bg__img so the pan translate
   + ambient `scale` zoom keep it in exact register with the photo.
   Browsers without mask-image simply fade the whole trace in on hover. */
.hero-bg__img--edges {
  position: absolute;
  inset: 0;
  z-index: 1;              /* above the dim overlay so the lines read bright */
  opacity: 0;
  transition: opacity 380ms var(--ease);
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(
    24rem circle at var(--gx, 60%) var(--gy, 40%),
    #000 18%, rgba(0, 0, 0, 0.5) 48%, transparent 72%
  );
  mask-image: radial-gradient(
    24rem circle at var(--gx, 60%) var(--gy, 40%),
    #000 18%, rgba(0, 0, 0, 0.5) 48%, transparent 72%
  );
}
.home .hero.is-lit .hero-bg__img--edges { opacity: 0.95; }

/* =====================================================================
   26. SERVICE-AREA MAP  (home "Areas we cover" — inline SVG)
   Real satellite imagery with town markers at real relative positions
   and a pulsing home marker. Decorative (aria-hidden in the markup);
   the chips below carry the same list accessibly. All motion is
   reduced-motion gated.
   ===================================================================== */
.area-map {
  max-width: 42rem;
  margin: 0 auto var(--space-6);
}
.area-map svg {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  background:
    radial-gradient(120% 120% at 78% 12%, rgba(37, 99, 235, 0.28), transparent 58%),
    var(--color-dark);
}
/* Satellite base layer (real Sentinel-2 imagery, same bbox as the
   projection) — shown untouched; labels get halos for legibility. */
.amap__credit {
  fill: rgba(226, 232, 240, 0.92);
  font-size: 6px;
  letter-spacing: 0.02em;
  paint-order: stroke;
  stroke: rgba(7, 11, 20, 0.85);
  stroke-width: 1.5px;
  stroke-linejoin: round;
}
.amap__context circle {
  fill: none;
  stroke: #cbd5e1;
  stroke-width: 1.2;
}
.amap__context text {
  fill: #e2e8f0;
  font-size: 8.5px;
  font-style: italic;
  paint-order: stroke;
  stroke: rgba(7, 11, 20, 0.9);
  stroke-width: 2.5px;
  stroke-linejoin: round;
}
.amap__dot {
  fill: #60a5fa;
  stroke: rgba(255, 255, 255, 0.85);
  stroke-width: 1.3;
  transition: transform 180ms var(--ease), fill 180ms var(--ease);
  transform-box: fill-box;
  transform-origin: center;
}
.amap__town:hover .amap__dot { transform: scale(1.5); fill: #93c5fd; }
.amap__town:hover .amap__label { fill: #fff; }
.amap__dot--home { fill: var(--color-accent); stroke: #fff; stroke-width: 1.6; }
/* Sonar ping on the home marker (motion-gated). */
.amap__pulse {
  fill: none;
  stroke: #60a5fa;
  stroke-width: 1.5;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
@media (prefers-reduced-motion: no-preference) {
  .amap__pulse { animation: amap-pulse 2.6s var(--ease) infinite; }
}
@keyframes amap-pulse {
  0%   { transform: scale(0.5); opacity: 0.8; }
  70%  { transform: scale(2.4); opacity: 0; }
  100% { transform: scale(2.4); opacity: 0; }
}
/* Labels get a navy halo (paint-order) so they stay crisp over the river. */
.amap__label {
  fill: #e2e8f0;
  font-size: 10px;
  font-weight: 600;
  paint-order: stroke;
  stroke: rgba(7, 11, 20, 0.9);
  stroke-width: 3px;
  stroke-linejoin: round;
  transition: fill 180ms var(--ease);
}
.amap__label--home { fill: #fff; font-size: 11px; font-weight: 800; }
.amap__sub {
  fill: #93c5fd;
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  paint-order: stroke;
  stroke: rgba(7, 11, 20, 0.9);
  stroke-width: 2.5px;
  stroke-linejoin: round;
}
.amap__region circle {
  fill: rgba(147, 197, 253, 0.10);
  stroke: #93c5fd;
  stroke-width: 1;
  stroke-dasharray: 2 3;
}
.amap__label--region { fill: #93c5fd; font-style: italic; font-weight: 600; }

/* Phones: the fixed SVG text sizes scale down by ~0.86 at 375px — bump them
   so the 19 town names stay legible. Clearances checked against the tightest
   label pairs; the paint-order halo isolates labels from the satellite base. */
@media (max-width: 40em) {
  .amap__label { font-size: 11.5px; }
  .amap__label--home { font-size: 12.5px; }
  .amap__sub { font-size: 8.5px; }
  .amap__context text { font-size: 9.5px; }
}

/* ---- Service landing pages: breadcrumb on the dark hero band ---------
   Sits inside the .hero--inner scrim card; light-blue links match the
   established on-dark link pairing, incl. the dark-band focus ring. */
.breadcrumb {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: var(--space-4);
  font-size: var(--fs-300);
  font-weight: 600;
  color: var(--color-on-dark-mut);
}
.hero--inner .breadcrumb a { color: #93c5fd; text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
/* (focus ring: covered by the central dark-band outline list in §4) */
.breadcrumb [aria-current] { color: var(--color-on-dark); }
/* Bigger tap targets without moving anything: padding grows the hit box to
   ~40px tall, the matching negative margin cancels the visual shift. */
.breadcrumb a { display: inline-block; padding: 0.55rem 0.3rem; margin: -0.55rem -0.3rem; border-radius: var(--radius-sm); }

/* ---- Home hero layout lives in ONE place --------------------------
   The full-viewport, single-column geometry (display:grid, min-height,
   grid-template-columns:1fr, content max-width, lead measure) is all in
   the "HERO: full-viewport photo band" section — not here. NOTE: the
   title keeps the standard --fs-fluid-900 scale (a larger display
   override was tried and reverted at the owner's request). */
