/* ==========================================================================
   CPC Cleaning Services stylesheet

   Tokens live in :root. The warm neutral ramp is derived from the cream
   surface rather than from neutral gray, so secondary text stays in the
   same family as the paper it sits on. Every text value here clears
   4.5:1 on the surface it is used on; display sizes clear 3:1.
   ========================================================================== */

:root {
  /* Brand accent. Alternates from the original design: #8E5540 · #4C5966 · #A8823F */
  --accent: #b87455;
  --accent-dark: #8e5540;
  --accent-light: #c98a6b;
  --accent-sand: #d9a183;
  /* Text-safe accent: 5.9:1 on white, for links and small type. The lighter
     --accent is display-only (3.7:1, large text and marks). */
  --accent-ink: #8e5540;
  --accent-ink-hover: #74422f;
  --accent-wash: rgba(184, 116, 85, 0.1);
  --accent-wash-strong: rgba(184, 116, 85, 0.14);
  --accent-line: rgba(184, 116, 85, 0.26);
  --accent-line-soft: rgba(184, 116, 85, 0.4);

  /* Warm neutrals: text */
  --ink-900: #1b1e22;
  --ink: #262b30;
  --ink-800: #33383e;
  --body: #574f49;   /* 7.5:1 on cream */
  --muted: #6d635c;  /* 5.5:1 on cream */
  --muted-2: #756a62; /* 4.9:1 on cream, the floor for small secondary text */

  /* Surfaces */
  --cream: #faf7f4;
  --cream-2: #f4efe9;
  --white: #fff;
  --line: #efe6df;
  --line-2: #eae0d8;
  --line-3: #ded3ca;
  --line-4: #cfc2b8;
  --footer-bg: #14171a;

  /* On dark */
  --on-dark: #f2ede8;
  --on-dark-2: #cfc7bf;
  --on-dark-3: #bdb4ab;
  --on-dark-4: #e8e3dd;
  --on-dark-5: #d3cbc3;
  --on-dark-muted: #a49a91; /* 6:1 on the darkest surface */
  --rule-dark: rgba(255, 255, 255, 0.1);

  /* Type */
  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans: "Jost", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Radii: cards sit at 14, small controls at 10, pills at 999 */
  --r-card: 14px;
  --r-sm: 10px;
  --r-lg: 16px;

  /* Motion: exponential ease-out, the one curve the page uses */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --fast: 0.14s;
  --base: 0.24s;

  /* Layout */
  --maxw: 1200px;
  --gutter: clamp(18px, 4vw, 44px);
  --section-y: clamp(72px, 9vw, 120px);
  --header-h: 93px;

  accent-color: var(--accent);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
  caret-color: var(--accent-ink);
  scrollbar-color: var(--line-4) var(--cream);
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 100%;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Browser surfaces the design still owns. */
::selection {
  background: rgba(184, 116, 85, 0.24);
  color: var(--ink-900);
}

::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

::-webkit-scrollbar-track {
  background: var(--cream);
}

::-webkit-scrollbar-thumb {
  background: var(--line-4);
  border: 3px solid var(--cream);
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--muted-2);
}

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

img,
svg {
  max-width: 100%;
}

a {
  color: var(--accent-ink);
  text-decoration: none;
  transition: color var(--fast) ease-out, border-color var(--fast) ease-out,
    background-color var(--fast) ease-out, text-decoration-color var(--fast) ease-out;
}

a:hover {
  color: var(--accent-ink-hover);
}

/* Links that sit inside running prose get a real underline with breathing room. */
.lede a,
.shero__facts a,
.pricing-note a,
.policy p a,
.faq-a a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--accent-line-soft);
}

.lede a:hover,
.shero__facts a:hover,
.pricing-note a:hover,
.policy p a:hover,
.faq-a a:hover {
  text-decoration-color: currentColor;
}

h1,
h2,
h3,
h4,
h5 {
  font-family: var(--serif);
  font-weight: 600;
  margin: 0;
  color: var(--ink-900);
  font-optical-sizing: auto;
  text-wrap: balance;
}

p {
  margin: 0;
}

strong {
  font-weight: 500;
  color: var(--ink-900);
}

:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 3px;
  border-radius: 4px;
}

.process :focus-visible,
.quote-section :focus-visible,
.site-header :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--accent-sand);
}

.skip-link {
  position: fixed;
  left: 12px;
  top: -60px;
  z-index: 200;
  background: var(--accent-dark);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  font-size: 0.875rem;
  letter-spacing: 0.04em;
  transition: top var(--base) var(--ease-out);
}

.skip-link:focus,
.skip-link:hover {
  color: #fff;
}

.skip-link:focus {
  top: 0;
}

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

.section {
  padding: var(--section-y) var(--gutter);
}

.section--tight {
  padding: clamp(64px, 8vw, 104px) var(--gutter);
}

.section--cream {
  background: var(--cream);
}

.section--white {
  background: var(--white);
}

.section--ruled {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.section--ruled-top {
  border-top: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   Type scale: tracking and leading are size-specific, never one value
   -------------------------------------------------------------------------- */

.h2 {
  font-size: clamp(2.1rem, 4.2vw, 3.25rem);
  line-height: 1.08;
  letter-spacing: -0.022em;
  margin-bottom: 0.5em;
}

.h2--sm {
  font-size: clamp(2rem, 3.7vw, 2.85rem);
  letter-spacing: -0.02em;
}

.h2--on-dark {
  color: var(--on-dark);
}

.lede {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--body);
  font-weight: 300;
  text-wrap: pretty;
  max-width: 68ch;
}

.lede + .lede {
  margin-top: 1em;
}

.section-head {
  max-width: 44rem;
  margin-bottom: clamp(38px, 5vw, 62px);
}

/* --------------------------------------------------------------------------
   Buttons: feedback lands on press, not on release
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  padding: 16px 30px;
  transition: background-color var(--fast) ease-out, border-color var(--fast) ease-out,
    color var(--fast) ease-out, box-shadow var(--base) var(--ease-out),
    transform 90ms ease-out;
}

.btn__icon {
  width: 18px;
  height: 18px;
  flex: none;
}

.btn:active {
  transform: scale(0.975);
}

/* Neutral shadow, so the button casts a shadow on the cream sections and on
   the dark ones instead of glowing on the dark ones. */
.btn--primary {
  background: var(--accent-dark);
  color: #fff;
  box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.5);
}

.btn--primary:hover {
  background: #7c4835;
  color: #fff;
  box-shadow: 0 14px 30px -10px rgba(0, 0, 0, 0.55);
}

.btn--outline {
  border-color: rgba(233, 226, 220, 0.32);
  color: var(--on-dark);
  background: transparent;
}

.btn--outline:hover {
  border-color: var(--accent-sand);
  color: var(--accent-sand);
  background: rgba(217, 161, 131, 0.08);
}

/* Outline button for light surfaces, where .btn--outline would vanish. */
.btn--outline-ink {
  border-color: var(--line-4);
  color: var(--ink-900);
  background: transparent;
}

.btn--outline-ink:hover {
  border-color: var(--accent-ink);
  color: var(--accent-ink);
  background: var(--cream-2);
}

.btn--sm {
  padding: 13px 24px;
  font-size: 0.875rem;
}

.btn--xs {
  padding: 11px 20px;
  font-size: 0.875rem;
  letter-spacing: 0.04em;
}

.btn--block {
  width: 100%;
  padding: 17px 24px;
  letter-spacing: 0.06em;
}

.btn--quiet {
  background: none;
  border-color: var(--line-4);
  color: var(--body);
  padding: 12px 22px;
  font-size: 0.875rem;
  letter-spacing: 0.04em;
  text-transform: none;
  font-weight: 400;
}

.btn--quiet:hover {
  border-color: var(--accent-ink);
  color: var(--accent-ink);
}

.btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   Header: a translucent layer the page scrolls under
   -------------------------------------------------------------------------- */

/* At rest the header is the top of the hero, same ink, no seam. It only
   becomes a floating translucent layer once there is content sliding under it. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: #1b1e22;
  border-bottom: 1px solid transparent;
  transition: background-color var(--base) ease-out, border-color var(--base) ease-out;
}

.site-header[data-scrolled="true"] {
  background: rgba(20, 23, 26, 0.84);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border-bottom-color: rgba(255, 255, 255, 0.09);
}

/* Scroll edge: content fades under the chrome instead of hitting a hard rule. */
.site-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 22px;
  background: linear-gradient(to bottom, rgba(20, 23, 26, 0.16), rgba(20, 23, 26, 0));
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--base) ease-out;
}

.site-header[data-scrolled="true"]::after {
  opacity: 1;
}

.header-inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 12px var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.brand {
  display: block;
  flex: none;
  line-height: 0;
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
  flex-wrap: wrap;
}

.nav a {
  color: var(--on-dark-4);
  font-size: 0.9375rem;
  letter-spacing: 0.01em;
  position: relative;
  padding-block: 4px;
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: var(--accent-sand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--base) var(--ease-out);
}

.nav a:hover,
.nav a[aria-current="true"] {
  color: var(--accent-sand);
}

.nav a:hover::after,
.nav a[aria-current="true"]::after {
  transform: scaleX(1);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}

.header-phone {
  color: var(--on-dark-5);
  font-size: 0.9375rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.header-phone:hover {
  color: var(--accent-sand);
}

.header-actions .btn--primary {
  box-shadow: none;
}

/* Tap-to-call in the bar itself. Below 900px the phone number and the quote
   button both fold into the menu, which puts the one thing a visitor is most
   likely to want two taps away. This is the same target size as the toggle
   beside it. */
.header-call {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  margin-left: auto;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--r-sm);
  color: var(--on-dark-4);
  transition: border-color var(--fast) ease-out, color var(--fast) ease-out,
    transform 90ms ease-out;
}

.header-call svg {
  display: block;
  width: 21px;
  height: 21px;
}

.header-call:hover {
  border-color: var(--accent-sand);
  color: var(--accent-sand);
}

.header-call:active {
  transform: scale(0.94);
}

/* Mobile menu button, hidden on desktop */
.nav-toggle {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--on-dark-4);
  cursor: pointer;
  transition: border-color var(--fast) ease-out, color var(--fast) ease-out,
    transform 90ms ease-out;
}

.nav-toggle:hover {
  border-color: var(--accent-sand);
  color: var(--accent-sand);
}

.nav-toggle:active {
  transform: scale(0.94);
}

.nav-toggle svg {
  display: block;
}

.nav-toggle .icon-close {
  display: none;
}

.nav-toggle[aria-expanded="true"] .icon-open {
  display: none;
}

.nav-toggle[aria-expanded="true"] .icon-close {
  display: block;
}

/* --------------------------------------------------------------------------
   Logo (image + typographic fallback)
   -------------------------------------------------------------------------- */

.logo-img {
  display: block;
  height: 68px;
  width: auto;
}

.wordmark {
  display: inline-flex;
  flex-direction: column;
  gap: 3px;
  line-height: 1;
  text-align: left;
}

.wordmark__mark {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 2.125rem;
  letter-spacing: 0.08em;
  color: var(--on-dark);
}

.wordmark__mark span {
  color: var(--accent-sand);
}

.wordmark__sub {
  font-size: 0.6875rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
  font-weight: 400;
}

.wordmark--lg .wordmark__mark {
  font-size: clamp(3.5rem, 8vw, 4.875rem);
}

.wordmark--lg .wordmark__sub {
  font-size: 0.75rem;
  letter-spacing: 0.34em;
}

.wordmark--footer .wordmark__mark {
  font-size: 2.75rem;
}

/* --------------------------------------------------------------------------
   Hero: a mop crossing the floor

   --p runs 0 to 1 over the scroll track, from a scroll timeline where the
   browser has one and from main.js where it does not. --sway is a damped sine
   of the same progress: the mop's side to side stroke.

   The scene is one-point perspective with the vanishing point at (50%,
   --horizon). A point on the floor at depth s projects with the scale
   k = 1 / (1 + s), and the mop's depth is s = --depth * p * (0.35 + 0.65 p),
   so it eases away rather than lurching: a linear trip spends most of its
   screen travel in the first few frames. The linear part keeps it moving
   away from the first frame, or the opening sway would lay the streak flat
   across the floor. Everything that travels is placed from k in
   container query units of the stage (cqw, cqh), never vh.

   Nothing repaints while it runs. The floor and the finished streak are each
   painted once; the mop moves by one composited transform, and the streak is
   revealed from the near edge up to the mop by a translate on .shero__wet and
   the opposite translate on its inner box.

   --p rests at 1, so no JS and reduced motion land on the floor and the copy
   with no mop. The copy sits above every layer and is never animated.
   -------------------------------------------------------------------------- */

.shero {
  --p: 1;
  --sway: 0;
  /* The scene. main.js reads these to draw the streak, so the streak and the
     mop are always computed from the same numbers. --horizon must also match
     the floor artwork in index.html, which is drawn for 58. */
  --horizon: 58;   /* vanishing point, % of the stage height */
  --foot: 99;      /* where the mop touches the floor at p 0, same units */
  --depth: 7;      /* how far it travels: k falls from 1 to 1 / 8 */
  --lane: -37;     /* its line across the floor, cqw from the centre at k 1 */
  --amp: 9;        /* the width of the stroke, cqw at k 1 */
  --mop-w: clamp(150px, 17vw, 250px);
  --flip: 1;       /* -1 mirrors the mop, so the handle leans the other way */
  position: relative;
  background: var(--cream);
  margin-top: calc(-1 * var(--header-h));
}

/* Registered so a scroll timeline can animate them. Without the @property
   declaration a custom property is an untyped token stream and does not
   interpolate. --p rests at 1 here too, so the registration cannot hand a
   browser a grimy hero when nothing is driving it. */
@property --p {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --sway {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.shero__track { height: 100vh; }
/* 180vh: the trip takes 0.8 of a screen of scrolling, then the finished
   hero dwells and unsticks. It was 220vh, which kept a phone in the hero for
   1.2 screens before anything else on the page appeared. */
.shero[data-active="true"] .shero__track { height: 180vh; }

/* svh, not vh. On iOS 100vh is the viewport with the URL bar retracted, so a
   100vh sticky stage runs further down the page than you can see: the scroll
   cue and the mop head sit in the strip behind the bar on the landing frame,
   and the whole stage jumps the moment the bar collapses. svh is the viewport
   with the bar showing, which always fits and never changes size mid-scroll. */
.shero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: grid;
  place-items: center;
  isolation: isolate;
}

/* The wipe, driven by the scroll position itself rather than by a scroll
   event. main.js writes --p from a scroll listener, which is the fallback; on
   iOS in particular that listener is served from a separate scrolling thread
   and arrives late, so the mop trails the finger even at a full frame rate.
   A scroll timeline is sampled as part of the frame it belongs to.

   The timeline lives on the track and the animation on the stage inside it,
   because a timeline name is visible to descendants, not to ancestors.
   `contain` on a subject taller than the viewport runs from the track's top
   edge reaching the viewport top to its bottom edge reaching the viewport
   bottom, which is the same travel main.js measures. 76% of it matches
   HERO_WIPE_BY, and leaves the same dwell on the finished hero. */
@supports (animation-timeline: scroll()) {
  .shero[data-active="true"] .shero__track {
    view-timeline: --shero-wipe block;
    /* A view timeline insets its scrollport by the root's scroll-padding, and
       this page sets scroll-padding-top to clear the fixed header. Left alone
       that starts the wipe a header's worth of scrolling early, so the page
       would open with a strip of floor already clean. Zero it: the wipe wants
       the real viewport, not the one anchor links aim at. */
    view-timeline-inset: 0;
  }

  /* main.js checks a frame in that this actually drove --p, and sets
     data-driver="js" if it did not. An animation outranks the inline style
     the fallback writes, so the animation has to stand down as well. */
  .shero[data-driver="js"] .shero__stage {
    animation: none;
  }

  .shero[data-active="true"]:not([data-driver="js"]) .shero__stage {
    animation: sheroWipe linear both;
    animation-timeline: --shero-wipe;
    /* `contain` on a subject taller than the viewport runs from the track's
       top edge reaching the viewport top to its bottom edge reaching the
       viewport bottom, which is the travel main.js divides by. 76% of it is
       HERO_WIPE_BY, and leaves the same dwell on the finished hero. */
    animation-range: contain 0% contain 76%;
  }
}

/* --sway is a damped sine of --p in the fallback, and main.js draws the streak
   from the exact sine. Keyframes cannot call sin(), so it is sampled every
   twelfth: three passes over the travel, eased to nothing at the end so the
   mop leaves straight. Keep all three in step. */
@keyframes sheroWipe {
  0%     { --p: 0;      --sway: 0; }
  8.33%  { --p: 0.0833; --sway: 0.648; }
  16.67% { --p: 0.1667; --sway: 0.833; }
  25%    { --p: 0.25;   --sway: 0.53; }
  33.33% { --p: 0.3333; --sway: 0; }
  41.67% { --p: 0.4167; --sway: -0.412; }
  50%    { --p: 0.5;    --sway: -0.5; }
  58.33% { --p: 0.5833; --sway: -0.295; }
  66.67% { --p: 0.6667; --sway: 0; }
  75%    { --p: 0.75;   --sway: 0.177; }
  83.33% { --p: 0.8333; --sway: 0.167; }
  91.67% { --p: 0.9167; --sway: 0.059; }
  100%   { --p: 1;      --sway: 0; }
}

/* The perspective, worked out once on the stage so the mop and the streak
   read the same values. Declared here, where the scroll timeline animates
   --p, not on .shero: a custom property resolves var() where it is declared,
   and on .shero it would only ever see the resting value. */
.shero__stage {
  container-type: size;
  --k: calc(1 / (1 + var(--p) * (0.35 + 0.65 * var(--p)) * var(--depth)));
  --x: calc(50 + (var(--lane) + var(--sway) * var(--amp)) * var(--k));
  --y: calc(var(--horizon) + (var(--foot) - var(--horizon)) * var(--k));
  /* How far above the mop's foot the streak is revealed to: the back edge of
     its footprint, so the leading edge of the wet is always under the cords. */
  --lead: calc(var(--mop-w) * var(--k) * 0.2);
}

/* The floor fills the stage; everything above --horizon in it is empty, so
   the cream of the stage reads as the wall. */
.shero__floor {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* The streak's reveal. The box slides down to the mop and the inner box slides
   back up by the same amount, so the streak holds still on screen and only
   the edge moves. The mask is constant: it softens the box's own top edge and
   rides along with it, rasterised once. */
.shero__wet {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  transform: translate3d(0, calc(var(--y) * 1cqh - var(--lead)), 0);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
  will-change: transform;
}

.shero__wet-inner {
  position: absolute;
  inset: 0;
  transform: translate3d(0, calc(-1 * (var(--y) * 1cqh - var(--lead))), 0);
  will-change: transform;
}

.shero__wet-inner svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* The mop is placed by its foot, the bottom centre of its box: moved there,
   then scaled by k and tipped by the sway about that same point. It fades in
   the last fifth of the trip, as it reaches the haze at the horizon. */
.shero__mop {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  width: var(--mop-w);
  pointer-events: none;
  transform-origin: 50% 100%;
  transform:
    translate3d(calc(var(--x) * 1cqw - 50%), calc(var(--y) * 1cqh - 100%), 0)
    scale(calc(var(--k) * var(--flip)), var(--k))
    rotate(calc(var(--sway) * 5deg));
  opacity: clamp(0, calc((1 - var(--p)) * 5), 1);
  will-change: transform, opacity;
}

.shero__mop svg { width: 100%; height: auto; display: block; }

/* The copy is a full-stage box that centres its block, above every layer. */
.shero__copy {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-items: center;
}

.shero__content {
  width: min(56rem, calc(100vw - 2 * var(--gutter)));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  text-align: center;
}

.shero__title {
  font-size: clamp(2.5rem, 6.4vw, 4.5rem);
  line-height: 1.03;
  letter-spacing: -0.035em;
  color: var(--ink-900);
  max-width: 18ch;
}

.shero__lede {
  font-size: clamp(1rem, 1.5vw, 1.1875rem);
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 44rem;
  text-wrap: pretty;
}

.shero__actions { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }
.shero__actions .btn--outline-ink { font-variant-numeric: tabular-nums; }

/* The Google rating as one quiet line, not the big-number block this site
   removed: a pill with the G, five stars, and the figures in running text. */
.shero__proof {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px 7px 7px;
  border: 1px solid var(--line-3);
  border-radius: 999px;
  background: var(--white);
  color: var(--muted);
  font-size: 0.9375rem;
  line-height: 1.2;
  text-decoration: none;
  transition: border-color var(--fast) ease-out, transform 90ms ease-out;
}

.shero__proof:hover {
  border-color: var(--accent-line-soft);
  color: var(--muted);
}

.shero__proof:active {
  transform: scale(0.98);
}

.shero__proof-g {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--cream-2);
}

.shero__proof-g svg {
  width: 17px;
  height: 17px;
}

.shero__proof-stars {
  display: flex;
  gap: 2px;
  color: var(--accent);
}

.shero__proof-stars svg {
  width: 16px;
  height: 16px;
}

.shero__proof-text strong {
  font-weight: 600;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
}

.shero__facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 24px;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.875rem;
  font-weight: 300;
  color: var(--muted);
}

.shero__facts li { position: relative; padding-left: 16px; }

.shero__facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.58em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

.shero__facts-place { display: flex; align-items: center; gap: 7px; padding-left: 0; color: var(--accent-ink); }
.shero__facts-place::before { display: none; }
.shero__facts-place svg { width: 15px; height: 15px; flex: none; }

/* The one instruction on the page. It sits at the bottom centre, between the
   mop's corner and the clutter's, and holds on for the first third of the
   trip so it is still there while the first stroke is being read as one. */
.shero__hint {
  position: absolute;
  z-index: 7;
  left: 50%;
  transform: translateX(-50%);
  bottom: clamp(20px, 4vh, 42px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 0.8125rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-ink);
  pointer-events: none;
  opacity: clamp(0, calc((0.34 - var(--p)) / 0.12), 1);
}

.shero__hint svg {
  width: 22px;
  height: 22px;
  animation: sheroNudge 2.2s var(--ease-out) infinite;
}

@keyframes sheroNudge {
  0%, 62%, 100% { transform: translateY(0); }
  30% { transform: translateY(6px); }
}

/* Nothing travels when the effect never arms: the floor and the copy are the
   whole hero. */
.shero:not([data-active="true"]) .shero__wet,
.shero:not([data-active="true"]) .shero__mop,
.shero:not([data-active="true"]) .shero__hint {
  display: none;
}

@media (max-width: 620px) {
  .shero__content { gap: 18px; }
  .shero__facts { gap: 6px 16px; font-size: 0.8125rem; }
  .shero__proof { gap: 8px; padding-right: 13px; font-size: 0.875rem; }
  .shero__proof-g { width: 26px; height: 26px; }
  .shero__proof-g svg { width: 15px; height: 15px; }
  .shero__proof-stars svg { width: 14px; height: 14px; }
  /* A phone has no column free beside the copy, so the mop starts at the
     right edge, with the clutter in the left corner it leaves. It is mirrored
     there: the handle leans a few degrees, and on the right it has to lean
     away from the copy, not into the facts line. */
  .shero {
    --lane: 40;
    --amp: 7;
    --flip: -1;
    --mop-w: clamp(100px, 29vw, 136px);
  }
  .shero__hint { font-size: 0.75rem; letter-spacing: 0.14em; gap: 8px; }
}

/* A short phone (an iPhone SE is 667px) has the copy filling nearly the
   whole stage, so the mop comes down a size to keep its handle below the
   rating badge and beside the facts rather than through them. */
@media (max-width: 620px) and (max-height: 720px) {
  .shero { --mop-w: 88px; }
}

@media (max-height: 640px) {
  .shero__lede { display: none; }
}

/* --------------------------------------------------------------------------
   Hero: the room being tidied (generated by tools/hero/room.py)

   Every piece reads a local progress from --p: q runs 0 to 1 across the
   piece's own window [--a, --b] of the trip, and e is q eased (smoothstep).
   Pieces move by transform and fade by opacity only, both composited, so the
   room costs no repaint while it is cleaned. At rest (--p 1) every e is 1: the
   bed made, the window clear, the floor clean.
   -------------------------------------------------------------------------- */

.shero__room {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.shero__room > *,
.shero__bed-part,
.shero__window-dirt,
.shero__window-glass {
  --q: clamp(0, calc((var(--p) - var(--a, 0)) / (var(--b, 1) - var(--a, 0))), 1);
  --e: calc(var(--q) * var(--q) * (3 - 2 * var(--q)));
}

/* A point on the mop's path: where the centre of its head is when the trip
   reaches --ps, plus --off of the head's half-width sideways. The same
   formulas as the mop and the streak (k, and the sway, which CSS can take the
   sine for), so the head passes exactly over it. */
.shero__dirt,
.shero__sparkle--3,
.shero__sparkle--4,
.shero__sparkle--5 {
  --ks: calc(1 / (1 + var(--ps) * (0.35 + 0.65 * var(--ps)) * var(--depth)));
  --ss: calc(sin(calc(var(--ps) * 540deg)) * (1 - var(--ps)));
  --px: calc((50 + (var(--lane) + var(--ss) * var(--amp)) * var(--ks)) * 1cqw + var(--off, 0) * var(--mop-w) * 0.45 * var(--ks));
  --py: calc((var(--horizon) + (var(--foot) - var(--horizon)) * var(--ks)) * 1cqh - var(--mop-w) * 0.117 * var(--ks));
}

/* Dirt on the floor, gone as the head goes over it: it starts to fade just
   before the head's centre reaches it and is gone just after. */
.shero__dirt {
  --a: calc(var(--ps) - 0.012);
  --b: calc(var(--ps) + 0.03);
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--mop-w) * var(--ks) * var(--size));
  transform: translate3d(var(--px), var(--py), 0) translate(-50%, -50%);
  opacity: calc(1 - var(--e));
  will-change: opacity;
}

.shero__dirt svg,
.shero__window svg,
.shero__sparkle svg {
  display: block;
  width: 100%;
  height: auto;
}

/* The two halves of a crossfade: the unmade one fades out as the made one
   fades in, over the same window and along the same path. */
.shero__bed-part--duvet-unmade,
.shero__bed-part--throw-unmade { opacity: calc(1 - var(--e)); will-change: transform, opacity; }

.shero__bed-part--duvet-made,
.shero__bed-part--throw-made { opacity: var(--e); will-change: transform, opacity; }

/* The window: dust that fades, then one glint across the clean glass. */
.shero__window {
  position: absolute;
  left: 3.2cqw;
  top: 14cqh;
  width: 14.5cqw;
  display: none;
}

.shero__window-dirt {
  --a: 0.1;
  --b: 0.46;
  position: absolute;
  inset: 0;
  opacity: calc(1 - var(--e));
  will-change: opacity;
}

.shero__window-glass {
  --a: 0.48;
  --b: 0.74;
  position: absolute;
  left: 12%;
  top: 8.8%;
  width: 76%;
  height: 72%;
  overflow: hidden;
}

.shero__window-glint {
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: 0;
  width: 26%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.85) 50%, transparent);
  transform: translateX(calc(-120% + var(--e) * 560%)) skewX(-18deg);
  will-change: transform;
}

/* The bed, drawn in the floor's own perspective. Its parts share one box. */
.shero__bed {
  position: absolute;
  inset: 0;
  display: none;
}


.shero__bed-part {
  position: absolute;
  left: 72.47cqw;
  top: 57.71cqh;
  width: 27.87cqw;
  height: 22.59cqh;
  transform:
    translate3d(calc(var(--dx, 0) * (1 - var(--e)) * 1cqw), calc(var(--dy, 0) * (1 - var(--e)) * 1cqh), 0)
    rotate(calc(var(--dr, 0) * (1 - var(--e)) * 1deg));
}

.shero__bed-part svg {
  display: block;
  width: 100%;
  height: 100%;
}

.shero__bed-part--bed-base { --a: 0; --b: 0; }
.shero__bed-part--throw-unmade { --a: 0.66; --b: 0.86; }
.shero__bed-part--duvet-unmade { --a: 0.46; --b: 0.76; --dx: 1.25; --dy: -0.33; }
.shero__bed-part--duvet-made { --a: 0.46; --b: 0.76; --dx: -1.53; --dy: 0.44; }
.shero__bed-part--throw-made { --a: 0.66; --b: 0.86; --dx: -0.69; }
.shero__bed-part--pillow-back { --a: 0.34; --b: 0.6; --dx: -3.19; --dy: 0.89; --dr: -16; transform-origin: 72.23% 36.83%; will-change: transform; }
.shero__bed-part--pillow-front { --a: 0.38; --b: 0.64; --dx: -1.25; --dy: 8.22; --dr: 22; transform-origin: 72.23% 36.83%; will-change: transform; }

.shero__dirt--1 { --ps: 0.07; --off: -0.55; --size: 0.62; }
.shero__dirt--2 { --ps: 0.095; --off: 0.72; --size: 0.42; }
.shero__dirt--3 { --ps: 0.125; --off: -0.12; --size: 0.66; }
.shero__dirt--4 { --ps: 0.155; --off: 0.78; --size: 0.34; }
.shero__dirt--5 { --ps: 0.185; --off: -0.5; --size: 0.52; }

/* A flash of clean on the tidy room just as the mop disappears: up from
   --p 0.82, full at 0.9, gone by 0.98, so the resting room has none. */
.shero__sparkle {
  --s: clamp(0, min(calc((var(--p) - 0.82) / 0.08), calc((0.98 - var(--p)) / 0.08)), 1);
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--w) * 1cqw);
  opacity: var(--s);
  transform:
    translate3d(calc(var(--x) * 1cqw), calc(var(--y) * 1cqh), 0)
    translate(-50%, -50%)
    scale(calc(0.3 + 0.7 * var(--s)))
    rotate(calc(var(--s) * 45deg));
  will-change: transform, opacity;
}

.shero__sparkle--1 { --x: 17.92; --y: 16.67; --w: 2.92; }
.shero__sparkle--2 { --x: 92.78; --y: 63.78; --w: 2.64; }

/* The three on the floor sit where the dirt was, sized to the depth. */
.shero__sparkle--3,
.shero__sparkle--4,
.shero__sparkle--5 {
  width: calc(var(--mop-w) * var(--ks) * 0.3);
  transform:
    translate3d(var(--px), var(--py), 0)
    translate(-50%, -50%)
    scale(calc(0.3 + 0.7 * var(--s)))
    rotate(calc(var(--s) * 45deg));
}
.shero__sparkle--3 { --ps: 0.07; --off: -0.5; }
.shero__sparkle--4 { --ps: 0.125; --off: -0.1; }
.shero__sparkle--5 { --ps: 0.185; --off: -0.45; }

/* The window and the bed need the wall and floor beside the copy, which only
   a wide, tall enough screen has. Below that they are not drawn at all, and
   neither are their sparkles. */
.shero__sparkle--1,
.shero__sparkle--2 { display: none; }

@media (min-width: 1280px) and (min-height: 760px) {
  .shero__window,
  .shero__bed,
  .shero__sparkle--1,
  .shero__sparkle--2 { display: block; }
}

/* The dirt follows the mop's own lane, so it needs no separate phone layout.
   It does need floor below the copy: on a short screen the further patches
   would sit under the facts line, so they go, furthest first. */
@media (max-height: 860px) {
  .shero__dirt--5,
  .shero__sparkle--5 { display: none; }
}

@media (max-height: 800px) {
  .shero__dirt--4 { display: none; }
}

@media (max-width: 620px) and (max-height: 720px) {
  .shero__dirt,
  .shero__sparkle--3,
  .shero__sparkle--4 { display: none; }
}


/* --------------------------------------------------------------------------
   Photography: fills the frame it is dropped into
   -------------------------------------------------------------------------- */

.media-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* --------------------------------------------------------------------------
   What CPC stands for: the About page's #letters band
   -------------------------------------------------------------------------- */

.brand-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(36px, 5vw, 72px);
  align-items: center;
  margin-bottom: clamp(48px, 6vw, 76px);
}

.brand-copy .lede {
  max-width: 34rem;
}

.brand-logo {
  display: flex;
  justify-content: center;
}

.logo-card {
  width: min(380px, 100%);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--ink-900);
  box-shadow: 0 24px 56px -20px rgba(27, 30, 34, 0.5);
}

.logo-card img {
  width: 100%;
  display: block;
  padding: clamp(22px, 3vw, 34px);
}

.logo-card__fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(38px, 6vw, 58px) 24px;
}

/* The three values read as a glossary, not as three matching cards. */
.values {
  margin: 0;
}

.value {
  display: grid;
  grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  gap: clamp(16px, 4vw, 56px);
  align-items: baseline;
  padding-block: clamp(24px, 3vw, 34px);
  border-top: 1px solid var(--line-2);
}

.value__term {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin: 0;
}

.value__initial {
  font-family: var(--serif);
  font-size: 3.25rem;
  font-weight: 600;
  line-height: 0.72;
  color: var(--accent);
  flex: none;
}

.value__es {
  display: block;
  font-family: var(--serif);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.012em;
  color: var(--ink-900);
}

.value__en {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 6px;
}

.value__def {
  margin: 0;
  font-size: 1rem;
  line-height: 1.72;
  font-weight: 300;
  color: var(--body);
  max-width: 62ch;
}

/* --------------------------------------------------------------------------
   Services & pricing: the price and the qualifier share one block
   -------------------------------------------------------------------------- */

.tier {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  padding-block: clamp(34px, 4.5vw, 54px);
  border-top: 1px solid var(--line);
}

.tiers > .tier:first-child {
  border-top: 0;
  padding-top: 0;
}

.tier__name {
  font-size: clamp(1.75rem, 2.6vw, 2.125rem);
  line-height: 1.12;
  letter-spacing: -0.018em;
}

/* The name is the link to the service's own page. A faint underline marks it
   as a link on a touch screen, where there is no hover to reveal it. */
.tier__name a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.14em;
  text-decoration-color: var(--line-3);
}

.tier__name a:hover {
  color: var(--accent-ink);
  text-decoration-color: currentColor;
}

.tier__en {
  font-size: 0.75rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 7px;
}

.tier__price {
  margin-top: 18px;
}

.tier__amount {
  font-family: var(--serif);
  font-size: clamp(3.5rem, 6.4vw, 4.75rem);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.035em;
  color: var(--accent);
}

/* The qualifier is the price. It sits in the same block, never in a footnote. */
.tier__basis {
  margin-top: 12px;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--body);
  letter-spacing: 0.005em;
}

.tier__basis span {
  display: block;
  font-size: 0.875rem;
  color: var(--muted-2);
}

.tier__for {
  margin-top: 16px;
  font-size: 0.9375rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 32ch;
}

.tier__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

.tier__cta-icon {
  width: 18px;
  height: 18px;
  transition: transform var(--base) var(--ease-out);
}

.tier__cta:hover .tier__cta-icon {
  transform: translateX(4px);
}

/* The checklist is a disclosure. It ships open, which is how it stays correct
   with JS off and how it reads on a wide screen next to the sticky price
   column; main.js closes it under 900px, where 35 checkmark rows stacked in
   one column were most of this section's height. */
.tier__label {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: var(--sans);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-2);
  cursor: pointer;
  list-style: none;
  transition: color var(--fast) ease-out, border-color var(--fast) ease-out;
}

.tier__label::-webkit-details-marker {
  display: none;
}

.tier__label:hover,
.tasks-disclosure[open] > .tier__label:hover {
  color: var(--accent-ink);
  border-color: var(--accent-line);
}

.tier__label:active {
  color: var(--accent-ink-hover);
}

.tier__count {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0.02em;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--muted-2);
}

.tasks-disclosure__chevron {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--accent-ink);
  transition: transform var(--base) var(--ease-out);
}

.tasks-disclosure[open] .tasks-disclosure__chevron {
  transform: rotate(180deg);
}

.tasks {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  column-gap: clamp(20px, 3vw, 44px);
  row-gap: 12px;
}

/* EXACTLY TWO CHILDREN: the check svg, and one element holding everything else.
   This is a grid, so a third child (a bare text node after a <strong>, say)
   becomes its own grid item and lands back in the 19px icon column, where it
   renders one character per line. If a task needs a bold lead plus a sentence,
   wrap both in a single <div>. */
.tasks li {
  display: grid;
  grid-template-columns: 19px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  font-size: 0.9375rem;
  line-height: 1.55;
  font-weight: 300;
  color: var(--body);
}

.tasks svg {
  width: 19px;
  height: 19px;
  color: var(--accent);
  margin-top: 1px;
}

/* The recommendation to start with a deep clean. Sits under the regular
   checklist, where a visitor has just seen what the $130 does and does not do. */
.tier__advice {
  margin-top: clamp(26px, 3vw, 34px);
  padding: clamp(20px, 2.5vw, 26px) clamp(20px, 2.5vw, 28px);
  background: var(--cream);
  border-radius: var(--r-card);
}

.tier__advice-title {
  font-size: 1.1875rem;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--ink-900);
  margin-bottom: 10px;
}

.tier__advice p {
  font-size: 0.9375rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 58ch;
}

.tier__advice p + p {
  margin-top: 0.8em;
}

.tier__advice-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

.tier__advice-link:hover .tier__cta-icon {
  transform: translateX(4px);
}

.pricing-note {
  margin-top: clamp(34px, 4.5vw, 52px);
  padding: clamp(22px, 3vw, 30px) clamp(22px, 3vw, 34px);
  background: var(--cream);
  border-radius: var(--r-card);
}

.pricing-note p {
  font-size: 1rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 68ch;
}

/* The services the client quotes per property rather than from a base rate.
   Same ruled rhythm as the priced tiers above, without the number column. */
.also {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  margin-top: clamp(34px, 4.5vw, 52px);
  padding-top: clamp(34px, 4.5vw, 52px);
  border-top: 1px solid var(--line);
}

.also__title {
  font-size: clamp(1.5rem, 2.3vw, 1.875rem);
  line-height: 1.18;
  letter-spacing: -0.016em;
}

.also__gloss {
  margin-top: 12px;
  font-size: 0.9375rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 34ch;
}

.also__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

.also__cta:hover .tier__cta-icon {
  transform: translateX(4px);
}

/* Names only, in a ruled list rather than a card grid. Each one used to carry
   a sentence that restated the head's "quoted to the property" line. */
.also__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  column-gap: clamp(20px, 3vw, 44px);
}

.also-item {
  border-top: 1px solid var(--line-2);
  padding-block: 13px;
  font-size: 1.0625rem;
  line-height: 1.4;
  letter-spacing: -0.006em;
  color: var(--ink-800);
}

/* Add-ons: the same ruled row, with the price at the end of the line. One
   column rather than the six-name grid above, so the number always lands at
   the right edge of the list instead of a third of the way across it. */
.addons__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.addon {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  border-top: 1px solid var(--line-2);
  padding-block: 13px;
  font-size: 1.0625rem;
  line-height: 1.4;
  letter-spacing: -0.006em;
  color: var(--ink-800);
}

.addon__price {
  flex: none;
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   Why CPC
   -------------------------------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: clamp(36px, 5vw, 64px);
  align-items: center;
}

.split__media {
  position: relative;
  min-height: 400px;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: 0 24px 56px -22px rgba(27, 30, 34, 0.45);
}

.split .h2 {
  margin-bottom: 0.62em;
  font-size: clamp(2.1rem, 4.2vw, 3.125rem);
}

.why-list {
  display: grid;
  gap: 20px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* What CPC stands for, one line under the heading of "why us". */
.why-lede {
  margin: -0.1em 0 28px;
}

.why-item {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.check {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent-wash-strong);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
}

.check svg {
  width: 16px;
  height: 16px;
}

.why-item__title {
  font-size: 1.0625rem;
  color: var(--ink-900);
  font-weight: 500;
  margin-bottom: 4px;
}

.why-item__text {
  font-size: 0.9375rem;
  color: var(--muted);
  font-weight: 300;
  line-height: 1.68;
  max-width: 52ch;
}

/* --------------------------------------------------------------------------
   Before and after
   -------------------------------------------------------------------------- */

/* The rail is the scroller, and it is a scroller with or without JS: overflow
   alone gives touch swipe, a trackpad, and (with tabindex) the arrow keys.
   main.js only adds the drift and the dots. */
.rail {
  /* Out to the viewport edge, past the section's gutter, so a slide can run
     off the side of a phone instead of stopping short of it. The strip below
     puts the gutter back, and on a wide screen it puts back enough to line the
     first photo up with the heading above it. */
  margin-inline: calc(-1 * var(--gutter));
  overflow-x: auto;
  overflow-y: hidden;
  position: relative;
  scrollbar-width: none;
  /* Snapping is for the manual gallery. JS turns it off while the rail is
     drifting, because a snap point pulls against a continuous scroll. */
  scroll-snap-type: x proximity;
  scroll-padding-left: max(var(--gutter), calc((100% - var(--maxw)) / 2));
  overscroll-behavior-x: contain;
}

.rail[data-auto="on"] {
  scroll-snap-type: none;
}

.rail::-webkit-scrollbar {
  display: none;
}

.rail__strip {
  list-style: none;
  margin: 0;
  padding: 4px max(var(--gutter), calc((100% - var(--maxw)) / 2));
  display: flex;
  gap: clamp(14px, 2vw, 24px);
  width: max-content;
}

/* Most of these frames were shot upright on a phone, and they stay upright:
   a room or an appliance read sideways is a photograph a visitor has to
   decode before they can see whether it is clean. On a phone the slide is
   sized by width, because one slide fills the screen and its height can vary
   freely. From tablet up it is sized by height, so a row of slides shares a
   baseline and only the widths differ. */
.rail__slide {
  flex: none;
  width: min(76vw, 17rem);
  scroll-snap-align: start;
}

.rail__slide[data-wide] {
  width: min(88vw, 22rem);
}

.shot {
  margin: 0;
}

.shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--r-card);
  box-shadow: 0 18px 40px -22px rgba(27, 30, 34, 0.55);
  background: var(--cream-2);
}

.rail__slide[data-wide] .shot img {
  aspect-ratio: 4 / 3;
}

@media (min-width: 760px) {
  .rail {
    --shot-h: clamp(20rem, 30vw, 27rem);
  }

  /* Widths derived from one height, so an upright frame and a landscape one
     end up exactly as tall as each other. */
  .rail__slide {
    width: calc(var(--shot-h) * 3 / 4);
  }

  .rail__slide[data-wide] {
    width: calc(var(--shot-h) * 4 / 3);
  }
}

.shot figcaption {
  margin-top: 16px;
  font-size: 0.9375rem;
  line-height: 1.68;
  font-weight: 300;
  color: var(--body);
}

/* One dot per pair, not per frame: the pair is the unit a reader is looking
   for, and twelve dots for six comparisons reads as twice the content. */
.rail__dots {
  display: flex;
  gap: 10px;
  margin-top: clamp(22px, 3vw, 30px);
}

.rail__dot {
  width: 34px;
  height: 3px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--line-3);
  cursor: pointer;
  transition: background var(--fast) ease-out;
}

.rail__dot[aria-current="true"] {
  background: var(--accent-ink);
}

.rail__dot:hover {
  background: var(--muted-2);
}

.rail__dot[aria-current="true"]:hover {
  background: var(--accent-ink);
}

.ba__tag {
  display: inline-block;
  margin-right: 10px;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--cream-2);
  color: var(--muted-2);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.ba__tag--after {
  background: var(--accent-wash-strong);
  color: var(--accent-ink);
}

.ba__note {
  margin-top: clamp(26px, 3vw, 36px);
  padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: 0.875rem;
  line-height: 1.65;
  font-weight: 300;
  color: var(--muted-2);
  max-width: 62ch;
}

/* --------------------------------------------------------------------------
   Reviews: a bento. The first review is a terracotta feature panel set large,
   the rest are white tiles. Elevation is the border alone; the feature panel
   carries none because its fill already separates it from the cream.
   -------------------------------------------------------------------------- */

.reviews-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 8px 32px;
  margin-bottom: clamp(30px, 4vw, 48px);
}

.reviews-head .section-head {
  margin-bottom: 0;
}

.reviews-head__cta {
  margin-bottom: 6px;
}

.reviews {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 16px;
}

.review {
  display: flex;
  background: var(--white);
  border: 1px solid var(--line-2);
  border-radius: var(--r-card);
}

.review__body {
  margin: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: clamp(24px, 2.6vw, 32px);
}

.review__stars {
  display: flex;
  gap: 3px;
  color: var(--accent);
  margin-bottom: 16px;
}

.review__stars svg {
  width: 17px;
  height: 17px;
}

.review__quote {
  margin: 0 0 24px;
  flex: 1;
}

.review__quote p {
  font-family: var(--serif);
  font-size: clamp(1.3rem, 1.8vw, 1.5rem);
  line-height: 1.32;
  letter-spacing: -0.01em;
  color: var(--ink-900);
  text-wrap: pretty;
}

.review__by {
  display: flex;
  align-items: center;
  gap: 12px;
}

.review__avatar {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent-wash-strong);
  color: var(--accent-ink);
  font-family: var(--serif);
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1;
}

.review__who {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.review__name {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ink-900);
}

.review__source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: var(--muted-2);
}

/* Google's mark sits on white, as its brand rules ask. On the white tiles the
   disc is invisible; on the terracotta panel it is what keeps the four colors
   readable. */
.review__g {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--white);
}

.review__g svg {
  width: 15px;
  height: 15px;
}

/* The feature panel: cream on the text-safe accent, 5.9:1. */
.review--feature {
  position: relative;
  overflow: hidden;
  background: var(--accent-dark);
  border-color: transparent;
  border-radius: var(--r-lg);
}

/* An oversized opening quotation mark, set in the display face as
   punctuation for the quotation it opens, not as an icon. */
.review--feature::before {
  content: "\201C";
  position: absolute;
  top: -0.2em;
  right: 0.06em;
  font-family: var(--serif);
  font-size: clamp(12rem, 22vw, 18rem);
  line-height: 1;
  color: rgba(250, 247, 244, 0.1);
  pointer-events: none;
}

.review--feature .review__body {
  position: relative;
  padding: clamp(28px, 4vw, 52px);
}

.review--feature .review__stars {
  color: var(--accent-sand);
  margin-bottom: clamp(20px, 3vw, 32px);
}

.review--feature .review__stars svg {
  width: 20px;
  height: 20px;
}

.review--feature .review__quote p {
  font-size: clamp(1.9rem, 3.4vw, 2.9rem);
  line-height: 1.14;
  letter-spacing: -0.024em;
  color: var(--cream);
  max-width: 26ch;
}

.review--feature .review__avatar {
  width: 46px;
  height: 46px;
  background: rgba(250, 247, 244, 0.16);
  color: var(--cream);
}

.review--feature .review__name {
  color: var(--cream);
}

.review--feature .review__source {
  color: var(--on-dark-4);
}

/* Phone: one card at a time. The list is the strip and its wrapper is the
   scroller, the same split as the #proof rail, so the strip's padding survives
   at the far end. It bleeds to the screen edge and the next card peeks in by
   about 40px, which is what tells a thumb the row goes on. No drift: this one
   only moves when the reader moves it. */
@media (max-width: 759px) {
  .reviews-rail {
    margin-inline: calc(-1 * var(--gutter));
    overflow-x: auto;
    overflow-y: hidden;
    position: relative;
    scrollbar-width: none;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    overscroll-behavior-x: contain;
  }

  .reviews-rail::-webkit-scrollbar {
    display: none;
  }

  .reviews {
    display: flex;
    gap: 12px;
    width: max-content;
    padding: 4px var(--gutter);
  }

  .review {
    flex: none;
    width: min(82vw, 22rem);
    scroll-snap-align: start;
  }

  .review--feature .review__quote p {
    font-size: 1.6rem;
  }

  .reviews__dots {
    justify-content: center;
  }
}

@media (min-width: 760px) {
  .reviews__dots {
    display: none;
  }
}

/* Tablet: the feature runs full width, the rest sit three across under it. */
@media (min-width: 760px) {
  .reviews {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .review--feature {
    grid-column: 1 / -1;
  }
}

/* Wide: the feature takes the left and the others stack beside it. */
@media (min-width: 1040px) {
  .reviews {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  }

  .review--feature {
    grid-column: 1;
    grid-row: 1 / span 3;
  }

  .review--feature .review__quote p {
    max-width: 20ch;
  }

  .review:not(.review--feature) .review__quote p {
    font-size: 1.375rem;
  }
}

/* --------------------------------------------------------------------------
   Process: a timeline, marked but not numbered
   -------------------------------------------------------------------------- */

.process {
  background: linear-gradient(150deg, #1b1e22 0%, #2a2521 100%);
  color: var(--on-dark);
  padding: clamp(72px, 9vw, 116px) var(--gutter);
}

.process .section-head {
  max-width: 38rem;
  margin-bottom: clamp(44px, 5vw, 64px);
}

.process .h2 {
  margin-bottom: 0;
  font-size: clamp(2.1rem, 4.2vw, 3.125rem);
}

.process-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: clamp(28px, 3.5vw, 46px);
}

.step {
  position: relative;
  padding-top: 32px;
}

.step::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 5px;
  height: 1px;
  background: var(--rule-dark);
}

.step::after {
  content: "";
  position: absolute;
  left: 0;
  top: 1px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}

.step h3 {
  font-size: 1.5rem;
  line-height: 1.2;
  letter-spacing: -0.012em;
  margin-bottom: 10px;
  color: var(--on-dark);
}

.step__text {
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--on-dark-3);
  font-weight: 300;
}

/* --------------------------------------------------------------------------
   Policies
   -------------------------------------------------------------------------- */

.policy {
  display: grid;
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
  gap: clamp(20px, 4vw, 56px);
  padding-block: clamp(30px, 4vw, 46px);
  border-top: 1px solid var(--line);
}

.policy:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.policy__title {
  font-size: clamp(1.5rem, 2.3vw, 1.875rem);
  line-height: 1.18;
  letter-spacing: -0.016em;
}

.policy__texts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: clamp(22px, 3vw, 44px);
}

.policy__col p {
  font-size: 1rem;
  line-height: 1.72;
  font-weight: 300;
  color: var(--body);
  max-width: 52ch;
}

.policy__col p + p {
  margin-top: 0.85em;
}

/* --------------------------------------------------------------------------
   Commitment
   -------------------------------------------------------------------------- */

.commitment {
  max-width: 62rem;
  margin-inline: auto;
  text-align: center;
}

.commitment .h2 {
  line-height: 1.12;
  margin-bottom: 0.42em;
}

/* The heading and this paragraph are the whole guarantee. A four-card grid
   used to sit under them restating pricing, crews, and support from three
   other sections, which turned the promise into marketing. */
.commitment__lede {
  font-size: 1.09rem;
  line-height: 1.75;
  color: var(--body);
  font-weight: 300;
  max-width: 44rem;
  margin-inline: auto;
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */

.faq-wrap {
  max-width: 56rem;
  margin-inline: auto;
}

.faq-wrap .h2 {
  margin-bottom: 1em;
}

.faq {
  border-top: 1px solid var(--line);
}

.faq-item {
  border-bottom: 1px solid var(--line);
}

.faq-q {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  width: 100%;
  padding: 24px 4px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--sans);
  font-size: 1.125rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--ink);
  transition: color var(--fast) ease-out;
}

.faq-q:hover {
  color: var(--accent-ink);
}

.faq-icon {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--accent-ink);
  overflow: visible;
}

.faq-icon line {
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.34s var(--ease-out), opacity 0.34s var(--ease-out);
}

/* The plus turns into a minus by rotating its own upright, not by swapping glyphs. */
.faq-item[data-open="true"] .faq-icon__v {
  transform: rotate(90deg);
  opacity: 0;
}

.faq-a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.36s var(--ease-out);
}

.faq-item[data-open="true"] .faq-a {
  grid-template-rows: 1fr;
}

.faq-a > div {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear 0.36s;
}

.faq-item[data-open="true"] .faq-a > div {
  visibility: visible;
  transition-delay: 0s;
}

.faq-a p {
  padding: 0 4px 26px;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--body);
  font-weight: 300;
  max-width: 68ch;
}

/* --------------------------------------------------------------------------
   Service area
   -------------------------------------------------------------------------- */

/* The map is Leaflet on OpenStreetMap tiles, vendored in assets/vendor/leaflet
   and loaded by main.js only as the section comes near. The tiles are pulled
   down to a warm grey so the terracotta shading is the only color on it. The
   town list beside it is the real content: it reads with JS off, and with the
   map up it is the legend and the keyboard way around the map. */
.areas-layout {
  display: grid;
  gap: clamp(20px, 3vw, 32px);
}

.areas-map {
  height: clamp(21rem, 52vw, 34rem);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--cream-2);
  /* One finger scrolls the page, two move the map, set in main.js. */
  isolation: isolate;
}

.areas-map.leaflet-container {
  font-family: var(--sans);
  background: var(--cream-2);
}

.areas-map:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 3px;
}

.areas-map .leaflet-tile-pane {
  filter: grayscale(1) sepia(0.14) contrast(0.9) brightness(1.05);
}

.areas-map .leaflet-control-attribution {
  font-size: 0.6875rem;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.86);
  border-top-left-radius: 8px;
  padding: 2px 8px;
}

.areas-map .leaflet-control-attribution a {
  color: var(--accent-ink);
}

/* Town names on the map: pills with a shadow and no border, and no arrow. */
.areas-map .area-label {
  background: var(--white);
  color: var(--ink-900);
  border: 0;
  border-radius: 999px;
  box-shadow: 0 2px 8px -2px rgba(27, 30, 34, 0.35);
  padding: 3px 10px;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.areas-map .area-label::before {
  display: none;
}

.areas-map .area-label[data-active="true"] {
  background: var(--accent-dark);
  color: #fff;
}

/* Zoom, drawn from the sprite rather than Leaflet's text glyphs. */
.areas-zoom {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.areas-zoom button {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line-3);
  border-radius: var(--r-sm);
  background: var(--white);
  color: var(--ink-900);
  cursor: pointer;
  transition: background-color var(--fast) ease-out, transform 90ms ease-out;
}

.areas-zoom button:hover {
  background: var(--cream-2);
}

.areas-zoom button:active {
  transform: scale(0.94);
}

.areas-zoom button:disabled {
  color: var(--line-4);
  cursor: default;
}

.areas-zoom svg {
  width: 18px;
  height: 18px;
}

.areas-list__title {
  font-size: clamp(1.4rem, 2vw, 1.6rem);
  line-height: 1.2;
  letter-spacing: -0.014em;
  margin-bottom: 10px;
}

.area-towns {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.25rem, 1fr));
  column-gap: 14px;
}

.area-town {
  border-top: 1px solid var(--line-2);
}

.area-town__row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 12px;
  width: 100%;
  padding: 12px 10px 12px 0;
}

.area-town__name {
  display: block;
  font-family: var(--serif);
  font-size: 1.3125rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}

.area-town__meta {
  display: block;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--muted-2);
}

/* Legend swatches, drawn only once there is a map for them to key. */
.area-town__swatch {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: rgba(184, 116, 85, 0.3);
  box-shadow: inset 0 0 0 1.5px var(--accent-dark);
}

.area-town[data-kind="pin"] .area-town__swatch {
  border-radius: 50%;
  background: var(--accent-dark);
  box-shadow: inset 0 0 0 2px var(--white), 0 0 0 1px var(--accent-dark);
}

button.area-town__row {
  margin: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--r-sm);
  padding-left: 8px;
  transition: background-color var(--fast) ease-out, transform 90ms ease-out;
}

button.area-town__row:hover {
  background: var(--cream-2);
}

button.area-town__row:active {
  transform: scale(0.985);
}

button.area-town__row[aria-pressed="true"] {
  background: var(--white);
}

button.area-town__row[aria-pressed="true"] .area-town__name {
  color: var(--accent-ink);
}

.areas-list__reset {
  display: inline-flex;
  margin-top: 14px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--accent-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--accent-line-soft);
  cursor: pointer;
}

.areas-list__reset:hover {
  color: var(--accent-ink-hover);
  text-decoration-color: currentColor;
}

@media (min-width: 900px) {
  .areas-layout {
    grid-template-columns: minmax(0, 1fr) 17rem;
    align-items: start;
  }

  .area-towns {
    grid-template-columns: 1fr;
  }
}

/* --------------------------------------------------------------------------
   Quote / contact
   -------------------------------------------------------------------------- */

.quote-section {
  background: linear-gradient(155deg, #1b1e22 0%, #262b31 55%, #2e2822 100%);
  color: var(--on-dark);
  padding: clamp(72px, 9vw, 116px) var(--gutter);
}

.quote-grid {
  max-width: var(--maxw);
  margin-inline: auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: clamp(36px, 5vw, 64px);
}

.quote-section .h2 {
  font-size: clamp(2.1rem, 4.4vw, 3.375rem);
  line-height: 1.06;
  margin-bottom: 0.36em;
  color: var(--on-dark);
}

.quote-section__lede {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--on-dark-2);
  font-weight: 300;
  max-width: 30rem;
  margin-bottom: clamp(32px, 4vw, 42px);
  text-wrap: pretty;
}

.contact-list {
  display: grid;
  gap: 22px;
  max-width: 24rem;
}

.contact-row {
  border-top: 1px solid var(--rule-dark);
  padding-top: 18px;
}

.contact-row__label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
  margin-bottom: 9px;
}

.contact-row__label svg {
  width: 15px;
  height: 15px;
  color: var(--accent-sand);
}

.contact-row__phone {
  font-family: var(--serif);
  font-size: 1.6875rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--on-dark);
  font-variant-numeric: tabular-nums;
}

.contact-row__email {
  font-size: 1.0625rem;
  color: var(--on-dark);
  font-weight: 300;
}

/* The same number, for a text. Smaller than the number itself, so the row
   still reads as one phone line with two ways to use it. */
.contact-row__text {
  display: block;
  width: fit-content;
  margin-top: 6px;
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--on-dark-2);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: rgba(233, 226, 220, 0.4);
}

.contact-row__phone:hover,
.contact-row__email:hover,
.contact-row__text:hover {
  color: var(--accent-sand);
}

.contact-row__hours {
  font-size: 1rem;
  color: var(--on-dark-2);
  font-weight: 300;
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
}

.form-card {
  background: var(--cream);
  border-radius: var(--r-lg);
  padding: clamp(28px, 3.5vw, 42px);
  color: var(--ink);
  box-shadow: 0 30px 70px -24px rgba(0, 0, 0, 0.6);
}

.form-card h3 {
  font-size: 1.875rem;
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin-bottom: 22px;
}

.form-grid {
  display: grid;
  gap: 16px;
}

.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 16px;
}

.field {
  display: block;
}

.field > span {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 8px;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 13px 15px;
  border: 1px solid var(--line-3);
  border-radius: var(--r-sm);
  background: var(--white);
  font-family: var(--sans);
  font-size: 0.96875rem;
  line-height: 1.4;
  color: var(--ink);
  outline: none;
  transition: border-color var(--fast) ease-out, box-shadow var(--fast) ease-out;
}

.field input::placeholder,
.field textarea::placeholder {
  color: var(--muted-2);
  opacity: 1;
}

.field textarea {
  resize: vertical;
  min-height: 6.5rem;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px rgba(184, 116, 85, 0.18);
}

.field input:user-invalid,
.field textarea:user-invalid {
  border-color: #9c3f2b;
}

.field select {
  appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1l6 6 6-6' stroke='%23756A62' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  padding-right: 40px;
}

/* The add-on opt-in. Sized and bordered like the inputs above it so it reads
   as one more field, with the whole row as the hit area. */
.field-check {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  border: 1px solid var(--line-3);
  border-radius: var(--r-sm);
  background: var(--white);
  cursor: pointer;
  transition: border-color var(--fast) ease-out;
}

.field-check input {
  flex: none;
  width: 1.0625rem;
  height: 1.0625rem;
  margin: 0.18em 0 0;
  cursor: pointer;
}

.field-check > span {
  font-size: 0.9375rem;
  line-height: 1.5;
  font-weight: 300;
  color: var(--body);
}

.field-check:has(input:checked) {
  border-color: var(--accent-ink);
}

.form-note {
  font-size: 0.8125rem;
  color: var(--muted-2);
  text-align: center;
  margin-top: 2px;
  font-weight: 300;
}

.form-error {
  font-size: 0.875rem;
  line-height: 1.6;
  color: #8c3823;
  background: rgba(156, 63, 43, 0.07);
  border: 1px solid rgba(156, 63, 43, 0.25);
  border-radius: var(--r-sm);
  padding: 12px 14px;
}

.form-success {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 14px;
  min-height: 26rem;
}

.form-success__icon {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent-wash-strong);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
}

.form-success__icon svg {
  width: 26px;
  height: 26px;
}

.form-success h3 {
  font-size: 2rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0;
}

.form-success p {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--body);
  font-weight: 300;
  max-width: 42ch;
}

.form-success .btn {
  margin-top: 8px;
}

[hidden] {
  display: none !important;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--footer-bg);
  color: var(--on-dark-3);
  padding: clamp(52px, 6vw, 76px) var(--gutter) 34px;
}

.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 40px;
  padding-bottom: 40px;
  border-bottom: 1px solid var(--rule-dark);
}

.footer-logo {
  display: block;
  height: 104px;
  width: auto;
  margin-bottom: 22px;
}

.footer-brand .wordmark {
  margin-bottom: 22px;
}

.footer-brand p {
  font-size: 0.9375rem;
  line-height: 1.7;
  font-weight: 300;
  max-width: 18rem;
  color: var(--on-dark-muted);
}

.footer-brand p + p {
  margin-top: 0.5em;
}

.footer-col__title {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-sand);
  margin-bottom: 16px;
}

.footer-links {
  display: grid;
  gap: 10px;
  font-size: 0.9375rem;
  font-weight: 300;
}

.footer-links a {
  color: var(--on-dark-3);
}

.footer-links a:hover {
  color: #fff;
}

.footer-links span {
  color: var(--on-dark-muted);
}

.footer-links .footer-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--accent-sand);
  margin-top: 6px;
}

.footer-links .footer-cta svg {
  width: 17px;
  height: 17px;
  transition: transform var(--base) var(--ease-out);
}

.footer-links .footer-cta:hover {
  color: #eec3ab;
}

.footer-links .footer-cta:hover svg {
  transform: translateX(4px);
}

.footer-bottom {
  padding-top: 24px;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 0.84375rem;
  color: var(--on-dark-muted);
  font-weight: 300;
}

/* --------------------------------------------------------------------------
   Motion: one authored entrance, then feedback that answers the pointer
   -------------------------------------------------------------------------- */

/* The mobile menu materializes from the control that opened it. */
@keyframes menuMaterialize {
  from {
    opacity: 0;
    transform: translateY(-8px) scale(0.99);
    filter: blur(8px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: blur(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  /* Reduced motion gets the finished hero immediately: main.js never sets
     data-active, so --p stays at 1 and nothing expands on scroll. */
  .shero__hint {
    display: none;
  }

  .site-header[data-menu-open="true"] .nav,
  .site-header[data-menu-open="true"] .header-actions {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   Accessibility preferences
   -------------------------------------------------------------------------- */

@media (prefers-reduced-transparency: reduce) {
  .site-header,
  .site-header[data-scrolled="true"] {
    background: #14171a;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .site-header::after {
    display: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --body: #3d3630;
    --muted: #4c443e;
    --muted-2: #4c443e;
    --line: #cfc2b8;
    --line-2: #cfc2b8;
    --on-dark-2: #f2ede8;
    --on-dark-3: #e8e3dd;
    --on-dark-muted: #d3cbc3;
  }

  .site-header {
    background: #14171a;
    border-bottom-color: rgba(255, 255, 255, 0.32);
  }

  .areas-map,
  .area-town,
  .review,
  .field-check,
  .field input,
  .field select,
  .field textarea {
    border-color: var(--muted-2);
  }

  .btn--outline {
    border-color: var(--on-dark);
  }
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

/* The pricing rail tracks alongside its checklist on wide screens. */
@media (min-width: 901px) {
  .tier__head {
    position: sticky;
    top: calc(var(--header-h) + 28px);
    align-self: start;
  }
}

@media (max-width: 1040px) {
  .header-phone {
    display: none;
  }
}

@media (max-width: 900px) {
  :root {
    --header-h: 76px;
  }

  .header-call {
    display: inline-flex;
    order: 2;
  }

  .nav-toggle {
    display: inline-flex;
    order: 3;
  }

  .header-inner {
    flex-wrap: wrap;
    column-gap: 12px;
    padding-block: 10px;
  }

  .nav,
  .header-actions {
    display: none;
    order: 4;
    width: 100%;
  }

  .site-header[data-menu-open="true"] .nav,
  .site-header[data-menu-open="true"] .header-actions {
    display: flex;
    animation: menuMaterialize 0.34s var(--ease-out) both;
    transform-origin: top right;
  }

  .site-header[data-menu-open="true"] .nav {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding-top: 8px;
    border-top: 1px solid var(--rule-dark);
    margin-top: 10px;
  }

  .site-header[data-menu-open="true"] .nav a {
    padding: 13px 2px;
    font-size: 1rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }

  .site-header[data-menu-open="true"] .nav a::after {
    display: none;
  }

  .site-header[data-menu-open="true"] .header-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 16px 0 10px;
    animation-delay: 0.05s;
  }

  .site-header[data-menu-open="true"] .header-phone {
    display: block;
    font-size: 1rem;
  }

  .site-header[data-menu-open="true"] .btn {
    width: 100%;
  }

  .logo-img {
    height: 54px;
  }

  .wordmark__mark {
    font-size: 1.75rem;
  }

  .tier,
  .policy,
  .value,
  .also {
    grid-template-columns: minmax(0, 1fr);
  }

  .also {
    gap: 24px;
  }

  .tier {
    gap: 26px;
  }

  .value {
    gap: 12px;
  }

  .policy {
    gap: 22px;
  }
}

@media (max-width: 620px) {
  .split__media {
    min-height: 280px;
  }

  .value__initial {
    font-size: 2.75rem;
  }

  .form-success {
    min-height: 0;
  }

  .footer-bottom {
    flex-direction: column;
    gap: 8px;
  }
}

/* --------------------------------------------------------------------------
   Article and hub pages

   Everything a guide needs beyond what the homepage already defines. The
   components a guide uses most, the checklist and the FAQ accordion, are not
   here: they are .tasks and .faq from the sections above, neither of which is
   scoped to its parent, so an article reuses them and inherits the accordion
   behaviour in main.js for free.

   No eyebrow rule, no border-left stripe on the callout, no section numbers.
   Those are removed patterns and an article does not get to bring them back.
   -------------------------------------------------------------------------- */

/* A measure narrow enough to read. 68ch is the .lede's limit and the right
   one for running prose too. It is centred in its container: .container is
   1200px wide and this column is roughly 700px, so without the auto margin the
   article sat flush against the left edge with a third of the page empty
   beside it. margin-inline longhand on purpose, never a `margin` shorthand:
   a shorthand here is what silently kills the centring. */
.prose {
  max-width: 68ch;
  margin-inline: auto;
}

.prose > * + * {
  margin-top: 1.1em;
}

.prose h2 {
  font-size: clamp(1.6rem, 2.8vw, 2.05rem);
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin-top: 2.2em;
}

.prose h3 {
  font-size: clamp(1.2rem, 2vw, 1.35rem);
  line-height: 1.25;
  letter-spacing: -0.012em;
  margin-top: 1.8em;
}

.prose p,
.prose li {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--body);
  font-weight: 300;
  text-wrap: pretty;
}

.prose strong {
  font-weight: 500;
  color: var(--ink-900);
}

/* :not(.btn) is load-bearing. Without it this rule outweighs .btn--primary,
   which is a single class, and paints the button's label --accent-ink on an
   --accent-dark background: brown on brown, an invisible CTA. Any future rule
   that styles links inside .prose needs the same exclusion. */
.prose a:not(.btn) {
  color: var(--accent-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--accent-line-soft);
}

.prose a:not(.btn):hover {
  text-decoration-color: currentColor;
}

/* Declared after .prose a on purpose: same specificity, so the later rule wins
   and the breadcrumb keeps reading as secondary rather than as body link. */
.crumbs {
  font-size: 0.9375rem;
  color: var(--muted);
  margin-bottom: 1.5rem;
}

.prose .crumbs a {
  color: inherit;
  text-decoration-color: var(--line-3);
}

/* .tasks is excluded because it draws its own check mark and lays out in two
   columns. Everything else, bulleted or numbered, sits on the same indent: an
   ol left at the browser default indents roughly three times as far as the
   ul beside it, which reads as a different page. */
.prose ul:not(.tasks),
.prose ol {
  padding-left: 1.15em;
}

.prose ul:not(.tasks) li + li,
.prose ol li + li {
  margin-top: 0.5em;
}

.article-meta {
  font-size: 0.9375rem;
  color: var(--muted-2);
  margin-top: 1.1rem;
  font-variant-numeric: tabular-nums;
}

/* The quick answer. This is the block a featured snippet or an AI answer lifts,
   so it is set apart by a wash and a full border rather than by a coloured
   stripe down one side. One elevation, declared once. */
.quick-answer {
  background: var(--accent-wash);
  border: 1px solid var(--accent-line-soft);
  border-radius: var(--r-card);
  padding: clamp(20px, 3vw, 28px);
  margin-top: 1.8em;
}

.quick-answer p {
  margin: 0;
  color: var(--ink-800);
}

.quick-answer__label {
  display: block;
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 0.6em;
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 2.4em;
}

/* An article's checklist is wider than a pricing tier's, so it gets one column
   on a phone and two only when there is genuine room. That suits short items,
   which is what a task list normally holds. */
.prose .tasks {
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}

/* The other kind: a few checked points that are each a sentence or three. Two
   narrow columns of that is unreadable, so it stays in one column and breathes.
   Use it when an item is longer than about a dozen words. */
.prose .tasks--notes {
  grid-template-columns: 1fr;
  row-gap: 20px;
}

.prose .tasks--notes li {
  line-height: 1.65;
}

.prose .faq {
  margin-top: 1.2em;
}

.prose figure {
  margin: 1.8em 0 0;
}

.prose figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-card);
}

.prose figcaption {
  margin-top: 0.9em;
}

/* A table is one of the structures search and AI engines lift whole, so an
   article gets a real one. The wrapper scrolls it on a narrow screen: the page
   body must never scroll sideways. */
.table-wrap {
  overflow-x: auto;
  margin-top: 1.8em;
  -webkit-overflow-scrolling: touch;
}

.prose table,
.svc table {
  width: 100%;
  min-width: 32rem;
  border-collapse: collapse;
}

.prose th,
.prose td,
.svc th,
.svc td {
  text-align: left;
  padding: 0.8em 0.9em;
  border-bottom: 1px solid var(--line);
  font-size: 1rem;
  line-height: 1.55;
  font-weight: 300;
  color: var(--body);
  vertical-align: top;
}

.prose thead th,
.svc thead th {
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom-color: var(--line-3);
}

.prose tbody th,
.svc tbody th {
  font-weight: 500;
  color: var(--ink-900);
  white-space: nowrap;
  padding-left: 0;
}

/* Named by the article skills. A quiet aside, not a warning box: no stripe, no
   alarm colour, just a rule and smaller muted text so it reads as a footnote
   the reader can take or leave. */
.disclaimer {
  margin-top: 1.8em;
  padding: clamp(16px, 2.4vw, 22px) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.prose .disclaimer p {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--muted);
}

.read-more {
  margin-top: 2.6em;
  padding-top: 1.6em;
  border-top: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   Service landing pages

   One page per priced service, at the URL its guide used to hold. A guide
   answers a question; a landing page answers "what does it cost and how do I
   book it", so the price and the way to book sit on the first screen. It is
   built from the homepage's own pieces rather than new ones: the tier's price
   and qualifier, the hero's rating pill, the checklist, the FAQ accordion,
   and the quote section's ink for the close. Everything is scoped to .svc on
   <main>, so none of it can leak into the homepage or a guide.
   -------------------------------------------------------------------------- */

.svc-top {
  padding-top: clamp(36px, 5vw, 72px);
}

/* The price spans both rows of the left column. The heading settles to the
   bottom of its row and the answer to the top of its own, so when the card
   is the taller side the two stay together and the slack goes above and
   below them rather than opening a gap between them. */
.svc-top__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 23rem);
  grid-template-areas:
    "head price"
    "body price";
  column-gap: clamp(32px, 5vw, 72px);
  row-gap: 26px;
}

.svc-top__head {
  grid-area: head;
  align-self: end;
}

.svc-top__body {
  grid-area: body;
  align-self: start;
}

.svc-top .svc-price {
  grid-area: price;
  align-self: center;
}

.svc .crumbs a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--line-3);
}

.svc .crumbs a:hover {
  text-decoration-color: currentColor;
}

.svc-top__title {
  font-size: clamp(2.4rem, 5.2vw, 3.9rem);
  line-height: 1.04;
  letter-spacing: -0.03em;
}

.svc-top__proof {
  margin-top: 26px;
}

/* The price card. One elevation, the shadow, and no border under it. */
.svc-price {
  background: var(--white);
  border-radius: var(--r-card);
  padding: clamp(26px, 3.4vw, 38px);
  box-shadow: 0 24px 48px -30px rgba(27, 30, 34, 0.45);
}

.svc-price .tier__price {
  margin-top: 0;
}

.svc-price__note {
  margin-top: 16px;
  font-size: 0.9375rem;
  line-height: 1.65;
  font-weight: 300;
  color: var(--body);
}

.svc-price__actions {
  display: grid;
  gap: 12px;
  margin-top: 24px;
}

/* A block button in a card as narrow as a small phone can run out of room
   for an uppercase label, so these wrap rather than overflow the card. */
.svc-price .btn {
  white-space: normal;
  text-align: center;
  line-height: 1.25;
}

/* The body sections: the heading and its lede in a narrow column that stays
   in view beside the detail, the same proportions as a pricing tier. */
.svc-split {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 72px);
}

.svc-split__head .h2 {
  margin-bottom: 0.45em;
}

.svc-split__text {
  font-size: 1.0625rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  max-width: 62ch;
  text-wrap: pretty;
}

.svc-split__text + .svc-split__text {
  margin-top: 1.1em;
}

.svc-split__text a,
.svc-steps a,
.svc-ba__note a,
.svc-ba__shot figcaption a,
.svc-more a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: var(--accent-line-soft);
}

.svc-split__text a:hover,
.svc-steps a:hover,
.svc-ba__note a:hover,
.svc-ba__shot figcaption a:hover,
.svc-more a:hover {
  text-decoration-color: currentColor;
}

/* A label over a checklist, set like the tier's "What's included" row. It
   names the list below it; it is not a kicker above a heading. */
.svc-group {
  font-family: var(--sans);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-2);
}

.tasks + .svc-group {
  margin-top: clamp(30px, 3.6vw, 40px);
}

.tasks + .svc-split__text {
  margin-top: clamp(28px, 3.4vw, 38px);
}

/* Whatever opens the column sits level with the heading beside it, so a
   checklist first in line gives up the top margin it carries elsewhere. */
.svc-split__body > :first-child {
  margin-top: 0;
}

.table-wrap + .svc-split__text,
.svc-steps + .svc-split__text {
  margin-top: 1.4em;
}

.svc-split__text + .svc-group {
  margin-top: clamp(30px, 3.6vw, 40px);
}

/* A short table whose cells wrap happily fits a phone instead of scrolling:
   the shared 32rem floor is for tables with long rows. */
.svc table.table--fit {
  min-width: 0;
}

.svc table.table--fit tbody th {
  white-space: normal;
}

/* Numbered steps: a real <ol>, on the same indent as the site's other lists. */
.svc-steps {
  margin: 20px 0 0;
  padding-left: 1.15em;
}

.svc-steps li {
  font-size: 1.0625rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
  text-wrap: pretty;
}

.svc-steps li + li {
  margin-top: 0.8em;
}

/* A before and after pair. Both frames are landscape, so they sit side by
   side and stack only on a phone; neither is cropped. Exactly two columns,
   never auto-fit: auto-fit opened a third, empty track on a wide screen and
   shrank the photographs to a third of the column. */
.svc-ba {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2.4vw, 24px);
  margin-top: clamp(32px, 4vw, 44px);
}

/* One photograph rather than a pair takes the full column. */
.svc-ba--single {
  grid-template-columns: minmax(0, 1fr);
}

.svc-ba__shot {
  margin: 0;
}

.svc-ba__shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-card);
  background: var(--cream-2);
}

.svc-ba__shot figcaption {
  margin-top: 12px;
  font-size: 0.9375rem;
  line-height: 1.6;
  font-weight: 300;
  color: var(--body);
}

.svc-ba__note {
  grid-column: 1 / -1;
  font-size: 0.9375rem;
  line-height: 1.65;
  font-weight: 300;
  color: var(--muted);
}

.svc-more {
  margin-top: clamp(44px, 5vw, 60px);
}

.svc-more__title {
  font-size: clamp(1.35rem, 2.2vw, 1.6rem);
  line-height: 1.2;
  letter-spacing: -0.014em;
}

.svc-more ul {
  margin: 0.9em 0 0;
  padding-left: 1.15em;
}

.svc-more li {
  font-size: 1.0625rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--body);
}

.svc-more li + li {
  margin-top: 0.4em;
}

/* The close carries .quote-section too, for its ink, its type and its focus
   ring; this only centres it. */
.svc-cta__inner {
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}

.svc-cta__lede {
  font-size: 1.0625rem;
  line-height: 1.7;
  font-weight: 300;
  color: var(--on-dark-2);
  text-wrap: pretty;
}

.svc-cta .cta-row {
  justify-content: center;
  margin-top: 2em;
}

@media (min-width: 901px) {
  .svc-split__head {
    position: sticky;
    top: calc(var(--header-h) + 28px);
    align-self: start;
  }
}

@media (max-width: 900px) {
  .svc-top__grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "price"
      "body";
  }

  .svc-split {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
  }
}

@media (max-width: 560px) {
  .svc-ba {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* On the narrowest phones the card and the buttons give back a little of
   their inset, so "Get a move-out quote" stays on one line. */
@media (max-width: 400px) {
  .svc-price {
    padding: 22px;
  }

  .svc-price .btn {
    padding-inline: 16px;
  }
}

/* The card's shadow is too faint to hold its edge here, so it trades the
   shadow for a border rather than stacking the two. */
@media (prefers-contrast: more) {
  .svc-price {
    box-shadow: none;
    border: 1px solid var(--muted-2);
  }
}

/* --------------------------------------------------------------------------
   Print
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Action bar: call, text, or a free quote, one tap each, on a phone

   Below 900px the header has no room for its Free Quote button, and the form
   is most of the page away. The bar stays out of the way while the page's own
   call to action is on screen (the hero, or a page's heading) and while a
   quote section is, and main.js marks it data-state="hidden" then, inert so a
   keyboard cannot reach what is off screen. With JS off it simply stays.
   -------------------------------------------------------------------------- */

.actionbar {
  display: none;
}

@media (max-width: 900px) {
  .actionbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 45;
    display: flex;
    gap: 8px;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    background: var(--footer-bg);
    box-shadow: 0 -10px 28px -14px rgba(0, 0, 0, 0.55);
    transition: transform var(--base) var(--ease-out);
  }

  .actionbar[data-state="hidden"],
  .site-header[data-menu-open="true"] ~ .actionbar {
    transform: translateY(calc(100% + 16px));
  }

  .actionbar__btn {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 46px;
    padding: 0 16px;
    border: 1px solid rgba(233, 226, 220, 0.3);
    border-radius: 999px;
    color: var(--on-dark);
    font-size: 0.9375rem;
    font-weight: 500;
    text-decoration: none;
    transition: background-color var(--fast) ease-out, transform 90ms ease-out;
  }

  .actionbar__btn svg {
    width: 18px;
    height: 18px;
    color: var(--accent-sand);
  }

  .actionbar__btn:active {
    transform: scale(0.97);
  }

  .actionbar__btn:focus-visible {
    outline: 2px solid var(--accent-sand);
    outline-offset: 2px;
  }

  .actionbar__btn--primary {
    flex: 1 1 auto;
    border-color: transparent;
    background: var(--accent-dark);
    color: #fff;
    font-size: 0.875rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }

  .actionbar__btn--primary:hover {
    background: #7c4835;
    color: #fff;
  }

  /* Room under the footer, so the bar never covers its last line. */
  .site-footer {
    padding-bottom: calc(34px + 66px + env(safe-area-inset-bottom));
  }
}

@media print {
  .site-header,
  .nav-toggle,
  .shero__hint,
  .shero__mop,
  .shero__wet,
  .actionbar {
    display: none !important;
  }

  body {
    background: #fff;
  }

  .faq-a {
    grid-template-rows: 1fr;
  }

  .faq-a > div {
    visibility: visible;
  }
}
