/* ===== fonts.css ===== */
/* Self-hosted variable font. Inter is used for both body and display
   from 2026-05-14 onwards (Fraunces removed; see tokens.css comment).
   Per-fork clients can override --font-display in their tokens.css if
   they prefer a serif display, and re-add a Fraunces.woff2 (or another
   font file) to src/fonts/ with a matching @font-face declaration. */

/* Display serif for this fork: EB Garamond variable (wght 400-800), the
   closest free classical serif to the client's own Minion-flavoured lockup.
   Self-hosted, latin subset. Body stays Inter. */
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/EBGaramondVariable.woff2') format('woff2-variations'),
       url('/fonts/EBGaramondVariable.woff2') format('woff2');
}

/* Inter ships as a latin subset (r19, 15 Sept 2026): Basic Latin, Latin-1,
   general punctuation, the arrows, star and tick the templates use, both axes
   (opsz and wght) kept, tabular figures kept. 95KB against 352KB for the full
   file, which covered Cyrillic, Greek and Vietnamese this site never sets.
   Regenerate from the upstream InterVariable.woff2 with fontTools:
   python3 -m fontTools.subset InterVariable.woff2 --flavor=woff2
     --unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+20AC,U+2122,U+2190-2199,U+2212,U+2215,U+2605-2606,U+2713-2714,U+FEFF,U+FFFD"
     --layout-features=kern,liga,calt,ccmp,locl,mark,mkmk,tnum,pnum,lnum,onum,case,cpsp,frac,numr,dnom,subs,sups,ordn,zero
     --notdef-outline --output-file=InterVariable-latin.woff2 */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/InterVariable-latin.woff2') format('woff2-variations'),
       url('/fonts/InterVariable-latin.woff2') format('woff2');
}

/* ===== reset.css ===== */
/* Modern CSS reset */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html, body { height: 100%; }
body {
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

/* The UA sheet's `[hidden] { display: none }` loses to any author `display`
   rule, so elements styled `display:grid`/`flex`/`block` stayed visible while
   carrying the hidden attribute. That left the wizard's options grid on screen
   underneath the emergency triage panel. Must be !important to beat the
   component rules that caused it. */
[hidden] {
  display: none !important;
}

/* ===== tokens.css ===== */
/* Design tokens: override per client fork */

:root {
  color-scheme: light;

  /* Brand: Hemsley Building Services, "Slate sett and stone".
   * Every value derives from the client's own assets, measured 2026-08-20:
   * #47576f is the slate blue of his paving-sett logo mark, #1a1f2d is the
   * ink his Squarespace site uses for headings and buttons, #70809b is the
   * grey blue of "Building Services" in his lockup, #f2f5f6 is the light
   * ground swatch his own site ships. Nothing invented.
   *
   * IMPORTANT: --color-secondary is a light stone FILL ONLY token (button
   * backgrounds, decorative bars, icon backgrounds, rules) carrying ink text
   * via --color-secondary-fg. --color-accent (his grey blue) is fill-only
   * under the same rule. Run `npm run check` to validate contrast after any
   * token override. */
  --color-primary: #47576f;
  --color-primary-hover: #38465b;
  --color-primary-fg: #ffffff;
  --color-secondary: #dfe5ec;
  --color-secondary-hover: #cbd4de;
  --color-secondary-fg: #1a1f2d;
  --color-accent: #70809b;

  /* Neutrals: his light ground, his ink. */
  --color-bg: #f2f5f6;
  --color-bg-alt: #ffffff;
  --color-bg-dark: #1a1f2d;
  --color-fg: #1a1f2d;
  --color-fg-muted: #46536b;
  --color-fg-subtle: #5d6a84;
  --color-fg-inverse: #f2f5f6;
  --color-border: #d8dee6;
  --color-border-strong: #aeb9c6;

  /* Status */
  --color-success: #2f8a5f;
  --color-danger: #c44a4a;
  --color-warning: #d99a39;

  /* Hero overlay (gradient on top of background image).
     Front-loaded on purpose. The landing still has a bright overcast sky across
     its top right, and the old 0.45 top stop left white headline text at 2.67:1
     over the brightest sky pixels, which fails AA. The scrim is now strongest
     where the h1 and lede sit and eases into the existing dark fade at the base,
     so the paving in the lower half keeps its detail. Measured worst case at
     0.66: 5.1:1. Do not lower the first stop without re-measuring against the
     brightest pixels behind the text, not the average. */
  --overlay-hero: linear-gradient(180deg, rgba(20, 25, 40, 0.66) 0%, rgba(20, 25, 40, 0.64) 55%, rgba(20, 25, 40, 0.78) 100%);

  /* Typography */
  /* Fonts: Inter for body and UI. Display is EB Garamond, a deliberate
     per-client serif override (allowed per CLAUDE.md): the client's own
     wordmark and site are set in a classical serif (minerva-modern), and
     identity is derived from his branding. Not Fraunces; that stays retired. */
  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --font-display: "EB Garamond", Georgia, "Times New Roman", serif;

  --fs-xs: 0.75rem;
  --fs-sm: 0.875rem;
  --fs-base: 1rem;
  --fs-lg: 1.125rem;
  --fs-xl: 1.25rem;
  --fs-2xl: 1.5rem;
  --fs-3xl: 2rem;
  --fs-4xl: 2.5rem;
  --fs-5xl: 3rem;
  --fs-6xl: 4rem;

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-black: 900;

  --lh-tight: 1.2;
  --lh-snug: 1.375;
  --lh-normal: 1.5;
  --lh-relaxed: 1.7;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;

  /* Layout */
  --container-max: 1200px;
  --container-narrow: 800px;
  --container-padding: var(--space-6);

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 20px;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
  --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.15);

  /* Motion */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --duration-fast: 150ms;
  --duration-base: 250ms;
  --duration-slow: 400ms;

  /* Z-index */
  --z-header: 100;
  --z-mobile-nav: 200;
  --z-back-to-top: 250;
  /* Below --z-mobile-nav (200) on purpose: when the phone menu is open it must
     cover the call bar, not sit behind it. */
  --z-callbar: 150;
  --z-wa: 145;
  --z-cookie: 300;
  --z-modal: 1000;
}

/* ===== main.css ===== */
/* Page-level layout. Component styles live in components.css. */

body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--color-fg);
  background: var(--color-bg);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
  color: var(--color-fg);
}

h1 { font-size: var(--fs-5xl); letter-spacing: -0.02em; }
h2 { font-size: var(--fs-4xl); }
h3 { font-size: var(--fs-2xl); }
h4 { font-size: var(--fs-xl); }

p { margin-block: 0; }
p + p { margin-block-start: var(--space-4); }

a {
  color: var(--color-primary);
  text-decoration: none;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
  transition: color var(--duration-fast) var(--ease-out);
}
a:hover { color: var(--color-primary-hover); }

ul, ol { list-style: none; padding: 0; margin: 0; }

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

*:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: 2px;
}
button:focus-visible,
.btn:focus-visible,
a.btn:focus-visible {
  outline-offset: 4px;
}

::selection {
  background: var(--color-primary);
  color: var(--color-primary-fg);
}

/* Skip-to-main: visually hidden until keyboard focus moves it on-screen. */
.skip-link {
  position: absolute;
  top: -100px;
  left: 0;
  background: var(--color-primary);
  color: var(--color-primary-fg);
  padding: var(--space-3) var(--space-4);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  z-index: 1000;
  transition: top var(--duration-base) var(--ease-out);
}
.skip-link:focus {
  top: 0;
}
.skip-link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

.container-narrow {
  max-width: var(--container-narrow);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

section {
  padding-block: var(--space-20);
}

.section-eyebrow {
  display: inline-block;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--color-primary);
  margin-block-end: var(--space-2);
  font-family: var(--font-sans);
}

.section-lede {
  font-size: var(--fs-lg);
  color: var(--color-fg-muted);
  max-width: 65ch;
  margin-block: var(--space-3) var(--space-12);
}

.section-cta {
  margin-block-start: var(--space-8);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===== components.css ===== */
/* components.css: entry point. Imports the per-section partials in cascade order.
   Each partial covers one logical chunk (header, wizard, etc). Edit the partials,
   not this manifest. Order matters: CSS cascade depends on it. */

/* components/buttons.css */
/* buttons.css: Buttons
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Buttons ===== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none;
  transition: all var(--duration-fast) var(--ease-out);
  white-space: nowrap;
}

.btn--primary {
  background: var(--color-primary);
  color: var(--color-primary-fg);
  border-color: var(--color-primary);
}
.btn--primary:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color: var(--color-primary-fg);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

.btn--secondary {
  background: transparent;
  color: var(--color-secondary);
  border-color: var(--color-secondary);
}
.btn--secondary:hover {
  background: var(--color-secondary);
  /* Buff is a light fill: it carries ink text, never white (tokens.css rule). */
  color: var(--color-secondary-fg);
}

.btn--ghost {
  background: transparent;
  color: var(--color-primary);
  padding-inline: var(--space-3);
}
.btn--ghost:hover { text-decoration: underline; }

.btn--large { padding: var(--space-4) var(--space-8); font-size: var(--fs-lg); }
.btn--small { padding: var(--space-2) var(--space-4); font-size: var(--fs-sm); }

/* components/layout.css */
/* layout.css: Header / Hero / Service grid / Accreditations / Trust / Process
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Header ===== */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
  backdrop-filter: blur(8px);
  transition: background var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
}
.site-header.is-scrolled {
  background: rgba(244, 246, 251, 0.92);
  border-bottom-color: transparent;
  box-shadow: 0 1px 0 var(--color-border-strong), 0 8px 24px rgba(16, 26, 52, 0.07);
}

.site-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  /* Tight vertical padding keeps the bar at its compact original height (~72px)
     while letting the logo grow into more of that height. Mobile drops further
     to let the logo near-fill the bar. */
  padding-block: 10px;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  color: var(--color-primary);
  text-decoration: none;
}
/* Logo mark sizing: prominent brand anchor in the header. Width is auto so
   per-fork client logos with non-square aspect ratios (e.g. 3:1 wordmark+mark
   lockups) render at their natural proportions. Just update `aspect-ratio`
   in the per-fork override below. Height drives the size. */
.logo-mark {
  flex-shrink: 0;
  display: block;
  height: 52px;
  width: auto;
  aspect-ratio: 1; /* placeholder is square; per-fork: change to match client logo, e.g. `aspect-ratio: 3` for a 3:1 lockup */
}
.logo-text {
  letter-spacing: -0.01em;
}

.primary-nav ul {
  display: flex;
  gap: var(--space-6);
}

/* `:not(.btn)` is load bearing, do not drop it. This rule is `.primary-nav a`,
   which outranks `.btn--primary` on specificity, so without the exclusion it
   repainted the "Call ..." button in the mobile menu with the dark page text
   colour and left it near-black on navy at about 1.6:1. The number was
   effectively invisible. It went unnoticed because that button is display:none
   on desktop and only appears once the phone menu is opened. */
.primary-nav a:not(.btn) {
  color: var(--color-fg);
  font-weight: var(--fw-medium);
  /* Multi-word nav labels ("How we quote") wrap to three lines and blow the
     header height out without this. Nav items never break across lines. */
  white-space: nowrap;
}
.primary-nav a:not(.btn):hover { color: var(--color-primary); }

.header-cta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.phone-link {
  font-weight: var(--fw-semibold);
  color: var(--color-primary);
  /* Landlines with spaced groups (three spaced blocks) wrap to three lines in the
     header without this. Phone numbers should never break across lines. */
  white-space: nowrap;
}

.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-2);
}
.nav-toggle-bar {
  display: block;
  width: 24px; height: 2px;
  background: var(--color-fg);
  position: relative;
  transition: background 200ms var(--ease-out);
}
.nav-toggle-bar::before, .nav-toggle-bar::after {
  content: "";
  position: absolute;
  left: 0;
  width: 24px; height: 2px;
  background: var(--color-fg);
  transition: top 200ms var(--ease-out), transform 200ms var(--ease-out);
}
.nav-toggle-bar::before { top: -8px; }
.nav-toggle-bar::after { top: 8px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar::after { top: 0; transform: rotate(-45deg); }

.primary-nav-cta { display: none; }

@media (max-width: 900px) {
  .nav-toggle { display: block; }
  .primary-nav {
    display: none;
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: var(--color-bg-alt);
    border-bottom: 1px solid var(--color-border);
    box-shadow: var(--shadow-lg);
  }
  .primary-nav[data-open="true"] { display: block; }
  .primary-nav ul {
    flex-direction: column;
    gap: 0;
    padding: var(--space-4);
  }
  .primary-nav a:not(.btn) {
    display: block;
    padding-block: var(--space-3);
    border-bottom: 1px solid var(--color-border);
  }
  .primary-nav-cta {
    display: block;
    padding-block: var(--space-3);
    border-bottom: 1px solid var(--color-border);
  }
  .primary-nav-cta .btn {
    width: 100%;
    text-align: center;
    justify-content: center;
  }
  .header-cta .phone-link { display: none; }
}

/* This client's trading name is long. At the header's default --fs-xl it broke
   into four lines on a 390px phone and turned the bar into a block. Dropped a
   step and tightened so it sits on two. */
@media (max-width: 560px) {
  /* space-between was parking ~115px of slack between the items while the
     wordmark shrank to fit its own content box, so let the logo claim it. */
  .logo { font-size: var(--fs-sm); gap: var(--space-3); flex: 1 1 auto; min-width: 0; }
  /* The wrap point used to be forced with `min-width: 17ch` on the span. That
     stopped the four-line wrap but set a floor wider than the space actually
     left on a 375px phone, so the box overflowed and the menu button rendered
     on top of the wordmark, overlapping it by 40px. Fixed 15 Aug.
     The span now grows into whatever the bar has spare and shrinks when it has
     to, so it can never overflow into its siblings. The toggle and the CTA are
     pinned so they are never the ones that give. */
  .logo-text { line-height: 1.15; flex: 1 1 auto; min-width: 0; }
  .nav-toggle { flex: 0 0 auto; }
  .header-cta { flex: none; }
  .header-cta .btn { padding-inline: var(--space-4); }
}

/* ===== Hero ===== */

.hero {
  /* Was --space-32 top and bottom. Trimmed so the hero, its CTAs and the top of
     the quote wizard all land above the fold on a 900px-tall laptop. */
  padding-block: var(--space-20) var(--space-24);
  background-color: var(--color-bg-dark);
  color: var(--color-fg-inverse);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.hero-bg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* Was -350px, inherited from the video-hero build. With a still that overhang
     made the visible window the TOP of the image no matter what object-position
     said, so the hero showed roof and rendered wall instead of the driveway.
     Kept slightly negative only to cover the Ken Burns scale(1.08). */
  bottom: -60px;
  z-index: -1;
  overflow: hidden;
  will-change: transform;
}
.hero-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--overlay-hero);
  z-index: 1;
  pointer-events: none;
}

/* Phones get the portrait hero, which is a brighter, better composed picture than
   the landscape crop it replaces. The standard overlay was tuned for that older
   crop and buried this one, so lighten it under 768px. Measured on the built page:
   worst single pixel behind the headline 7.1, behind the sub copy 6.6, both well
   clear of the 4.5 AA floor for white text. */
@media (max-width: 767px) {
  .hero-bg::after {
    background: linear-gradient(180deg, rgba(20, 25, 40, 0.50) 0%, rgba(20, 25, 40, 0.38) 45%, rgba(20, 25, 40, 0.72) 100%);
  }
}
/* The poster is wrapped in <picture> so phones get the portrait crop. A bare
   <picture> is auto-height, which leaves the img with no percentage height to
   resolve against and collapses the fill, so give it the same box as .hero-bg. */
.hero-bg picture {
  position: absolute;
  inset: 0;
}
.hero-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Biased down the frame, not up. The interest in the hero still is the block
     paving and the border course in the lower half; at 30% the crop filled the
     screen with roof and rendered wall. */
  object-position: center 72%;
  display: block;
  transform-origin: 50% 40%;
  animation: heroKenBurns 36s ease-in-out infinite alternate;
  will-change: transform;
}
.hero-bg video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
}
@media (max-width: 720px) {
  .hero-bg video {
    inset: 0 0 auto 0;
    height: 64vh;
    object-position: center 35%;
    -webkit-mask-image: linear-gradient(180deg, #000 82%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 82%, transparent 100%);
  }
  /* On phones the hero section stacks tall, so a full-height fill scaled the
     portrait poster past the point where the drive was visible. Cap the poster
     at the top 64vh and fade its base into the ink, then bias the crop low so
     the porch and the resin drive land inside that band. */
  .hero-bg picture {
    inset: 0 0 auto 0;
    height: 64vh;
    -webkit-mask-image: linear-gradient(180deg, #000 82%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 82%, transparent 100%);
  }
  .hero-bg img {
    object-position: center 78%;
    /* Static on phones. The 36s Ken Burns animates transform AND filter on a
       full-bleed image under a mask, which a phone GPU has to repaint every
       frame while also scrolling; with the JS parallax it read as a shake.
       Parallax is off on touch too (main.js). Hold the mid-zoom frame. */
    animation: none;
    transform: scale(1.16);
    will-change: auto;
  }
}
/* Desktop hero band.
   The quote wizard makes this hero about 1150px tall, which is far taller than
   it is wide once the container is accounted for. A full-height background then
   scales the still to fit the HEIGHT, leaving no vertical excess for
   object-position to work with, so the visible top of the fold got roof and
   rendered wall while the driveway sat below the fold.
   Capping the photo to a band shorter than its own natural aspect restores the
   vertical crop, so object-position can pull the paving up into view. The mask
   fades the base into --color-bg-dark, which is the same device the phone
   breakpoint below already uses. */
@media (min-width: 721px) {
  .hero-bg {
    bottom: auto;
    height: min(100%, 84vh);
    -webkit-mask-image: linear-gradient(180deg, #000 74%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 74%, transparent 100%);
  }
  .hero-bg img { object-position: center 90%; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg video { display: none; }
  .hero-bg img { animation: none; }
}

/* Legacy drawn-hero fallback, unused on this fork: the hero ships a real
   generated Swansea still. Kept because other pages can still reach for it. */
.hero-bg--drawn {
  background-color: var(--color-bg-dark);
  background-image:
    repeating-linear-gradient(90deg, rgba(245, 244, 240, 0.05) 0 1px, transparent 1px 168px),
    repeating-linear-gradient(180deg, rgba(245, 244, 240, 0.05) 0 1px, transparent 1px 84px);
  background-position: center top;
}

/* On the ink hero the default ink-filled primary button disappears. The
   light blue carries the primary action here, because the navy primary would sit
   navy-on-navy against the ink ground. It is a FILL, taking the deep navy
   --color-secondary-fg as its label. The call button drops to a paper outline so
   the hierarchy reads quote first. */
.hero .btn--primary {
  background: var(--color-secondary);
  border-color: var(--color-secondary);
  color: var(--color-secondary-fg);
}
.hero .btn--primary:hover {
  background: var(--color-secondary-hover);
  border-color: var(--color-secondary-hover);
  color: var(--color-secondary-fg);
}
.hero .btn--secondary {
  color: var(--color-fg-inverse);
  border-color: color-mix(in srgb, currentColor 65%, transparent);
}
.hero .btn--secondary:hover {
  background: var(--color-secondary);
  border-color: var(--color-secondary);
  color: var(--color-secondary-fg);
}

@keyframes heroKenBurns {
  0% {
    transform: scale(1.08) translate3d(-2%, -1%, 0);
    filter: saturate(1) brightness(1);
  }
  100% {
    transform: scale(1.24) translate3d(2.5%, 2%, 0);
    filter: saturate(1.08) brightness(1.04);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg img {
    animation: none;
    transform: scale(1.08);
  }
  .hero-bg {
    transform: none !important;
  }
}

.hero .container { position: relative; }
.hero-content { max-width: 720px; position: relative; }
.hero-text { position: relative; }

/* Hero-side inline wizard card. Sits in the right grid cell on desktop,
   stacks below the hero text on mobile. Opaque cream surface keeps the
   form legible above the photographic background. */
.hero-form-card {
  position: relative;
  background: var(--color-bg);
  color: var(--color-fg);
  padding: var(--space-6);
  border-radius: var(--radius-lg, 12px);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
  margin-block-start: var(--space-10);
}
.hero-form-card .wizard {
  background: transparent;
  padding: 0;
}

@media (min-width: 960px) {
  .hero .container {
    display: grid;
    grid-template-columns: 1fr minmax(380px, 460px);
    gap: var(--space-8);
    align-items: center;
  }
  .hero-form-card {
    margin-block-start: 0;
    max-width: 460px;
    justify-self: end;
    width: 100%;
  }
}

.hero h1 {
  /* Fluid rather than a flat --fs-6xl. At 64px the Swansea and Neath headline
     ran to four lines in the narrow hero column and pushed the CTAs past the
     fold at 1440x900. Clamped so it holds two lines from mobile up. */
  font-size: clamp(2.25rem, 3.4vw, 3.25rem);
  line-height: 1.08;
  color: var(--color-fg-inverse);
  margin-block-end: var(--space-5);
  letter-spacing: -0.02em;
}

.hero .lede {
  font-size: var(--fs-xl);
  color: var(--color-fg-inverse);
  opacity: 0.92;
  margin-block-end: var(--space-8);
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-end: var(--space-10);
}
.hero-cta .btn--secondary {
  background: transparent;
  color: var(--color-fg-inverse);
  border-color: var(--color-fg-inverse);
}
.hero-cta .btn--secondary:hover {
  background: var(--color-fg-inverse);
  color: var(--color-primary);
}

.trust-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}
.trust-badges li {
  position: relative;
  padding-inline-start: var(--space-6);
  font-weight: var(--fw-medium);
  color: var(--color-fg-inverse);
  opacity: 0.88;
  font-size: var(--fs-sm);
}
.trust-badges li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: var(--color-accent);
  font-weight: var(--fw-bold);
}

@media (max-width: 700px) {
  .hero { padding-block: var(--space-20); }
  .hero h1 { font-size: var(--fs-4xl); }
  .hero .lede { font-size: var(--fs-lg); }
}

/* ===== Service grid ===== */

.service-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-6);
}

.service-card {
  /* Anchor for the stretched link below. The whole tile is the click target,
     not just the heading text, which was 1.2% to 3.1% of the card. */
  position: relative;
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* Soft resting shadow. Barely visible, gives the card lift on the warm
     cream page now that bg contrast alone no longer defines its edges. */
  box-shadow: 0 2px 8px rgba(31, 37, 48, 0.06);
  overflow: hidden;
  transition: transform var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
  display: flex;
  flex-direction: column;
}
.service-card-media {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--color-bg);
}
.service-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-out);
}
.service-card-body {
  /* Note: --space-7 doesn't exist in tokens.css (scale skips 7: 6, 8, 10...).
     Pre-patch this rule referenced --space-7 and silently rendered as
     padding: 0. That's why the body text looked suffocated. Using only
     defined tokens now. */
  padding: var(--space-6) var(--space-6) var(--space-8);
}
.service-card:hover {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-lg);
  transform: translateY(-3px);
}
.service-card:hover .service-card-media img {
  transform: scale(1.04);
}

.service-card h3, .service-card h2 { font-size: var(--fs-lg); margin-block-end: var(--space-3); }
.service-card h3 a, .service-card h2 a { color: var(--color-fg); }
.service-card h3 a:hover, .service-card h2 a:hover { color: var(--color-primary); }

/* ===== Stretched link =====
   The heading link's pseudo-element covers the whole card, so a click anywhere
   on the tile follows it. The link text stays the accessible name, so screen
   readers and the keyboard tab order are unchanged.
   The card already zoomed its image on hover, which promised clickability the
   markup did not deliver; this makes the promise true. */
.service-card h3 a::after,
.service-card h2 a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.service-card { cursor: pointer; }

/* The before-and-after slider must stay draggable, so it sits ABOVE the
   stretched link rather than under it. On those cards the image compares and
   the rest of the tile navigates, which is the right split: the slider carries
   a drag handle and BEFORE/AFTER labels, so it reads as a control, not a link. */
.service-card .ba-slider { z-index: 2; }
.service-card p { font-size: var(--fs-sm); line-height: 1.6; color: var(--color-fg-muted); }

/* ===== Accreditations strip ===== */

.accreditations {
  padding-block: var(--space-10) var(--space-12);
  background: var(--color-bg-alt);
  border-block: 1px solid var(--color-border);
}
.accreditations-eyebrow {
  text-align: center;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-weight: var(--fw-semibold);
  color: var(--color-fg-muted);
  margin-block-end: var(--space-6);
}
.accreditations-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-8) var(--space-12);
}
.accreditations-list li { display: flex; }
.accreditations-list img {
  height: 52px;
  width: auto;
  opacity: 0.85;
  transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-base) var(--ease-out);
}
.accreditations-list img:hover { opacity: 1; transform: translateY(-2px); }

@media (max-width: 700px) {
  .accreditations { padding-block: var(--space-8) var(--space-10); }
  .accreditations-list { gap: var(--space-5) var(--space-8); }
  .accreditations-list img { height: 40px; }
}

/* ===== Trust strip ===== */

.trust-strip {
  background: var(--color-primary);
  color: var(--color-fg-inverse);
  padding-block: var(--space-12);
}

.trust-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-8);
  text-align: center;
}

.trust-stat {
  font-size: var(--fs-5xl);
  font-weight: var(--fw-black);
  color: var(--color-accent);
  line-height: 1;
}

.trust-label {
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-block-start: var(--space-2);
  color: var(--color-fg-inverse);
  opacity: 0.8;
}

/* ===== Process steps ===== */

.process-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-8);
  counter-reset: step;
  list-style: none;
  position: relative;
}
.process-steps li {
  counter-increment: step;
  background: var(--color-bg-alt);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  /* Match the service-card resting shadow for cross-section consistency. */
  box-shadow: 0 2px 8px rgba(31, 37, 48, 0.06);
  position: relative;
  display: flex;
  flex-direction: column;
}
.process-steps li::before {
  content: counter(step);
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--color-primary);
  color: var(--color-primary-fg);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  margin-block-end: var(--space-4);
  box-shadow: var(--shadow-md);
  position: relative;
  z-index: 1;
}
.process-steps li img {
  width: calc(100% + var(--space-12));
  margin: 0 calc(-1 * var(--space-6)) var(--space-4);
  margin-block-start: calc(-1 * var(--space-6));
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.process-steps li img + ::before,
.process-steps li:has(img)::before {
  margin-block-start: calc(-1 * var(--space-12));
  margin-inline-start: var(--space-2);
}
.process-steps h3, .process-steps h2 {
  font-size: var(--fs-lg);
  margin-block-end: var(--space-2);
  color: var(--color-fg);
}
.process-steps p { font-size: var(--fs-sm); color: var(--color-fg-muted); }

@media (min-width: 880px) {
  .process-steps::before {
    content: "";
    position: absolute;
    top: calc(var(--space-6) + 1.5rem);
    left: 12%;
    right: 12%;
    height: 2px;
    background: var(--color-border);
    z-index: 0;
    pointer-events: none;
  }
}

/* components/cards.css */
/* cards.css: Gallery / Testimonials / Areas / CTA band
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Gallery ===== */

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
}
.gallery-grid li {
  aspect-ratio: 4 / 3;
  background: var(--color-bg-alt);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.gallery-grid img { width: 100%; height: 100%; object-fit: cover; }

/* ===== Gallery v2: honest count, filter pills, filtered grid ===== */

/* The filtered grid is the full gallery page only, so the project-detail
   strip that also uses .gallery-grid keeps its auto-fit columns. Column
   counts here match the `sizes` attribute on every tile: 2 up, 3 up, 4 up. */
.gallery-grid--filtered { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 600px) { .gallery-grid--filtered { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 880px) { .gallery-grid--filtered { grid-template-columns: repeat(4, 1fr); } }
.gallery-grid--filtered li[hidden] { display: none; }

.gallery-count {
  color: var(--color-fg-muted);
  margin-block-end: var(--space-5);
}

.gallery-filters {
  display: flex;
  gap: var(--space-2);
  margin-block-end: var(--space-6);
  /* One row on every width. On a phone it scrolls sideways rather than
     wrapping into a block of buttons that pushes the photos off screen. */
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.gallery-filters::-webkit-scrollbar { display: none; }

.gallery-pill {
  flex: 0 0 auto;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-alt);
  color: var(--color-fg-muted);
  cursor: pointer;
  white-space: nowrap;
}
.gallery-pill:hover { border-color: var(--color-border-strong); color: var(--color-fg); }
.gallery-pill[aria-pressed="true"] {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-fg);
}
.gallery-pill:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}

/* The one link each service page carries through to its slice of the gallery.
   Scoped under .cta-band so it beats the band's own paragraph margin. */
.cta-band p.cta-band-gallery-link {
  font-size: var(--fs-base);
  margin-block: var(--space-6) 0;
}
.cta-band-gallery-link a { color: var(--color-primary-fg); text-decoration: underline; }

/* ===== Testimonials ===== */

.testimonials-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-6);
}

.testimonial {
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  position: relative;
}
.testimonial::before {
  content: "\201C";
  position: absolute;
  top: var(--space-2);
  left: var(--space-5);
  font-family: var(--font-display);
  font-size: 4rem;
  line-height: 1;
  color: var(--color-primary);
  opacity: 0.18;
  pointer-events: none;
}
.testimonial blockquote { margin: 0; line-height: var(--lh-relaxed); position: relative; }
.testimonial cite {
  display: block;
  margin-block-start: var(--space-4);
  font-style: normal;
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
}
.rating {
  color: var(--color-accent);
  font-size: var(--fs-lg);
  margin-block-start: var(--space-2);
}
.review-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  margin-block-end: 0;
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}
.review-source {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: inherit;
  text-decoration: none;
}
.review-source:hover span,
.review-source:focus-visible span {
  text-decoration: underline;
}
.review-source img,
.review-source .google-g {
  width: 16px;
  height: 16px;
  display: block;
  flex: none;
}
.review-meta time { color: inherit; }
.testimonials-grid > .testimonial:nth-child(1) .review-meta,
.testimonials-grid > .testimonial:nth-child(1) .review-source { color: rgba(247, 242, 235, 0.75); }

@media (min-width: 880px) {
  .testimonials-grid {
    grid-template-columns: repeat(12, 1fr);
    align-items: stretch;
  }
  .testimonials-grid > .testimonial:nth-child(1) {
    grid-column: span 6;
    background: var(--color-primary);
    color: var(--color-fg-inverse);
    border-color: var(--color-primary);
  }
  .testimonials-grid > .testimonial:nth-child(1)::before {
    color: var(--color-fg-inverse);
    opacity: 0.32;
  }
  .testimonials-grid > .testimonial:nth-child(1) blockquote {
    font-family: var(--font-display);
    font-size: var(--fs-xl);
    line-height: var(--lh-snug);
  }
  .testimonials-grid > .testimonial:nth-child(1) cite { color: var(--color-fg-inverse); opacity: 0.92; }
  .testimonials-grid > .testimonial:nth-child(1) .rating { color: var(--color-accent); }
  .testimonials-grid > .testimonial:nth-child(2),
  .testimonials-grid > .testimonial:nth-child(3) { grid-column: span 3; }
  .testimonials-grid > .testimonial:nth-child(4) { grid-column: span 4; }
  .testimonials-grid > .testimonial:nth-child(5) { grid-column: span 4; }
  .testimonials-grid > .testimonial:nth-child(6) { grid-column: span 4; }
  .testimonials-grid > .testimonial:nth-child(n+7) { grid-column: span 4; }

  /* Empty-state n=1: single card spans the full row but constrained width
     and centered so it doesn't stretch absurdly wide on a 12-col grid. */
  .testimonials-grid:has(> .testimonial:only-child) > .testimonial:only-child {
    grid-column: span 12;
    max-width: 720px;
    margin-inline: auto;
  }

  /* Empty-state n=2: split 50/50; both cards span 6.
     The first card keeps the featured-style slate background from the rule above. */
  .testimonials-grid:has(> .testimonial:nth-child(2):last-child) > .testimonial:nth-child(1),
  .testimonials-grid:has(> .testimonial:nth-child(2):last-child) > .testimonial:nth-child(2) {
    grid-column: span 6;
  }
}

/* ===== Areas list ===== */

.areas-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-2);
  margin-block: var(--space-6);
}
.areas-list a {
  display: block;
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-alt);
  border-radius: var(--radius-md);
  color: var(--color-primary);
  font-weight: var(--fw-medium);
}
.areas-list a:hover {
  background: var(--color-primary);
  color: var(--color-primary-fg);
}

/* ===== CTA band ===== */

.cta-band {
  background-image: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
  color: var(--color-primary-fg);
  text-align: center;
  position: relative;
  overflow: hidden;
}
.cta-band::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at top, rgba(247, 242, 235, 0.08) 0%, transparent 60%);
  pointer-events: none;
}
.cta-band > .container { position: relative; z-index: 1; }
.cta-band h2 {
  color: var(--color-primary-fg);
  margin-block-end: var(--space-3);
  font-family: var(--font-display);
  letter-spacing: -0.01em;
}
.cta-band p {
  font-size: var(--fs-lg);
  margin-block-end: var(--space-8);
  opacity: 0.95;
}
.cta-band-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: center;
}
.cta-band .btn--primary {
  background: var(--color-bg);
  color: var(--color-primary);
  border-color: var(--color-bg);
}
.cta-band .btn--primary:hover {
  background: var(--color-bg-alt);
  color: var(--color-primary);
}
.cta-band .btn--secondary {
  background: transparent;
  color: var(--color-fg-inverse);
  border-color: var(--color-fg-inverse);
}
.cta-band .btn--secondary:hover {
  background: var(--color-fg-inverse);
  color: var(--color-primary);
}

@media (max-width: 700px) {
  .cta-band {
    padding-block: var(--space-12);
  }
  .cta-band h2 { font-size: var(--fs-3xl); }
  .cta-band p { font-size: var(--fs-base); margin-block-end: var(--space-6); }
  .cta-band-actions { flex-direction: column; gap: var(--space-3); }
  .cta-band-actions .btn { width: 100%; }
}

/* components/ba-slider.css */
/* ba-slider.css: before and after comparison slider.
   Lives inside an aspect-locked media container; both layers cover it.
   --ba (0-100%) is the reveal position, driven by main.js. */

.ba-slider {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: pan-y;
  /* Dragging across the panes otherwise starts a native image selection, which
     Chrome renders as a solid blue wash over the image. */
  -webkit-user-select: none;
  user-select: none;
}
.ba-slider picture,
.ba-slider img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-user-drag: none;
  user-drag: none;
}
.ba-before {
  position: absolute;
  inset: 0;
  clip-path: inset(0 calc(100% - var(--ba, 50%)) 0 0);
}
.ba-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--ba, 50%);
  width: 44px;
  margin-left: -22px;
  cursor: ew-resize;
  touch-action: none;
}
.ba-handle::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: rgba(245, 244, 240, 0.92);
}
.ba-handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  border-radius: 50%;
  background: rgba(20, 29, 39, 0.82);
  border: 2px solid rgba(245, 244, 240, 0.92);
}
.ba-arrows {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32px;
  height: 32px;
  margin: -16px 0 0 -16px;
  z-index: 1;
}
.ba-arrows::before,
.ba-arrows::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 7px;
  height: 7px;
  border-top: 2px solid rgba(245, 244, 240, 0.95);
  border-left: 2px solid rgba(245, 244, 240, 0.95);
}
.ba-arrows::before {
  left: 7px;
  transform: translateY(-50%) rotate(-45deg);
}
.ba-arrows::after {
  right: 7px;
  transform: translateY(-50%) rotate(135deg);
}
.ba-handle:focus-visible {
  outline: 2px solid var(--color-secondary);
  outline-offset: 2px;
}
.ba-label {
  position: absolute;
  bottom: var(--space-3);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: var(--fw-semibold);
  color: rgba(245, 244, 240, 0.95);
  background: rgba(20, 29, 39, 0.55);
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  transition: opacity 0.4s ease;
  pointer-events: none;
}
.ba-label--before { left: var(--space-3); }
.ba-label--after { right: var(--space-3); }
.ba-slider.ba-touched .ba-label { opacity: 0; }

/* components/footer.css */
/* footer.css: Footer
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Footer ===== */

/* No photography yet on this fork, so the footer band is drawn rather than
   shot: an offset-bond sett pattern in ink, matching the logo device. Swap the
   background-image block for a real image-set once job photos land. */
.site-footer {
  background-color: var(--color-bg-dark);
  background-image:
    linear-gradient(180deg, rgba(29, 39, 51, 0.9) 0%, var(--color-bg-dark) 60%),
    repeating-linear-gradient(90deg, rgba(245, 244, 240, 0.05) 0 1px, transparent 1px 150px),
    repeating-linear-gradient(180deg, rgba(245, 244, 240, 0.05) 0 1px, transparent 1px 75px);
  background-position: top center;
  background-repeat: no-repeat, repeat, repeat;
  color: var(--color-fg-inverse);
  padding-block: var(--space-20) var(--space-8);
  position: relative;
}
.site-footer::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-primary), var(--color-secondary), var(--color-accent));
}

.footer-grid {
  display: grid;
  /* 180px floor so the five columns (brand, services, areas, company, contact)
     still fit one row on desktop; below that it wraps as before. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-10);
  margin-block-end: var(--space-12);
}

.footer-col h3, .footer-col h4 {
  color: var(--color-fg-inverse);
  font-size: var(--fs-base);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-block-end: var(--space-4);
}
.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}
.footer-brand .logo-mark {
  height: 48px;
  /* width: auto and aspect-ratio inherited from .logo-mark */
  /* A true inverse mark is wired via business.logo.urlInverse, so the light
     chip that used to sit behind the dark version is no longer needed. */
}

.footer-col ul { display: flex; flex-direction: column; gap: var(--space-2); }

.footer-col a {
  color: var(--color-fg-inverse);
  opacity: 0.7;
  font-size: var(--fs-sm);
}
.footer-col a:hover { opacity: 1; color: var(--color-accent); }

.footer-col p, .footer-col address {
  color: var(--color-fg-inverse);
  opacity: 0.7;
  font-size: var(--fs-sm);
  font-style: normal;
}

.footer-cert { margin-block-start: var(--space-3); font-size: var(--fs-xs); }

.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding-block-start: var(--space-6);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: var(--fs-xs);
}

.footer-bottom p { color: var(--color-fg-inverse); opacity: 0.5; }

.legal-links { display: flex; gap: var(--space-4); }
.legal-links a {
  color: var(--color-fg-inverse);
  opacity: 0.5;
  font-size: var(--fs-xs);
}
.legal-links a:hover { opacity: 1; }

/* Portfolio demo notice. Only renders when business.isDemo is true. */
.footer-demo-note {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(247, 242, 235, 0.18);
}
.footer-demo-note p {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  max-width: 70ch;
  color: var(--color-primary-fg, #f7f2eb);
  opacity: 0.85;
}

/* Freephone label and the coverage line, both fork-local additions. */
.footer-phone-note {
  /* --color-fg-subtle measured 4.48:1 on the footer ink, just under AA for text
     this small. Tinted off the inverse instead, which lands around 6:1 and still
     reads as a quiet label next to the number. */
  color: rgba(242, 246, 253, 0.62);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.footer-coverage {
  color: var(--color-fg-subtle);
  font-size: var(--fs-sm);
  margin-top: var(--space-3);
}
/* Visible NAP. The address is already public on his own site footer and his
   Google Business Profile, so it carries here too: citation checkers and
   customers both look for it in the footer. */
.footer-address {
  font-style: normal;
  color: var(--color-fg-subtle);
  font-size: var(--fs-sm);
  margin-top: var(--space-3);
  line-height: 1.5;
}
.footer-hours {
  color: var(--color-fg-subtle);
  font-size: var(--fs-sm);
  margin-top: var(--space-2);
}

/* components/floating.css */
/* floating.css: Cookie pill / Back to top
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Cookie consent pill ===== */

.cookie-pill {
  position: fixed;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--color-bg-dark);
  color: var(--color-fg-inverse);
  border: 1px solid rgba(247, 242, 235, 0.1);
  border-radius: 9999px;
  padding: 6px 6px 6px 22px;
  box-shadow:
    0 16px 36px rgba(31, 37, 48, 0.32),
    0 4px 10px rgba(31, 37, 48, 0.18);
  z-index: var(--z-cookie);
  font-size: var(--fs-sm);
  max-width: calc(100vw - var(--space-6) * 2);
  cursor: pointer;
  transition:
    transform var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
  animation: cookiePillIn 420ms var(--ease-out) both;
}
.cookie-pill[hidden] { display: none !important; }
.cookie-pill.is-dismissing {
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 16px);
}
.cookie-pill:hover {
  transform: translate(-50%, -2px);
  box-shadow:
    0 22px 44px rgba(31, 37, 48, 0.38),
    0 6px 14px rgba(31, 37, 48, 0.2);
}
.cookie-pill:focus-within {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
}

.cookie-pill-text {
  white-space: nowrap;
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
}
.cookie-pill-text a {
  color: var(--color-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.cookie-pill-text a:hover { color: var(--color-fg-inverse); }

.cookie-pill-decline,
.cookie-pill-accept {
  appearance: none;
  border: none;
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  cursor: pointer;
  border-radius: 9999px;
  padding: 8px 16px;
  white-space: nowrap;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    opacity var(--duration-fast) var(--ease-out);
}
.cookie-pill-decline {
  background: transparent;
  color: var(--color-fg-inverse);
  opacity: 0.7;
}
.cookie-pill-decline:hover { opacity: 1; background: rgba(247, 242, 235, 0.08); }

.cookie-pill-accept {
  background: var(--color-fg-inverse);
  color: var(--color-bg-dark);
}
.cookie-pill-accept:hover {
  background: var(--color-accent);
  color: var(--color-bg-dark);
}

@keyframes cookiePillIn {
  from { opacity: 0; transform: translate(-50%, 32px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

@media (max-width: 560px) {
  /* Logo sizing on phones. The template hid the business name here and let the
     mark stand alone. This fork keeps the name visible: the mark is an abstract
     paving-sett device with no wordmark in it, so hiding the text would leave a
     phone user with no business name on screen at all. In a trade where buyers
     are primed for scams, the name is the trust signal. The mark shrinks and the
     name wraps to two tight lines to pay for the space. */
  /* 24px of side padding is generous on a phone and it was the difference
     between three lines and four for this long trading name: at 360px the name
     got 80px and stranded "and" on its own line, at 16px it gets 96px and wraps
     to three. This padding is the header's own, not the shared .container, so
     nothing else on the site moves. */
  .site-header-inner { padding-block: 6px; padding-inline: 16px; gap: var(--space-3); }
  /* 44px left the name only 91px of bar, one pixel short of fitting
     "Driveways and" on a line, so the name broke to four lines with "and"
     stranded on its own. 40px gives it 95px and three tidy lines. */
  .site-header .logo-mark { height: 40px; }
  .footer-brand .logo-mark { height: 40px; }
  .site-header .logo { gap: var(--space-2); }
  /* This rule and the one in layout.css were fighting. This file set
     max-width 6.5em, layout.css set min-width 17ch, and in CSS min-width always
     beats max-width, so the box resolved to 160px inside a bar with only 99px
     spare and the menu button rendered on top of the name, overlapping it by
     40px on a 375px phone. Fixed 15 Aug: layout.css now sets min-width 0 and
     lets flex do the constraining, and the cap here is gone so the name uses
     whatever the bar actually has. 13px is the size that wraps it to three
     tidy lines in that space and takes the bar from 76px down to 64px.
     Measure before changing either rule, and measure them together. */
  .site-header .logo-text {
    font-size: 0.8125rem;
    line-height: 1.15;
    white-space: normal;
    max-width: none;
  }
  .cookie-pill {
    bottom: var(--space-3);
    padding: 5px 5px 5px 16px;
    font-size: var(--fs-xs);
    gap: var(--space-2);
  }
  .cookie-pill-decline,
  .cookie-pill-accept {
    padding: 7px 12px;
    flex: 0 0 auto;
  }
  /* 381px to 560px is the band where the sentence is visible but the viewport
     cannot hold sentence plus both buttons on one line. Everything in the pill
     is nowrap, so instead of shrinking, the Accept button overflowed the pill's
     right edge by ~60px on 390 to 430px phones (found by Hugh on his own phone,
     20 Aug). Let the sentence wrap and shrink; the buttons hold their row. */
  .cookie-pill-text {
    white-space: normal;
    min-width: 0;
    flex: 1 1 auto;
    line-height: 1.35;
  }
}

@media (min-width: 381px) and (max-width: 560px) {
  /* Shrink-to-fit on a fixed inline-flex box resolves far too narrow once the
     text is allowed to wrap, giving a tall skinny pill. Take the full capped
     width instead; the sentence then sits on two lines at most. */
  .cookie-pill { width: 100%; }
}

@media (max-width: 380px) {
  .cookie-pill-text { display: none; }
  .cookie-pill {
    padding-inline-start: var(--space-3);
  }
  .cookie-pill::before {
    content: "Cookies";
    font-weight: var(--fw-medium);
    margin-inline-end: var(--space-2);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cookie-pill { animation: none; transition: opacity 200ms ease; }
  .cookie-pill:hover { transform: translateX(-50%); box-shadow: 0 16px 36px rgba(31, 37, 48, 0.32); }
}

/* ===== Back to top ===== */

.back-to-top {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  width: 48px; height: 48px;
  background: var(--color-primary);
  color: var(--color-primary-fg);
  border: none;
  border-radius: var(--radius-full);
  font-size: var(--fs-xl);
  cursor: pointer;
  box-shadow: var(--shadow-lg);
  z-index: var(--z-back-to-top);
  transition: opacity var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out);
}
.back-to-top:hover {
  background: var(--color-primary-hover);
  transform: translateY(-2px);
}

/* ===== Sticky call bar and WhatsApp bubble ===== */
/* Phone widths only. The desktop header already carries the number and a Free
   Quote button, so on desktop this would be duplicate furniture.
   Geometry follows the Plymouth plumbing build, which measured it on real phone widths. */

.callbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-callbar);
  display: none;
  gap: var(--space-3);
  /* The safe-area pad is what keeps the buttons clear of the iPhone home
     indicator. Without it the bar looks right in a simulator and gets a bite
     taken out of it on a real handset. */
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--color-bg-alt) 94%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--color-border);
}
.callbar .btn {
  flex: 1;
  padding: 14px 10px;
  /* Two words each, so they must not wrap inside a 50% column. */
  white-space: nowrap;
}
/* The stock .btn--secondary is the light blue built for the ink hero, where it
   reads at 10.2:1. On this near-white bar the same blue measures 1.64:1 and the
   label is barely there. Restated as a navy outline, which is 9.07:1 here and
   keeps the hierarchy right: filled navy calls, outline quotes. */
.callbar .btn--secondary {
  background: transparent;
  color: var(--color-primary);
  border-color: var(--color-primary);
}
.callbar .btn--secondary:hover {
  background: var(--color-primary);
  color: var(--color-primary-fg);
  border-color: var(--color-primary);
}

/* 56px, comfortably over the 44px minimum tap target. WhatsApp green is only
   about 1.9:1 against a light page, so the darker hairline ring is what gives
   the bubble a defined edge rather than a green smudge. The glyph is the
   official mark kept white on that green, which is logo territory rather than
   text, so the contrast rule that applies is the 3:1 for graphical objects and
   the ring carries it. */
.wa {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-5);
  z-index: var(--z-wa);
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  display: none;
  align-items: center;
  justify-content: center;
  background: #25d366;
  border: 1px solid #0a5c2b;
  text-decoration: none;
  box-shadow: var(--shadow-lg);
  transition: transform 150ms var(--ease-out), background 150ms var(--ease-out);
}
.wa svg { width: 30px; height: 30px; display: block; fill: #fff; }
.wa:hover { background: #1eb85a; }
.wa:active { transform: translateY(1px); }
.wa:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }

@media (max-width: 820px) {
  .callbar { display: flex; }
  .wa { display: flex; }
  /* Clears the bar so the last of the footer is reachable rather than sitting
     underneath it. */
  body { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }
  /* Everything else that floats at the bottom has to move up by the height of
     the bar, or it lands on top of it. */
  .wa { bottom: calc(92px + env(safe-area-inset-bottom)); }
  .cookie-pill { bottom: calc(92px + env(safe-area-inset-bottom)); }
  /* Back to top goes left so it never fights the bubble for the same corner. */
  .back-to-top {
    bottom: calc(92px + env(safe-area-inset-bottom));
    right: auto;
    left: var(--space-4);
  }
}

/* Off entirely on any page carrying the quote wizard. Measured on the Plymouth plumbing
   build at 390 wide: the bubble covered the right end of the wizard's option
   rows, so a thumb aiming at an answer opened WhatsApp instead. The call bar
   stays, since it sits below the content rather than over it. */
.has-wiz .wa { display: none; }

@media (prefers-reduced-motion: reduce) {
  .wa { transition: none; }
}

/* components/pages.css */
/* pages.css: Page header / Prose
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Page header (sub-pages) ===== */

.page-header {
  background: var(--color-bg-alt);
  padding-block: var(--space-16);
  text-align: center;
}
.page-header h1 { margin-block-end: var(--space-3); }
.page-header > .container > p {
  color: var(--color-fg-muted);
  max-width: 65ch;
  margin-inline: auto;
}

.breadcrumb {
  display: flex;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-end: var(--space-4);
  justify-content: center;
}
.breadcrumb a { color: var(--color-fg-muted); }
.breadcrumb a:hover { color: var(--color-primary); }
.breadcrumb-sep { opacity: 0.5; }

/* ===== Prose (long-form text) ===== */

.prose {
  max-width: 65ch;
  margin-inline: auto;
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
}
.prose h2 { font-size: var(--fs-3xl); margin-block: var(--space-12) var(--space-4); }
.prose h3 { font-size: var(--fs-xl); margin-block: var(--space-8) var(--space-3); }
.prose ul, .prose ol {
  list-style: revert;
  padding-inline-start: var(--space-6);
  margin-block: var(--space-4);
}
.prose ul li, .prose ol li { margin-block: var(--space-2); }

/* Small print under a price table or a guide figure. Deliberately quiet: it is
   there so a guide band is never mistaken for a quote. */
.prose .note {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  line-height: 1.6;
  margin-block-start: calc(-1 * var(--space-2));
}

/* components/reviews.css */
/* Reviews band: real Google reviews plus the live score badge.
   Every quote here is verbatim from the client's Google Business Profile and the
   badge figures are the live rating and count. Nothing in this component may be
   softened, trimmed or topped up with invented copy. If the profile changes, the
   data changes; the component does not fake the difference.

   Deliberately NO AggregateRating or Review JSON-LD anywhere: the house rule is a
   visible, linked badge only. */

.reviews {
  padding-block: var(--space-20);
  background: var(--color-bg-alt);
  border-block: 1px solid var(--color-border);
}

.reviews-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-10);
}

.reviews-head h2 {
  margin: 0;
  max-width: 20ch;
}

/* The badge is a link to the live profile, so it gets a real hover and focus. */
.reviews-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: #fff;
  color: var(--color-fg);
  text-decoration: none;
  font-size: var(--fs-sm);
  line-height: 1;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.reviews-badge:hover,
.reviews-badge:focus-visible {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.reviews-badge-g {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}

.reviews-badge-score {
  font-weight: var(--fw-bold);
  font-size: var(--fs-lg);
}

.reviews-badge-stars svg {
  width: 1rem;
  height: 1rem;
  color: #b26a00; /* Amber deepened for 3:1 on the white badge; the Downham fix. */
  display: block;
}

.reviews-badge-count {
  color: var(--color-fg-muted);
}

.reviews-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-6);
}

.review {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  /* Tinted to the navy ground rather than a flat black drop. */
  box-shadow: 0 1px 2px rgba(26, 31, 45, 0.05);
}

.review-stars {
  margin: 0;
  color: #b26a00; /* 3:1 on the white card; never the raw Google amber on white. */
  font-size: var(--fs-lg);
  line-height: 1;
  letter-spacing: 0.1em;
}

.review-text {
  margin: 0;
  flex: 1;
}

.review-text p {
  margin: 0;
  color: var(--color-fg);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
}

.review-attrib {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: var(--fs-sm);
}

.review-when {
  color: var(--color-fg-subtle);
}

.review-text--rating-only {
  color: var(--color-fg-muted);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
}

.reviews-foot {
  margin: var(--space-8) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}

@media (max-width: 640px) {
  .reviews {
    padding-block: var(--space-16);
  }
  .reviews-head {
    display: block;
  }
  .reviews-head h2 {
    max-width: none;
    margin-bottom: var(--space-5);
  }
  .reviews-list {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reviews-badge {
    transition: none;
  }
  .reviews-badge:hover,
  .reviews-badge:focus-visible {
    transform: none;
  }
}

/* components/wizard.css */
/* wizard.css: Quote wizard
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Quote wizard ===== */

.wizard {
  max-width: 600px;
  margin-inline: auto;
}

.wizard-progress-sticky {
  margin-block-end: var(--space-4);
}
@media (max-width: 640px) {
  .wizard-progress-sticky {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--color-bg);
    padding-block: var(--space-2);
    margin-inline: calc(var(--space-6) * -1);
    padding-inline: var(--space-6);
  }
}

.wizard-progress {
  height: 8px;
  background: var(--color-border);
  border-radius: var(--radius-full);
  overflow: hidden;
  margin-block-end: var(--space-2);
}
@media (min-width: 640px) {
  .wizard-progress { height: 10px; }
}
.wizard-progress-bar {
  height: 100%;
  width: 20%;
  background: var(--color-primary);
  transition: width 300ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .wizard-progress-bar { transition: none; }
}

.wizard-step-counter {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-end: 0;
}

.wizard-time-estimate {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  text-align: center;
  margin-block-end: var(--space-2);
}

.wizard-testimonial {
  margin-block-end: var(--space-5);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-alt);
  border-radius: var(--radius-md);
  border-left: 3px solid var(--color-secondary);
}
.wizard-testimonial-body {
  font-size: var(--fs-sm);
  font-style: italic;
  color: var(--color-fg);
  margin-block-end: var(--space-1);
}
.wizard-testimonial-author {
  display: block;
  font-size: var(--fs-xs);
  color: var(--color-fg-muted);
}

@keyframes wizard-step-in-forward {
  from { opacity: 0; transform: translateX(12px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes wizard-step-in-back {
  from { opacity: 0; transform: translateX(-12px); }
  to   { opacity: 1; transform: translateX(0); }
}
.wizard-step[data-anim="forward"] { animation: wizard-step-in-forward 200ms ease both; }
.wizard-step[data-anim="back"]    { animation: wizard-step-in-back 200ms ease both; }
@media (prefers-reduced-motion: reduce) {
  .wizard-step[data-anim] { animation: none; }
}

.wizard-step h2 { margin-block-end: var(--space-2); }
.wizard-step > p { color: var(--color-fg-muted); margin-block-end: var(--space-8); }

.wizard-options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
@media (max-width: 380px) {
  .wizard-options { grid-template-columns: 1fr; }
}

.wizard-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  gap: var(--space-2);
  min-height: 124px;
  background: var(--color-bg);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-3);
  cursor: pointer;
  transition: all var(--duration-fast) var(--ease-out);
  font-family: inherit;
  width: 100%;
  color: var(--color-fg);
}
.wizard-option:hover {
  border-color: var(--color-primary);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}
.wizard-option[data-selected="true"] {
  background: var(--color-primary);
  color: var(--color-fg-inverse);
  border-color: var(--color-primary);
  box-shadow: 0 8px 24px rgba(36, 63, 163, 0.18);
}
.wizard-option-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  color: var(--color-primary);
  transition: color var(--duration-fast) var(--ease-out);
}
.wizard-option[data-selected="true"] .wizard-option-icon {
  color: var(--color-fg-inverse);
}
.wizard-option strong {
  font-size: var(--fs-base);
  color: var(--color-fg);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.005em;
}
.wizard-option span {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  line-height: var(--lh-snug);
}
.wizard-option[data-selected="true"] strong { color: var(--color-fg-inverse); }
.wizard-option[data-selected="true"] span { color: rgba(247, 242, 235, 0.78); }

.wizard-form p { margin-block-end: var(--space-4); }
.wizard-form--inline {
  margin-block-start: var(--space-6);
  max-width: 320px;
}
.wizard-form--inline p:last-child { margin-block-end: 0; }

.wizard-photo-upload { margin-block-start: var(--space-2); }
.wizard-photo-trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-5) var(--space-4);
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.wizard-photo-trigger:hover {
  border-color: var(--color-primary);
  background: rgba(58, 86, 115, 0.04);
}
.wizard-photo-trigger:focus-within {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.wizard-photo-icon {
  width: 28px;
  height: 28px;
  color: var(--color-primary);
}
.wizard-photo-label {
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
}
.wizard-photo-hint {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}
.wizard-photo-previews {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  padding: 0;
  list-style: none;
}
.wizard-photo-previews li {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-bg-muted, #ece6dc);
}
.wizard-photo-previews img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.wizard-photo-previews .wizard-photo-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: rgba(31, 37, 48, 0.78);
  color: var(--color-fg-inverse);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.wizard-photo-previews .wizard-photo-remove:hover { background: var(--color-danger); }

.wizard-photo-pickers {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-block-end: var(--space-2);
}
@media (max-width: 380px) {
  .wizard-photo-pickers { grid-template-columns: 1fr; }
}
.wizard-photo-picker {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-4) var(--space-3);
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
}
.wizard-photo-picker:hover {
  border-color: var(--color-primary);
  background: rgba(58, 86, 115, 0.04);
}
.wizard-photo-picker:focus-within {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.wizard-photo-picker svg {
  width: 24px;
  height: 24px;
  color: var(--color-primary);
}
/* Busy state while client-side compression runs. Disables further input,
   overlays a brief "Processing photos…" label. Usually <1s; longer on
   multi-megapixel photos picked from a flagship Android camera. */
.wizard-photo-pickers.is-busy {
  pointer-events: none;
  opacity: 0.55;
  position: relative;
}
.wizard-photo-pickers.is-busy::after {
  content: "Processing photos…";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium, 500);
  color: var(--color-fg);
  background: rgba(247, 242, 235, 0.85);
  border-radius: var(--radius-md);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .wizard-photo-pickers.is-busy { transition: none; }
}
.wizard-photos-trust {
  font-size: var(--fs-xs);
  color: var(--color-fg-muted);
  margin-block-start: var(--space-2);
  margin-block-end: var(--space-2);
}


/* Location step: the postcode field sits above an optional photo picker, so the
   photos block gets its own sub-heading rather than a step of its own. */
.wizard-photos-step {
  margin-block: var(--space-6) var(--space-2);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--color-border);
}
.wizard-photos-step h3 {
  font-size: var(--fs-lg);
  margin: 0 0 var(--space-1);
  color: var(--color-fg);
}
.wizard-photos-step > p {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin: 0 0 var(--space-4);
}

/* Plain-English handover notice above the trust list on the contact step.
   Required by the fork's privacy position: the enquiry goes to one local
   installer and nowhere else. */
.wizard-handover-note {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
  margin-block: var(--space-4) var(--space-2);
}


.wizard-captcha {
  margin-block-start: var(--space-4);
}
.wizard-captcha .cf-turnstile {
  display: inline-block;
}
.wizard-captcha .form-error {
  display: block;
  margin-block-start: var(--space-1);
}
.wizard-submit-error {
  margin-block-start: var(--space-3);
  padding: var(--space-3);
  background: #fef2f2;
  border: 1px solid #fca5a5;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: #b91c1c;
}
.wizard-submit-error p { margin: 0; }
.wizard-submit-error a { color: inherit; font-weight: var(--fw-medium); }

.wizard-form label {
  display: block;
  font-weight: var(--fw-medium);
  color: var(--color-primary);
}
.wizard-form input:not([hidden]):not([type="hidden"]),
.wizard-form textarea {
  display: block;
  width: 100%;
  padding: var(--space-3) var(--space-4);
  margin-block-start: var(--space-2);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  color: var(--color-fg);
  transition: border-color var(--duration-fast) var(--ease-out);
}
.wizard-form input:focus,
.wizard-form textarea:focus {
  outline: none;
  border-color: var(--color-primary);
}
.wizard-form input:user-invalid,
.wizard-form textarea:user-invalid,
.wizard-form input[aria-invalid="true"] {
  border-color: var(--color-danger);
}

.form-error {
  display: block;
  color: var(--color-danger);
  font-size: var(--fs-sm);
  margin-block-start: var(--space-1);
  font-weight: var(--fw-medium, 500);
}

.wizard-field-note {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-start: var(--space-2);
  margin-block-end: var(--space-4);
}

.wizard-trust-list {
  list-style: none;
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0;
  background: var(--color-bg-alt);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  font-size: var(--fs-xs);
  color: var(--color-fg-muted);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.wizard-trust-list li::before {
  content: '✓ ';
  color: var(--color-primary);
}
.wizard-trust-list a {
  color: inherit;
  text-decoration: underline;
}

.wizard-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-block-start: var(--space-8);
  gap: var(--space-3);
}
.wizard-nav button[hidden] { display: none; }
.wizard-nav button:only-child { margin-inline-start: auto; }

.wizard-success {
  padding-block: var(--space-6);
}
.wizard-success-heading {
  font-size: var(--fs-3xl);
  color: var(--color-primary);
  margin-block-end: var(--space-3);
}
.wizard-success .lede {
  font-size: var(--fs-lg);
  color: var(--color-fg);
  margin-block: 0 var(--space-6);
}
.wizard-success-emailed {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-end: var(--space-4);
}
.wizard-success-reference {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--color-primary);
  color: var(--color-primary-fg);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-6);
  width: fit-content;
}
.wizard-success-reference-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.75;
  white-space: nowrap;
}
.wizard-success-reference code {
  font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
  font-size: var(--fs-base);
  font-weight: 600;
  user-select: all;
  -webkit-user-select: all;
}
.wizard-success-recap {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-block-end: var(--space-5);
}
.wizard-success-recap dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-fg-muted);
  padding-block: var(--space-1);
  align-self: center;
}
.wizard-success-recap dd {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--color-fg);
  margin: 0;
  padding-block: var(--space-1);
  align-self: center;
}
@media (min-width: 560px) {
  .wizard-success-recap {
    grid-template-columns: repeat(2, max-content 1fr);
  }
}

/* Closing line under the recap: what actually happens next, no deposit talk. */
.wizard-success-note {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-end: var(--space-5);
}
.wizard-success-crosssell {
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-block-end: var(--space-6);
}
.wizard-success-crosssell > p {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  margin-block-end: var(--space-2);
}
.wizard-success-crosssell ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}
.wizard-success-crosssell a {
  font-size: var(--fs-sm);
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.wizard-fallback {
  text-align: center;
  margin-block-start: var(--space-8);
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}

/* components/extras.css */
/* extras.css: Project detail / Gallery item / Lightbox / FAQ / Carousel / Image bands / Reveal / Owner photo / Social links / Visually-hidden
   Imported by components.css. Cascade order matters; do not reorder imports. */

/* ===== Project detail meta ===== */

.project-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--space-6);
  row-gap: var(--space-3);
  margin-block: var(--space-5);
  padding: var(--space-5);
  background: var(--color-bg-muted, var(--color-bg));
  border: 1px solid var(--color-border, rgba(0,0,0,0.08));
  border-radius: var(--radius-md, 0.5rem);
}
.project-meta dt {
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}
.project-meta dd { margin: 0; color: var(--color-fg); }

/* ===== Gallery item (clickable) ===== */

.gallery-item {
  display: block;
  width: 100%;
  height: 100%;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  border-radius: inherit;
  color: inherit;
  text-decoration: none;
}
.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-base) var(--ease-out);
}
.gallery-item:hover img,
.gallery-item:focus-visible img {
  transform: scale(1.05);
}
.gallery-item:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}

/* ===== Lightbox ===== */

.lightbox {
  border: none;
  background: transparent;
  padding: 0;
  width: 100vw;
  height: 100vh;
  max-width: 100vw;
  max-height: 100vh;
}
.lightbox::backdrop {
  background: rgba(0, 0, 0, 0.92);
  backdrop-filter: blur(8px);
}
.lightbox[open] {
  display: grid;
  place-items: center;
  animation: lightboxIn var(--duration-base) var(--ease-out);
}
@keyframes lightboxIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.lightbox-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  max-width: 90vw;
  max-height: 90vh;
}
.lightbox-img {
  max-width: 100%;
  max-height: 70vh;
  display: block;
  border-radius: var(--radius-md);
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
}
.lightbox-caption {
  width: min(720px, 90vw);
  background: rgba(247, 242, 235, 0.06);
  border: 1px solid rgba(247, 242, 235, 0.12);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  color: rgba(247, 242, 235, 0.92);
  font-size: var(--fs-sm);
  backdrop-filter: blur(6px);
}
.lightbox-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-3) var(--space-6);
  margin: 0;
}
.lightbox-meta > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lightbox-meta dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: rgba(247, 242, 235, 0.55);
  font-weight: var(--fw-semibold);
}
.lightbox-meta dd {
  margin: 0;
  color: rgba(247, 242, 235, 0.95);
  font-weight: var(--fw-medium);
}
.lightbox:not(.has-caption) .lightbox-caption { display: none; }
.lightbox:not(.has-caption) .lightbox-img { max-height: 85vh; }

.lightbox-close,
.lightbox-nav {
  position: fixed;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-full);
  cursor: pointer;
  display: grid;
  place-items: center;
  font-family: inherit;
  line-height: 1;
  transition: background var(--duration-fast) var(--ease-out);
}
.lightbox-close:hover,
.lightbox-nav:hover {
  background: rgba(255, 255, 255, 0.2);
}
.lightbox-close:focus-visible,
.lightbox-nav:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.lightbox-close {
  top: var(--space-6);
  right: var(--space-6);
  width: 44px; height: 44px;
  font-size: var(--fs-2xl);
}

.lightbox-nav {
  top: 50%;
  width: 56px; height: 56px;
  font-size: var(--fs-3xl);
  transform: translateY(-50%);
}
.lightbox-prev { left: var(--space-6); }
.lightbox-next { right: var(--space-6); }

.lightbox-counter {
  position: fixed;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.7);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  margin: 0;
}

@media (max-width: 700px) {
  .lightbox-nav { width: 44px; height: 44px; font-size: var(--fs-2xl); }
  .lightbox-prev { left: var(--space-3); }
  .lightbox-next { right: var(--space-3); }
  .lightbox-close { top: var(--space-3); right: var(--space-3); }
}

/* ===== Details / FAQ accordion ===== */

.prose details {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-block: var(--space-2);
  background: var(--color-bg);
}
.prose details summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  color: var(--color-primary);
  background: var(--color-bg-alt);
  transition: background var(--duration-fast);
  list-style: none;
  user-select: none;
}
.prose details summary::-webkit-details-marker { display: none; }
.prose details summary::marker { content: ""; }
.prose details summary::after {
  content: "+";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-normal);
  color: var(--color-primary);
  transition: transform var(--duration-base) var(--ease-out);
  flex-shrink: 0;
  margin-inline-start: var(--space-3);
  line-height: 1;
}
.prose details[open] summary::after {
  transform: rotate(45deg);
}
.prose details summary:hover {
  background: var(--color-bg);
}
.prose details > *:not(summary) {
  padding: var(--space-4) var(--space-5);
  margin-block: 0;
  animation: detailsContentIn var(--duration-base) var(--ease-out);
}
@keyframes detailsContentIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ===== Testimonials carousel (mobile only) ===== */

@media (max-width: 700px) {
  .testimonials-grid {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: var(--space-4);
    margin-inline: calc(-1 * var(--container-padding));
    padding: 0 var(--container-padding) var(--space-4);
    scroll-padding-inline: var(--container-padding);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .testimonials-grid::-webkit-scrollbar {
    display: none;
  }
  .testimonials-grid > .testimonial {
    flex: 0 0 85%;
    scroll-snap-align: start;
  }
}

/* ===== Page image band (sub-page hero photo) ===== */

.page-image-band {
  padding-block: 0 var(--space-12);
  background: var(--color-bg-alt);
}
.page-image-band img {
  width: 100%;
  /* One shape for every page image, so the bands line up down the site. 3:2 is
     the compromise: taller than the old 16:7, which cropped the work out of the
     walling and patios shots, and shallower than 4:3, which made the band tower
     over the copy. Sources are cut to 3:2 where the framing matters. */
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

/* ===== Blog post hero image ===== */

.post-hero {
  margin-block: 0 var(--space-12);
}
.post-hero img {
  width: 100%;
  aspect-ratio: 21 / 9;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

/* ===== Reveal animations ===== */

.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 380ms var(--ease-out),
    transform 380ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 60ms);
  will-change: opacity, transform;
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ===== Owner photo component =====
   Renders as either a placeholder (div with data-needs-photo attribute,
   shows a dashed circle + camera icon) or a real <img class="owner-photo-frame">.
   Wrap <img> in <picture> for WebP+JPEG; the .owner-photo picture rule
   keeps it from collapsing in flex parents.
   Pattern: drop <div class="owner-photo-frame" data-needs-photo aria-label="Photo to come">
   on every page that needs an owner shot. Visible to user while photo is pending. */
.owner-photo {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-block: var(--space-6);
}
.owner-photo-frame {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-full);
  background: var(--color-bg-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-fg-muted);
  font-size: 1.5rem;
  flex-shrink: 0;
  overflow: hidden;
  object-fit: cover;
  object-position: 50% 25%;
}
img.owner-photo-frame {
  display: block;
  box-shadow: 0 2px 8px rgba(31, 37, 48, 0.12);
}
.owner-photo picture {
  flex: 0 0 auto;
  display: inline-block;
  line-height: 0;
}
.owner-photo--large picture .owner-photo-frame {
  width: 140px;
  height: 140px;
}
.owner-photo-frame[data-needs-photo] {
  border: 2px dashed var(--color-border-strong);
}
.owner-photo-frame[data-needs-photo]::after {
  content: "📷";
}
.owner-photo-caption {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  line-height: 1.5;
  margin: 0;
}
.owner-photo-caption strong {
  display: block;
  font-size: var(--fs-base);
  color: var(--color-fg);
  font-weight: var(--fw-semibold);
  margin-bottom: 2px;
}
.owner-photo--large .owner-photo-frame {
  width: 140px;
  height: 140px;
  font-size: 2.5rem;
}

/* ===== Social links (footer brand column) =====
   Renders as 36px circular buttons with translucent-white background.
   Each link is gated on the matching business.social.* JSON key, so
   an unset socials object renders no row at all. */
.social-links {
  display: flex;
  gap: var(--space-3);
  margin-block-start: var(--space-4);
  padding: 0;
  list-style: none;
}
.social-links li { margin: 0; }
.social-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  opacity: 1;
  transition: background var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out);
}
.social-links a:hover,
.social-links a:focus-visible {
  background: rgba(255, 255, 255, 0.18);
  transform: translateY(-1px);
  opacity: 1;
}
.social-links img { width: 18px; height: 18px; display: block; }

/* Visually-hidden utility: visible to screen readers, removed from layout.
   Use instead of the `hidden` attribute on controls that should remain
   focusable and announced by AT (file inputs, screen-reader-only labels). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===== Photo placeholder =====
   This fork has no job photography yet. Every image slot renders
   the photo-pending partial instead of a borrowed or broken
   picture: a dashed sett-patterned panel that names the shot it is waiting on.
   Honest to the client, honest to anyone reviewing, and never looks broken.
   Swap the partial for the picture partial when real photos land. */
.photo-pending {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 2px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-alt);
  background-image:
    repeating-linear-gradient(90deg, rgba(29, 39, 51, 0.07) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(180deg, rgba(29, 39, 51, 0.07) 0 1px, transparent 1px 36px);
  color: var(--color-fg-muted);
  overflow: hidden;
}
.photo-pending::before {
  content: "📷";
  font-size: 1.5rem;
  line-height: 1;
  opacity: 0.55;
}
.photo-pending-label {
  position: absolute;
  inset-block-end: var(--space-3);
  inset-inline: var(--space-3);
  text-align: center;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: var(--fw-semibold);
  color: var(--color-fg-subtle);
}

/* Slot-specific fits. The parent already sets the aspect ratio and clipping in
   each of these, so the placeholder just fills it. */
.service-card-media .photo-pending,
.gallery-grid .photo-pending {
  height: 100%;
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
}
.page-image-band .photo-pending {
  aspect-ratio: 3 / 2;
  border-radius: var(--radius-lg);
}
.hero .photo-pending {
  aspect-ratio: 4 / 3;
  border-color: rgba(245, 244, 240, 0.28);
  background-color: rgba(29, 39, 51, 0.55);
  background-image:
    repeating-linear-gradient(90deg, rgba(245, 244, 240, 0.08) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(180deg, rgba(245, 244, 240, 0.08) 0 1px, transparent 1px 36px);
}
.hero .photo-pending-label { color: rgba(246, 244, 239, 0.62); }

/* ===== Conduct promises band =====
   Replaces the template's accreditation logo strip. This fork holds no scheme
   memberships, so it states how we behave instead of showing badges we have
   not earned. Fed by _data/accreditations.json. */
.promises {
  padding-block: var(--space-10) var(--space-12);
  background: var(--color-bg-alt);
  border-block: 1px solid var(--color-border);
}
.promises-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-6) var(--space-10);
}
.promise {
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  column-gap: var(--space-3);
  align-items: start;
}
.promise-tick {
  width: 1.5rem;
  height: 1.5rem;
  grid-row: span 2;
  color: var(--color-accent);
  margin-block-start: 2px;
}
.promise strong {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}
.promise span {
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
  line-height: 1.55;
  margin-block-start: 2px;
}

@media (max-width: 700px) {
  .promises { padding-block: var(--space-8) var(--space-10); }
}

/* ===== Empty-state panel =====
   Used where a data-driven grid has nothing in it yet (the gallery, before any
   job photos exist). Says what is coming without pretending it is already there. */
.empty-state {
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-lg);
  background: var(--color-bg-alt);
  padding: var(--space-8);
  text-align: center;
  color: var(--color-fg-muted);
  max-width: 65ch;
  margin-inline: auto;
  margin-block-start: var(--space-8);
}
.empty-state p { margin: 0; }
.empty-state p + p { margin-block-start: var(--space-3); }

/* Preview-build note under any form that has no live integration key behind it.
   Deliberately plain and quiet: it is an honesty notice, not a warning banner. */
.form-preview-note {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--color-secondary);
  background: var(--color-bg);
  color: var(--color-fg-muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}

/* ===== Proof band (r2) =====
   Sits directly under the homepage hero so the three promised things, his
   reviews, his photos and his towns, land inside the first two screens instead
   of four to eight screens down (the r1 review round's top finding). Light
   ground, clickable badge, three of his strongest finished jobs. */

.proof-band {
  padding-block: var(--space-10);
  background: var(--color-bg-alt);
  border-bottom: 1px solid var(--color-border);
}

.proof-band-inner {
  display: grid;
  gap: var(--space-6);
  align-items: center;
}

@media (min-width: 880px) {
  .proof-band-inner {
    grid-template-columns: 5fr 7fr;
    gap: var(--space-10);
  }
}

.proof-band-lead {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: flex-start;
}

.proof-band-quote { margin: 0; }

.proof-band-quote p {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--color-fg);
}

.proof-band-quote footer {
  margin-top: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}

.proof-band-towns {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--color-fg-muted);
}

.proof-band-thumbs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.proof-band-thumbs a {
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.proof-band-thumbs img {
  display: block;
  width: 100%;
  height: auto;
}

