/* ============================================================================
   PLATINUM PANES — pass 11, "Minted Midnight" (variant P promoted 2026-09-01).

   One design, one file, no override layers: this replaced the platinum-era
   base + Front Range append-block wholesale. The old stylesheet is preserved
   at the git tag `design-frontrange-final`.

   System: the FINAL brand package (assets/Branding, gitignored) —
     navy #0c111f · gray #f2f0ef · sky #658dc0 · teal #96cec6
     Public Sans (Black caps headlines, Light/Regular body)
     BenchNine Bold (the logotype's condensed face: subheads, buttons, labels)

   Structure: tokens → base → site components (both pages) → the wizard block
   and reviews-carousel block PORTED from pass 10 (geometry untouched — the
   44px floors and the pills scroll-strip were measured, not eyeballed; they
   read old token names, which the :root below aliases to the new palette) →
   mobile floors → reduced motion.
   ========================================================================= */

:root {
  /* ---- the brand package ---- */
  --navy: #0c111f;
  --gray: #f2f0ef;
  --sky:  #658dc0;
  --teal: #96cec6;
  --body-ink: #2a3140;
  /* Sky is a FILL color — at #658dc0 it is 3.4:1 on white and cannot carry
     small text (the §4 lesson, relearned by audit on 2026-09-01). Text-sized
     sky uses this darkened tier: 5.3:1 on white, 4.8:1 on the gray wash. */
  --sky-text: #446ba0;
  --gyellow: #FBBC05;          /* Google-review stars, nothing else */
  --shadow: 0 10px 30px rgba(12,17,31,.10);
  --r: 18px;
  --nav-h: 64px;
  --ease: cubic-bezier(.22, 1, .36, 1);

  /* ---- aliases for the PORTED wizard + carousel blocks ----
     Those blocks were written against the old token names. Mapping the names
     here means the ported CSS runs byte-identical while rendering in the new
     palette — change the mapping, not the ported block. */
  --ink:        var(--navy);
  --ink-soft:   var(--body-ink);
  --grey:       #4c5566;
  --grey-dim:   #5a6372;
  --paper:      var(--gray);
  --panel:      #ffffff;
  --panel-2:    #e9e6e4;
  --platinum:   #d9d5d2;
  --blue:       var(--sky);
  --blue-ink:   #33567f;       /* the text-safe sky tier on light ground */
  --teal-ink:   #1f5a54;
  --line:        rgba(12,17,31,.14);
  --line-strong: rgba(12,17,31,.30);
  /* Archivo's width axis is gone; Public Sans has none, so these are inert —
     kept only so ported rules that read them stay valid. */
  --w-display: 100%;
  --w-label:   100%;
  /* The four tokens the ported block reads that the rewrite dropped (audit,
     2026-09-01): without them the wizard rendered square-cornered, validation
     errors printed in body ink, and the review cards lost their shadow. */
  --radius: 12px;
  --error:  #a8473d;
  --font:   'Public Sans', system-ui, sans-serif;
  --soft:   var(--shadow);
}

/* ================================ BASE ==================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 32px); }
body {
  font-family: 'Public Sans', system-ui, sans-serif;
  font-weight: 300; line-height: 1.65; color: var(--body-ink);
  background: #fff; -webkit-font-smoothing: antialiased;
}
body.nav-locked { overflow: hidden; }
img, svg, video { display: block; max-width: 100%; }
/* The hidden attribute must beat any author display rule — .wiz-nav is
   display:flex and the success panel sets hidden on it. Without this the
   Back button and a dead "Sending…" sat above "Request received". */
[hidden] { display: none !important; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--teal); color: var(--navy); }

.wrap { max-width: 1180px; margin-inline: auto; padding-inline: clamp(1.1rem, 4vw, 2.5rem); }
.wrap--narrow { max-width: 820px; }

.skip {
  position: fixed; top: -100px; left: 1rem; z-index: 100;
  background: var(--navy); color: #fff; padding: .8em 1.4em; border-radius: 10px;
  transition: top .2s ease;
}
.skip:focus-visible { top: calc(var(--nav-h) + 24px); }

/* ============================ TYPE SYSTEM ================================= */
h1, h2 {
  font-weight: 900; text-transform: uppercase; letter-spacing: -.03em;
  line-height: .96; color: var(--navy);
}
h2 { font-size: clamp(1.9rem, 4.4vw, 3rem); }
h1 em, h2 em { font-style: normal; color: var(--sky); }
.eyebrow {
  font-family: 'BenchNine', 'Public Sans', sans-serif; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3em; font-size: .95rem;
  color: var(--sky-text); margin-bottom: .5rem;
}
.sec-sub { margin-top: .9rem; color: var(--grey); max-width: 38rem; }

/* ============================== BUTTONS =================================== */
/* Buttons keep the LIVE SITE's label voice (owner, 2026-09-01): Archivo
   letterspaced caps at the pass-10 sizes, not BenchNine — the nav bar and
   every Instant Quote pill read exactly like the site the customers know. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  min-height: 46px; padding: .8em 1.7em; border-radius: 999px;
  font-family: 'Archivo', 'Public Sans', sans-serif; font-weight: 600;
  font-size: .78rem; letter-spacing: .14em; text-transform: uppercase;
  white-space: nowrap; border: 0; cursor: pointer;
  transition: transform .18s ease, background .18s ease, color .18s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn--lg { min-height: 54px; padding: .95em 2.1em; font-size: .84rem; }
.btn--teal { background: var(--teal); color: var(--navy); }
.btn--teal:hover { background: #fff; }
.btn--navy { background: var(--navy); color: #fff; }
.btn--navy:hover { background: var(--sky); color: var(--navy); }
.btn--frost {
  background: rgba(255,255,255,.16); color: #fff;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.btn--frost:hover { background: rgba(255,255,255,.32); }
/* soft fill, not an outline — the doc's "no white outline bubbles" */
.btn--ghost { background: var(--gray); color: var(--navy); }
.btn--ghost:hover { background: #e4e1de; }

/* -------------------- NAV — floating pill, PINNED ------------------------- */
/* Owner directive (redesign doc, closing note): the bar never retracts.
   position:fixed with no scroll logic at all — Get Instant Quote is on
   screen from first paint to the footer at every width. */
.nav {
  position: fixed; z-index: 60; top: 14px; left: 14px; right: 14px;
  margin-inline: auto; max-width: 1240px; height: var(--nav-h);
  display: flex; align-items: center; gap: clamp(.9rem, 2.5vw, 2rem);
  padding-inline: clamp(.9rem, 2vw, 1.6rem);
  border-radius: 999px;
  background: rgba(12,17,31,.55);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  transition: background .3s ease;
}
/* Over the footage the translucency is the look; over a white section the
   same pill composites to a mid grey and white links drop to 4.2:1. main.js
   adds .is-scrolled once the hero has left the viewport. */
.nav.is-scrolled { background: rgba(12,17,31,.88); }
/* flex:none — the lockup is a fixed-height image, and as a shrinkable flex
   item it was squeezed to 103px at 390 and 33px at 320. */
.nav-brand { display: inline-flex; align-items: center; min-height: 44px; flex: none; }
.nav-brand img { height: 30px; width: auto; }
.nav-links { display: flex; gap: clamp(.9rem, 2vw, 1.6rem); margin-inline: auto; }
.nav-links a {
  font-family: 'Archivo', 'Public Sans', sans-serif; font-weight: 600;
  font-size: .72rem; letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255,255,255,.85); padding-block: .9em;
}
.nav-links a:hover { color: var(--teal); }
.nav-cta { display: flex; align-items: center; gap: 1rem; }
.nav-tel {
  font-family: 'Archivo', 'Public Sans', sans-serif; font-weight: 600;
  font-size: .86rem; color: #fff; white-space: nowrap;
}

.burger {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; border: 0; background: none; cursor: pointer;
  align-items: center;
}
.burger span {
  width: 20px; height: 2px; background: #fff; border-radius: 2px;
  transition: transform .25s ease, opacity .25s ease;
}
.burger.is-open span:first-child { transform: translateY(3.5px) rotate(45deg); }
.burger.is-open span:last-child  { transform: translateY(-3.5px) rotate(-45deg); }

.mnav {
  position: fixed; inset: 0; z-index: 55; display: none;
  flex-direction: column; justify-content: center; align-items: center; gap: .4rem;
  background: rgba(12,17,31,.96);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.mnav.is-open { display: flex; }
.mnav a {
  font-family: 'Archivo', 'Public Sans', sans-serif; font-weight: 700;
  font-size: 1.15rem; letter-spacing: .18em; text-transform: uppercase;
  color: #fff; padding: .55em 1em; min-height: 46px;
  display: inline-flex; align-items: center;
}
.mnav a:hover { color: var(--teal); }

/* ============================ HERO — VIDEO ================================ */
.hero {
  position: relative; min-height: 100svh; overflow: hidden;
  display: grid; place-items: center; text-align: center;
  padding: calc(var(--nav-h) + 3.5rem) 1.25rem 4.5rem;
}
.hero-poster, .hero-vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
.hero-vid { z-index: 1; }
/* P's deeper scrim — the owner's pick over M's lighter one */
.hero-scrim {
  position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(180deg,
    rgba(12,17,31,.64) 0%, rgba(12,17,31,.40) 42%, rgba(12,17,31,.74) 100%);
}
.hero-in { position: relative; z-index: 3; max-width: 880px; }
.hero h1 {
  color: #fff; font-size: clamp(2.5rem, 6.4vw, 4.9rem);
  text-shadow: 0 2px 24px rgba(12,17,31,.35);
}
.hero h1 em { color: var(--teal); }
.hero-sub { margin-top: 1.3rem; font-size: 1.12rem; font-weight: 400; color: #fff; }
.hero-tag {
  margin-top: .45rem; font-family: 'BenchNine', sans-serif; font-weight: 700;
  font-size: 1.15rem; letter-spacing: .18em; text-transform: uppercase; color: var(--teal);
}
.hero-ctas {
  margin-top: 1.7rem; display: flex; gap: .9rem; justify-content: center; flex-wrap: wrap;
}

/* the Google sticker — real rating, refreshed from the baked reviews block */
.gsticker {
  display: inline-flex; align-items: center; gap: .6em; margin-top: 1.4rem;
  min-height: 44px; padding: .55em 1.2em; border-radius: 999px;
  background: rgba(12,17,31,.55);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  color: #fff; font-weight: 600; font-size: .95rem;
}
.gsticker:hover { background: rgba(12,17,31,.78); }
.gstars { color: var(--gyellow); letter-spacing: .12em; }
.glogo { width: 20px; height: 20px; }

/* ---------------- the lockup assembly, over the video ---------------------
   Ported from pass 6 unchanged — same two-copy clip trick, same timings.
   The wordmark is art columns 192-719 of the lockup, the mark 0-174. The
   mark flies in on its side (+90°, handle leading, blade trailing), paints
   the wordmark at its trailing edge, unrotates over its slot and drops in;
   bloom + sparkle fire through the star knocked out of the blade (~1.73s).
   The sweep and the reveal are welded by identical easing across the same
   t-span — retime one and you must retime the other. */
.hero-brand {
  position: relative;
  width: clamp(232px, 31vw, 410px);
  aspect-ratio: 720 / 145;
  margin: 0 auto clamp(1.4rem, 3vh, 2.2rem);
  --sweep-ease: cubic-bezier(.32, .72, .3, 1);
  --turn-ease:  cubic-bezier(.5, .05, .2, 1);
  --drop-ease:  cubic-bezier(.34, 1.28, .64, 1);
}
.hero-word {
  position: absolute; top: 0; left: 26.6667%;
  width: 73.3333%; height: 100%;
  overflow: hidden; z-index: 2;
  clip-path: inset(0 0 0 100%);
  animation: paintOn .728s var(--sweep-ease) .43s forwards;
}
.hero-word .hero-lockup {
  position: absolute; top: 0; left: -36.3636%;
  /* max-width:none is load-bearing — the global img rule otherwise clamps
     the deliberately oversized copies and the artwork renders wrong */
  width: 136.3636%; max-width: none; height: auto;
}
.hero-mark-fly {
  position: absolute; top: 0; left: 0;
  width: 24.3056%; height: 100%; z-index: 3;
  transform: translateX(55vw);
  animation: markFlyIn .28s var(--sweep-ease) .15s forwards;
}
.hero-mark {
  width: 100%; height: 100%; overflow: hidden;
  transform: translate(320%, -14%) rotate(90deg);
  animation: markSweep 1.3s .43s forwards;
}
.hero-mark img {
  position: absolute; top: 0; left: 0;
  width: 411.4286%; max-width: none; height: auto;
}
@keyframes markFlyIn { to { transform: translateX(0); } }
@keyframes paintOn { to { clip-path: inset(0 0 0 0); } }
@keyframes markSweep {
  0% {
    transform: translate(320%, -14%) rotate(90deg);
    animation-timing-function: var(--sweep-ease);
  }
  56% {
    transform: translate(18.2857%, -14%) rotate(90deg);
    animation-timing-function: var(--turn-ease);
  }
  82% {
    transform: translate(0, -14%) rotate(0deg);
    animation-timing-function: var(--drop-ease);
  }
  100% { transform: translate(0, 0) rotate(0deg); }
}
/* 12.2% / 24.8% — the knocked-out star, measured off the artwork */
.hero-bloom, .hero-sparkle {
  position: absolute; left: 12.2%; top: 24.8%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.hero-bloom {
  z-index: 1;
  width: 15%; aspect-ratio: 1;
  background: radial-gradient(circle, rgba(255,255,255,.92) 0 16%,
                              rgba(150,206,198,.55) 40%, transparent 70%);
  opacity: 0;
  animation: sparkBloom .95s var(--ease) 1.66s forwards;
}
.hero-sparkle {
  z-index: 4;
  width: 11%; aspect-ratio: 1;
  opacity: 0;
  background:
    linear-gradient(to right,  transparent 46%, rgba(255,255,255,.9) 50%, transparent 54%),
    linear-gradient(to bottom, transparent 46%, rgba(255,255,255,.9) 50%, transparent 54%);
  -webkit-mask-image: radial-gradient(circle, #000 0 10%, transparent 58%);
          mask-image: radial-gradient(circle, #000 0 10%, transparent 58%);
  animation: sparkFlash .7s var(--ease) 1.70s forwards;
}
@keyframes sparkBloom {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.5); }
  34%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.25); }
}
@keyframes sparkFlash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.35) rotate(-14deg); }
  30%  { opacity: 1; transform: translate(-50%, -50%) scale(1.35) rotate(0deg); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.7) rotate(8deg); }
}

/* ============================== SECTIONS ================================== */
.sec { padding-block: clamp(3.2rem, 7vw, 5.6rem); }
.sec--gray { background: var(--gray); }
.sec--navy { background: var(--navy); }
.sec-head { max-width: 700px; margin-bottom: clamp(1.6rem, 4vw, 2.6rem); }
.sec-head--center { margin-inline: auto; text-align: center; }

/* gentle entrance for anything carrying .reveal (the reviews block does) */
.reveal { opacity: 0; translate: 0 14px; transition: opacity .6s var(--ease), translate .6s var(--ease); }
.reveal.is-in { opacity: 1; translate: 0 0; }

/* ============================== SERVICES ================================== */
.tile-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: 1rem;
}
.tile {
  position: relative; overflow: hidden; border-radius: var(--r);
  aspect-ratio: 4 / 3; box-shadow: var(--shadow); display: block;
  background: var(--navy);
}
.tile img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform .35s ease;
}
.tile:hover img { transform: scale(1.05); }
/* .tile-pane / .tile-badge (the "photo from the shoot" placeholder) are gone:
   every tile carries a real photograph now. The lab variants still use them
   and keep their own copies in css/variant-*.css. */
.tile-face {
  position: absolute; inset: auto 0 0 0; z-index: 1;
  padding: 2.6rem 1.2rem 1.1rem;
  background: linear-gradient(180deg, transparent 0%, rgba(12,17,31,.85) 80%);
  color: #fff;
}
.tile-face h3 {
  font-family: 'BenchNine', sans-serif; font-weight: 700; font-size: 1.3rem;
  letter-spacing: .08em; text-transform: uppercase; line-height: 1.1;
}
.tile-face p {
  font-size: .86rem; font-weight: 300; color: #dfe5ee; margin-top: .35rem;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .35s ease, opacity .35s ease;
}
.tile:hover .tile-face p, .tile:focus-visible .tile-face p { max-height: 5em; opacity: 1; }
@media (hover: none), (max-width: 640px) {
  .tile-face p { max-height: none; opacity: 1; }
}

/* ================================ WHY ===================================== */
/* P's navy band: open columns, teal rules, white type — no boxes. */
.sec--navy .eyebrow { color: var(--teal); }
.sec--navy h2 { color: #fff; }
.sec--navy h2 em { color: var(--teal); }
.why-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: clamp(1.4rem, 4vw, 3rem);
}
.why { border-top: 4px solid var(--teal); padding-top: 1.1rem; }
.why h3 {
  font-weight: 900; text-transform: uppercase; letter-spacing: -.02em;
  font-size: 1.3rem; color: #fff; margin-bottom: .5rem;
}
.why p { font-size: .96rem; color: #c3cad6; }

/* =============================== ABOUT ==================================== */
/* Stacked and centred (owner, 2026-09-02): the founders video sits UNDER the
   copy at full column width rather than beside it in a narrow right-hand
   column. Two faces are the point of this section, so they get the room —
   at 880px the video is nearly twice the width it had in the old 5fr slot. */
.about { display: block; text-align: center; }
/* The measure stays ~62ch even though the column is wider: centred prose past
   about 75 characters is genuinely harder to track line-to-line. */
.about-copy { max-width: 62ch; margin-inline: auto; }
.about-copy h2 ~ p { margin-top: 1rem; }

/* ---- the founders video (click-to-load facade) ----
   The box holds 16:9 from the start so nothing shifts when the poster
   decodes or when the iframe replaces it — the swap is layout-neutral. */
.about-video {
  margin: clamp(1.8rem, 4vw, 2.8rem) auto 0;
  max-width: 880px;
}
.video {
  position: relative; aspect-ratio: 16 / 9;
  border-radius: var(--r); overflow: hidden;
  box-shadow: var(--shadow); background: var(--navy);
}
.video-play {
  position: absolute; inset: 0; width: 100%; height: 100%;
  padding: 0; border: 0; background: none; cursor: pointer; display: block;
}
.video-play img { width: 100%; height: 100%; object-fit: cover; }
.video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* YouTube's own play glyph, drawn inline: recognisable enough that nobody has
   to be told it is a video, and it costs no request. */
/* Scales with the box: YouTube's glyph is a fixed 68px, which read as a small
   detail once the video went full-column at 880px. Ratio stays 68:48. */
.video-btn {
  position: absolute; top: 50%; left: 50%; translate: -50% -50%;
  width: clamp(60px, 8vw, 92px); aspect-ratio: 68 / 48; pointer-events: none;
}
.video-btn svg { width: 100%; height: 100%; }
.video-btn-bg { fill: #212121; fill-opacity: .8; transition: fill-opacity .2s ease, fill .2s ease; }
.video-btn-arrow { fill: #fff; }
.video-play:hover .video-btn-bg,
.video-play:focus-visible .video-btn-bg { fill: #f00; fill-opacity: 1; }
.video-play:focus-visible { outline: 3px solid var(--teal); outline-offset: 3px; }
.about-video figcaption {
  margin-top: .7rem; font-size: .82rem; color: var(--grey);
}
@media (prefers-reduced-motion: reduce) {
  .video-btn-bg { transition: none; }
}

/* ====================== PROOF — DRAG SLIDERS ============================== */
.ba-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 1rem;
}
.slider {
  position: relative; --pos: 50%;
  overflow: hidden; border-radius: var(--r); box-shadow: var(--shadow);
  aspect-ratio: 4 / 3; background: var(--navy);
  touch-action: none;
}
.s-before, .s-after {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.s-after { clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.s-handle {
  position: absolute; top: 0; bottom: 0; left: var(--pos); width: 3px;
  margin-left: -1.5px; background: #fff; z-index: 2; pointer-events: none;
  box-shadow: 0 0 10px rgba(12,17,31,.5);
}
.s-handle::after {
  content: "⟷"; position: absolute; top: 50%; left: 50%;
  translate: -50% -50%; width: 40px; height: 40px; border-radius: 50%;
  background: #fff; color: var(--navy); font-size: 1.05rem;
  display: grid; place-items: center;
}
.slider input[type="range"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: ew-resize; z-index: 3; margin: 0;
  -webkit-appearance: none; appearance: none;
}
.s-tag {
  position: absolute; bottom: .8rem; z-index: 2;
  font-family: 'BenchNine', sans-serif; font-weight: 700; font-size: .95rem;
  letter-spacing: .18em; text-transform: uppercase;
  padding: .3em .9em; border-radius: 999px;
}
.s-tag--b { right: .8rem; background: rgba(12,17,31,.72); color: #fff; }
.s-tag--a { left: .8rem; background: var(--teal); color: var(--navy); }

/* ================================ AREA ==================================== */
.towns {
  font-family: 'BenchNine', sans-serif; font-weight: 700;
  font-size: clamp(1.5rem, 3.6vw, 2.4rem); letter-spacing: .06em;
  text-transform: uppercase; color: var(--navy); line-height: 1.5;
}
.towns i { font-style: normal; color: var(--sky); padding-inline: .25em; }
.area-note { margin-top: 1.1rem; color: var(--grey); font-size: .95rem; }

/* ================================ FAQ ===================================== */
.faq-list details { border-bottom: 1px solid rgba(12,17,31,.12); }
.faq-list summary {
  cursor: pointer; list-style: none; padding: 1.05rem .2rem; min-height: 46px;
  font-weight: 600; color: var(--navy); font-size: 1rem;
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary::after { content: "+"; font-weight: 300; font-size: 1.4rem; color: var(--sky); }
.faq-list details[open] summary::after { content: "–"; }
.faq-list details p { padding: 0 .2rem 1.2rem; font-size: .95rem; max-width: 44rem; }

/* ============================ CLOSING CTA ================================= */
.close { text-align: center; padding-block: clamp(3.6rem, 9vw, 6.5rem); background: var(--navy); }
.close h2 { color: #fff; margin-bottom: 1.7rem; }
.close h2 em { color: var(--teal); }

/* =============================== FOOTER =================================== */
footer { background: var(--navy); color: #c3cad6; padding: 0 0 1.6rem; }
footer .foot { border-top: 1px solid rgba(255,255,255,.12); padding-top: clamp(2.2rem, 5vw, 3.4rem); }
.foot { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 2rem; }
.foot-lockup { height: 40px; width: auto; margin-bottom: 1rem; }
.foot p { font-size: .9rem; max-width: 24rem; }
.foot-hours { margin-top: .5rem; }
.foot h4 {
  font-family: 'BenchNine', sans-serif; font-weight: 700; font-size: .95rem;
  letter-spacing: .22em; text-transform: uppercase; color: #8b96ab; margin-bottom: .7rem;
}
.foot a { display: block; padding-block: .3em; font-size: .95rem; color: #eef1f5; min-height: 44px; display: flex; align-items: center; }
.foot a:hover { color: var(--teal); }
.foot-bottom {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: .6rem;
  margin-top: 2.4rem; padding-top: 1.4rem; border-top: 1px solid rgba(255,255,255,.12);
  font-size: .78rem; color: #8b96ab;
}
.foot-bottom a { color: #c3cad6; min-height: 44px; display: inline-flex; align-items: center; }
.foot-bottom a:hover { color: var(--teal); }
.foot-bottom--solo { margin-top: 0; border-top: 0; padding-top: clamp(1.6rem, 4vw, 2.4rem); }

/* ========================= THE /quote PAGE ================================ */
/* The nav floats over the hero on the homepage; /quote has no hero, so the
   page itself clears the pinned bar — and the pill goes near-solid: 55%
   navy over a white page blends to a washed gray that can't carry the
   white links. Over the homepage's footage the translucency is the look;
   over white it's a legibility bug. */
.page-quote .nav { background: rgba(12,17,31,.94); }
.page-quote main { padding-top: calc(var(--nav-h) + 14px); }
.page-quote .book { padding-top: clamp(2rem, 5vw, 3.4rem); }
.side-label {
  font-family: 'BenchNine', sans-serif; font-weight: 700; font-size: 1rem;
  letter-spacing: .22em; text-transform: uppercase; color: var(--grey-dim);
  margin-bottom: .9rem;
}

/* ======================== REVIEWS — SECTION FRAME ========================= */
/* The header and CTA row are styled ONCE, in the ported skin further down
   (.rev-head / .rev-score / .rev-count / .rev-cta) — a second copy here was
   dead by source order and misled edits. */

/* ====================== PORTED — WIZARD (pass 10) ======================= */
/* Verbatim from the tagged stylesheet; geometry (44px floors, scroll strip,
   the estimate panel) untouched. Colors arrive through the :root aliases.
   Font weights were re-pointed 2026-09-01: the block asked for 520–640 on
   Archivo's variable axis, and on static Public Sans (300/400/600/900) the
   browser sent everything above 600 to Black. Labels are 600 now, display
   figures 900. */
/* =============================== WIZARD =================================== */

.wiz {
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 30px 80px -40px rgba(11, 25, 40, .28);
  max-width: 860px;
}

.wiz-top { padding: 1.4rem clamp(1.2rem, 3vw, 2.2rem) 0; }

.wiz-track { height: 2px; background: var(--platinum); position: relative; overflow: visible; }
.wiz-fill {
  position: absolute; inset: 0 auto 0 0; width: 20%;
  background: var(--blue-ink); transition: width .5s var(--ease);
}
.wiz-fill::after {  /* the blade at the head of the stroke */
  content: ''; position: absolute; right: -2px; top: -3px; width: 4px; height: 8px;
  background: var(--ink);
}

.wiz-pills { display: flex; gap: 1.4rem; margin-top: 1rem; flex-wrap: wrap; }
.wiz-pill {
  background: none; border: none; padding: .2rem 0;
  font-size: .6rem; font-weight: 600; letter-spacing: .22em; text-transform: uppercase;
  color: var(--grey-dim); cursor: default;
}
.wiz-pill.is-done { color: var(--grey); cursor: pointer; }
.wiz-pill.is-active { color: var(--ink); }

.wiz-viewport { padding: clamp(1.6rem, 4vw, 2.4rem) clamp(1.2rem, 3vw, 2.2rem); min-height: 350px; }

.wiz-q { font-stretch: var(--w-label); font-weight: 900; font-size: 1.25rem; letter-spacing: -.01em; text-transform: uppercase; color: var(--navy); margin-bottom: .3rem; }
.wiz-hint { color: var(--grey); font-size: .9rem; margin-bottom: 1.5rem; }

/* Screen-reader-only — the wizard's <legend>s carry the accessible name while
   .group-label carries the visible one. The pass-11 stylesheet rebuild lost
   this rule and every legend rendered as a doubled label. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.choice-group { border: none; margin-bottom: 1.6rem; }
.group-label {
  display: block; font-size: .62rem; font-weight: 600; letter-spacing: .24em;
  text-transform: uppercase; color: var(--grey-dim); margin-bottom: .7rem;
}

.opt-grid { display: flex; flex-wrap: wrap; gap: .6rem; }
.opt {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: transparent; color: var(--ink-soft);
  font-size: .78rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  padding: .85em 1.3em;
  transition: border-color .25s var(--ease), background .25s var(--ease), color .25s var(--ease);
}
.opt:hover { border-color: var(--ink); }
.opt.is-sel { background: var(--ink); color: var(--paper); border-color: var(--ink); }
/* A time the calendar says is taken. Disabled but still rendered: "afternoon
   is full" is information the customer plans around, where a chip that
   silently vanishes reads as a glitch. */
.opt:disabled { opacity: .42; cursor: not-allowed; }
.opt:disabled:hover { border-color: var(--line-strong); }

.wiz input[type="text"], .wiz input[type="tel"], .wiz input[type="email"],
.wiz input[type="date"], .wiz input[type="number"], .wiz textarea {
  width: 100%; background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  color: var(--ink); font-family: inherit; font-size: .95rem;
  padding: .85em 1em;
  transition: border-color .3s var(--ease);
}
.wiz input:focus, .wiz textarea:focus { outline: none; border-color: var(--ink); }
.wiz input::placeholder, .wiz textarea::placeholder { color: var(--grey-dim); }

.field { margin-bottom: 1.1rem; }
.field label {
  display: block; font-size: .62rem; font-weight: 600; letter-spacing: .24em;
  text-transform: uppercase; color: var(--grey-dim); margin-bottom: .5rem;
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@media (max-width: 640px) { .field-row { grid-template-columns: 1fr; } }

/* Sits under whatever it qualifies — an input, or a row of option chips. On
   the window-count question the hint defines the unit the answer is counted
   in, so it is load-bearing rather than decorative (see COUNT_BANDS). */
.field-hint { font-size: .76rem; line-height: 1.5; color: var(--grey); margin-top: .55rem; }

/* The small-panes question's reference figure: photo left, the squeegee rule
   as its caption. Stays a row at every width — the caption is short — and the
   caption side carries min-width: 0 so it can shrink (a flex item otherwise
   refuses to go narrower than its content and widens the page; see the 320px
   overflow note in CLAUDE.md §11). */
.pane-ref { display: flex; gap: .95rem; align-items: flex-start; margin: .8rem 0 0; }
.pane-ref img {
  width: 110px; height: auto; aspect-ratio: 240 / 353; flex: none;
  border-radius: var(--r); border: 1px solid var(--line);
}
.pane-ref figcaption { min-width: 0; font-size: .76rem; line-height: 1.5; color: var(--grey); }
.pane-ref strong { color: var(--ink); font-weight: 600; }
@media (max-width: 640px) { .pane-ref img { width: 88px; } }

/* Spinners earn nothing on a square-footage guess and read as a stray control
   in a form that is otherwise flat. */
.wiz input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.wiz input[type="number"]::-webkit-outer-spin-button,
.wiz input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.wiz-error { color: var(--error); font-size: .84rem; margin-top: .4rem; min-height: 1.3em; }

/* The running estimate. A recessed band rather than a raised pane — it reports,
   it does not sell. Blue marks it as a callout per the brand book's supporting
   pair, and --blue-ink rather than --blue because small text on this ground
   needs the darkened tier to clear AA (4.6:1 on --panel-2). */
/* Block flow, not a two-column grid.
   The grid version put the label and the figure in one row and spanned the
   note beneath — which is fine until the note is two sentences long, at which
   point the whole thing reads as one grey paragraph with a number loose in
   it. Three stacked tiers, each with its own weight, is what makes the figure
   findable at a glance. */
.wiz-est {
  margin: 0 clamp(1.2rem, 3vw, 2.2rem) 1.3rem;
  padding: .95rem 1.15rem 1rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 2px solid var(--blue-ink);
  border-radius: var(--radius);
}
/* Label and figure share a baseline, so the eye lands on the number and the
   word only qualifies it. `gap` rather than a margin so the two never collide
   when the figure wraps to its own line on a narrow phone. */
.wiz-est-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .15rem .7rem; margin: 0;
}
.wiz-est-label {
  font-size: .6rem; font-weight: 600; letter-spacing: .22em;
  text-transform: uppercase; color: var(--blue-ink); white-space: nowrap;
}
.wiz-est-value {
  /* font-family repeated deliberately: this element carried no family of its
     own and inherited whatever the band sat in. Every other tier here states
     its own, and the figure is the one that must not silently fall back. */
  font-family: var(--font); font-stretch: var(--w-label); font-weight: 900;
  font-size: 1.4rem; line-height: 1.15; letter-spacing: -.01em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.wiz-est.is-call .wiz-est-value {
  font-size: .82rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
}
.wiz-est-note {
  font-family: var(--font);
  margin: .4rem 0 0; font-size: .78rem; line-height: 1.55; color: var(--grey);
  max-width: 46ch;
}
/* The exclusion. Darker than the note above it and separated by a hairline,
   because this is the line that changes what somebody pays and it must not
   read as more of the same reassurance. */
.wiz-est-excl {
  font-family: var(--font);
  margin: .55rem 0 0; padding-top: .55rem;
  border-top: 1px solid var(--line);
  font-size: .78rem; line-height: 1.5; font-weight: 600;
  color: var(--ink-soft); max-width: 46ch;
}

.wiz-nav {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  padding: 0 clamp(1.2rem, 3vw, 2.2rem) clamp(1.4rem, 3vw, 2rem);
}

/* The tall card control, used on the service and add-on steps. A chip is the
   right shape for "Morning / Afternoon" and the wrong one for a choice that
   sets the whole price — these get a line of copy and the full width, so the
   two steps that matter most do not look like the two that matter least. */
.pick-list { display: grid; gap: .6rem; }
.pick {
  display: block; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer;
  background: var(--panel); border: 1px solid var(--line);
  border-left: 2px solid transparent;
  border-radius: var(--radius); padding: .85rem 1.05rem;
  transition: border-color .18s var(--ease), background .18s var(--ease);
}
.pick:hover { border-color: var(--ink); }
/* Inverts, exactly as .opt does. A card carrying two tiers of text wants a
   softer selected state than a chip, and the first cut gave it one — a left
   accent bar on the same ground. Rendered next to the chips on the property
   step it was not readable as "chosen" at all, and a form that answers the
   same question in two different visual languages makes the weaker one look
   broken. Consistency beats the lighter touch here. */
.pick.is-sel { background: var(--ink); border-color: var(--ink); }
/* Flex-and-wrap rather than a block with an inline tag. The tag is nowrap by
   necessity — "QUOTED IN PERSON" broken across two lines is worse than no tag
   — so on a 320px card it has to be able to drop to its own line instead.
   Left inline it pushed the document 42px wider than the viewport. */
.pick-name {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .55rem;
  font-stretch: var(--w-label); font-weight: 600;
  font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink);
}
.pick-note { display: block; margin-top: .3rem; font-size: .8rem; line-height: 1.5; color: var(--grey); }
/* The count field that follows a counted add-on card (skylights, solar). A
   sibling in the pick-list, not a child — .pick is a <button> and an input
   inside a button is unfocusable. Indented and pulled toward its card so the
   pair reads as one choice; margin-bottom 0 hands spacing back to the list's
   own gap. */
.pick-count { margin: -.15rem 0 0; padding-left: 1.05rem; }
.pick-count input { max-width: 11rem; }
.pick.is-sel .pick-name { color: var(--paper); }
/* Not --grey: on the inverted card the note needs a light tier, and this one
   lands ~8:1 on the navy. */
.pick.is-sel .pick-note { color: rgba(243, 243, 243, .74); }
/* Says "quoted in person" on the card itself. Somebody who ticks hard-water
   removal and then watches the figure not move has been confused unless the
   card told them first. */
.pick-tag {
  padding: .12rem .45rem;
  font-size: .58rem; font-weight: 600; letter-spacing: .14em;
  color: var(--ink); background: var(--teal); border-radius: 2px;
  white-space: nowrap; vertical-align: middle;
}

/* The itemised estimate. Same mullion-hairline grid as the recap below it —
   this is a statement of parts, not a raised pane. */
.quote { border: 1px solid var(--line); background: var(--line); display: grid; gap: 1px; margin-bottom: 1rem; border-radius: var(--radius); overflow: hidden; }
/* Wraps, and the amount keeps its right edge when it does. A range set in
   tabular figures — "$1,803 – $2,243" — plus a label does not fit a 320px row,
   and the amount cannot be allowed to break mid-figure, so the row has to give
   instead: the amount drops to its own line and `margin-left: auto` holds it to
   the right rather than letting it fall back under the label. */
.quote-row {
  background: var(--panel); padding: .8rem 1.1rem;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem 1.2rem;
}
.quote-label { font-size: .9rem; color: var(--ink-soft); flex: 1 1 auto; min-width: 0; }
.quote-sub { display: block; font-size: .74rem; line-height: 1.45; color: var(--grey); margin-top: .25rem; }
.quote-amt {
  font-family: var(--font); font-variant-numeric: tabular-nums;
  font-weight: 600; font-size: .95rem; color: var(--ink); white-space: nowrap;
  margin-left: auto;
}
/* The total sits on the recessed ground so it reads as the sum of the panes
   above it rather than as one more line among them. */
.quote-total { background: var(--panel-2); }
.quote-total .quote-label {
  font-size: .6rem; font-weight: 600; letter-spacing: .22em;
  text-transform: uppercase; color: var(--blue-ink);
}
.quote-total .quote-amt { font-stretch: var(--w-label); font-weight: 900; font-size: 1.4rem; letter-spacing: -.01em; }
.quote-amt--call { font-size: .82rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
/* Same weight as .wiz-est-excl, for the same reason: the line that changes
   what somebody pays must not read like the reassurance beneath it. */
.quote-excl {
  margin: 0 0 .8rem; font-size: .78rem; line-height: 1.5;
  font-weight: 600; color: var(--ink-soft); max-width: 46ch;
}
.quote-foot { margin: 0 0 1.4rem; font-size: .78rem; line-height: 1.55; color: var(--grey); max-width: 46ch; }

/* confirm recap */
.recap { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); margin-bottom: 1.4rem; border-radius: var(--radius); overflow: hidden; }
.recap > div { background: var(--panel); padding: .85rem 1.1rem; display: grid; grid-template-columns: 130px 1fr; gap: 1rem; font-size: .9rem; }
.recap dt { font-size: .6rem; font-weight: 600; letter-spacing: .22em; text-transform: uppercase; color: var(--grey-dim); align-self: center; }
.recap dd { color: var(--ink-soft); }
/* The caveat travels with the figure, on the same row. A disclaimer parked at
   the foot of the recap is a disclaimer read after the number. */
.recap-note { display: block; font-size: .74rem; line-height: 1.5; color: var(--grey); margin-top: .35rem; }
/* Carries the same weight as .wiz-est-excl and for the same reason: this is the
   line that changes what somebody pays, and the confirm step is the last thing
   they read before submitting. Grey reassurance and a real exclusion must not
   look alike. */
.recap-excl {
  display: block; margin-top: .4rem; padding-top: .4rem;
  border-top: 1px solid var(--line);
  font-size: .74rem; line-height: 1.5; font-weight: 600; color: var(--ink-soft);
}
/* A fixed 130px label column leaves under 180px for the value on a phone, which
   sets the estimate's caveat in an eight-line ribbon. Stacked, the label reads
   as the placard it already looks like. */
@media (max-width: 560px) {
  .recap > div { grid-template-columns: 1fr; gap: .4rem; }
  .recap dt { align-self: start; }
}

/* success */
.wiz-done { text-align: center; padding: clamp(2rem, 6vw, 3.5rem) clamp(1.2rem, 3vw, 2.2rem); }
/* the brand's sparkle — navy mark on a teal bloom */
.wiz-done .glint {
  width: 44px; height: 44px; margin: 0 auto 1.4rem; color: var(--ink);
  border-radius: 50%;
  filter: drop-shadow(0 0 18px var(--teal)) drop-shadow(0 0 5px var(--teal));
}
.wiz-done h3 { font-stretch: var(--w-display); font-weight: 900; font-size: 1.7rem; letter-spacing: -.02em; text-transform: uppercase; color: var(--navy); margin-bottom: .7rem; }
.wiz-done p { color: var(--grey); max-width: 26rem; margin-inline: auto; }

.book-side { display: flex; flex-direction: column; gap: 1rem; }
.book-grid { display: grid; grid-template-columns: 1fr 300px; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
.book-side .placard { align-self: flex-start; }
.book-side p { color: var(--grey); font-size: .92rem; }
.book-side a { font-weight: 600; color: var(--ink); border-bottom: 1px solid var(--line-strong); }

/* The phone number, given the weight it earns. On a window-cleaning site the
   number is the highest-intent target on the page, and it used to be a link
   inside a sentence — the same visual weight as the email beside it. It is now
   a pane: the number set in the display tier, the instruction above it in the
   etched-label tier, the whole block one tap target.

   Descendant selector, not `.book-call` alone: `.book-side a` above is
   0,1,1 and would keep its underline against a single class. */
.book-side .book-call {
  display: block;
  padding: .9rem 1.1rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-left: 2px solid var(--blue-ink);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.book-side .book-call:hover { border-color: var(--ink); border-left-color: var(--ink); }
.book-call-label {
  display: block;
  font-size: .6rem; font-weight: 600; letter-spacing: .22em;
  text-transform: uppercase; color: var(--blue-ink);
}
.book-call-num {
  display: block; margin-top: .3rem;
  font-family: var(--font); font-stretch: var(--w-display); font-weight: 900;
  font-size: 1.75rem; line-height: 1.05; letter-spacing: -.02em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
/* The text pane: same shape as the call pane above it, quieter voice. Its
   action line stays out of the 1.75rem display tier — two lines that size,
   stacked, read as one control repeated rather than two ways in. */
.book-side .book-call--sms { margin-top: .8rem; }
.book-call-num--sms { font-size: 1.2rem; }
@media (max-width: 1000px) { .book-grid { grid-template-columns: 1fr; } }


/* ================ PORTED — REVIEWS CAROUSEL (pass 10) =================== */
/* The slider JS measures real card rects — .rev-track/--rev-per/--rev-gap
   are its contract. The `` scoping prefix is stripped: the
   redesign has one skin, not a base plus an override. */
.rev { position: relative; }

/* THE TRACK.
   min-width: 0 is load-bearing and the reason is documented in CLAUDE.md §11:
   `overflow-x: auto` DOES NOT let a box shrink on its own. A flex or grid item
   defaults to min-width:auto — never narrower than its own content — so a
   non-wrapping row of cards propagates its full min-content width up the
   ancestor chain and makes the whole PAGE wider than the phone. This is the
   exact bug that once made the document 524px wide on a 320px viewport. */
.rev-track {
  display: flex;
  gap: var(--rev-gap, 1rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;   /* don't hand the swipe back to the page */
  scroll-snap-type: x mandatory;
  min-width: 0;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* the page must still scroll vertically under a thumb that starts on a card */
  touch-action: pan-y;
  padding-bottom: 2px;              /* room for the focus ring */
}
.rev-track::-webkit-scrollbar { display: none; }
.rev-track:focus-visible { outline: 2px solid var(--blue-ink); outline-offset: 4px; }

/* Cards per page is a token, and the width is derived from it rather than
   typed as a percentage: `flex-basis: 33.333%` plus two gaps overflows the
   track by exactly two gaps, which puts every snap point slightly off the
   page boundary. Subtracting the gaps first makes N cards + N-1 gaps come to
   precisely 100%. Skins set --rev-per; nothing else has to change. */
.rev-track { --rev-per: 3; }
.rev-card {
  flex: 0 0 calc((100% - (var(--rev-per) - 1) * var(--rev-gap, 1rem)) / var(--rev-per));
  scroll-snap-align: start;
  min-width: 0;                      /* same trap, one level down */
  display: flex; flex-direction: column; gap: .9rem;
}

/* Stars are text, not an icon set — the brand book has no icon language and
   five glyphs cost nothing. aria-label on the element carries the real value. */
.rev-stars { letter-spacing: .18em; font-size: .95rem; line-height: 1; }

.rev-card blockquote { margin: 0; }
.rev-card figcaption { margin-top: auto; display: flex; flex-direction: column; gap: .15rem; }
.rev-name {
  font-size: .68rem; font-weight: 600; letter-spacing: .2em; text-transform: uppercase;
}
.rev-meta { font-size: .74rem; }

/* ── controls ────────────────────────────────────────────────────────────── */
.rev-ui { display: flex; align-items: center; gap: 1rem; margin-top: 1.6rem; }
.rev.is-static .rev-ui { display: none; }   /* everything already fits */

.rev-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; flex: 0 0 auto;
  border-radius: 999px; border: 1px solid var(--line-strong);
  background: transparent; color: var(--ink);
  font-size: 1rem; line-height: 1;
  transition: border-color .25s var(--ease), background .25s var(--ease), opacity .25s var(--ease);
}
.rev-btn:hover:not(:disabled) { border-color: var(--blue-ink); color: var(--blue-ink); }
.rev-btn:disabled { opacity: .3; cursor: default; }

/* THE BUTTON IS THE TAP TARGET AND THE DOT IS DRAWN INSIDE IT.
   This replaced an 8px button whose hit area was grown by a 44px ::after. That
   reached the floor on paper and was wrong in the hand: the dots sit 16px
   apart, so 44px hit boxes overlapped their neighbours by ~28px and the last
   one in the DOM won every contested point. Tapping the middle of three dots
   measurably jumped to page 3 — elementFromPoint at dot 2's own centre did not
   return dot 2.

   Sizing the button itself and tiling the row with no gap makes each target
   exactly its own slot: no overlap, nothing to arbitrate, and the visual dot
   is unchanged because it moved to ::before. 44px tall for the thumb; 32px
   wide keeps five pages of dots inside a 320px screen alongside the arrows. */
.rev-dots { display: flex; align-items: center; gap: 0; }
.rev-dot {
  width: 32px; height: 44px; padding: 0; border: 0; background: none;
  display: grid; place-items: center; cursor: pointer;
}
.rev-dot::before {
  content: ''; display: block;
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--platinum);
  transition: background .25s var(--ease), width .25s var(--ease);
}
.rev-dot.is-on::before { width: 26px; background: var(--blue-ink); }

/* the sample-content marker — this is placeholder copy and must never be
   mistakable for real customer feedback */
.rev-sample {
  display: inline-flex; align-items: center; gap: .5em;
  font-size: .58rem; font-weight: 600; letter-spacing: .2em; text-transform: uppercase;
  padding: .5em .9em; border-radius: 999px;
  background: rgba(168, 71, 61, .1); color: #8C3A31;
  border: 1px dashed rgba(168, 71, 61, .45);
  margin-bottom: 1.2rem;
}

/* One card at a time on a phone, whatever the skin asked for on desktop. */
@media (max-width: 900px) { .rev-track { --rev-per: var(--rev-per-sm, 1); } }

@media (prefers-reduced-motion: reduce) {
  .rev-track { scroll-behavior: auto; }
  /* ::before, because that is where the width/background transition lives now
     — the button itself no longer animates anything. */
  .rev-dot::before { transition: none; }
}
.rev-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: .4rem 1.1rem; margin-top: .9rem; margin-bottom: clamp(1.6rem, 3vw, 2.4rem);
}
.rev-score {
  font-stretch: var(--w-display); font-weight: 900;
  font-size: 2.4rem; line-height: 1; letter-spacing: -.02em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
/* Colour comes from .gstars (Google yellow) so the header, the cards and the
   hero sticker all say the same thing — the value lives in the aria-label. */
.rev-score-stars { letter-spacing: .14em; font-size: 1.1rem; }
.rev-count {
  font-size: .68rem; font-weight: 600; letter-spacing: .2em; text-transform: uppercase;
  color: var(--grey-dim);
}

/* ── read all / leave a review ─────────────────────────────────────────────
   Both open Google, so both are marked external. "Leave a review" is the solid
   one: the business needs more reviews far more than the visitor needs to read
   the rest of them. */
.rev-cta {
  display: flex; flex-wrap: wrap; gap: .8rem; justify-content: center;
  margin-top: clamp(1.8rem, 3.5vw, 2.6rem);
}
.rev-track { --rev-per: 3; --rev-gap: clamp(.9rem, 2vw, 1.4rem); }
@media (max-width: 1100px) { .rev-track { --rev-per: 2; } }

/* Cards in a carousel are set by their neighbours, not their content: without
   a floor, a two-line review and a six-line one make the row ragged as it
   pages. Equal heights come free from the flex stretch; this just stops the
   shortest page from collapsing. */
.rev-card { min-height: 15rem; }
@media (max-width: 900px) { .rev-card { min-height: 0; } }

.rev-card {
  background: #F7FAFD;
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: clamp(1.3rem, 2.4vw, 1.8rem);
  box-shadow: var(--soft);
}
.rev-stars { color: var(--gyellow); }
.rev-card blockquote {
  font-size: .95rem; line-height: 1.65; color: var(--grey);
}
.rev-name { color: var(--ink); }
.rev-meta { color: var(--grey-dim); }

/* a hairline between the quote and the attribution, so the eye can find the
   name without it needing a heavier weight than the quote itself */
.rev-card figcaption {
  padding-top: .9rem; border-top: 1px solid var(--line);
}

.rev-ui { justify-content: flex-start; }
.rev-btn { border-radius: 999px; }

/* ================= PORTED — MOBILE FLOORS (pass 10) ===================== */
/* Measured at 320/360/390/430, not eyeballed. Selectors for retired
   components (.placard, .stat-title, .cta-bar…) are inert, kept to keep
   the block byte-comparable with its pass-10 source. */
@media (max-width: 640px) {
  .placard   { font-size: .75rem; }
  .eyebrow   { font-size: .75rem; }
  .stat-title { font-size: .75rem; }
  .ba-copy .ba-note { font-size: .75rem; }   /* two classes: the base rule is .ba-copy .ba-note */
  .wiz-est-label    { font-size: .75rem; }
  /* The new wizard controls, held to the same floor. .quote-sub and .pick-note
     are lowercase body text, not letterspaced caps, so they get the floor
     exactly rather than the caps tier's .75rem. */
  .quote-total .quote-label { font-size: .75rem; }
  .pick-tag  { font-size: .75rem; letter-spacing: .1em; }
  .quote-sub { font-size: .75rem; }
  .pick-name { font-size: .82rem; }
  .ba .tag   { font-size: .75rem; letter-spacing: .16em; }  /* over a photo, so it also gets tighter */
  .town      { font-size: .75rem; }
  .foot-bottom span { font-size: .72rem; }   /* copyright line — the one place 12px is not worth the width */

  /* ---- components the Front Range restyle brought in ------------------- */
  /* Each of these was written against the desktop scale and never met the
     floor. Found by measuring at 320/360/390/430, not by eye — at 9.6px the
     proof captions still *look* deliberate in a screenshot and are simply
     small in the hand. */
  .ba-item figcaption, .proof-row figcaption { font-size: .75rem; }
  .field label      { font-size: .75rem; }   /* NAME / PHONE / EMAIL in the wizard */
  .group-label      { font-size: .75rem; }   /* WHICH TOWN? / STORIES / TIME */
  .recap dt         { font-size: .75rem; }   /* PROPERTY / SERVICES / CONTACT */
  /* The carousel's floors, which pass 10 kept after its theme block and the
     pass-11 port lost: measured 10.9 / 10.9 / 11.8px before this. */
  .rev-name, .rev-count { font-size: .75rem; }
  .rev-meta         { font-size: .78rem; }
  .foot-grid h4     { font-size: .75rem; }   /* CONTACT / SERVICE AREA column heads */
  .card-cta         { font-size: .75rem; }   /* "ADD TO MY QUOTE →" on all five cards */

  /* The review carousel's floors are NOT here — see the foot of this file.
     They were written here first and never applied: the Front Range theme is
     appended at the end and wins by cascade rather than specificity, so
     `.rev-name` at line ~1913 overrode this block's identical-specificity rule
     from 400 lines earlier. It measured as broken the moment real reviews
     rendered. Anything targeting a component the theme defines has to sit
     after the theme, not in here. */

  /* ---- the wizard's own nav row --------------------------------------- */
  /* "SHOW MY ESTIMATE →" broke onto two lines inside the pill on every phone
     width. The button was sized to its content while .btn's generous 1.9em
     side padding and .14em tracking ate the row, so the label had nowhere to
     go. Letting the primary claim the leftover width — and trimming the
     padding and tracking that were tuned for desktop — puts it back on one
     line without dropping below the 12px floor.

     Back keeps its natural width and does not grow: a ghost button as wide as
     the primary reads as two equal choices, and it is not one. */
  .wiz-nav { gap: .55rem; }
  .wiz-nav .btn { padding-inline: 1.15em; letter-spacing: .09em; }
  .wiz-nav .btn:not(.btn--ghost) { flex: 1 1 auto; }

  /* ---- 44px tap floor -------------------------------------------------- */
  /* Height comes from min-height + centring rather than extra padding, so the
     visual weight of these controls does not change — only the area a thumb
     can land on. */
  /* 46 rather than 44: these resolve to fractional heights, and a control that
     measures 43.98px is a control that misses the floor. */
  .btn, .card-cta, .opt, .wiz-pill, .faq-list summary, .skip {
    min-height: 46px;
  }

  /* Phone and email inside prose — "call or text (970) 379-2270 or write to…" —
     were 17px tall, and on a window-cleaning site the phone number is the
     highest-intent target on the page. They cannot be given a 44px box without
     wrecking the paragraph, so they get vertical padding instead: on an INLINE
     element that expands the hit area (hit-testing uses the border box) while
     the line box, and therefore the layout, is untouched. */
  /* inline-block, so the padding grows the line box and the two targets are
     genuinely separated. Left inline, their 44px hit boxes would overlap each
     other in a 27px line rhythm and the mis-tap would just move. */
  /* :not(.book-call) — the call pane is already a block with its own padding
     and comfortably over the floor. Left in, this rule's higher specificity
     (0,2,1 against 0,2,0) would flip it back to inline-block and overwrite its
     vertical padding. */
  .book-side a[href^="tel:"]:not(.book-call), .book-side a[href^="mailto:"] {
    display: inline-block; padding-block: .65rem;
  }
  .book-call-label { font-size: .75rem; }
  /* The footer's are already display:block, so they take a height directly. */
  footer .foot-grid a { min-height: 44px; display: flex; align-items: center; }
  .opt, .btn { display: inline-flex; align-items: center; justify-content: center; }

  /* The step pills were 16px tall and are real back-navigation. They get the
     tap area without becoming buttons visually — the row stays a progress
     indicator that happens to be operable. */
  .wiz-pill {
    font-size: .75rem;
    display: inline-flex; align-items: center;
    padding-block: .5rem;
    flex: 0 0 auto;                      /* never shrink into an ellipsis */
    scroll-snap-align: start;
  }

  /* ONE ROW THAT SCROLLS, NOT A WRAPPED GRID.
     Giving the pills a 44px tap floor pushed five of them onto three rows, and
     the step header went from a strip to 207px — 25% of the viewport spent
     before the customer reads the first question. Measured, not guessed.
     A horizontal strip keeps every step tappable (they are the back-navigation)
     at one row's cost; js/main.js scrolls the active one into view. */
  .wiz-pills {
    gap: .9rem;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    min-width: 0;
  }
  .wiz-pills::-webkit-scrollbar { display: none; }

  /* overflow-x: auto DOES NOT SHRINK THE BOX ON ITS OWN. A flex or grid item
     defaults to min-width:auto, i.e. never narrower than its own content — so
     the non-wrapping pill row propagated its ~460px min-content width up
     through .wiz-top, .wiz and .book-grid and made the whole PAGE 524px wide
     on a 320px phone. The scroll container only works once the chain of
     ancestors is allowed to be narrower than what it holds. */
  .wiz, .wiz-top { min-width: 0; }

  /* "Add to my quote" was a 21px-tall line of text at the bottom of a card —
     the primary conversion control on the page, and the hardest thing on it to
     hit. */
  .card-cta { display: inline-flex; align-items: center; }

  /* The nav lockup is a link and was a 23px target. */
  .brand { display: inline-flex; align-items: center; min-height: 44px; }

  /* The sticky bar is the last CTA a thumb reaches; it should not be the
     smallest text on screen. */
  .cta-bar .btn { font-size: .75rem; }
}

/* At 320 the wizard's button row is genuinely out of room: Back is already at
   its minimum width and "SHOW MY ESTIMATE →" still needs ~15px more than the
   leftover space. Rather than shave the type under the 12px floor for the
   narrowest phones, the row wraps and the primary takes a full-width line of
   its own beneath Back — which is the better mobile shape anyway.

   ITS OWN QUERY, AFTER THE 640 BLOCK CLOSES — not nested inside it. Written
   as a second `@media` in the middle of that block, the stray brace closed it
   early and silently demoted every rule below (the whole 44px tap floor, the
   pill and CTA-bar type sizes) to phones 340px and under. 320 audited clean
   while 360 and 390 sprouted twenty violations, which is a confusing shape of
   bug to read: the NARROW width was the correct one. */
@media (max-width: 340px) {
  .wiz-nav { flex-wrap: wrap; }
  .wiz-nav .btn:not(.btn--ghost) { flex: 1 0 100%; }
}


/* ======================= SITE MOBILE + REDUCED MOTION ===================== */
@media (max-width: 940px) {
  .nav-links, .nav-tel { display: none; }
  .burger { display: inline-flex; }
  .nav { height: 58px; top: 10px; left: 10px; right: 10px; }
}
@media (max-width: 640px) {
  .hero { padding-top: calc(var(--nav-h) + 2.5rem); }
  .hero-ctas .btn { width: 100%; }
  .nav-brand img { height: 26px; }
  .nav .btn { font-size: .75rem; padding-inline: 1.1em; letter-spacing: .1em; }
}
/* Under 480px the lockup, the CTA pill and the burger cannot share the bar:
   at 390 the three need ~390px of a 341px pill. The lockup gives way to its
   own squeegee mark — cropped out of the same image with object-fit, so no
   extra request — and the CTA stays on screen at every width (owner
   directive). The hero shows the full lockup a few pixels below anyway. */
@media (max-width: 480px) {
  .nav-brand { padding-inline: 8px; margin-left: -8px; }   /* 44px tap width around a 28px mark */
  .nav-brand img { width: 28px; height: 26px; object-fit: cover; object-position: left center; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-vid { display: none; }
  .hero-bloom, .hero-sparkle { display: none; }
  .hero-word { animation: none; clip-path: none; }
  .hero-mark-fly, .hero-mark { animation: none; transform: none; }
  .reveal { opacity: 1; translate: none; transition: none; }
  .tile img { transition: none; }
}
