/* Osteen & Sons — design tokens & site styles
   Ported from the Claude Design handoff. Purple-black, marigold CTAs only. */

/* Self-hosted licensed webfonts (Envato): Mortend display, Rundale body,
   Crato Mono data. font-display: swap keeps text visible during load. */
@font-face {
  font-family: 'Mortend';
  src: url('/assets/fonts/mortend-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Mortend';
  src: url('/assets/fonts/mortend-extrabold.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Rundale';
  src: url('/assets/fonts/rundale-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Rundale';
  src: url('/assets/fonts/rundale-medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Rundale';
  src: url('/assets/fonts/rundale-semibold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Crato Mono';
  src: url('/assets/fonts/crato-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Crato Mono';
  src: url('/assets/fonts/crato-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink: #0C0913;
  --ink-raised: #191227;
  --violet: #7C3AED;
  --violet-deep: #3B1078;
  --marigold: #F5B544;
  --marigold-hover: #ffc75e;
  --bone: #EFEAE1;
  --bone-80: rgba(239, 234, 225, 0.8);
  --bone-65: rgba(239, 234, 225, 0.65);
  --bone-60: rgba(239, 234, 225, 0.6);
  /* 0.55 keeps ".field .opt" at 5.30:1 on --ink-raised (WCAG 1.4.3 AA). */
  --bone-45: rgba(239, 234, 225, 0.55);
  /* Component boundaries, not decoration: these must clear 3:1 (WCAG 1.4.11). */
  --line: rgba(157, 111, 240, 0.45);
  --line-strong: rgba(168, 128, 245, 0.85);
  --font-display: 'Mortend', 'Archivo', sans-serif;
  --font-body: 'Rundale', 'Instrument Sans', sans-serif;
  --font-mono: 'Crato Mono', 'IBM Plex Mono', monospace;
  --pad-x: clamp(20px, 4vw, 40px);
  --sec-pad: clamp(56px, 9vw, 110px);
  --header-h: 76px;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; background: var(--ink); }
/* Keep anchor jumps and focus moves clear of the sticky header (WCAG 2.4.11).
   The mobile bar's matching scroll-padding-bottom lives in the ≤719px block. */
html { scroll-padding-top: calc(var(--header-h) + 12px); }
body {
  font-family: var(--font-body);
  color: var(--bone);
  -webkit-font-smoothing: antialiased;
  font-size: 16px;
  line-height: 1.5;
}
a { color: var(--marigold); text-decoration: none; }
a:hover { color: var(--bone); }
/* Links inside a run of text need more than hue to read as links (WCAG 1.4.1).
   Standalone links styled as buttons/chips/nav items are exempt. */
.footer-area a,
.hero-note a,
.towns-note a,
p a:not(.btn):not(.chip):not(.text-link):not(.mono-link) {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
img { max-width: 100%; }
h1, h2, h3 {
  font-family: var(--font-display);
  margin: 0;
  text-wrap: pretty;
  /* Display leading — h1 keeps its tighter 1.02 override below. */
  line-height: 1.08;
  letter-spacing: -0.02em;
  /* "walkthrough" is wider than a 320px column; let it break (WCAG 1.4.10). */
  overflow-wrap: break-word;
}
/* Skip past the header on every page (WCAG 2.4.1). */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 60;
  background: var(--marigold);
  color: var(--ink);
  padding: 12px 18px;
  border-radius: 0 0 8px 0;
  font-family: var(--font-body);
  font-weight: 600;
}
.skip-link:focus { left: 0; }
/* Sections clip anything that overhangs instead of scrolling the document. */
.section, .page-head, .final-cta { overflow-x: clip; }
/* In-flight buttons keep focus (aria-disabled), so style the busy state here. */
.btn[aria-disabled="true"] { opacity: .6; cursor: progress; }
*:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px rgba(239, 234, 225, 0.22);
}
input::placeholder { color: rgba(239, 234, 225, 0.55); }

.wrap { max-width: 1100px; margin: 0 auto; }
.wrap-narrow { max-width: 720px; margin: 0 auto; text-align: center; }
.wrap-narrow h1 { max-width: none; }
.mono-note { font-family: var(--font-mono); font-size: 13px; color: var(--bone-60); }
.mono-note.dim { color: var(--bone-60); margin-top: 14px; }
.mono-link { font-family: var(--font-mono); font-size: 13px; }
/* 13px standalone links are 13px tall; pad the box to 24px (WCAG 2.5.8). */
a.mono-link,
.rail-note a,
.review figcaption a { display: inline-flex; align-items: center; min-height: 24px; }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 8px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 16px;
  line-height: 1;
  /* Rundale reserves a deep descent; the tiny top padding recenters the ink. */
  padding: 3px 18px 0;
  min-height: 44px;
  cursor: pointer;
  white-space: nowrap;
}
a.btn { text-decoration: none; }
.btn-cta {
  position: relative;
  overflow: hidden;
  background: var(--marigold);
  color: var(--ink);
}
.btn-cta:hover { background: var(--marigold-hover); color: var(--ink); }
/* One diagonal light sweep per hover. */
@keyframes btnShine {
  from { transform: translateX(-120%) skewX(-18deg); }
  to { transform: translateX(320%) skewX(-18deg); }
}
.btn-cta::after {
  content: '';
  position: absolute;
  top: -50%;
  left: 0;
  width: 40%;
  height: 200%;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0) 100%);
  transform: translateX(-120%) skewX(-18deg);
}
.btn-cta:hover::after { animation: btnShine 600ms ease-out; }
.btn-ghost {
  background: transparent;
  color: var(--bone);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
}
.btn-ghost:hover { border-color: var(--violet); }
.grow { flex: 1; min-width: 150px; padding: 3px 18px 0; min-height: 48px; border-radius: 10px; }

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: var(--header-h);
  padding: 16px var(--pad-x);
  /* Transparent at the top of the page; the bar fades in once you scroll. */
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: padding 200ms ease, background 250ms ease, border-color 250ms ease;
}
.site-header.condensed,
.site-header:has(.nav-panel:not([hidden])) {
  background: rgba(12, 9, 19, 0.86);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--line);
}
.site-header.condensed { padding-top: 8px; padding-bottom: 8px; }

/* Heroes and page heads tuck under the transparent bar */
.hero, .page-hero, .page-head { margin-top: calc(-1 * var(--header-h)); }
.hero { padding-top: var(--header-h); }
.brand { display: flex; align-items: center; gap: 10px; color: var(--bone); }
.brand-mark { height: 34px; width: auto; display: block; }
/* No brightness boost: it pushed the violet wordmark off-hue into magenta.
   A soft dark halo lifts it off the ink instead, leaving the colour alone. */
.brand-word { height: 13px; width: auto; display: block; filter: drop-shadow(0 1px 2px rgba(12, 9, 19, 0.8)); }
.header-actions { display: flex; align-items: center; gap: 12px; }
.header-phone {
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 12px 8px;
  min-height: 44px;
  display: flex;
  align-items: center;
}

/* Top nav — inline on desktop, slide-down panel on mobile */
.site-nav { display: flex; align-items: center; gap: 2px; }
.site-nav a {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 12px 10px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone-80);
  border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--bone); }
.site-nav a[aria-current="page"] { color: var(--bone); border-bottom-color: var(--violet); }
/* Hover underline wipes in from the left; the current page keeps its solid rule. */
.site-nav a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: var(--violet);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 200ms ease;
}
.site-nav a:hover::after { transform: scaleX(1); }
.site-nav a[aria-current="page"]::after { display: none; }

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  color: var(--bone);
  cursor: pointer;
}
.nav-toggle:hover { border-color: var(--violet); }
.nav-bars {
  position: relative;
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: background-color 150ms ease;
}
.nav-bars::before,
.nav-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 150ms ease, top 150ms ease;
}
.nav-bars::before { top: -6px; }
.nav-bars::after { top: 6px; }
.nav-toggle.open .nav-bars { background: transparent; }
.nav-toggle.open .nav-bars::before { top: 0; transform: rotate(45deg); }
.nav-toggle.open .nav-bars::after { top: 0; transform: rotate(-45deg); }

@keyframes navDrop { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
.nav-panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  flex-direction: column;
  padding: 4px var(--pad-x) 12px;
  /* Fully opaque: at 0.97 the hero kicker still read through the CONTACT row,
     which made the open menu look like a rendering fault instead of a mode. */
  background: var(--ink-raised);
  border-bottom: 1px solid var(--line);
  animation: navDrop 180ms ease-out both;
}
/* Scrim the rest of the page while the menu is open. Lives on the header so it
   needs no JS; z-index -1 keeps it under the header's own content. */
.site-header:has(.nav-panel:not([hidden]))::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background: rgba(12, 9, 19, 0.72);
  pointer-events: none;
}
.nav-panel a {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 4px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone);
  border-bottom: 1px solid var(--line);
}
.nav-panel a:last-child { border-bottom: none; }
.nav-panel a[aria-current="page"] { color: var(--bone); box-shadow: inset 2px 0 0 var(--violet); padding-left: 12px; }
/* The highest-intent action for a local trade belongs in the primary menu —
   .header-phone is hidden at these widths, so reinstate it here. */
.nav-panel .nav-phone {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line-strong);
  color: var(--marigold);
  font-size: 16px;
}

/* Inner page heads (quieter than the home hero — no canvas) */
.page-head {
  padding: calc(clamp(48px, 7vw, 84px) + var(--header-h)) var(--pad-x) clamp(28px, 4vw, 44px);
  background: linear-gradient(180deg, rgba(59, 16, 120, 0.3), rgba(59, 16, 120, 0));
  border-bottom: 1px solid var(--line);
}
.page-head h1 { max-width: 18ch; margin-bottom: 18px; }
.page-lede { max-width: 60ch; margin: 0; font-size: 20px; line-height: 1.5; color: var(--bone-80); }
.h2-link {
  display: inline-block;
  margin-left: 12px;
  padding: 8px 4px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-stretch: normal;
  color: var(--marigold);
  white-space: nowrap;
  vertical-align: middle;
}
.h2-link span { display: inline-block; transition: transform 200ms ease; }
.h2-link:hover span { transform: translateX(4px); }

/* Hero */
.hero {
  position: relative;
  /* x only: the address suggestions (.addr-suggest) hang below the hero's edge. */
  overflow-x: clip;
  min-height: min(86vh, 760px);
  display: flex;
  align-items: center;
  touch-action: pan-y;
}
#mow-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.hero-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: clamp(56px, 10vh, 120px) var(--pad-x);
}
/* 4% of canvas blades render at full violet, which drops the 13px hero copy to
   3.18:1 wherever a blade lands behind a glyph. The small copy gets a tight ink
   text-shadow (below) for that; this wash stays faint on purpose — the mower
   cutting stripes IS the hero, and a heavy left-side scrim hid it. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg,
              rgba(12, 9, 19, 0.28) 0%, rgba(12, 9, 19, 0.12) 30%, rgba(12, 9, 19, 0) 50%);
}
.hero .hero-note,
.hero h1 {
  text-shadow: 0 1px 2px rgba(12, 9, 19, 0.95), 0 0 10px rgba(12, 9, 19, 0.7);
}
/* The canvas is generated, not photographed, so "where the glyph lands" is a
   different answer every frame: sampled over a full mower pass, the 13px copy
   bottomed out at 4.36:1 (marigold kicker over a #7C3AED blade) and 1.80:1
   (the note with the mower's own marigold deck behind it) — with the shadows
   above already counted. The wash stays faint on purpose, so the two small
   lines get the same plate the rest of the site puts under small copy over
   imagery (.ph-mark, the in-frame before/after chips): same ink, same 0.72,
   same radius. Worst case with it: 6.0:1 for the kicker, 9.1:1 for the note.
   h1 is large text and clears 3:1 unaided — it keeps the bare canvas behind it. */
.hero .kicker,
.hero .hero-note {
  display: inline-block;
  background: rgba(12, 9, 19, 0.72);
  border-radius: 6px;
  padding: 4px 9px;
  margin-left: -9px;
}
.kicker {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--marigold);
  letter-spacing: 0.08em;
  margin-bottom: 16px;
  /* Same reason, for the kicker over the photo heroes. */
  text-shadow: 0 1px 6px rgba(12, 9, 19, 0.9);
}
h1 {
  font-weight: 800;
  /* 32px floor, not 40px: at 40px "walkthrough" alone rendered 428px wide in a
     280px column on /contact, scrolling the document sideways at 320 and 390. */
  font-size: clamp(32px, 7vw, 64px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0 0 28px;
  max-width: 12ch;
}
.addr-form {
  position: relative;
  display: flex;
  gap: 10px;
  max-width: 520px;
  background: var(--ink-raised);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  padding: 8px;
  text-align: left;
}
.final-cta .addr-form { max-width: none; }
.addr-form input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  color: var(--bone);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 10px 12px;
  min-height: 44px;
}
/* Address suggestions (main.js). Floats under the bar in the hero and final CTA;
   sits in the flow inside the modal and /contact, whose scroll boxes would clip
   anything absolutely placed. */
.addr-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 30;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--ink-raised);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  max-height: 300px;
  overflow-y: auto;
  text-align: left;
}
.qm-field .addr-suggest,
.field .addr-suggest { position: static; box-shadow: none; }
.addr-suggest li {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 44px;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
}
.addr-suggest li:hover,
.addr-suggest li[aria-selected="true"] { background: rgba(124, 58, 237, 0.32); }
.addr-suggest li[aria-selected="true"] { outline: 2px solid var(--line-strong); outline-offset: -2px; }
.addr-suggest-street { color: var(--bone); font-family: var(--font-body); font-size: 16px; }
.addr-suggest-place { color: var(--bone-80); font-family: var(--font-mono); font-size: 12px; }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* 0.86, not --bone-65: at 65% this sits at 2.84:1 over the brightest canvas. */
.hero-note { font-family: var(--font-mono); font-size: 13px; color: rgba(239, 234, 225, 0.86); margin-top: 12px; }

/* Trust strip */
.trust-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  justify-content: center;
  padding: 14px var(--pad-x);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 13px;
  color: rgba(239, 234, 225, 0.75);
}

/* Sections & mow-stripe washes */
.section { padding: var(--sec-pad) var(--pad-x); }
.stripe-a { background: repeating-linear-gradient(45deg, rgba(59, 16, 120, 0.05) 0 60px, transparent 60px 120px); }
.stripe-b { background: repeating-linear-gradient(-45deg, rgba(59, 16, 120, 0.04) 0 60px, transparent 60px 120px); }
.section h2 { font-weight: 700; font-size: clamp(28px, 4vw, 40px); margin: 0 0 32px; }
/* The frame is a centred 4:5 portrait, so its heading, hint and caption centre with it. */
#before-after .wrap { text-align: center; }
#before-after h2 { margin: 0 auto 8px; }
#before-after .wrap > .mono-note { margin-bottom: 28px; }
.ba-frame .mono-note { text-align: left; }
.ba-caption { text-align: center; }

/* Scroll-driven reveals (graceful no-op without support) */
@keyframes fadeUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
@supports (animation-timeline: view()) {
  .reveal {
    animation: fadeUp both;
    animation-timeline: view();
    animation-range: entry 0% entry 40%;
  }
  /* Staggered entry: --rv shifts each item's slice of its own view() timeline,
     so siblings on the same row land one after another instead of together. */
  .svc-card,
  .step,
  .marks > .mark,
  .tile,
  .reviews-grid > *,
  .contact-cols > div,
  .trust-strip span {
    --rv: 0%;
    animation: fadeUp both;
    animation-timeline: view();
    animation-range: entry var(--rv) entry calc(var(--rv) + 40%);
  }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(2) { --rv: 7%; }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(3) { --rv: 14%; }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(4) { --rv: 21%; }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(5) { --rv: 28%; }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(6) { --rv: 35%; }
  :is(.svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span):nth-child(n + 7) { --rv: 42%; }
}

/* Before / after */
/* Every frame carries the aspect class of the pair inside it, so the photos
   fill it without a crop. max-width 620px keeps the widest source (656px) at
   or under 1x — nothing here is stretched past its own pixels. */
.ba-frame {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid rgba(124, 58, 237, 0.35);
  aspect-ratio: 4 / 5;
  max-width: 620px;
  margin-inline: auto;
  min-height: 0;
}
.ba-frame.ar-2x3 { aspect-ratio: 2 / 3; }
.ba-frame.ar-3x4 { aspect-ratio: 3 / 4; }
.ba-frame.ar-1x1 { aspect-ratio: 1 / 1; }
/* Job 05 came off the phone at 449px wide; cap the frame so a wide column
   doesn't blow it up soft. */
.ba-frame.ba-narrow { max-width: 449px; }
.ba-caption { max-width: 620px; margin: 12px auto 0; }
.ba-after {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(-38deg, #1a1230 0 26px, #150e26 26px 52px);
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 20px;
}
.ba-after img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ba-after .mono-note {
  position: relative;
  z-index: 1;
  background: rgba(12, 9, 19, 0.72);
  border-radius: 6px;
  padding: 4px 9px;
}
.ba-before {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(84deg, #241a3d 0 7px, #191227 7px 15px, #2a1d49 15px 21px);
  display: flex;
  align-items: flex-start;
  padding: 20px;
}
.ba-before img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ba-before .mono-note {
  position: relative;
  z-index: 1;
  background: rgba(12, 9, 19, 0.72);
  border-radius: 6px;
  padding: 4px 9px;
}

/* /work — one slider per job. One column on a phone, two from 900px up
   (the second column is set in the responsive block at the end of the file). */
.jobs-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 28px;
}
.jobs-grid figure { margin: 0; }
#jobs h2 { margin-bottom: 8px; }
#jobs .wrap > .mono-note { margin-bottom: 28px; }
/* Caption sits clear of the handle's 42px knob, which is centred vertically. */
.jobs-cap {
  margin-top: 12px;
  max-width: 620px;
  margin-inline: auto;
  font-size: 15px;
  line-height: 1.5;
  color: var(--bone-80);
}
.job-tag {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--marigold);
}
/* Services detail sections: the slider replaces the old single photo, capped
   so a 2:3 portrait doesn't tower over the three paragraphs beside it. */
.svc-ba { margin: 0; }
.svc-ba .ba-frame { max-width: 440px; }
/* Squarer crop on the services page: the tall 2:3 pairs towered over three
   paragraphs. Cover-cropping the middle keeps the house and the lawn. */
.svc-ba .ba-frame.ar-2x3,
.svc-ba .ba-frame.ar-3x4 { aspect-ratio: 5 / 4; max-width: 520px; }
.svc-ba .jobs-cap { max-width: 520px; }

/* Services */
.services-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}
.svc-card {
  position: relative;
  background: var(--ink-raised);
  border: 1px solid rgba(124, 58, 237, 0.35);
  border-radius: 12px;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 200px;
}
.svc-card.promoted { border-color: rgba(245, 181, 68, 0.55); }
.svc-card > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.svc-card > svg rect {
  fill: none;
  stroke: var(--marigold);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  transition: stroke-dashoffset 250ms ease-out;
}
.svc-card:hover > svg rect { stroke-dashoffset: 0; }
/* Titles run 1-3 lines and one card carries a badge; reserve three lines and
   float the badge into the top padding so every row (title, copy, price) lines
   up across the four cards. */
.svc-card { padding-top: 46px; }
.svc-card h3 { font-weight: 700; font-size: 20px; min-height: 4.4em; }
.svc-card p { margin: 0; color: var(--bone-80); flex: 1; }
.svc-card .badge { position: absolute; top: 14px; left: 24px; }
.svc-price { font-family: var(--font-mono); font-size: 13px; color: var(--marigold); }
.badge {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--marigold);
  border: 1px solid rgba(245, 181, 68, 0.5);
  border-radius: 99px;
  padding: 3px 10px;
}

/* How it works */
.steps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
.step { display: flex; flex-direction: column; gap: 12px; position: relative; }
.step h3 { font-weight: 700; font-size: 20px; }
/* Ordinal tag sitting on the card's top rule beside the node — the order is
   the content here, so it earns a number. */
.step-num {
  position: absolute;
  top: -9px;
  left: 70px;
  padding: 0 7px;
  background: var(--ink-raised);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  color: var(--bone-60);
  line-height: 18px;
}
.step p { margin: 0; color: var(--bone-80); }

/* How-it-works flow: a line draws across the sequence; each step's node
   lights up and its card rises as the line reaches it. */
.steps-flow { position: relative; padding-top: 26px; }
.flow-line {
  position: absolute;
  top: 26px;
  left: 40px;
  right: 40px;
  height: 2px;
  background: var(--line);
}
.flow-progress {
  display: block;
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--violet), var(--marigold));
  transform: scaleX(0);
  transform-origin: left;
}
.steps-flow.play .flow-progress { transform: scaleX(1); transition: transform 2.4s linear; }
.steps-flow .steps-grid { list-style: none; margin: 0; padding: 0; position: relative; }
.steps-flow .step {
  background: var(--ink-raised);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0 22px 22px;
}
.step-node {
  width: 52px;
  height: 52px;
  margin: -26px 0 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--ink);
  border: 2px solid var(--line-strong);
  color: var(--bone-60);
  transition: color 400ms ease, border-color 400ms ease, box-shadow 400ms ease;
}
/* armed by JS: hide until the sequence plays; without JS nothing hides */
.steps-flow.armed:not(.play) .step { opacity: 0; transform: translateY(16px); }
.steps-flow.play .step {
  opacity: 1;
  transform: none;
  transition: opacity 500ms ease, transform 500ms ease;
}
.steps-flow.play .step:nth-child(1) { transition-delay: 100ms; }
.steps-flow.play .step:nth-child(2) { transition-delay: 1050ms; }
.steps-flow.play .step:nth-child(3) { transition-delay: 2000ms; }
.steps-flow.play .step:nth-child(1) .step-node { transition-delay: 100ms; }
.steps-flow.play .step:nth-child(2) .step-node { transition-delay: 1050ms; }
.steps-flow.play .step:nth-child(3) .step-node { transition-delay: 2000ms; }
.steps-flow.play .step .step-node {
  color: var(--marigold);
  border-color: var(--violet);
  box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.18);
}
/* Real <button>s, so AT gets a role and a name. The note text is never removed
   from the accessibility tree, so the name honestly reads
   "Same crew every time — you'll know our names" whatever the visual state. */
.mark {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 18px;
  background: rgba(25, 18, 39, 0.55);
  cursor: pointer;
  transition: border-color 250ms ease;
  align-self: flex-start;
  /* button reset */
  font: inherit;
  color: inherit;
  text-align: left;
  display: block;
}
.mark:hover,
.mark:focus,
.mark:focus-visible { border-color: var(--marigold); }
/* Detail blooms open on hover/focus (grid-rows height animation).
   :focus, not only :focus-visible — a touch tap focuses the button and must
   still open the note. */
.mark-more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease;
}
.mark-more .mark-note { overflow: hidden; }
.mark:hover .mark-more,
.mark:focus .mark-more,
.mark:focus-visible .mark-more { grid-template-rows: 1fr; }
.marks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}
/* display:block — these are <span>s inside a <button> now, and .mark-note's
   overflow:hidden (which drives the grid-rows reveal) needs a block box. */
.mark-title { display: block; font-family: var(--font-mono); font-size: 20px; font-weight: 600; color: var(--marigold); }
.mark-note { display: block; font-size: 13px; color: var(--bone-60); }

/* Gallery */
/* 300px, not 230px: six tiles land as 3 + 3 on a desktop column instead of
   4 + 2 with a hole in the second row, and the 650px-wide sources still stop
   short of upscaling. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: 12px;
}
.tile {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 10px;
  overflow: hidden;
  background: repeating-linear-gradient(45deg, #1c1332 0 10px, #191227 10px 20px);
  border: 1px solid rgba(124, 58, 237, 0.3);
  margin: 0;
}
.tile-tint {
  position: absolute;
  inset: 0;
  /* Real job photos now: keep the violet cast light enough that cut grass reads green. */
  background: rgba(59, 16, 120, 0.2);
  mix-blend-mode: multiply;
  transition: opacity 200ms ease;
}
.tile:hover .tile-tint { opacity: 0.05; }
.tile .mono-note {
  position: absolute;
  left: 12px;
  bottom: 10px;
  pointer-events: none;
  background: rgba(12, 9, 19, 0.8);
  border-radius: 6px;
  padding: 4px 9px;
}
/* Gallery tiles sit under the duotone tint: purple at rest, full colour on
   hover. The compare frames are never tinted — their whole job is the photo. */
.tile img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.7) contrast(1.04);
  transition: filter 200ms ease, transform 400ms ease;
}
.tile:hover img { filter: none; transform: scale(1.05); }

/* Service section photo frames */
.svc-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(24px, 4vw, 48px);
  /* Copy sits centred against the photo, so a short section never leaves a
     column of empty ink beside a tall frame. */
  align-items: center;
}
.svc-body.photo-left .svc-ba { order: -1; }

/* Crew photo */
.crew-photo { margin: 24px 0 0; }
.crew-photo img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid rgba(124, 58, 237, 0.3);
}
.crew-photo figcaption { margin-top: 10px; }

/* Reviews */
.review {
  background: var(--ink-raised);
  border: 1px solid rgba(124, 58, 237, 0.35);
  border-radius: 12px;
  padding: 22px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.review-featured { border-color: rgba(124, 58, 237, 0.6); }
.review-featured blockquote { font-size: 19px; line-height: 1.5; }
.review-cta {
  border: 1px dashed var(--line-strong);
  border-radius: 12px;
  padding: 22px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.review-cta p { margin: 0; line-height: 1.55; color: rgba(239, 234, 225, 0.75); flex: 1; }
a.chip { display: inline-flex; align-items: center; gap: 8px; width: fit-content; text-decoration: none; }
.stars { color: var(--marigold); font-size: 13px; letter-spacing: 2px; }
.review blockquote { margin: 0; font-size: 16px; line-height: 1.55; color: rgba(239, 234, 225, 0.9); flex: 1; }
.review figcaption { font-family: var(--font-mono); font-size: 13px; color: var(--bone-60); }

/* Towns */
#towns h2 { margin-bottom: 24px; }
.footer-area { flex-basis: 100%; }
.footer-credit { margin-top: 6px; color: var(--bone-45); }
.footer-credit a { color: var(--bone-60); text-decoration: underline; text-underline-offset: 2px; }
.footer-area a { font-family: inherit; }
.town-chips { display: flex; flex-wrap: wrap; gap: 10px; }
.town-chips span {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone);
  border: 1px solid var(--line-strong);
  border-radius: 99px;
  padding: 10px 16px;
}
.towns-note { max-width: 52ch; line-height: 1.55; color: rgba(239, 234, 225, 0.75); margin: 24px 0 0; }

/* Reviews grid (featured review + review CTA) */
.reviews-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}
.reviews-grid .review-featured { grid-column: span 1; }
@media (min-width: 720px) {
  .reviews-grid { grid-template-columns: 3fr 2fr; align-items: stretch; }
}

/* Service detail sections (services page) */
/* Two-row grid, not a wrapping flex row: the flex version rendered three
   different head heights across four parallel sections (86/154/60/60px)
   depending on whether a .badge was present. */
/* Title with the starting price directly beneath it — no pills floating in
   the top-right corner competing with the heading. */
.svc-detail-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "title" "meta";
  row-gap: 10px;
  margin-bottom: 24px;
}
.svc-detail-head h2 { grid-area: title; margin: 0; }
.svc-meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-start;
  gap: 8px 14px;
}
/* Reserve the price measure so the Crato swap can't reflow the head (CLS). */
.svc-detail-head .svc-price { min-width: 14ch; text-align: left; }
.svc-detail p { max-width: 62ch; margin: 0 0 16px; color: var(--bone-80); line-height: 1.6; }
.svc-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-top: 28px; }
.text-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 6px;
  font-family: var(--font-mono);
  font-size: 13px;
}

/* Contact page */
.contact-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: start;
}
.lead-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  background: var(--ink-raised);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  padding: 20px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field-wide { grid-column: 1 / -1; }
.field label { font-family: var(--font-mono); font-size: 13px; color: var(--bone-65); }
.field .opt { color: var(--bone-45); }
/* Mark what's required, not only what's optional (the inverse of the useful
   convention). The legend explains the asterisk once, in the form. */
.field .req { color: var(--marigold); }
.form-legend {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone-60);
}
.lead-form input,
.lead-form select,
.lead-form textarea {
  width: 100%;
  background: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  color: var(--bone);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 12px;
  min-height: 44px;
}
.lead-form textarea { min-height: 92px; line-height: 1.5; resize: vertical; }
.lead-form select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23EFEAE1' stroke-width='1.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 8px;
}
.lead-form option { background: var(--ink); color: var(--bone); }
.lead-form .btn { grid-column: 1 / -1; }
.form-err { grid-column: 1 / -1; font-size: 13px; color: var(--bone-65); }
.form-ok {
  background: var(--ink-raised);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  padding: 24px;
  font-size: 20px;
  line-height: 1.45;
}
.contact-direct { display: flex; flex-direction: column; gap: 12px; }
.big-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 68px;
  padding: 14px 18px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: 20px;
}
.big-link:hover { border-color: var(--violet); color: var(--bone); }
.big-link span { font-size: 13px; color: var(--bone-60); }

/* Final CTA */
.final-cta {
  padding: clamp(72px, 11vw, 140px) var(--pad-x);
  background: linear-gradient(180deg, rgba(59, 16, 120, 0), rgba(59, 16, 120, 0.35));
  border-top: 1px solid var(--line);
}
.final-cta h2 { font-weight: 800; font-size: clamp(24px, 5vw, 40px); margin: 0 0 28px; }

/* Footer */
.site-footer {
  position: relative;
  overflow-x: clip;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 32px var(--pad-x);
  border-top: 1px solid var(--line);
}
.footer-logo { height: 56px; width: auto; display: block; filter: drop-shadow(0 1px 2px rgba(12, 9, 19, 0.8)); }

/* Signature echo: a mini mower cuts along the footer's top rule, once a loop. */
/* The mower stays wholly inside the footer and fades at each end, so it is
   never caught half-cut by the page edge and the loop reset is invisible. */
@keyframes mowerDrive {
  0% { left: 0; opacity: 0; }
  7% { left: 7%; opacity: 1; }
  93% { left: calc(93% - 34px); opacity: 1; }
  100% { left: calc(100% - 34px); opacity: 0; }
}
@keyframes mowerBob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-1px); }
}
.footer-mower {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-50%);
  pointer-events: none;
  /* 14s, not 35s — long enough to read, short enough to play during a glance. */
  animation: mowerDrive 14s linear infinite;
}
.footer-mower svg { display: block; animation: mowerBob 1.5s ease-in-out infinite; }
.fm-handle { fill: none; stroke: #2a2138; stroke-width: 2.2; stroke-linecap: round; }
.fm-wheel { fill: var(--ink-raised); }
.fm-deck { fill: var(--marigold); stroke: var(--ink); stroke-width: 1.4; }
.fm-intake { fill: var(--ink); }
.fm-cap { fill: var(--ink-raised); }
.fm-dot { fill: var(--marigold); }

/* Mobile sticky bar */
@keyframes barUp { from { transform: translateY(100%); } to { transform: none; } }
.mobile-bar {
  display: none;
  animation: barUp 400ms ease-out 300ms both;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  grid-template-columns: 1fr 1fr 1.4fr;
  gap: 8px;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  background: rgba(25, 18, 39, 0.96);
  backdrop-filter: blur(10px);
  border-top: 1px solid rgba(124, 58, 237, 0.4);
}
.mbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  line-height: 1;
  padding-top: 3px;
  border: 1px solid rgba(124, 58, 237, 0.6);
  border-radius: 10px;
  color: var(--bone);
  font-weight: 600;
  font-size: 16px;
}
.mobile-bar .btn {
  border-radius: 10px;
  text-align: center;
  min-height: 48px;
  white-space: normal;
  font-size: 15px;
  padding: 10px 8px;
}

/* Quote modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(12, 9, 19, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.quote-modal {
  width: min(560px, 100%);
  background: var(--ink-raised);
  border: 1px solid rgba(124, 58, 237, 0.6);
  border-radius: 16px;
  overflow: hidden;
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
}
.qm-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid rgba(124, 58, 237, 0.3);
}
.qm-head .mono-note { color: rgba(239, 234, 225, 0.8); }
.qm-close {
  background: none;
  border: none;
  color: var(--bone);
  font-size: 20px;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
}
/* Step 0 — stands in for the hero form when the modal is opened from a button
   that has no address to hand it. Replaces the map + body, not stacked above. */
.qm-address {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px;
}
.qm-address .btn { min-height: 48px; border-radius: 10px; }
.qm-map { position: relative; height: 240px; overflow: hidden; background: var(--ink); }
.qm-grid {
  position: absolute;
  inset: -40%;
  background-image:
    linear-gradient(rgba(124, 58, 237, 0.22) 1px, transparent 1px),
    linear-gradient(90deg, rgba(124, 58, 237, 0.22) 1px, transparent 1px),
    linear-gradient(rgba(124, 58, 237, 0.12) 3px, transparent 3px);
  background-size: 46px 46px, 46px 46px, 138px 138px;
  transform: scale(1);
  transition: transform 1200ms cubic-bezier(0.3, 0, 0.2, 1);
}
/* While the county lookup is in flight the map drifts and a violet sweep runs.
   Only background-position moves — the zoom transform is JS-owned. */
@keyframes qmDrift {
  from { background-position: 0 0, 0 0, 0 0; }
  to { background-position: 46px 46px, 46px 46px, 138px 138px; }
}
@keyframes qmSweep {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}
.qm-map.scanning .qm-grid { animation: qmDrift 7s linear infinite; }
.qm-map.scanning::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(100deg, rgba(124, 58, 237, 0) 42%, rgba(124, 58, 237, 0.3) 50%, rgba(124, 58, 237, 0) 58%);
  animation: qmSweep 2.6s linear infinite;
}
.qm-map svg {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 180px;
  height: 162px;
  transform: translate(-50%, -50%);
  overflow: visible;
}
#qm-outline {
  fill: rgba(124, 58, 237, 0.14);
  stroke: var(--violet);
  stroke-width: 1.6;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  transition: stroke-dashoffset 900ms ease-out;
  opacity: 0;
}
.qm-body { padding: 20px; display: flex; flex-direction: column; gap: 18px; }
.qm-figures { display: flex; flex-wrap: wrap; gap: 20px 36px; font-family: var(--font-mono); }
.qm-big { font-size: 28px; }
.qm-big.gold { color: var(--marigold); }
.qm-sub { font-size: 13px; color: rgba(239, 234, 225, 0.55); }
.qm-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.qm-days { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--bone);
  border: 1px solid rgba(124, 58, 237, 0.6);
  border-radius: 99px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1;
  padding: 2px 16px 0;
  min-height: 44px;
  cursor: pointer;
}
.chip:hover { border-color: var(--marigold); }
.qm-contact { display: flex; flex-direction: column; gap: 12px; }
.qm-contact-title { font-weight: 600; }
.qm-fields { display: flex; flex-wrap: wrap; gap: 10px; }
/* Real labels, not placeholders — the placeholder vanishes on first keystroke
   and /contact already does this properly (WCAG 3.3.2). */
.qm-field { flex: 1; min-width: 160px; display: flex; flex-direction: column; gap: 6px; }
.qm-flabel { font-family: var(--font-mono); font-size: 13px; color: var(--bone-65); }
.qm-flabel .req { color: var(--marigold); }
.qm-hint { flex-basis: 100%; margin: 0; }
.qm-fields input {
  width: 100%;
  background: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  color: var(--bone);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 12px;
  min-height: 44px;
}
.hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; opacity: 0; }
.qm-err { font-size: 13px; color: var(--bone-65); }
.qm-booked {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  padding: 16px;
  font-size: 16px;
  line-height: 1.5;
}
/* Off the home page there's no obvious "what now" behind the modal, so the
   success state carries its own way out. */
.qm-booked-line { margin: 0; }

/* Chat */
.chat-launcher {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 46;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  /* Violet, not marigold: the booking button should be the only warm thing on screen. */
  background: var(--violet);
  color: var(--bone);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
  animation: launcherPulse 7s ease-out 4s infinite;
}
/* A soft marigold ring, once every 7s — stops on hover/focus and for good
   once the visitor has opened the chat (main.js adds .engaged). */
@keyframes launcherPulse {
  0%, 80%, 100% { box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45), 0 0 0 0 rgba(124, 58, 237, 0); }
  86% { box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45), 0 0 0 9px rgba(124, 58, 237, 0.3); }
  94% { box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45), 0 0 0 18px rgba(124, 58, 237, 0); }
}
.chat-launcher:hover,
.chat-launcher:focus-visible,
.chat-launcher.engaged { animation: none; }
.chat-launcher:hover { background: #8B5CF6; }
.chat-panel {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 48;
  width: min(380px, calc(100vw - 24px));
  height: min(560px, calc(100vh - 90px));
  display: flex;
  flex-direction: column;
  background: var(--ink-raised);
  border: 1px solid rgba(124, 58, 237, 0.6);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.6);
}
.chat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(124, 58, 237, 0.3);
  background: rgba(12, 9, 19, 0.5);
}
.chat-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; }
.chat-status { font-family: var(--font-mono); font-size: 13px; color: var(--bone-60); }
.chat-head-actions { display: flex; align-items: center; gap: 6px; }
.chat-close {
  background: none;
  border: none;
  color: var(--bone);
  font-size: 22px;
  cursor: pointer;
  min-width: 40px;
  min-height: 40px;
}
.chat-thread {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.msg {
  max-width: 85%;
  padding: 10px 14px;
  border-radius: 14px;
  font-size: 16px;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.msg.user {
  align-self: flex-end;
  background: var(--violet-deep);
  color: var(--bone);
  border-bottom-right-radius: 4px;
}
.msg.agent {
  align-self: flex-start;
  background: var(--ink);
  border: 1px solid var(--line);
  color: rgba(239, 234, 225, 0.92);
  border-bottom-left-radius: 4px;
}
.msg.sys {
  align-self: center;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone-60);
  background: none;
  text-align: center;
}
.chat-input-row {
  display: flex;
  gap: 8px;
  padding: 12px;
  border-top: 1px solid rgba(124, 58, 237, 0.3);
}
.chat-input-row input {
  flex: 1;
  min-width: 0;
  background: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  color: var(--bone);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 10px 12px;
  min-height: 44px;
}
.chat-send { border-radius: 10px; }
/* Busy send: aria-disabled, not disabled, so focus stays put while we wait. */
.chat-send.is-busy { opacity: 0.55; cursor: default; }
.chat-send.is-busy::after { display: none; }
/* Pending reply — three dots in an agent bubble until the answer lands */
.chat-typing {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 14px;
}
.chat-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--bone-60);
  animation: chatDot 1.3s ease-in-out infinite;
}
.chat-dot:nth-child(2) { animation-delay: 0.18s; }
.chat-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes chatDot {
  0%, 60%, 100% { opacity: 0.25; }
  30% { opacity: 1; }
}
.chat-typing-label {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.chat-fail a { color: var(--marigold); }
@media (prefers-reduced-motion: reduce) {
  .chat-dot { animation: none; opacity: 0.6; }
}

/* Before/after compare slider — auto-wipes once on entry, then draggable */
.ba-frame { cursor: ew-resize; touch-action: pan-y; }
/* The before layer sits on the left, so its caption lives bottom-left; after's goes bottom-right. */
.ba-before > .mono-note {
  position: absolute;
  bottom: 10px;
  left: 12px;
  right: auto;
  background: rgba(12, 9, 19, 0.72);
  border-radius: 6px;
  padding: 4px 10px;
}
/* 24px of pointer target around a 2px line (WCAG 2.5.8). The line is painted
   by the background gradient rather than the box fill, so ::before (the arrow
   glyph) and ::after (the 42px knob) stay free for the visible handle. */
.ba-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 24px;
  margin-left: -12px;
  background: linear-gradient(90deg,
              transparent 0 11px, var(--marigold) 11px 13px, transparent 13px 100%);
  z-index: 3;
  outline-offset: 4px;
}
.ba-handle::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--ink-raised);
  border: 2px solid var(--marigold);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
.ba-handle::before {
  content: '\25C2\00a0\25B8';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  color: var(--marigold);
  font-size: 15px;
  letter-spacing: 1px;
  line-height: 1;
}

/* Reviews rail — uniform cards on a horizontal scroller; long quotes clamp
   with a fade and release on hover/focus (internal scroll for the very long). */
.reviews-rail {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: 4px 4px 14px;
  scrollbar-color: var(--line-strong) transparent;
}
.reviews-rail .review {
  flex: 0 0 min(340px, 84vw);
  /* min-height, not height: the fixed 230px clipped 4 lines off the longest
     review with no touch or keyboard way to reach them. Three cards do not
     need clamping — the rail scrolls sideways, the cards grow. */
  min-height: 230px;
  scroll-snap-align: start;
  background: var(--ink-raised);
  border: 1px solid rgba(124, 58, 237, 0.35);
  border-radius: 12px;
  padding: 20px 22px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: border-color 200ms ease;
}
.reviews-rail .review:hover,
.reviews-rail .review:focus-within { border-color: var(--marigold); }
.reviews-rail blockquote {
  margin: 0;
  flex: 1;
  min-height: 0;
  font-size: 16px;
  line-height: 1.5;
  color: rgba(239, 234, 225, 0.9);
  /* The fade mask is gone with the fixed height. overflow stays as a backstop
     for a future review long enough to outgrow its card. */
  overflow: hidden;
}
/* If that backstop ever engages, every input can release it — including the
   rail's own focus stop, which the card-level :focus-within cannot reach
   (the long review card has no focusable descendant). */
.reviews-rail:focus-visible .review blockquote,
.reviews-rail .review:hover blockquote,
.reviews-rail .review:focus-within blockquote {
  overflow-y: auto;
}
.reviews-rail figcaption {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bone-60);
  /* Pin the attribution to the card's foot so it never crowds the quote. */
  margin-top: auto;
}
.rail-note { margin: 6px 0 0; }

/* Rating summary card — leads the rail, quietly */
.review-score { justify-content: center; text-align: left; }
.stars-lg { font-size: 22px; letter-spacing: 4px; }
.review-score .score-sub {
  flex: 0 0 auto;
  color: var(--bone-80);
  -webkit-mask-image: none;
  mask-image: none;
}
.review-score figcaption a { font-family: inherit; }

/* Full-height photo heroes for inner pages — duotone job photo under an
   ink gradient so the Mortend headline owns the frame. */
.page-hero {
  position: relative;
  overflow: hidden;
  min-height: min(88svh, 860px);
  display: flex;
  align-items: flex-end;
  border-bottom: 1px solid var(--line);
}
.page-hero .ph-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.65);
}
.page-hero .ph-tint {
  position: absolute;
  inset: 0;
  background:
    /* 0.38 at the 38% stop, not 0.12 — lifts the /reviews kicker off the bright
       canopy behind it from 3.78:1 to 5.1:1 (measured against the lightest pixel
       in the glyph run). 0.45 also clears AA but murks the photograph, and the
       extra 0.07 buys no conformance. */
    linear-gradient(180deg, rgba(12, 9, 19, 0.4) 0%, rgba(12, 9, 19, 0.38) 38%, rgba(12, 9, 19, 0.9) 86%, var(--ink) 100%),
    rgba(59, 16, 120, 0.28);
  mix-blend-mode: normal;
}
.page-hero .ph-tint::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(59, 16, 120, 0.3);
  mix-blend-mode: multiply;
}
.page-hero .wrap {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: calc(clamp(72px, 12vh, 140px) + var(--header-h)) var(--pad-x) clamp(48px, 8vh, 84px);
}
.page-hero h1 { max-width: 16ch; margin-bottom: 18px; }
.page-hero .page-lede {
  max-width: 56ch;
  margin: 0 0 26px;
  font-size: 20px;
  line-height: 1.5;
  color: rgba(239, 234, 225, 0.92);
}
.hero-cta-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
/* /work and /reviews open on a single before|after composite. These name the
   two halves so the seam in the middle reads as the point, not as a mistake.
   They sit in the bottom corners, in the band below the CTA row. */
.page-hero .ph-mark {
  position: absolute;
  z-index: 3;
  bottom: 14px;
  background: rgba(12, 9, 19, 0.72);
  border-radius: 6px;
  padding: 4px 9px;
}
.page-hero .ph-mark-before { left: var(--pad-x); }
.page-hero .ph-mark-after { right: var(--pad-x); }

/* 404 — fill the viewport so the footer doesn't float on a black band, and
   give a lost visitor more than one way out. */
body.not-found { min-height: 100dvh; display: flex; flex-direction: column; }
body.not-found .site-footer { margin-top: auto; }
.nf-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 20px;
  margin-top: 24px;
}
.nf-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 6px;
  font-family: var(--font-mono);
  font-size: 13px;
}

/* No-JS fallbacks: <html class="no-js"> is removed by main.js on load. Without JS
   the booking forms would GET their fields into the URL and the chat launcher
   would be a dead button, so both hide and a <noscript> note offers the phone. */
.no-js .lead-form,
.no-js .addr-form,
.no-js .chat-launcher { display: none; }
.noscript-note { margin: 0 0 16px; color: var(--bone-80); }

/* Inline tel/sms/mailto links: grow the hit area to ~44px without moving the
   line box (padding + equal negative margin). Buttons and rows have their own. */
a[href^="tel:"]:not(.btn):not(.mbtn):not(.big-link):not(.nav-phone),
a[href^="sms:"]:not(.btn):not(.mbtn):not(.big-link):not(.text-link),
a[href^="mailto:"]:not(.big-link) { padding-block: 12px; margin-block: -12px; }

/* Ten sliders on /work: skip layout and paint for the ones below the fold. */
.jobs-grid .ba-frame { content-visibility: auto; contain-intrinsic-size: auto 620px 930px; }

/* ── Responsive overrides ───────────────────────────────────────────────
   Kept LAST in the file on purpose: every selector below also has a base
   rule earlier (.chat-launcher, .chat-panel, .mobile-bar, .site-footer,
   .header-phone, .ba-frame, .brand-word, .addr-form, .site-nav, .nav-toggle,
   .nav-panel, .contact-cols, .lead-form, .svc-body, .h2-link, .hero). Same specificity, so
   source order decides — these must stay after the base rules to win.
   Do not move this block upward. */
@media (max-width: 899px) {
  /* stacked steps keep their nodes; the horizontal sequence line goes away */
  .flow-line { display: none; }
  .header-phone { display: none; }
  .site-nav a { padding: 12px 7px; }
  .header-actions .btn-cta { font-size: 15px; padding: 12px 14px; }
}
@media (max-width: 839px) {
  .brand-word { display: none; }
}
@media (max-width: 767px) {
  .contact-cols { grid-template-columns: 1fr; }
  .svc-body { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  /* The auto meta column starves the h2 on a phone — stack instead. */
  .svc-detail-head { grid-template-columns: 1fr; grid-template-areas: "title" "meta"; }
  .svc-meta { justify-content: flex-start; }
  .svc-detail-head .svc-price { text-align: left; }
  /* Not enough room beside a wrapped CTA row — the seam still reads. */
  .page-hero .ph-mark { display: none; }
}
/* Two jobs across once a column can hold two 620px frames side by side. */
@media (min-width: 900px) {
  .jobs-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* ---------- /accessibility ----------
   Long-form prose page: a bordered list of plain statements plus the reach-us
   block reused from /contact. No new components, no new colours — the only
   thing this needs that the rest of the site didn't is a readable measure and
   an email address that can break inside a 320px column. */
/* Every other h1 on the site is several words and wraps between them. This one
   is a single 14-glyph word in a wide display face: at the global 32px floor it
   runs a few pixels past a 320–390px column, so overflow-wrap breaks it and
   orphans the full stop on a line of its own. Drop the floor for this page only
   and the word keeps its period. */
.a11y-head h1 { font-size: clamp(24px, 7vw, 64px); }
.a11y-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 26px;
}
.a11y-list > li {
  padding-left: 18px;
  border-left: 2px solid var(--line);
}
.a11y-lead {
  display: block;
  font-weight: 700;
  margin-bottom: 6px;
}
.a11y-list p,
.a11y-prose p {
  margin: 0;
  max-width: 62ch;
  color: var(--bone-80);
  line-height: 1.6;
}
.a11y-prose p + p { margin-top: 16px; }
.a11y-contact { max-width: 560px; margin-top: 28px; }
/* 18px and a wrap: the help@ address is one unbreakable token at 20px mono and
   pushed the page sideways on a phone (WCAG 1.4.10). */
.a11y-contact .big-link {
  font-size: 18px;
  flex-wrap: wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 719px) {
  /* The fixed chat launcher sits over the footer's last line otherwise. */
  .footer-area { padding-right: 64px; }
  .svc-card { padding-top: 24px; }
  .svc-card h3 { min-height: 0; }
  .svc-card .badge { position: static; }
  .mobile-bar { display: grid; }
  /* Pad the whole document, not just the footer: mid-page content (the home
     trust strip) was landing under the fixed bar too. */
  body { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }
  /* And reserve the bar's height when scrolling focus into view (WCAG 2.4.11). */
  html { scroll-padding-bottom: calc(78px + env(safe-area-inset-bottom)); }
  .header-phone { display: none; }
  .brand-word { display: block; }
  .site-nav { display: none; }
  .nav-toggle { display: flex; }
  .nav-panel { display: flex; }
  /* Aspect comes from each frame's own ar- class now; just drop the floor. */
  .ba-frame { min-height: 0; }
  /* 78vh, not 86vh: at 86vh the trust strip landed behind the fixed bar on
     first paint instead of peeking above the fold as intended. */
  .hero { min-height: 78vh; }
  /* 92px, not 78px: at 78px only 9px separated the launcher from the bar. */
  .chat-launcher { bottom: calc(92px + env(safe-area-inset-bottom)); }
  .chat-panel {
    right: 8px;
    left: 8px;
    width: auto;
    bottom: calc(78px + env(safe-area-inset-bottom));
    height: min(520px, calc(100dvh - 140px));
  }
}
@media (max-width: 480px) {
  .addr-form { flex-wrap: wrap; }
  .addr-form input { flex: 1 0 100%; }
  .addr-form .btn { width: 100%; }
  .lead-form { grid-template-columns: 1fr; }
  .h2-link { display: block; margin-left: 0; }
}
@media (max-width: 419px) {
  .brand-word { display: none; }
  .header-actions .btn-cta { font-size: 14px; padding: 12px 10px; }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .reveal { animation: none !important; opacity: 1 !important; transform: none !important; }
  .flow-progress, .steps-flow .step, .step-node, .mark-more { transition: none !important; }
  .qm-grid { transition: none; }
  #qm-outline { transition: none; }
  .site-header { transition: none; }
  .nav-panel { animation: none; }
  .nav-bars, .nav-bars::before, .nav-bars::after { transition: none; }
  .svc-card, .step, .marks > .mark, .tile, .reviews-grid > *, .contact-cols > div, .trust-strip span {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .btn-cta::after { display: none; }
  .chat-launcher { animation: none !important; }
  .footer-mower { display: none; }
  .mobile-bar { animation: none !important; }
  .site-nav a::after { transition: none; }
  .h2-link span { transition: none; }
  .h2-link:hover span { transform: none; }
  .tile img { transition: none; }
  .tile:hover img { transform: none; }
  .qm-map.scanning .qm-grid { animation: none; }
  .qm-map.scanning::after { display: none; }
  .svc-card > svg rect { transition: none; }
  .tile-tint { transition: none; }
}
