/* ============================================================
   Components. Reads tokens from site.css — defines NO colours of its own.
   ============================================================ */

/* --- REVIEW WALL ------------------------------------------- */
.rw-sub{max-width:56ch; margin-bottom:var(--s4)}

.rw-grid{
  /* Explicit columns, balanced at BUILD TIME (scripts/build_review_wall.py).
     CSS `columns` sizes cards to their content but fills column 1 before starting
     column 2, so the last column ends up carrying 2-3 extra cards. We assign each
     card to the shortest column ourselves and render real column divs. */
  display:flex; gap:var(--s3); align-items:flex-start;
}
.rw-col{flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:var(--s3)}
@media (max-width:1000px){ .rw-grid{flex-wrap:wrap} .rw-col{flex:1 1 300px} }
@media (max-width:640px){ .rw-col{flex:1 1 100%} }



.rw-card{
  background:var(--white); border:1px solid var(--line);
  border-radius:0; padding:var(--s3);
  margin:0;
}

.rw-head{display:flex; align-items:center; gap:var(--s2)}

.rw-av{
  width:48px; height:48px; border-radius:0; object-fit:cover; flex:0 0 48px;
  background:var(--cream2);
}
.rw-av--txt{
  display:grid; place-items:center;
  font-weight:700; font-size:var(--step--1); color:var(--char);
  /* letter-spacing on initials, but never a negative inline-size */
  letter-spacing:.02em;
}

.rw-name{margin:0; font-weight:700; line-height:1.3}
.rw-meta{margin:0; font-size:var(--step--1); color:var(--mut); display:flex; gap:var(--s1); align-items:baseline}
.rw-stars{color:var(--orange-d); letter-spacing:.06em}

.rw-text{margin:0; line-height:1.6}
.rw-text strong{font-weight:700; color:var(--char)}

.rw-shots{display:flex; gap:var(--s1); flex-wrap:wrap}
.rw-shots img{
  width:120px; height:160px; border-radius:0; object-fit:cover;
  cursor:zoom-in; transition:opacity .12s ease;
  background:var(--cream2);
  /* If the image 404s, the alt text must not overflow the box and wreck the card.
     A broken image degrades to a small grey tile. */
  font-size:0; color:transparent; overflow:hidden;
}
.rw-av{font-size:0; color:transparent; overflow:hidden}

.rw-reply{
  border-left:3px solid var(--orange);   /* fill/border use — correct token */
  padding:var(--s1) 0 var(--s1) var(--s2);
  margin-top:auto;
}
.rw-reply-tag{
  display:block; font-size:var(--step--1); font-weight:700;
  color:var(--mut); margin-bottom:.15rem;
}
.rw-reply p{margin:0; font-size:var(--step--1); color:var(--mut); line-height:1.55}

/* --- HERO ---------------------------------------------------- */
.hero{
  position:relative; isolation:isolate;
  background:var(--char); color:var(--cream);
  padding-block:var(--s6) var(--s7);
  overflow:hidden;
}
.hero h1{
  color:var(--cream); margin-bottom:var(--s3);
  /* Four lines at EVERY width, each breaking at a different point — that ragged stack
     is what reads as "indentations". The cause was max-width:16ch, not the font size:
     the heading sat in a 587px column but was capped at roughly 16 characters, so it
     broke into 3-4 word lines regardless of how much room it had.

     Raising the cap was necessary but not sufficient: the H1 then filled its 587px
     column and the OLD headline still needed 1660px on one line, so four lines was
     arithmetic, not a CSS bug. The headline was shortened too — see build_hero.py. */
  font-size:clamp(1.9rem, 1.3rem + 1.8vw, 2.8rem); line-height:1.06;
  max-width:22ch; text-wrap:balance;
}
.hero-bg{position:absolute; inset:0; z-index:-2}
.hero-bg img{width:100%; height:100%; object-fit:cover; display:block}
/* Scrim: the photo must never cost us contrast. Measured against --cream text below. */
.hero::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(100deg,
    rgba(27,24,19,.88) 0%, rgba(27,24,19,.72) 42%, rgba(27,24,19,.34) 100%);
}
.hero-in{position:relative}

.hero-lede{font-size:var(--step-1); line-height:1.5; color:var(--cream2); max-width:36ch; margin-bottom:var(--s4)}
.hero-grid{display:grid; gap:var(--s4); align-items:start}
@media (min-width:900px){ .hero-grid{grid-template-columns:1.05fr .95fr} }

.hero-call{
  display:inline-flex; align-items:center; gap:.5rem; margin-top:var(--s2);
  color:var(--cream); font-size:var(--step-1); font-weight:700; text-decoration:none;
}
.hero-call:hover{color:var(--cream2)}

/* --- QUOTE FORM ---------------------------------------------- */
.qf{
  background:var(--white); color:var(--ink);
  border-radius:0; padding:var(--s4);
  border-left:5px solid var(--orange);      /* the reference's signature accent */
  box-shadow:var(--shadow);
}
.qf h2{font-size:var(--step-2); margin-bottom:.35rem}
.qf-sub{color:var(--mut); font-size:var(--step--1); margin-bottom:var(--s3)}
.qf-row{display:grid; gap:var(--s2)}
@media (min-width:520px){ .qf-row--2{grid-template-columns:1fr 1fr} }
.qf-field{display:block; margin-bottom:var(--s2)}
.qf-field > span{
  display:block; font-weight:800; font-size:var(--step--1);
  letter-spacing:.05em; text-transform:uppercase; color:var(--mut);
  margin-bottom:.35rem;
}
.qf-field input, .qf-field select, .qf-field textarea{
  width:100%; padding:.7em .75em; font:var(--step-0)/1.4 var(--font);
  border:1px solid var(--line); border-radius:0;
  background:var(--white); color:var(--ink);
}
.qf-field input:focus, .qf-field select:focus, .qf-field textarea:focus{
  outline:3px solid var(--orange); outline-offset:1px; border-color:var(--orange);
}
.qf-field textarea{min-height:5.5em; resize:vertical}
.qf-hp{position:absolute; left:-9999px}    /* honeypot, not a captcha */
.qf-note{font-size:var(--step--1); color:var(--mut); margin-top:var(--s2)}
.qf-fallback{
  margin-top:var(--s2); padding:var(--s2); border-left:3px solid var(--orange);
  background:var(--cream2); font-size:var(--step--1);
}

/* --- OBJECTIONS ---------------------------------------------- */
.obj-grid{display:grid; gap:var(--s3); margin-top:var(--s4)}
@media (min-width:760px){ .obj-grid{grid-template-columns:repeat(2,1fr)} }
.obj{
  background:var(--white); border:1px solid var(--line);
  border-radius:0; padding:var(--s3);
}
.obj-fear{
  display:flex; gap:.6rem; align-items:baseline;
  font-weight:800; color:var(--orange-d); margin:0 0 .4rem;
}
.obj-stat{font-size:var(--step--1); color:var(--mut); font-weight:600; margin:0 0 var(--s2)}
.obj-answer{margin:0 0 var(--s2)}
.obj-proof{
  border-left:3px solid var(--orange-d); padding-left:var(--s2);
  font-size:var(--step--1); color:var(--mut); font-style:italic; margin:0;
}

/* --- city / content pages ------------------------------------ */
.stack ul{margin:0 0 var(--s2); padding-left:1.35em}
.stack li{margin-bottom:.45em}
.stack h2{margin-top:var(--s4)}
.city-list{
  list-style:none; padding:0; margin:var(--s2) 0 0;
  display:grid; gap:var(--s1) var(--s3);
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
}
.city-list li{margin:0}
.city-quote{max-width:640px; margin-inline:auto}
.benefits{columns:2; column-gap:var(--s4); padding-left:1.35em}
@media (max-width:700px){ .benefits{columns:1} }
.benefits li{margin-bottom:.5em; break-inside:avoid}
.faq{border-bottom:1px solid var(--line); padding:var(--s2) 0}
.faq summary{cursor:pointer; font-weight:700; font-size:var(--step-1); color:var(--char)}
.faq summary::marker{color:var(--orange-d)}
.faq > div{padding-top:var(--s2); color:var(--mut)}

/* --- SITE HEADER / NAV --------------------------------------- */
.site-head{
  background:var(--ink); color:var(--cream);
  position:sticky; top:0; z-index:50;
  /* the reference defines its header with a 3px accent rule; his is the brand tan */
  border-bottom:3px solid var(--tan);
}
.site-head-in{display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; padding-block:.85rem}
.brand{
  /* His real logo: a distressed stencil wordmark, GROUNDS in the brand khaki and
     CREW in white. Designed for DARK grounds — the white half would vanish on cream,
     so it only ever sits on --ink or --char. */
  display:block; margin-right:auto; line-height:0;
}
.brand img{width:150px; height:auto; display:block}
@media (max-width:700px){ .brand img{width:130px} }
.brand:hover{opacity:.85}
.nav-list{display:flex; gap:var(--s3); list-style:none; margin:0; padding:0; flex-wrap:wrap}
.nav-list a{
  color:var(--cream2); text-decoration:none; font-weight:700;
  font-size:var(--step--1); padding-block:.35rem;
  border-bottom:2px solid transparent;
}
.nav-list a:hover,.nav-list a[aria-current]{color:var(--white); text-decoration:underline}
.head-call{
  background:var(--tan); color:var(--ink); text-decoration:none;
  margin-left:var(--s2);
  padding:.5em 1em; border-radius:0; font-weight:700; font-size:var(--step--1);
  white-space:nowrap;
}
.head-call:hover{background:var(--orange-d); color:var(--cream)}
@media (max-width:700px){
  .site-head{position:static}
  .site-head-in{flex-wrap:nowrap; gap:var(--s2)}
  .brand{margin-right:auto; width:auto; flex:0 0 auto}
  /* Nav scrolls on ONE line rather than wrapping. Wrapping made the header 194px —
     a quarter of a 390px viewport — before any content. No JS, nothing hidden. */
  /* A scrolling child needs min-width:0 on ITSELF AND its flex parent. Flex items
     default to min-width:auto — "at least as wide as my content" — so without this
     the nav pushed the whole page wide instead of scrolling. Caught by
     assert-no-hscroll at 8 widths. */
  .site-head-in{min-width:0}
  /* Superseded 2026-09-16: this was overflow-x:auto — a one-line scroll strip.
     The nav is now a hamburger (see .nav-burger), so it is a fixed-width button
     and never a scroller. The strip's min-width:0 dance is gone with it. */
  .site-head nav{flex:0 0 auto; min-width:0}
  .nav-list{gap:var(--s2)}
  .head-call{flex:0 0 auto; margin-left:var(--s1); padding:.45em .7em}
  /* Owner 2026-09-16: phone number and hamburger swap places — burger on the far
     right. Done with `order`, NOT by moving the markup: the checkbox toggles the
     menu through a sibling selector (.nav-toggle:checked ~ .nav-list), which
     depends on the SOURCE order. Reordering the HTML would break the toggle;
     `order` moves only the paint. The panel is pinned left:0/right:0 so it still
     spans the header wherever the button sits. */
     The flex children of .site-head-in are .brand, <nav> and .head-call — the
     burger lives INSIDE <nav>, so it is the <nav> that gets ordered, not the
     label. */
  .brand{order:1}
  .head-call{order:2}
  .site-head nav[aria-label="Main"]{order:3}
}
@media (max-width:520px){
  /* The nav no longer needs its own row: it is a 44px button, so logo, burger and
     call button all fit one line. Kept as a single line down to 320px. */
  .site-head-in{flex-wrap:nowrap}
}
@media (max-width:374px){
  /* Measured, not guessed: at 320px the row wanted 364px and pushed the PAGE wide
     (assert-no-hscroll, 3 of 18 widths). Logo 130->104px and a tighter call button
     bring it inside 320. The number stays VISIBLE — the mobile audit named a
     tappable phone number in the first screen as one of this site's strengths, so
     hiding it would trade one defect for a worse one. */
  .brand img{width:104px}
  .site-head-in{gap:var(--s1)}
  /* A literal rem, NOT var(--step--2): that variable does not exist (the scale in
     site.css stops at --step--1). An undefined custom property is invalid at
     computed-value time, so the declaration does not fall back to the previous
     rule — it INHERITS, which silently shipped 16px here and cost a measure-fix
     cycle. Undefined var = inherited, never "ignored". */
  .head-call{padding:.45em .5em; font-size:.75rem; margin-left:0}
  .nav-burger{padding:.55rem .35rem}
}

/* --- BREADCRUMBS --------------------------------------------- */
.crumbs{background:var(--cream2); font-size:var(--step--1)}
.crumbs ol{display:flex; flex-wrap:wrap; gap:.4rem; list-style:none; margin:0; padding:.7rem 0}
.crumbs li+li::before{content:"›"; margin-right:.4rem; color:var(--mut)}
.crumbs a{color:var(--orange-d)}
.crumbs span{color:var(--mut)}

/* --- GALLERY -------------------------------------------------- */
.g-grid{
  display:grid; gap:var(--s3); margin-top:var(--s3);
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
}
/* before/after composites are ~2:1 — give them more room so the pair reads.
   Sources vary (1600x1200, 480x480 squeezed pairs), so pin a shared ratio and crop:
   without it a short tile leaves a blank band under the image inside its card. */
.g-grid--ba{grid-template-columns:repeat(auto-fill,minmax(min(340px,100%),1fr))}
.g-grid--ba .g-item img{aspect-ratio:4/3; object-fit:cover; object-position:center}
.g-grid--vid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}

.g-item{
  margin:0; border-radius:0; overflow:hidden;
  background:var(--cream2); border:1px solid var(--line);
  /* a broken image must not spill alt text across the layout */
  font-size:0; color:transparent;
}
.g-item img{width:100%; height:auto; display:block}
.g-single img,.g-video img{aspect-ratio:3/4; object-fit:cover}
.g-wide img{aspect-ratio:1/1; object-fit:cover}

.g-video{position:relative}
.g-video figcaption{
  position:absolute; right:.4rem; bottom:.4rem;
  background:rgba(26,22,20,.78); color:var(--white);
  font-size:.72rem; padding:.15em .5em; border-radius:0;
}

/* --- article figure ------------------------------------------- */
.art-fig{margin:var(--s3) 0; border-radius:0; overflow:hidden; background:var(--cream2)}
.art-fig img{width:100%; height:auto; display:block; aspect-ratio:4/3; object-fit:cover}
.art-fig figcaption{padding:var(--s1) var(--s2); font-size:var(--step--1); color:var(--mut)}


/* --- HOW WE WORK (promises) ---------------------------------- */
.pr-grid{
  display:grid; gap:var(--s3); margin-top:var(--s4);
  grid-template-columns:1fr; align-items:start;
}
@media (min-width:700px){ .pr-grid{grid-template-columns:1fr 1fr} }   /* 2x2 */
.pr-card{
  background:var(--white); border:1px solid var(--line);
  border-radius:0; padding:var(--s3);
}
.pr-card h3{font-size:var(--step-1); margin-bottom:.5rem; line-height:1.2}
.pr-card p{margin:0; color:var(--ink)}
.pr-proof{
  margin-top:var(--s2)!important; padding-left:var(--s2);
  border-left:3px solid var(--orange);
  font-size:var(--step--1); font-style:italic; color:var(--mut)!important;
}

/* --- SERVICE AREA CARDS -------------------------------------- */
.area-grid{
  display:grid; gap:var(--s3); margin-top:var(--s4);
  grid-template-columns:repeat(auto-fill,minmax(240px,1fr));
}
.area-card{
  display:block; text-decoration:none; color:inherit;
  background:var(--white); border:1px solid var(--line);
  border-radius:0; overflow:hidden;
  transition:transform .12s ease, box-shadow .12s ease;
}
.area-card:hover{transform:translateY(-2px); box-shadow:var(--shadow); color:inherit}
.area-card img{width:100%; aspect-ratio:4/3; object-fit:cover; display:block}
.area-body{padding:var(--s3)}
.area-body h3{font-size:var(--step-1); margin-bottom:.15rem}
.area-county{margin:0 0 .5rem; font-size:var(--step--1); color:var(--mut);
  text-transform:uppercase; letter-spacing:.05em; font-weight:700}
.area-blurb{margin:0 0 var(--s2); font-size:var(--step--1); line-height:1.5; color:var(--ink)}
.area-go{font-size:var(--step--1); font-weight:800; color:var(--orange-d)}

/* --- BEFORE/AFTER BAND ---------------------------------------- */
.band{background:var(--cream2); padding-block:var(--s4)}
.band-grid{display:grid; gap:var(--s2); grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.band-grid img{
  width:100%; aspect-ratio:4/3; object-fit:cover;
  border-radius:0; border:1px solid var(--line);
}
.band-note{margin-top:var(--s2); font-size:var(--step--1); color:var(--mut); text-align:center}

.rw-shots img:hover{opacity:.85}

/* --- LIGHTBOX (native <dialog>, no library) -------------------- */
.lb{
  border:0; padding:0; background:transparent;
  max-width:96vw; max-height:96vh; width:auto;
}
.lb::backdrop{background:rgba(27,24,19,.92)}
.lb img{max-width:96vw; max-height:88vh; width:auto; height:auto; display:block}
.lb-close{
  position:absolute; top:.5rem; right:.5rem;
  background:var(--orange); color:var(--ink); border:0;
  font:800 1rem/1 var(--font); padding:.6em .9em; cursor:pointer;
}
.lb-cap{
  color:var(--cream); font-size:var(--step--1); text-align:center;
  padding:var(--s2) var(--s2) 0;
}

/* --- REASSURANCE STRIP (directly under the hero) -------------- */
.strip{background:var(--ink); color:var(--cream); padding-block:var(--s2)}
.strip-list{
  /* Four items on ONE row at desktop. Measured: uppercase + .04em letter-spacing
     inflated the items to 1267px inside a 1150px container, so the row wrapped and
     orphaned the fourth. Fixed by tightening the TYPE, not by shortening the copy
     further — the pricing line is the strongest thing here and should not be trimmed
     to fit a layout. */
  display:flex; flex-wrap:wrap; gap:var(--s2);
  list-style:none; margin:0; padding:0;
  justify-content:center; align-items:center;
}
@media (min-width:1260px){
  /* FOUR items again (the client's $1M insurance line came back in 2026-09-14), and
     they need 1174px in a 1150px wrap — over by 24px, less than the page gutter. The
     content is not too big; the container is fractionally too small. Same call as the
     first time this happened: give this one row the full width rather than shrink the
     type or cut a claim to fit a number. Three items fit normally and this rule is
     harmless for them.

     🔴 BREAKPOINT MEASURED, NOT GUESSED. The first version used min-width:1000px and the
     no-horizontal-scroll gate failed at 1180px: nowrap forced four items totalling ~1174px
     plus gaps onto one row inside a 1180px viewport, so an <li> reached 1201px. Between
     1000 and ~1240 there is genuinely not room. 1260px is above the point where they fit,
     so below it they wrap normally instead of overflowing. */
  .strip .wrap{max-width:none; padding-inline:var(--s3)}
  .strip-list{flex-wrap:nowrap}
}
.strip-list li{
  /* clamp: shrinks just enough to hold one row at 1150px, never below 12px. */
  font-size:clamp(.72rem, .62rem + .35vw, .8125rem);
  font-weight:700; letter-spacing:.02em; text-transform:uppercase;
  white-space:nowrap;          /* never break a claim mid-phrase */
}
@media (max-width:1150px){
  /* Below the point where four fit, let them wrap — but as whole phrases. */
  .strip-list li{white-space:normal}
}
.strip-list li::before{content:"\2713"; color:var(--tan); margin-right:.35em; font-weight:900}

/* --- PROMISE CARDS: real photos, not glyphs ------------------- */
.pr-card{
  /* Deliberately overrides the padded .pr-card above: a promise card whose top edge is
     a PHOTO must be edge-to-edge, or the image floats in a box of white. */
  padding:0; overflow:hidden;
}
.pr-photo{
  width:100%; display:block; border-radius:0; background:var(--ink);
  /* No fixed ratio at all. These are labelled BEFORE/AFTER composites of differing
     shapes: cropping cuts the label strip off the bottom, and `contain` in a fixed box
     pillarboxes with black bars. Let each image render at its natural size — the grid
     tolerates uneven card heights. */
  height:auto;
}
.pr-body{padding:var(--s3)}

/* Promise card carrying video: match the photo tiles' framing exactly so the
   card reads as one of the set, not as an embed dropped in. */

/* --- ONE FOOTER: map + NAP + rating + links, all in the same dark block ------
   It used to be two stacked dark sections — a "Find us" block carrying address,
   phone and rating, then a separate footer repeating the address and phone. */
.site-foot{background:var(--ink); color:var(--cream); padding-block:var(--s6) var(--s3)}
.site-foot a{color:var(--cream)}
.site-foot a:hover{color:var(--orange)}

.foot-top{display:grid; gap:var(--s4); align-items:start}
@media (min-width:880px){ .foot-top{grid-template-columns:.85fr 1.15fr} }

.foot-brand{margin-bottom:.75rem; line-height:0}
.foot-brand img{width:170px; height:auto}
.foot-rating{font-size:var(--step-1); margin-bottom:var(--s2)}
.map-stars{color:var(--orange); letter-spacing:.05em}
.foot-nap{font-style:normal; line-height:1.8; margin-bottom:var(--s2)}
.foot-area{color:var(--cream2); max-width:46ch; margin-bottom:var(--s3)}
.site-foot .btn{color:var(--ink)}
.site-foot .btn:hover{color:var(--ink)}

.map-embed{overflow:hidden; border:1px solid rgba(243,238,228,.18)}
.map-embed iframe{width:100%; height:360px; display:block; border:0}

.foot-links{
  display:grid; gap:var(--s4);
  grid-template-columns:repeat(auto-fit,minmax(170px,1fr));
  margin-top:var(--s6); padding-top:var(--s4);
  border-top:1px solid rgba(243,238,228,.15);
}
.foot-h{font-weight:800; margin-bottom:.6rem; text-transform:uppercase;
  letter-spacing:.05em; font-size:var(--step--1); color:var(--cream)}
.foot-links ul{list-style:none; margin:0; padding:0}
.foot-links li{margin-bottom:.4rem}
.foot-links a{color:var(--cream2); text-decoration:none; font-size:var(--step--1)}
.foot-links a:hover{color:var(--cream); text-decoration:underline}

.foot-legal{margin-top:var(--s4); padding-top:var(--s2);
  border-top:1px solid rgba(243,238,228,.15)}
.foot-legal p{margin:0; font-size:var(--step--1); color:var(--cream2)}


/* --- PAIN-POINT VIDEO ROWS (reference/STYLING-BRIEF.md §2.2) -----------------
   The frame is a CONSTANT. Copy length must never change it. Previous passes tied
   the video size to the text block via align-items:stretch, height:100%,
   aspect-ratio on a growable wrapper, and width:min(100%,340px) — each moved the
   coupling rather than removing it. A fixed 240px column removes it. */
.pain-stack { display: grid; gap: var(--s3); margin-top: var(--s4); }
/* Two rows SIDE BY SIDE. A 240px frame plus a copy column fills a ~560px track;
   in a full-width 1150px row it left 433px of empty cream. The reference site
   (stumpservicesnearme.com) runs a median of 22px slack — ours was 433px. */
@media (min-width: 900px){ .pain-stack--2up { grid-template-columns: 1fr 1fr; } }

.pain-row {
  display: grid;
  grid-template-columns: 240px 1fr;   /* FIXED. Not minmax. Not 1fr 1fr. */
  gap: var(--s3);
  align-items: start;                 /* NOT stretch */
  background: var(--white);
  border: 1px solid var(--line);
}

.pain-row--flip { grid-template-columns: 1fr 240px; }
.pain-row--flip .pain-frame { order: 2; }
.pain-row--flip .pain-copy  { order: 1; }

.pain-frame {
  width: 240px;
  flex: 0 0 240px;
  aspect-ratio: 608 / 1080;
  height: auto;
  max-height: none;
  background: #111;
  overflow: hidden;
}

.pain-video {
  display: block;
  width: 240px;
  height: auto;
  aspect-ratio: 608 / 1080;
  object-fit: cover;
  object-position: center;
  background: #111;
  vertical-align: top;
}

.pain-copy {
  padding: 1.25rem 1.5rem 1.5rem;
  max-width: 46ch;
  align-self: start;
}
@media (min-width:900px){ .pain-stack--2up .pain-copy{padding:1rem 1.25rem 1.25rem} }

.pain-copy h3 { font-size: var(--step-1); margin: 0 0 .5rem; line-height: 1.2; }
.pain-copy p  { margin: 0 0 .75rem; }
.pain-copy .pr-proof { margin: .75rem 0 0; }

/* 560-899px: wide enough to sit the video beside the copy, which fills the row.
   Below 560 it stacks and the video takes the full width. */
@media (min-width: 560px) and (max-width: 899px) {
  .pain-row, .pain-row--flip { grid-template-columns: 240px 1fr; }
  .pain-row--flip .pain-frame, .pain-row--flip .pain-copy { order: 0; }
  .pain-frame, .pain-video { width: 240px; margin-inline: 0; }
}

@media (max-width: 559px) {
  .pain-row,
  .pain-row--flip { grid-template-columns: 1fr; }
  .pain-row--flip .pain-frame,
  .pain-row--flip .pain-copy { order: 0; }
  /* Fill the row. The 240px cap is a DESKTOP rule — it keeps a fixed column so copy
     length cannot move the frame. On mobile the row is single-column and full-width,
     so the same cap left 55px of dead cream each side at 390px. Sized by VIEWPORT
     here, never by the text, so the original principle still holds. The vh ceiling
     stops a 9:16 clip filling a short screen. */
  .pain-frame,
  .pain-video {
    /* Cap by WIDTH, never height. max-height fights aspect-ratio: at 430px the frame
       wanted 388x689, the cap truncated it to 612, and the video stretched to a 0.63
       ratio (source 0.563). Width-capping keeps the shape exactly. 380px is roughly
       a phone's usable width; wider screens get the 2-up desktop grid at 900px. */
    width: min(100%, 380px);
    margin-inline: auto;
  }
  /* No object-fit override: the frame's aspect-ratio already matches the source
     (608/1080), so `cover` would only risk a crop if the vh cap ever bites. */
  .pain-copy { padding: 1rem 1.1rem 1.25rem; max-width: none; }
}

/* --- photo tiles after the videos (§2.3) --- */
.pr-grid--photos {
  display: grid;
  gap: var(--s3);
  margin-top: var(--s5);
  grid-template-columns: 1fr;
}
@media (min-width: 700px) { .pr-grid--photos { grid-template-columns: 1fr 1fr; } }
/* .pr-photo is defined ABOVE (see the PROMISE CARDS block). This duplicate once
   re-imposed `aspect-ratio: 4/3; object-fit: cover` at the same specificity and,
   being later in source order, WON. Measured in Chrome: a natively square (1.000)
   before/after composite rendered at 1.333 — a live ~25% crop that cut the
   BEFORE/AFTER label strip off the bottom, which is the whole point of the image.
   Only the padding rule below is unique to this block, so that is all that remains. */
.pr-body { padding: var(--s3); }

/* --- pricing band (§3.5): tighten, do not enlarge --- */
#pricing.section { padding-block: var(--s5); }
#pricing h2 { letter-spacing: -0.02em; }

/* --- QUOTE MODAL ---------------------------------------------- */
.qm{
  border:0; padding:0; background:transparent;
  width:min(560px, 92vw); max-height:92vh;
}
.qm::backdrop{background:rgba(27,24,19,.86)}
.qm .qf{margin:0}
.qm-close{
  position:absolute; top:.5rem; right:.75rem; z-index:2;
  background:transparent; border:0; cursor:pointer;
  font:800 1.6rem/1 var(--font); color:var(--mut);
}
.qm-close:hover{color:var(--ink)}

/* --- PAGE HERO (inner pages) ---------------------------------- */
.page-hero{
  background:var(--char); color:var(--cream);
  padding-block:var(--s5) var(--s5);
}
.page-hero h1{
  color:var(--cream); margin:0;
  font-size:clamp(1.9rem, 1.4rem + 1.9vw, 2.9rem); line-height:1.1; max-width:20ch;
}
.page-hero .eyebrow{color:var(--tan)}
.ph-lede{
  margin:var(--s2) 0 0; max-width:52ch;
  font-size:var(--step-1); line-height:1.5; color:var(--cream2);
}
.ph-cta{display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; margin:var(--s3) 0 0}
.ph-call{color:var(--tan); font-weight:700; text-decoration:none}
.ph-call:hover{color:var(--cream)}

/* --- INNER PAGE BODY ------------------------------------------
   Scoped heading scale: --step-3 H2s were sized for a homepage with few
   headings. A body page carries six or more and every one shouts. */
.page-body h2{font-size:var(--step-2); margin-top:var(--s5)}
.page-body h3{font-size:var(--step-1); margin-top:var(--s4)}
.page-body h2:first-child,.page-body h3:first-child{margin-top:0}
/* 🔴 The 68ch reading measure is right; NOTHING WAS CENTRING IT. Inside a 1200px
   .wrap the capped column hugged the left edge and the right ~52% of a 1440px
   screen was dead space, which is what made the top of /stump-grinding read as a
   dumped document rather than a page. Owner 2026-09-16: "Structure of this page
   needs to be fixed."

   Centre the PROSE ELEMENTS, not their container. Capping .page-body itself and
   then breaking the card grids back out needed a negative margin, and a percentage
   width against a percentage margin resolves on two different boxes — it overflowed
   the page at four widths. Constraining only the text leaves the grids at .wrap's
   full width with no escape hatch to get wrong. */
/* 🔴 ch is relative to the ELEMENT's own font size, so 68ch on a --step-3 heading
   is ~1514px — the cap applied and did nothing, leaving headings full-width while
   the prose beneath them was centred at 692px. The reading column is defined once,
   in rem, and every block shares it. */
.page-body{--measure:44rem}
.page-body > p,
.page-body > ul,
.page-body > ol,
.page-body > h2,
.page-body > h3,
.page-body > blockquote,
.page-body > .faq{max-width:var(--measure); margin-inline:auto}
.page-body ul{margin-bottom:var(--s3)}
/* ---- Audience cards ("Who benefits from professional stump grinding?") -----
   🔴 OWNER 2026-09-16: "just simple little cards with no spacing anywhere ... it
   just looks really bad, looks like crap, because optically, there's nothing
   really there."

   Measured: padding 0px. An unqualified `.pr-card{padding:0}` at line ~411 exists
   for the PHOTO promise cards (a card whose top edge is an image must be edge to
   edge) and applies to every .pr-card on the site. Giving these cards their own
   GRID class did not escape it — the override is on .pr-card itself, not on a grid
   modifier. Restated here explicitly.

   Spacing is what makes a card read as a card: padding inside, gap between, and a
   heading that is not welded to its own text. */
.pr-grid--aud{align-items:stretch; gap:var(--s3)}
.pr-grid--aud .pr-card{
  display:flex; flex-direction:column;
  padding:var(--s3);
  background:var(--white);
  border:1px solid var(--line);
}
.pr-grid--aud .pr-card h3{
  margin:0 0 .6rem; font-size:var(--step-0); line-height:1.25;
  padding-bottom:.55rem; border-bottom:2px solid var(--tan);
}
.pr-grid--aud .pr-card p{margin:0; color:var(--mut); font-size:var(--step--1); line-height:1.6}
@media (min-width:860px){
  .pr-grid--aud{grid-template-columns:repeat(3,1fr); gap:var(--s4)}
}
/* Three across for the six audience cards. A DISTINCT class, not .pr-grid--3:
   that modifier already exists for the how-we-work media cards and sets
   padding:0 on .pr-card (they hold a photo/video), which would collapse the
   padding on these text-only cards. */
@media (min-width:860px){
  .pr-grid--aud{grid-template-columns:repeat(3,1fr)}
}
.ph-grid{display:grid; gap:var(--s4); align-items:center}
@media (min-width:900px){ .page-hero--img .ph-grid{grid-template-columns:1.15fr .85fr} }
.ph-media{overflow:hidden; border:1px solid rgba(247,243,234,.15)}
.ph-media img{width:100%; height:auto; aspect-ratio:4/3; object-fit:cover; display:block}
/* A page with only a few blocks does not need a full --s6 band of cream. */
.section--thin{padding-block:var(--s4)}

/* 🔴 OWNER 2026-09-16: "Above the form, below the FAQ there's a horizontal white
   bar. It's the color of the background."

   MEASURED on the live page: the FAQ band ends at y=6702, <main> ends at 6774, the
   form starts at 6774. That 72px is <main>'s OWN bottom padding — it carries
   .section, so it is a padded section that also CONTAINS padded sections. Below the
   last banded child that padding is just a bare stripe of page background between
   two coloured bands.

   (My first read blamed the run of blank lines left by the card swap. Measured:
   they cost 0px. The blank lines are ugly source, not the defect.)

   The top padding is kept — it is what separates the body copy from the trust strip
   above it. Only the bottom goes, and only when the last child is a section that
   brings its own. */
main.section:has(> section:last-child){padding-bottom:0}

/* --- OWNER QUOTE: Carson's own published words --------------------------------
   Set as a quotation, not as body copy: the reader should be able to tell at a
   glance that these are HIS words and not our marketing. Left rule in his olive,
   no italics (long italic runs read as decoration and hurt legibility). */
.ow-quote{
  margin:0; padding:var(--s3) var(--s4);
  background:var(--white); border:1px solid var(--line);
  border-left:4px solid var(--olive);
}
.ow-quote p{margin:0 0 var(--s2); font-size:var(--step-0); line-height:1.6}
.ow-quote p:last-child{margin-bottom:0}
.ow-src{font-size:var(--step--1)}
.ow-src a{color:var(--mut)}

/* --- CITY FACTS: the checkable geography block --------------------------------
   A definition list in spirit: label left, fact right. Reads as reference data,
   not as marketing copy — which is the point, since it is the one part of a city
   page a resident can verify at a glance. */
.cf-facts{list-style:none; margin:0 0 var(--s3); padding:0; border-top:1px solid var(--line)}
.cf-facts li{
  display:grid; grid-template-columns:8.5rem 1fr; gap:var(--s2);
  padding:.6rem 0; border-bottom:1px solid var(--line);
}
.cf-facts li span{color:var(--mut); font-size:var(--step--1); text-transform:uppercase;
  letter-spacing:.06em; align-self:center}
.cf-facts strong{font-weight:700}
.cf-src{font-size:var(--step--1)}
.cf-near{margin:0 0 var(--s3); padding-left:1.1rem}
.cf-near li{margin-bottom:.3rem}
@media (max-width:520px){
  /* Stack rather than squeeze: an 8.5rem label column leaves too little for the ZIPs. */
  .cf-facts li{grid-template-columns:1fr; gap:.15rem}
  .cf-facts li span{align-self:start}
}

/* --- WHAT HAPPENS NEXT: the contact page's process steps ----------------------
   Numbered column, not a bulleted list: the order is the content. The customer
   quote sits inside its step rather than in a separate testimonial block, so the
   evidence reads as evidence for THAT claim. */
.wh-list{
  list-style:none; padding:0; counter-reset:wh;
  /* Constrain the LIST, not the text. The paragraph measure is already right at
     ~628px (about 75 characters at 16px); widening it would hurt readability.
     What looked wrong was a full-width section wrapping a half-width column, so
     the container narrows to match and the block reads as deliberate. */
  margin:0 auto var(--s4); max-width:46rem;
}
.wh-step{
  display:grid; grid-template-columns:2.5rem 1fr; gap:var(--s3);
  padding:var(--s3) 0; border-bottom:1px solid var(--line);
}
.wh-step:last-child{border-bottom:0}
.wh-n{
  display:flex; align-items:center; justify-content:center;
  width:2.5rem; height:2.5rem; background:var(--olive); color:var(--white);
  font-weight:800; font-size:var(--step-0); line-height:1;
}
.wh-body h3{font-size:var(--step-1); margin:0 0 .4rem}
.wh-body p{margin:0 0 var(--s2)}
.wh-body p:last-child{margin-bottom:0}
.wh-q{
  margin:var(--s2) 0 0; padding:var(--s2) var(--s3);
  background:var(--cream2); border-left:3px solid var(--tan);
  font-size:var(--step--1);
}
.wh-q cite{display:block; margin-top:.4rem; color:var(--mut); font-style:normal;
  font-size:var(--step--1)}
.wh-q cite::before{content:"— "}
@media (max-width:520px){
  .wh-step{grid-template-columns:2rem 1fr; gap:var(--s2)}
  .wh-n{width:2rem; height:2rem; font-size:var(--step--1)}
}

/* --- TABS: horizontal switcher, zero JavaScript -------------------------------
   Taxonomy pattern #2 (radio-button hack). Markup per set: N radios, then the bar,
   then N panels. For radio i in a set of n tabs the panel is child (n + 1 + i).

   🔴 TWO traps, both of which rendered NOTHING while markup and gates passed:

   1. :nth-of-type() counts by TAG, not class. .tb-panel and .tb-bar are both <div>,
      so .tb-panel:nth-of-type(1) means "a .tb-panel that is ALSO the first <div>
      among its siblings" — which the BAR always is. Every panel was off by one.

   2. A rule written for a 2-tab set ALSO matches a 5-tab set, because a 5-tab set
      does have a child at position 4. Every set matched the rules for every smaller
      size at once, so all five panels showed together. Each rule is therefore pinned
      to an EXACT set size with :has(nth-child(n)):not(:has(nth-child(n+1))).

   Generated for sizes 1-10 rather than hand-written: this is exactly the arithmetic a
   human gets wrong once and never notices. */
.tb{margin:0 0 var(--s4); min-width:0}
.tb-r{position:absolute; opacity:0; pointer-events:none; width:1px; height:1px}

.tb-bar{display:flex; flex-wrap:wrap; gap:2px; border-bottom:2px solid var(--olive)}
.tb-tab{
  padding:.6rem 1rem; cursor:pointer;
  background:var(--cream2); color:var(--mut);
  border:1px solid var(--line); border-bottom:0;
  font-size:var(--step--1); font-weight:700; line-height:1.2; white-space:nowrap;
}
.tb-tab:hover{background:var(--white); color:var(--ink)}

.tb-panel{
  display:none;
  padding:var(--s3) var(--s4);
  border:1px solid var(--line); border-top:0;
  background:var(--white);
}
.tb-panel > :first-child{margin-top:0}
.tb-panel > :last-child{margin-bottom:0}

.tb:has(> .tb-r:nth-child(1)):not(:has(> .tb-r:nth-child(2))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(3),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(4),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(5),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(5),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(6),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(7),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(6),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(7),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(8),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(9),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(7),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(8),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(9),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(10),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(11),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(8),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(9),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(10),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(11),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(12),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(6):checked) > .tb-panel:nth-child(13),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(9),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(10),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(11),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(12),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(13),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(6):checked) > .tb-panel:nth-child(14),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(7):checked) > .tb-panel:nth-child(15),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(10),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(11),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(12),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(13),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(14),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(6):checked) > .tb-panel:nth-child(15),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(7):checked) > .tb-panel:nth-child(16),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(8):checked) > .tb-panel:nth-child(17),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(11),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(12),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(13),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(14),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(15),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(6):checked) > .tb-panel:nth-child(16),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(7):checked) > .tb-panel:nth-child(17),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(8):checked) > .tb-panel:nth-child(18),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(9):checked) > .tb-panel:nth-child(19),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(1):checked) > .tb-panel:nth-child(12),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(2):checked) > .tb-panel:nth-child(13),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(3):checked) > .tb-panel:nth-child(14),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(4):checked) > .tb-panel:nth-child(15),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(5):checked) > .tb-panel:nth-child(16),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(6):checked) > .tb-panel:nth-child(17),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(7):checked) > .tb-panel:nth-child(18),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(8):checked) > .tb-panel:nth-child(19),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(9):checked) > .tb-panel:nth-child(20),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(10):checked) > .tb-panel:nth-child(21){display:block}

.tb:has(> .tb-r:nth-child(1)):not(:has(> .tb-r:nth-child(2))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(6):checked) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(6):checked) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(7):checked) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(6):checked) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(7):checked) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(8):checked) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(6):checked) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(7):checked) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(8):checked) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(9):checked) > .tb-bar > .tb-tab:nth-child(9),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(1):checked) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(2):checked) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(3):checked) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(4):checked) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(5):checked) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(6):checked) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(7):checked) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(8):checked) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(9):checked) > .tb-bar > .tb-tab:nth-child(9),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(10):checked) > .tb-bar > .tb-tab:nth-child(10){background:var(--olive); color:var(--white); border-color:var(--olive)}

/* The input is off-screen, so focus has to be visible on its LABEL. A radio group is
   natively arrow-navigable, so the strip is keyboard-operable with no JS. */
.tb:has(> .tb-r:nth-child(1)):not(:has(> .tb-r:nth-child(2))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(2)):not(:has(> .tb-r:nth-child(3))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(3)):not(:has(> .tb-r:nth-child(4))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(4)):not(:has(> .tb-r:nth-child(5))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(5)):not(:has(> .tb-r:nth-child(6))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(6)):not(:has(> .tb-r:nth-child(7))):has(> .tb-r:nth-child(6):focus-visible) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(6):focus-visible) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(7)):not(:has(> .tb-r:nth-child(8))):has(> .tb-r:nth-child(7):focus-visible) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(6):focus-visible) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(7):focus-visible) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(8)):not(:has(> .tb-r:nth-child(9))):has(> .tb-r:nth-child(8):focus-visible) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(6):focus-visible) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(7):focus-visible) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(8):focus-visible) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(9)):not(:has(> .tb-r:nth-child(10))):has(> .tb-r:nth-child(9):focus-visible) > .tb-bar > .tb-tab:nth-child(9),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(1):focus-visible) > .tb-bar > .tb-tab:nth-child(1),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(2):focus-visible) > .tb-bar > .tb-tab:nth-child(2),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(3):focus-visible) > .tb-bar > .tb-tab:nth-child(3),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(4):focus-visible) > .tb-bar > .tb-tab:nth-child(4),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(5):focus-visible) > .tb-bar > .tb-tab:nth-child(5),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(6):focus-visible) > .tb-bar > .tb-tab:nth-child(6),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(7):focus-visible) > .tb-bar > .tb-tab:nth-child(7),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(8):focus-visible) > .tb-bar > .tb-tab:nth-child(8),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(9):focus-visible) > .tb-bar > .tb-tab:nth-child(9),
.tb:has(> .tb-r:nth-child(10)):not(:has(> .tb-r:nth-child(11))):has(> .tb-r:nth-child(10):focus-visible) > .tb-bar > .tb-tab:nth-child(10){outline:3px solid var(--tan); outline-offset:-3px}

@media (max-width:560px){
  .tb-bar{flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch}
  .tb-tab{padding:.55rem .8rem}
  .tb-panel{padding:var(--s3) var(--s3)}
}

/* --- CITY MAP inside a tab -----------------------------------------------------
   The iframe carries width/height attributes so the box is reserved before it loads
   (no layout shift), but those are 600x450 and a panel is wider. Override in CSS and
   hold the shape with aspect-ratio rather than a fixed height, so it scales down on
   mobile without letterboxing.

   NOT a fixed height + aspect-ratio together: those fight, which cost real time twice
   on this project already. aspect-ratio alone, height auto. */
.map-city{margin:0 0 var(--s3); border:1px solid var(--line); background:var(--cream2)}
.map-city iframe{
  display:block; width:100%; height:auto; aspect-ratio:4/3; border:0;
}
@media (min-width:900px){
  /* A 4:3 map at full panel width is very tall. Widen the frame instead. */
  .map-city iframe{aspect-ratio:16/9}
}

/* --- PER-TOWN promise elaboration ---------------------------------------------
   Sits UNDER the fixed tagline, never replacing it: the promise is the same
   everywhere (it is a claim about how he works), only the elaboration is local.
   Set slightly quieter so the promise still leads. */
.pr-local{
  margin-top:.6rem; padding-top:.6rem;
  border-top:1px solid var(--line);
  color:var(--mut); font-size:var(--step--1); line-height:1.55;
}

/* --- PROOF CARD: faces, a big rating, and a real quote -------------------------
   Rebuilt from the ORIGINAL stumpservicesnearme landing page, which is a CARD, not a
   chip: avatars, then a large rating, an uppercase label, and an italic pull-quote.
   My first pass matched the avatars and missed the composition — the visual weight
   there comes from the big number and the quote.

   Difference from the original: there it owned the hero's right column; here the FORM
   does, so this sits under the lede and is sized for that.

   🔴 Circles are a DECLARED exception to the square-corner rule — Google delivers the
   avatars circle-cropped, so a square frame leaves dead corners. See
   assert-square-corners.py ALLOWED. */
.rb{
  display:block; max-width:34rem;
  padding:1rem 1.15rem;
  background:rgba(20,18,15,.62);
  border-left:5px solid var(--tan);
  text-decoration:none; color:var(--white);
  backdrop-filter:blur(2px);
}
.rb:hover{background:rgba(20,18,15,.74)}

.rb-faces{display:flex; gap:6px; align-items:center; margin-bottom:.6rem}
.rb-face{
  width:32px; height:32px; object-fit:cover; display:block;
  border-radius:50%;                      /* declared exception */
  border:2px solid rgba(253,251,246,.9);
}

.rb-big{
  display:block; font-size:2.4rem; font-weight:900; line-height:1;
  color:var(--white);
}
.rb-stars{color:var(--tan); font-size:1.5rem; margin-left:.15rem}
.rb-lab{
  display:block; margin:.35rem 0 .7rem;
  color:var(--tan); font-size:.78rem;
  text-transform:uppercase; letter-spacing:.08em;
}
.rb-lab strong{font-weight:800; color:var(--white)}
.rb-q{
  display:block; font-style:italic; line-height:1.5;
  font-size:var(--step--1); color:rgba(253,251,246,.92);
}
.rb-who{
  display:block; margin-top:.4rem; font-style:normal;
  font-size:.78rem; color:var(--tan);
}
.rb-who::before{content:"— "}

@media (max-width:560px){
  .rb{padding:.85rem 1rem}
  .rb-face,.rb-more{width:28px; height:28px}
  .rb-big{font-size:2rem}
}

/* --- CONSENT CHECKBOX ---------------------------------------------------------
   Sits ABOVE the submit button, because consent has to be readable before the act
   it authorises. Unchecked and required — a pre-ticked box is not consent.

   Deliberately small but NOT hidden: 13px, full contrast on the label text. Legal
   text set in grey-on-grey is the pattern regulators treat as concealment. */
.qf-consent{
  display:grid; grid-template-columns:auto 1fr; gap:.55rem;
  align-items:start; margin:0 0 var(--s3);
  font-size:.8125rem; line-height:1.45; color:var(--ink);
  cursor:pointer;
}
.qf-consent input{
  width:18px; height:18px; margin:.12rem 0 0; flex:0 0 18px;
  accent-color:var(--olive); cursor:pointer;
}
.qf-consent a{color:var(--olive); text-underline-offset:2px}
.qf-consent input:focus-visible{outline:3px solid var(--tan); outline-offset:2px}
/* Browsers show their own "please tick this" bubble on submit; make the box itself
   read as the problem too. */
.qf-consent input:invalid:not(:placeholder-shown),
.qf-consent input.is-missing{outline:2px solid var(--olive-d)}

/* --- EQUAL-HEIGHT PROMISE CARDS ------------------------------------------------
   The two photo cards differ because their source images are different shapes —
   576x432 and 576x576 — so a card sized to its photo is always taller or shorter.

   🔴 I tried both halves of a false choice first. object-fit:cover crops a BEFORE/AFTER
   composite HORIZONTALLY, straight into the BEFORE panel (I measured VERTICAL crop at
   0% and called it safe — wrong axis). object-fit:contain then letterboxed it with dark
   bars. Both were attempts to force two different shapes into one box.

   The fix is to stop forcing it: the photo renders at its NATURAL size, and the cards
   equalise by letting the BODY take up the slack. Text has flexible space; a labelled
   composite does not. */
.pr-grid--photos{align-items:stretch}
.pr-grid--photos .pr-card{display:flex; flex-direction:column}
.pr-grid--photos .pr-photo{
  width:100%; height:auto;        /* natural ratio, never cropped, never letterboxed */
  flex:0 0 auto;
}
.pr-grid--photos .pr-body{
  flex:1 1 auto;                  /* the body absorbs the height difference */
  display:flex; flex-direction:column;
}

/* --- ABOUT: Carson's bio, his words ------------------------------------------- */
.ab{display:grid; gap:var(--s4); align-items:center}
@media (min-width:760px){ .ab:has(.ab-photo){grid-template-columns:250px 1fr} }
.ab-photo img{
  /* 🔴 NEVER upscaled. The source is 250x284 — the client offered better resolution and
     we should take it, but enlarging a 250px portrait just makes it visibly soft. The
     box caps at the native width so it renders sharp at whatever size it really is. */
  width:100%; max-width:250px; height:auto; display:block; border:1px solid var(--line);
}
.ab-body p{max-width:62ch}

/* --- AUDIT 2026-09-14: rendered-contrast fixes --------------------------------
   Measured in Chrome against the real computed backdrop, never read off the token
   table. That distinction is the point: --tan is 7.70:1 on the dark stage and 1.74:1
   on cream. A context failure, not a bad token.

   🔴 MY FIRST FIX MADE IT WORSE. I scoped the dark replacement to `.wrap .eyebrow`,
   and `.page-hero`'s copy sits inside a `.wrap` — so twelve eyebrows that were
   readable tan on a DARK band got --olive-d on near-black: 1.88:1. The light rule is
   now scoped to the light sections by name, and every dark band is listed explicitly.
   Listing the dark cases is safer than excluding them: a new dark section defaults to
   the light rule and fails loudly, rather than defaulting to tan and passing quietly. */
.section .eyebrow,
.ab .eyebrow{color:var(--olive-d)}

/* Dark backdrops keep the tan — measured 7.70:1. */
.hero .eyebrow,
.page-hero .eyebrow,
.section--dark .eyebrow,
.rb .eyebrow{color:var(--tan)}

/* #pricing .muted was 2.29:1 — --mut is a LIGHT-background token used on the dark
   band, so the band needs its own. */
.section--dark .muted,
#pricing .muted{color:#cfc6b2}

/* One unbreakable URL overflowed /terms-of-service by 3px at 320. Body copy only, so
   headings keep normal wrapping. */
.page-body p,
.section p{overflow-wrap:anywhere}

/* ---- Header dropdowns (Locations, Advice) --------------------------------
   <details> inside the nav strip. Every link is in the initial HTML; this only
   controls whether it is PAINTED. Opens on click and on keyboard Enter/Space
   for free — no JS, and nothing hover-only, which would be unreachable on touch. */
.nav-menu{position:relative; list-style:none}
.nav-menu details{position:relative}
.nav-menu summary{
  color:var(--cream2); font-weight:700; font-size:var(--step--1);
  padding-block:.35rem; cursor:pointer; list-style:none;
  border-bottom:2px solid transparent; white-space:nowrap;
}
.nav-menu summary::-webkit-details-marker{display:none}
.nav-menu summary::after{content:" \25BE"; font-size:.8em}
.nav-menu summary:hover,
.nav-menu details[open] > summary{color:var(--white); text-decoration:underline}
.nav-menu summary:focus-visible{outline:2px solid var(--tan); outline-offset:2px}
.nav-sub{
  /* Anchored to the menu's RIGHT edge, not its left. These menus sit at the end of
     the nav row, so a left-anchored 14rem panel hung past the viewport between 768
     and 900px -- the document did not scroll, but the panel was partly offscreen
     and unreachable. Opening inward is also what a right-hand menu conventionally
     does. */
  position:absolute; z-index:30; top:calc(100% + .35rem); right:0; left:auto;
  min-width:14rem; max-width:min(18rem,90vw);
  background:var(--ink); border:1px solid var(--olive-d);
  margin:0; padding:.35rem 0; list-style:none;
  max-height:min(60vh,26rem); overflow-y:auto;
}
.nav-sub li{display:block}
.nav-sub a{display:block; padding:.45rem .85rem; white-space:nowrap; border-bottom:0}
.nav-sub a:hover,.nav-sub a[aria-current]{background:var(--olive-d); color:var(--white)}
/* On a narrow screen the nav is a horizontal scroll strip, so an absolutely
   positioned panel would be clipped by it. Let it flow inline instead. */
@media (max-width:700px){
  .nav-sub{position:static; right:auto; min-width:0; max-width:none; border:0;
           background:transparent; padding:.25rem 0 .25rem .75rem; max-height:none}
  .nav-sub a{padding:.35rem 0}
}

/* ---- Blog cards ---------------------------------------------------------
   Same shape as .area-card (cover, body, action) so the hub reads as part of
   the site rather than a sixth card style. The action is a real button here
   because the owner asked for one; .area-go is a text arrow. */
.blog-grid{
  display:grid; gap:var(--s3);
  grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr));
}
.blog-card{
  display:flex; flex-direction:column; background:var(--white);
  border:1px solid var(--line); text-decoration:none; color:inherit;
  transition:transform .12s ease, box-shadow .12s ease;
}
.blog-card:hover{transform:translateY(-2px); box-shadow:var(--shadow); color:inherit}
.blog-card img{width:100%; aspect-ratio:4/3; object-fit:cover; display:block}
.blog-body{padding:var(--s3); display:flex; flex-direction:column; gap:.5rem; flex:1}
.blog-body h3{font-size:var(--step-1); margin:0; line-height:1.25}
.blog-blurb{margin:0; font-size:var(--step--1); line-height:1.5; color:var(--ink)}
.blog-more{
  margin-top:auto; align-self:flex-start;
  background:var(--olive); color:var(--white);
  padding:.55em 1.1em; font-weight:800; font-size:var(--step--1);
}
.blog-card:hover .blog-more{background:var(--olive-d)}

/* ---- "How we work": ONE row of three (owner 2026-09-14) -------------------
   Was two containers — a 2-up video stack, then a 2-up photo grid — which could
   never form a single row. Now one grid. Cards are media-on-top so a video card
   and a photo card can sit in the same track; the old .pain-row shape put a fixed
   240px frame BESIDE the copy, which has nowhere to go in a ~330px column.
   Media keeps its natural ratio: the clips are 608x1080 portrait, the photo is
   landscape, and the owner accepted the height difference rather than a crop. */
.pr-grid--3{grid-template-columns:1fr}
@media (min-width:900px){ .pr-grid--3{grid-template-columns:repeat(3,1fr)} }
/* Owner 2026-09-14: "Can we make them all the same height." They sat at their
   natural heights (align-items:start), so a longer paragraph or a heading that
   wrapped to two lines pushed one card 26-76px taller than its neighbours. Same
   mechanism the About columns use: stretch the row, then let the body absorb the
   slack so the card backgrounds end level. No copy is touched. */
.pr-grid--3{align-items:stretch}
.pr-grid--3 .pr-card{display:flex; flex-direction:column; padding:0; overflow:hidden}
.pr-grid--3 .pr-body{flex:1 1 auto}
.pr-grid--3 .pr-body{flex:1 1 auto; padding:var(--s3)}
.pr-grid--3 .pr-photo{width:100%; height:auto; display:block}
/* The video frame fills the column width and keeps the clip's own 608:1080 ratio,
   so nothing is cropped and nothing is letterboxed. */
.pr-grid--3 .pr-frame{width:100%; background:#111; overflow:hidden}
.pr-grid--3 .pr-video{
  display:block; width:100%; height:auto;
  aspect-ratio:608/1080; object-fit:cover; object-position:center;
  background:#111; vertical-align:top;
}
/* Below 900px the three stack, and a full-width portrait clip is far too tall —
   cap it and let the frame crop from the centre rather than run off the screen. */
@media (max-width:899px){
  .pr-grid--3 .pr-frame{max-height:60vh}
  .pr-grid--3 .pr-video{max-height:60vh; object-fit:cover}
}

/* ---- About: OWNER | MACHINE, one row of two -------------------------------
   Owner 2026-09-14: "a 1X2 of columns. Have the owner. Then the machine on the
   side of him". One section, two equal columns -- not a bio section with a
   machine section bolted underneath.

   The portrait is 250x284 native and is NEVER upscaled, so it sits at its own
   size inside the left column rather than defining the column width; the bio
   text sets that. The clip is capped for the same reason the promises row was:
   a full-width 608x1080 portrait video flattens everything beside it. */
.ab2{display:grid; gap:var(--s4); align-items:stretch}
/* Owner 2026-09-14: "this entire section or card centered... put them back close to
   each other." So the two columns are a PAIR again at --s5, and the pair itself is
   a card centred in the page rather than a grid spanning the full 1150px wrap.
   The ink only ever needed ~1030px; capping the block and centring it is what makes
   the whole thing read as one centred card instead of a full-bleed row. */
@media (min-width:860px){
  .ab2{grid-template-columns:1fr 1fr; gap:var(--s5);
       max-width:960px; margin-inline:auto}
}
.ab2-col > h2{margin-top:.15rem}
.ab2-col p{max-width:62ch}
.ab2 .eyebrow{color:var(--olive-d)}
/* FLOATED so the bio wraps beside AND below it — the owner asked for this column
   to read like an article rather than a photo with a caption under it. Wrapping
   also makes the left column taller, which is what closes the gap against the
   video on the right. */
.ab-portrait{float:left; width:250px; max-width:45%; height:auto;
  margin:0 var(--s3) var(--s2) 0; border:1px solid var(--line)}
/* The float must not escape the column. */
.ab2-owner::after{content:""; display:block; clear:both}
/* A 62ch cap on a paragraph flowing around a 250px float leaves a narrow ragged
   channel; let the wrapped text use the column it has. */
.ab2-owner p{max-width:none}
/* The clip FILLS the column rather than being sized by its own 9:16 ratio.
   Sized by width it stood 533px tall against a ~527px owner column — a 184px
   mismatch, and the owner asked for these two to be the same height. Stretching
   the row and letting the video cover its box matches at EVERY width instead of
   one hand-picked aspect-ratio. Cropping costs nothing here: measured at 533 /
   440 / 380 / 349 the machine stays fully legible; only sky and grass go. */
.ab2-video{display:block; width:100%; max-width:300px; height:100%;
  min-height:260px; object-fit:cover; object-position:center;
  background:#111; vertical-align:top}

/* Make the media the flexible part of the column so it takes up the slack. */
.ab2-machine{display:flex; flex-direction:column}
.ab2-machine > .ab2-video{flex:1 1 auto}
@media (max-width:859px){
  /* Owner 2026-09-16: "The text right next to Carson's photo has no spacing. It's
     riding against the photo."

     margin-inline:auto is shorthand for BOTH side margins, so it replaced the
     var(--s3) right gutter set above with auto — which computes to 0 on a float
     (floats do not centre via auto margins). The rule destroyed the gutter and
     centred nothing. Measured: margin-right 0px at 390 and 768, still 24px at
     1024 — which is why it only showed on a phone.

     The video is not floated, so auto DOES centre it; it keeps the shorthand. */
  .ab-portrait{margin-right:var(--s3)}
  .ab2-video{margin-inline:auto}
}
/* ---- The review inside the About column, as a QUOTE ------------------------
   Owner 2026-09-14: "Remove the box, make it look like a quote more." So: no
   card border, no tinted panel, no 48px avatar tile — those are the three
   things that made it read as a UI widget. Follows the .wh-q idiom already on
   the site (left accent rule, em-dashed cite) so it is not a sixth quote style. */
.ab2-quote{
  margin:var(--s3) 0; padding:0 0 0 var(--s3);
  border-left:3px solid var(--olive); background:none;
}
.ab2-quote p{margin:0; font-style:italic; line-height:1.6}
.ab2-quote cite{
  display:block; margin-top:.5rem; font-style:normal;
  font-size:var(--step--1); color:var(--mut);
}
.ab2-quote cite::before{content:"— "}
.ab2-stars{color:var(--olive-d); letter-spacing:.05em}

/* The ported CTA block: a heading whose body was the phone number with a null href.
   It used to be dropped entirely, leaving the heading dangling. */
.cta-line{margin:var(--s3) 0 0}

/* ---- Mobile readability (audit 2026-09-15) --------------------------------
   Measured at 390x844: footer links 12.8px/15px tall, .area-blurb 12.8px,
   .rb-q 12.8px, .strip-list 11.52px — the trust claims were the smallest text
   on the site. These are sentences customers are meant to read, so they get a
   readable size on phones. Desktop keeps the tighter scale. */
@media (max-width:860px){
  .strip-list li{font-size:.95rem}          /* 11.52px -> ~15px */
  .area-blurb{font-size:.95rem}             /* 12.8px  -> ~15px */
  .rb-q{font-size:.95rem}                   /* 12.8px  -> ~15px */
  .foot-links a{font-size:.95rem; display:inline-block; padding-block:.35rem}
  .foot-legal a{padding-block:.35rem; display:inline-block}
}

/* ---- The phone hamburger (owner 2026-09-16) -------------------------------
   REPLACES the one-line scroll strip and its right-edge fade. The strip hid 188px
   at 320px — the whole Locations menu and Blog were off the edge — and the fade
   only ANNOUNCED that something was hidden.

   CHECKBOX + LABEL + SIBLING <ul>, the same crawlable hack the tab components use.
   Zero JavaScript; every link still ships in the initial HTML.

   🔴 A <details> WRAPPER WAS TRIED FIRST AND SHIPPED BROKEN TO THE OWNER. A closed
   <details> hides its non-summary children at the UA level, and that hiding survives
   display:contents on the details AND display:flex on the <ul>: the desktop nav
   rendered at height 0 behind the header — every element reporting visible,
   opacity 1, non-zero geometry, and nothing on screen. Measured: .open=true took
   <nav> 0px -> 34px. A `display` declaration on the CHILD cannot override the
   parent's UA suppression. The checkbox drives a SIBLING, so the <ul> is never
   inside anything that can hide it, and DESKTOP NEEDS NO RULE AT ALL. */
.nav-toggle{position:absolute; opacity:0; width:1px; height:1px; pointer-events:none}
.nav-burger{display:none}

@media (max-width:700px){
  .site-head{position:relative}
  .nav-burger{
    display:flex; align-items:center; cursor:pointer;
    padding:.55rem .6rem; margin:0;
    min-height:44px; box-sizing:border-box;   /* tap-target floor from the mobile audit */
  }
  .nav-toggle:focus-visible + .nav-burger{outline:2px solid var(--tan); outline-offset:2px}
  /* Three lines: one box, a border top/bottom and a middle bar. */
  .nav-burger-ico{
    display:block; width:26px; height:18px;
    border-top:3px solid var(--cream2); border-bottom:3px solid var(--cream2);
    position:relative;
  }
  .nav-burger-ico::after{
    content:""; position:absolute; left:0; right:0; top:calc(50% - 1.5px);
    height:3px; background:var(--cream2);
  }
  .nav-burger:hover .nav-burger-ico,
  .nav-toggle:checked + .nav-burger .nav-burger-ico{border-color:var(--white)}
  .nav-toggle:checked + .nav-burger .nav-burger-ico::after{background:var(--white)}

  /* Closed by default; the checked state reveals it. Spans the header edge to edge,
     so the width comes from the viewport and nothing can hang off either side. */
  .nav-list{display:none}
  .nav-toggle:checked ~ .nav-list{
    display:block; position:absolute; z-index:40;
    top:100%; left:0; right:0; width:auto; min-width:0;
    background:var(--ink); border-top:1px solid var(--olive-d);
    padding:.35rem 0; margin:0;
    max-height:min(76vh,34rem); overflow-y:auto;
  }
  .nav-list > li{display:block}
  /* 20px side padding matches .wrap's gutter so labels line up with the logo. */
  .nav-list > li > a,
  .nav-list .nav-menu summary{
    display:flex; align-items:center; padding:.7rem 20px; white-space:normal;
    font-size:.95rem; border-bottom:0; min-height:44px; box-sizing:border-box;
  }
  .nav-sub{padding:0}
  .nav-sub a{padding:.55rem 20px .55rem 36px; min-height:44px;
             box-sizing:border-box; display:flex; align-items:center}
}

/* ---- Gallery: click-to-load video (owner 2026-09-15) -----------------------
   The tiles were stills captioned "28s" with nothing behind them. The poster is
   now a button with a play badge, and the <video> is created on tap — so the
   gallery weighs the same as before for anyone who never plays one. */
.g-play{
  display:block; width:100%; padding:0; border:0; background:none;
  position:relative; cursor:pointer; line-height:0;
}
.g-play img{width:100%; height:auto; display:block}
/* Square, not a circle. Every corner on this site is 90 degrees (owner ruling,
   enforced by assert-square-corners.py). The one declared exception is reviewer
   avatars, which Google delivers circle-cropped. A play badge is my own decoration,
   so it follows the rule rather than earning a second exception. */
.g-play-badge{
  position:absolute; inset:0; margin:auto;
  width:56px; height:56px; border-radius:0;
  background:rgba(0,0,0,.55); border:2px solid rgba(255,255,255,.9);
  transition:background .12s ease, transform .12s ease;
}
/* the triangle */
.g-play-badge::after{
  content:""; position:absolute; top:50%; left:54%;
  transform:translate(-50%,-50%);
  border-style:solid; border-width:11px 0 11px 18px;
  border-color:transparent transparent transparent #fff;
}
.g-play:hover .g-play-badge,
.g-play:focus-visible .g-play-badge{background:rgba(0,0,0,.75); transform:scale(1.06)}
.g-play:focus-visible{outline:3px solid var(--tan); outline-offset:2px}
.g-video-el{width:100%; height:auto; display:block; background:#111}

/* ---- Form destination pages (/thank-you/, /quote-error/) -------------------
   Two buttons side by side, stacking on a phone. Without the gap they stack
   flush against each other -- seen in the first phone screenshot, not inferred. */
.cta-row{display:flex; flex-wrap:wrap; gap:var(--s2); margin-top:var(--s3)}

/* ---- Phone links: a 44px tap target on every one (cold audit 2026-09-18) ------
   MEASURED at 390px: header call button 32px, the form's "Prefer to talk?" link 35px,
   and every inline or footer number 19px. The live Squarespace site has ZERO undersized
   tap targets, so this was a regression we introduced -- on the one element that IS the
   conversion for a phone-driven, one-man business.

   Two techniques, on purpose:
   - links that stand alone (header button, footer address, form fallback) get REAL height;
   - links INSIDE a sentence get an invisible ::after that extends the tappable area to
     44px. Padding a link mid-paragraph would shove the lines apart; the pseudo-element
     changes nothing visible. Its box is part of the link for hit-testing, which is what a
     finger does -- and what assert-tap-targets.py checks, because the link's own bounding
     box still reads 19px. Geometry is not the tap area (MISTAKES.md section 1, inverted). */
/* A phone number never breaks across lines. Found by the tap-target gate on /contact/ at
   390px: "(319)" ended one line and "988-1953" began the next, so the link was two
   fragments with a dead gap between them -- bad to read and impossible to enlarge. */
a[href^="tel:"]{white-space:nowrap}
@media (max-width:860px){
  .head-call{display:inline-flex; align-items:center; min-height:44px}
  .foot-nap a,
  .qf-fallback a{display:inline-block; padding-block:.8rem}
  a[href^="tel:"]:not(.btn):not(.head-call){position:relative}
  a[href^="tel:"]:not(.btn):not(.head-call)::after{
    content:""; position:absolute; left:-8px; right:-8px;
    top:50%; height:44px; transform:translateY(-50%);
  }
}
