/* ⛔⛔ THIS FILE IS HAND-MAINTAINED. A REBUILD DESTROYS IT. ⛔⛔ ═════════════
   public/demo is NORMALLY generated from ~/Desktop/demo-<name>/dist through
   tools/sync-demo.mjs. This demo is the documented exception: the source no
   longer reproduces what is published here, and running sync-demo.mjs, build.py
   or any build against landscaping SILENTLY takes back four things —
     · the 20 ph-*.jpg photo bands
     · the 10 @font-face rules below and the whole fonts/ folder, after which
       every page on the site renders in Helvetica with no error anywhere
     · the town, which reverts
     · photo-credits.json, after which the credits gate refuses the next build
   ⭐ public/demo IS in git, so a hand-edit here is recoverable and a rebuild is
   not. Edit this file directly and commit it. If the generator ever has to own
   this site again, the four items above are the migration, not a diff.
   ── 23 Sep 2026: hand-edited here for the hero and the yard card. ────────── */

/* ── 🔤 SELF-HOSTED TYPE ───────────────────────────────────────────────────
   🔴 11 Sep 2026. These four demos were the only ones still pulling Bricolage
   Grotesque, Figtree and IBM Plex Mono from fonts.googleapis.com, and it is why
   they were never in sync-demo.mjs: the external-resource guard refuses a demo
   that loads a CDN, so all four were being hand-copied into public/demo instead
   — and then they drifted, serving a phone number the owner had already changed.
   ⚠️ A demo is served in an IFRAME on another domain. A webfont that is slow,
   blocked or privacy-filtered renders the whole page in the fallback face with
   no error anywhere, which is the worst way for a sales tool to fail.
   ⭐ Latin subset only — 10 faces, ~388 KB. The Cyrillic, Greek and Vietnamese
   subsets Google serves are megabytes these pages will never draw a glyph from. */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-500.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-600.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-700.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-800.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/figtree-400.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/figtree-500.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/figtree-600.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-400.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-500.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-700.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root{
  /* DARK FIRST. The shop floor at night, lit by a worklight — and the service
     symbols are dashboard telltales, in the colors a real dash uses. */
  --paper:#0A0C0F; --surface:#12161B; --surface-2:#181D24;
  --ink:#EDF1F4; --ink-2:#9AA6B1; --ink-3:#6B7681;
  --rule:#242C34; --rule-soft:#1A1F26;
  --accent:#4CC96B; --accent-ink:#052914; --accent-soft:#07230F; --heat:#FFB020; --heat-soft:#2A1E06;
  --pass:#3FD07A; --pass-soft:#122419;
  --todo:#E0B860; --todo-soft:#2A2113; --todo-line:#6B5322;
  /* telltale palette — red critical, amber caution, green ok, blue info */
  --tt-red:#FF4438; --tt-amber:#FFB020; --tt-green:#3FD07A;
  --tt-blue:#54A8FF; --tt-cyan:#2BD4D9;
  --shadow:0 1px 2px rgba(0,0,0,.5), 0 14px 40px -18px rgba(0,0,0,.85);
  --f-display:"Bricolage Grotesque","Helvetica Neue",Arial,sans-serif;
  --f-body:"Figtree","Helvetica Neue",Arial,sans-serif;
  --f-mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace;
  --step-0:1.0625rem; --step-1:1.25rem;
  --step-2:clamp(1.5rem,1.15rem + 1.4vw,2rem);
  --step-3:clamp(1.9rem,1.35rem + 2.4vw,2.85rem);
  /* 🔴 THE PHONE FLOOR WAS THE DEFECT, NOT THE CEILING — 23 Sep 2026. Measured:
     the h1 ran 4.24x body at 1440 and only 2.35x on a 375px phone, because the
     clamp floor of 2.5rem is what every phone lands on (1.5rem + 4.2vw only
     overtakes it at ~1000px wide). Most people meet this page on the phone, so
     the ratio they actually get was the small one. Floor raised to 3rem — 2.82x
     body — which still fits the longest word in this headline at 320px.
     ⚠️ Break-word on .hero h1 is the backstop: the headline is per-shop copy and
     no fixed size is safe forever, so a longer word degrades instead of pushing
     the page sideways. */
  --step-4:clamp(3rem,1.5rem + 4.2vw,4.5rem);
  --rail:12.5rem; --gutter:clamp(1.25rem,4vw,3rem); --measure:64ch;
}
@media (prefers-color-scheme:light){
  :root:not([data-theme="dark"]){
    --paper:#E9EBEE; --surface:#FFFFFF; --surface-2:#F4F5F7;
    --ink:#131A21; --ink-2:#4C5761; --ink-3:#7A848E;
    --rule:#C6CDD4; --rule-soft:#DCE1E6;
    --accent:#1F7A4D; --accent-ink:#FFFFFF; --accent-soft:#DEF0E4; --heat:#9A6B00; --heat-soft:#FBF0DA;
    --pass:#1F7A4D; --pass-soft:#DFF0E7;
    --todo:#8A5A00; --todo-soft:#FBF0DA; --todo-line:#E0B860;
    --tt-red:#C81E14; --tt-amber:#9A6B00; --tt-green:#1F7A4D;
    --tt-blue:#1C63C4; --tt-cyan:#0E7C8A;
    --shadow:0 1px 2px rgba(19,26,33,.06), 0 8px 24px -12px rgba(19,26,33,.18);
  }
}
:root[data-theme="light"]{
    --paper:#E9EBEE; --surface:#FFFFFF; --surface-2:#F4F5F7;
    --ink:#131A21; --ink-2:#4C5761; --ink-3:#7A848E;
    --rule:#C6CDD4; --rule-soft:#DCE1E6;
    --accent:#1F7A4D; --accent-ink:#FFFFFF; --accent-soft:#DEF0E4; --heat:#9A6B00; --heat-soft:#FBF0DA;
    --pass:#1F7A4D; --pass-soft:#DFF0E7;
    --todo:#8A5A00; --todo-soft:#FBF0DA; --todo-line:#E0B860;
    --tt-red:#C81E14; --tt-amber:#9A6B00; --tt-green:#1F7A4D;
    --tt-blue:#1C63C4; --tt-cyan:#0E7C8A;
    --shadow:0 1px 2px rgba(19,26,33,.06), 0 8px 24px -12px rgba(19,26,33,.18);
}
*{box-sizing:border-box}
body{margin:0; background:var(--paper); color:var(--ink); font-family:var(--f-body); font-size:var(--step-0); line-height:1.6; -webkit-font-smoothing:antialiased}
h1,h2,h3,h4{font-family:var(--f-display); margin:0; text-wrap:balance; letter-spacing:-.02em; line-height:1.06}
p{margin:0}
a{color:inherit}
:focus-visible{outline:2.5px solid var(--accent); outline-offset:3px; border-radius:2px}
.mono{font-family:var(--f-mono); font-variant-numeric:tabular-nums}
.lbl{font-family:var(--f-mono); font-size:.6875rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-3)}
.shell{max-width:78rem; margin-inline:auto; padding-inline:var(--gutter)}


.top{border-bottom:1px solid var(--rule); background:var(--surface)}
.top__in{display:flex; align-items:center; gap:1.25rem; padding-block:.85rem; flex-wrap:wrap}
/* the logo is the home link — standard, and people reach for it before the nav */
.brand{display:flex; align-items:center; gap:.7rem; text-decoration:none; margin-right:auto; cursor:pointer; border-radius:.3rem}
.brand:hover .brand__logo{opacity:.85}
.brand__logo{transition:opacity .12s ease}
/* ⭐ His own logo, 240x127 and the largest that exists — shown at 132px so the
   source still has ~1.8x density on a retina screen. It is a real RGBA PNG, so
   it sits on the black ground without a plate behind it. On a light theme the
   silver wordmark would disappear, so there it gets a dark plate. */
.brand__logo{width:8.25rem; height:auto; flex:none; display:block}
.brand__txt{display:none}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]) .brand__logo{background:var(--ink); border-radius:.3rem; padding:.3rem .45rem}}
:root[data-theme="light"] .brand__logo{background:var(--ink); border-radius:.3rem; padding:.3rem .45rem}
@media (max-width:30rem){ .brand__logo{width:6.75rem} }
.brand__nm{font-family:var(--f-display); font-weight:800; font-size:1.0625rem; letter-spacing:-.03em; line-height:1.1}
.brand__sub{font-family:var(--f-mono); font-size:.625rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3)}

/* dropdown — the same telltales as the service grid, so the menu teaches the
   symbol set before you ever reach a service page */
.nav{position:relative}
.nav>ul{display:flex; gap:1.15rem; flex-wrap:wrap; list-style:none; margin:0; padding:0}
.nav li{position:relative}

/* ⭐ MOBILE MENU. Below 60rem the nav becomes a panel and the button becomes an X.
   The bars are three spans so the animation is a real transform, not a glyph swap —
   a font character cannot morph, and a morph is what makes it feel built. */
.burger{display:none; width:2.9rem; height:2.9rem; flex:none; align-items:center; justify-content:center;
  background:transparent; border:1px solid var(--rule); border-radius:.35rem; cursor:pointer; padding:0}
.burger:hover{border-color:var(--accent)}
.burger i{display:block; position:relative; width:1.15rem; height:2px; background:var(--ink);
  border-radius:2px; transition:transform .22s cubic-bezier(.3,1.2,.4,1), background-color .18s ease}
.burger i::before,.burger i::after{content:""; position:absolute; left:0; width:100%; height:2px;
  background:var(--ink); border-radius:2px; transition:transform .22s cubic-bezier(.3,1.2,.4,1)}
.burger i::before{top:-6px} .burger i::after{top:6px}
.burger[aria-expanded="true"] i{background:transparent}
.burger[aria-expanded="true"] i::before{transform:translateY(6px) rotate(45deg); background:var(--accent)}
.burger[aria-expanded="true"] i::after{transform:translateY(-6px) rotate(-45deg); background:var(--accent)}

@media (max-width:60rem){
  .burger{display:inline-flex}
  .top__in{flex-wrap:nowrap; gap:.6rem}
  .nav{order:10; flex-basis:100%; display:none}
  .nav.open{display:block; padding-block:.6rem .2rem; border-top:1px solid var(--rule); margin-top:.7rem}
  .nav>ul{flex-direction:column; gap:0}
  .nav>ul>li{border-bottom:1px solid var(--rule-soft)}
  .nav>ul>li>a{display:flex; padding:.75rem .1rem; font-size:1rem}
  .nav .sub{display:grid; position:static; opacity:1; visibility:visible; transform:none;
    grid-template-columns:1fr 1fr; border:0; box-shadow:none; background:transparent;
    margin:0 0 .5rem; gap:.15rem}
  .nav .sub a{background:transparent; padding:.45rem .1rem; font-size:.8125rem}
  .nav .sub a:hover{background:transparent}
  .top__act .btn--go{padding:.55rem .8rem}
}

/* a menu that opens should say so — chevron flips when the panel is open */
.nav a[aria-haspopup]{display:inline-flex; align-items:center; gap:.4rem}
.nav a[aria-haspopup]::after{content:""; width:.4rem; height:.4rem; flex:none;
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:translateY(-2px) rotate(45deg); opacity:.7;
  transition:transform .15s ease, opacity .15s ease}
.nav li:hover>a[aria-haspopup]::after,
.nav li:focus-within>a[aria-haspopup]::after{transform:translateY(1px) rotate(-135deg); opacity:1}
.nav .sub{position:absolute; top:calc(100% + .55rem); left:-.75rem; z-index:30;
  display:grid; grid-template-columns:repeat(2,minmax(11.5rem,1fr)); gap:1px;
  background:var(--rule); border:1px solid var(--rule); border-radius:.45rem;
  box-shadow:var(--shadow); list-style:none; margin:0; padding:0; overflow:hidden;
  opacity:0; visibility:hidden; transform:translateY(-4px); transition:opacity .12s ease, transform .12s ease, visibility .12s}
.nav li:hover>.sub, .nav li:focus-within>.sub{opacity:1; visibility:visible; transform:none}
.nav .sub a{display:flex; align-items:center; gap:.6rem; background:var(--surface);
  padding:.6rem .8rem; border-bottom:0; font-size:.875rem; white-space:nowrap; color:var(--ink)}
.nav .sub a:hover{background:var(--surface-2); border-bottom:0}
.nav .sub .sym{width:1.85rem; height:1.85rem; border-radius:.3rem}
.nav .sub .sym svg{width:1.05rem; height:1.05rem}
@media (max-width:60rem){
  .nav .sub{position:static; opacity:1; visibility:visible; transform:none; grid-template-columns:1fr;
    box-shadow:none; margin:.4rem 0 .2rem; display:none}
  .nav li:focus-within>.sub{display:grid}
}

.nav a{text-decoration:none; font-size:.9375rem; font-weight:500; color:var(--ink-2); padding-block:.25rem; border-bottom:2px solid transparent}
.nav a:hover{color:var(--ink); border-bottom-color:var(--accent)}
.nav a[aria-current="page"]{color:var(--ink); border-bottom-color:var(--accent)}
.top__act{display:flex; align-items:center; gap:.85rem}
/* 🔴 MEASURED, NOT GUESSED (7 Sep 2026): at 375px — an iPhone — `.top__act`
   ran from x=194 to x=398 on EVERY page, so the site's primary call to action
   sat 23px off the right edge and the whole document scrolled sideways.
   ⭐ Found by walking every element's getBoundingClientRect() inside an iframe
   at 375, not by looking: the header still LOOKS fine because the overflow is
   off-screen, which is exactly why it survived.
   ⛔ The phone is what goes, not the button. A shop's number is one tap away in
   the menu; "Book a slot" is the whole point of the header. */
@media (max-width: 460px){
  .top__act .tel{display:none}
  .top__act{gap:.5rem}
}
.tel{text-decoration:none; font-family:var(--f-mono); font-weight:700; font-size:.9375rem; white-space:nowrap}
.tel>span:first-child{display:block; font-size:.625rem; letter-spacing:.13em; color:var(--ink-3); font-weight:400}
.btn{display:inline-flex; align-items:center; justify-content:center; gap:.5rem; min-height:2.75rem; padding:.6rem 1.15rem; border-radius:.25rem; font-family:var(--f-display); font-weight:700; font-size:.9375rem; text-decoration:none; border:1.5px solid transparent; cursor:pointer; transition:transform .12s ease, background-color .12s ease}
.btn--go{background:var(--accent); color:var(--accent-ink)}
.btn--go:hover{transform:translateY(-1px)}
.btn--line{border-color:var(--rule); color:var(--ink); background:transparent}
.btn--line:hover{border-color:var(--ink); background:var(--surface-2)}

.bay{display:grid; grid-template-columns:var(--rail) minmax(0,1fr); gap:clamp(1.5rem,4vw,3.5rem)}
@media (max-width:60rem){ .bay{grid-template-columns:minmax(0,1fr)} .rail{display:none} }
.rail{border-right:1px solid var(--rule); padding-right:1.25rem; padding-top:.4rem}
.rail dl{margin:0; display:grid; gap:1.35rem}
.rail dt{font-family:var(--f-mono); font-size:.625rem; font-weight:700; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3)}
.rail dd{margin:.25rem 0 0; font-family:var(--f-mono); font-size:.9375rem; font-weight:500; line-height:1.35}
.rail dd em{font-style:normal; color:var(--ink-3); font-size:.75rem; display:block; margin-top:.15rem}


/* ⭐ THE HERO IS AN INSTRUMENT CLUSTER. No stock photograph of a smiling mechanic —
   the shop has no photography yet, and a borrowed garage is worse than none. The
   gauge sweep is the one moment of motion on the page and it settles into a
   resting state that is fully legible with JS off and with motion reduced. */
.hero{position:relative; isolation:isolate; overflow:hidden}
.hero__bg{position:absolute; inset:0; z-index:-1; pointer-events:none}
/* ⭐ TWO LIGHTS, NOT ONE. A single accent wash reads as a gradient; a cool one
   high on the right and a warm one low on the left reads as a room with a booth
   lamp in it — which is exactly what the photograph beside it shows. */
.hero__bg::before{content:""; position:absolute; inset:0;
  background:radial-gradient(58% 62% at 78% 8%, color-mix(in srgb, var(--accent) 26%, transparent) 0%, transparent 70%),
             radial-gradient(48% 52% at 6% 96%, color-mix(in srgb, var(--heat) 18%, transparent) 0%, transparent 72%);}
.hero__bg::after{content:""; position:absolute; inset:0; opacity:.5;
  background-image:radial-gradient(currentColor .6px, transparent .6px);
  background-size:22px 22px; color:var(--rule);
  -webkit-mask-image:linear-gradient(to bottom, #000 0%, transparent 78%);
  mask-image:linear-gradient(to bottom, #000 0%, transparent 78%);}
.gauge{position:absolute; right:-6%; top:-14%; width:min(38rem,64vw); aspect-ratio:1;
  z-index:-1; opacity:.5; pointer-events:none}
.gauge svg{width:100%; height:100%; overflow:visible}
.gauge .arc{fill:none; stroke:var(--rule); stroke-width:1.5; stroke-linecap:round}
.gauge .arc--live{stroke:var(--accent); stroke-dasharray:var(--dash); stroke-dashoffset:var(--off)}
.gauge .tick{stroke:var(--ink-3); stroke-width:1.5; stroke-linecap:round}
.gauge .tick--hot{stroke:var(--accent)}
.gauge .needle{stroke:var(--ink); stroke-width:2.5; stroke-linecap:round;
  transform-origin:120px 120px; transform:rotate(var(--ang,128deg))}
@media (prefers-reduced-motion:no-preference){
  .gauge .needle{animation:sweep 2.1s cubic-bezier(.22,1.1,.3,1) .25s both}
  @keyframes sweep{ 0%{transform:rotate(-38deg)} 55%{transform:rotate(150deg)} 100%{transform:rotate(var(--ang,128deg))} }
}
@media (max-width:52rem){ .gauge{display:none} }

/* ═══════════════════════════════════════════════════════════════════════════
   MOTION. Two pieces, both cheap, both specific to this subject.

   ⛔ WHY THERE IS NO WebGL YARD. The best-known Three.js showcases measure
      around 47s to first paint and Apple ships ZERO canvas on its product
      pages. A slow hero loses more customers than a clever one wins, and the
      yard card says more than a rendered garden ever would.
   ⭐ So the depth is real but costs nothing: layered transforms, no library,
      no 3D model, no canvas.
   ═══════════════════════════════════════════════════════════════════════════ */

/* 1 ── THE BULB CHECK. Every instrument panel built since the nineties lights every lamp for
      about two seconds on power-up, then drops them one by one as each system
      reports in. An irrigation controller does the same
      thing. The service grid does exactly that when it comes into
   view. Nobody else can do this, because nobody else made the icons telltales. */
.svc .sym{transition:box-shadow .35s ease, background-color .35s ease}
@media (prefers-reduced-motion:no-preference){
  .svc.bulbcheck .sym{
    animation:bulb 2.1s ease-out both;
    animation-delay:calc(var(--i,0) * 90ms);
  }
  @keyframes bulb{
    0%   {background:color-mix(in srgb, currentColor 13%, transparent); box-shadow:none}
    8%   {background:color-mix(in srgb, currentColor 46%, transparent);
          box-shadow:0 0 0 1px color-mix(in srgb, currentColor 55%, transparent), 0 0 22px -2px currentColor}
    62%  {background:color-mix(in srgb, currentColor 46%, transparent);
          box-shadow:0 0 0 1px color-mix(in srgb, currentColor 55%, transparent), 0 0 22px -2px currentColor}
    100% {background:color-mix(in srgb, currentColor 13%, transparent); box-shadow:none}
  }
}

/* ⭐ THE STEPS REPORT IN AS YOU PASS THEM. Each number goes green the moment its
   step reaches reading position — the same language as the bulb check: a system
   confirming it is good. It is a five-step process and this makes the page feel
   like it is being worked through rather than scrolled past.
   ⛔ Resting state is the plain number, fully legible with JS off. */
.step__n{position:relative; transition:color .35s ease}
.step__n::after{content:""; position:absolute; left:0; bottom:-.3rem; width:1.6rem; height:2px;
  border-radius:2px; background:var(--pass); transform:scaleX(0); transform-origin:left;
  transition:transform .4s cubic-bezier(.2,.9,.3,1)}
.step.done{--n-col:var(--pass)}
.step.done .step__n::after{transform:scaleX(1)}
@media (prefers-reduced-motion:reduce){
  .step__n,.step__n::after{transition:none}
}

/* 2 ── HERO DEPTH. The gauge, the figure and the dot grid sit on three planes
   and move at three rates against the pointer. It is a flat cut-out; this is
   what makes it read as standing in a room. Scroll adds a slow vertical drift.
   ⛔ Everything is at its resting position with JS off — nothing is parked at
      opacity 0 waiting for an observer. */
.hero__bg,.gauge,.hero__fig img{will-change:transform}
@media (prefers-reduced-motion:no-preference){
  .hero__bg{transform:translate3d(calc(var(--px,0) * 6px), calc(var(--py,0) * 5px + var(--sy,0) * 14px), 0)}
  .gauge{transform:translate3d(calc(var(--px,0) * -16px), calc(var(--py,0) * -12px + var(--sy,0) * 34px), 0) rotate(calc(var(--px,0) * 1.1deg))}
  .hero__fig img{transform:translate3d(calc(var(--px,0) * 9px), calc(var(--sy,0) * -10px), 0)}
  .hero__bg,.gauge,.hero__fig img{transition:transform .45s cubic-bezier(.2,.8,.3,1)}
  .hero.tracking .hero__bg,.hero.tracking .gauge,.hero.tracking .hero__fig img{transition:none}
}

/* ⭐ THE HERO FIGURE SLOT HOLDS THE WATER PANEL, not a photograph.
     ⛔ There is no photography for this trade yet. ⏭️ If one ever exists these
     rules are what it inherits: flat on the ground, no fake drop shadow. */
.shots .shot--cut{background:linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent)}
.shots .shot--cut img{aspect-ratio:auto; object-fit:contain; max-height:15rem; margin-inline:auto; padding-top:.75rem}

/* ⭐ THE HERO FIGURE — a cut-out PNG with real alpha, so he stands on the black
   ground with the gauge glow behind rather than sitting in a box.
   ⚠️ This image is AI-GENERATED. It is the most prominent place on the site, so
   it is the FIRST one to replace with a photograph of Six Oaks. The flag says so. */
.hero__fig{position:relative; display:grid; justify-items:center; align-self:end}
.hero__fig{position:relative; align-self:center; justify-items:stretch; display:block}
/* ⚠️ The glow under the figure stays — it is what stops the panel looking
   pasted onto a very dark page. */
/* ⚠️ A hero photo shorter than the headline beside it reads as an
   afterthought. A 5:4 crop holds the column. */
.hero__fig img{width:100%; height:auto; display:block; border-radius:1rem;
  aspect-ratio:5/4; object-fit:cover;
  border:1px solid color-mix(in srgb, var(--accent) 28%, var(--rule));
  box-shadow:0 26px 70px -28px rgba(0,0,0,.95),
             0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent);
}
/* ⭐ THE CAPTION SITS ON THE PHOTOGRAPH, not under it — it keeps the hero one
   object instead of two stacked things, and it lands where the eye already is. */
.hero__figcap{display:block; margin:.55rem 0 1.1rem; font-family:var(--f-mono);
  font-size:.6875rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3)}
/* ⚠️ The photo is the brightest thing on a very dark page, so it needs a floor
   under it or it looks pasted on. */
.hero__fig::before{content:""; position:absolute; inset:10% -8% -14% -8%; z-index:-1;
  border-radius:50%; filter:blur(48px);
  background:radial-gradient(60% 60% at 50% 50%,
    color-mix(in srgb, var(--accent) 30%, transparent), transparent 72%)}
.hero__fig .shot__flag{position:absolute; top:.4rem; right:.4rem; z-index:2}
.hero__fig::after{content:""; position:absolute; left:50%; bottom:-2px; translate:-50% 0;
  width:76%; height:2px; background:linear-gradient(90deg,transparent,var(--accent),transparent); opacity:.75}
@media (max-width:56rem){
  .hero__fig{display:block; margin-top:1.5rem}
  .hero__fig img{aspect-ratio:16/10}
}
.ticketband{display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  gap:clamp(1.5rem,4vw,3rem); align-items:center}
@media (max-width:56rem){ .ticketband{grid-template-columns:minmax(0,1fr)} }
.hero{padding-block:clamp(2.25rem,5vw,4rem) clamp(1.75rem,4vw,3rem); border-bottom:1px solid var(--rule)}

/* ═══ FULL-BLEED PHOTOGRAPH HERO — 23 Sep 2026, owner ══════════════════════
   "all need full picture hero done prodfessionaly" · "no cartoons or diagrams
   … only modern elements so we can sell them".
   ⛔ WHAT THIS REPLACED, and why each piece had to go:
     · `.hero__bg` — two radial washes and a dot grid. A coloured panel with a
       gradient on it is the thing he named; it is also 2018.
     · the photograph in `.hero__fig` — a 5:4 crop with `border-radius:1rem`, a
       blurred green glow under it and an accent hairline. A photograph inside a
       rounded card with margin around it is explicitly not a full-picture hero;
       it is a picture of a picture.
   ⭐ The frame is `ph-slot-zero-turn.jpg`: wide, late-afternoon, real depth, a
   crew member actually working, and an open left third for the headline. It is
   the GROUND the headline stands on, not an illustration beside it.
   ⚠️ IT WAS SWAPPED WITH THE `.shots` GRID, NOT COPIED. zero-turn was the first
   figure in that grid; `ph-slot-mower-deck.jpg` took its place there and got a
   NEW caption and alt describing what that picture actually shows (a trimmer at
   a kerb edge, not a mower). check-photo-reuse.mjs exists because one picture
   twice is how a demo stops looking finished, and check-photo-subject.mjs
   exists because a swapped photo under an old caption is its own defect.
   photo-credits.json carries the corrected alt for both.
   ⛔ NO GENERATED IMAGE IS ALLOWED IN THIS SLOT. It depicts the work. */
.hero--photo{padding-block:clamp(3rem,6vw,5.5rem) clamp(2.5rem,5vw,4.5rem); border-bottom:0}
.hero__photo{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  object-position:50% 58%; z-index:-2}
/* 🔴 ux-build 3.6 — A POOL UNDER THE WORDS, NOT A LID ON THE PICTURE.
   ⛔ Every VERTICAL stop is in px. A gradient percentage resolves against the
   ELEMENT, and this hero runs ~1000px tall against a 900px screen, so a stop
   written as "58%" aims below the fold.
   ⭐ The horizontal stops are keyed to where the words actually end — the rail
   starts at 0 and the text column runs out around 56% — and the last stretch
   holds at .10 because that stretch IS the photograph. The yard card is opaque
   and sits on top of it, so nothing on the right needs darkening for contrast
   and darkening it anyway is how a hero ends up at mean luma 29. */
.hero__scrim{position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:
    linear-gradient(0deg, rgba(6,9,7,.26) 0px, rgba(6,9,7,0) 240px),
    linear-gradient(180deg, rgba(6,9,7,.28) 0px, rgba(6,9,7,.02) 220px),
    linear-gradient(94deg, rgba(6,9,7,.68) 0%, rgba(6,9,7,.62) 34%,
      rgba(6,9,7,.42) 50%, rgba(6,9,7,.16) 58%, rgba(6,9,7,.02) 74%,
      rgba(6,9,7,0) 86%)}
/* ⭐ ux-build 3.7 — FOREGROUND FIRST, THEN TAKE THE DARKNESS BACK. Measured on
   the shipped bytes at 1440: the first pass passed every node comfortably (the
   binding one, the accent italic, at 8.95:1 against a floor of 3) while the
   band sat at 56.6 mean against 60 and the picture side at 78.2 against 80 —
   ux-build 3.2a's exact tell that a scrim has been tuned against a phantom.
   There was ~3x the headroom needed and it was being spent on the photograph.
   ⭐ FINAL, MEASURED ON REAL PIXELS at 1440 (ON/OFF capture, glyph-diff, worst
   tenth — the first instrument composited a JS COPY of these stops and came back
   BIT-IDENTICAL across a real edit, which is a check that cannot fail):
     · white type          6.83:1   (worst single pixel 2.97) · floor 3 / 4.5
     · accent #BFF3CE      5.50:1   (worst single pixel 2.40) · floor 3
     · hero band mean luma 62.7                                · floor 60
     · the PHOTOGRAPH where it is actually visible (the strip to the right of
       the yard card)       97.7                               · floor 80
   ⚠️ REMEASURED after the frame was swapped to zero-turn, which is a SHADED park
   scene and ~13 luma points darker than the trimmer shot it replaced. The stops
   that were right for the old picture were wrong for this one. ⛔ A scrim is
   tuned to a photograph, not to a site — changing the image invalidates the
   number, and the number in a comment is the only thing that remembers.
   ⚠️ The "picture side" figure reads 69.1 if you take the whole right third,
   because the opaque yard card sits in most of it and is dark BY DESIGN. That is
   the card, not the photograph. Measure the strip the picture actually occupies
   or the number describes a panel you chose. Likewise the band figure is 59.6 if
   the capture's dark header strip is left in. */
/* 🔴 THE HERO HOLDS DARK IN BOTH THEMES. In light mode --ink is #131A21 and
   --ink-3 is #7A848E: near-black type and grey labels on a scrimmed photograph,
   which is a phone in daylight reading nothing. The hero gets its own ink. */
.hero--photo{--ph-ink:#FFFFFF; --ph-ink-2:#DFE7E3; --ph-ink-3:#B7C4BC}
.hero--photo h1,.hero--photo .rail dd{color:var(--ph-ink)}
.hero--photo .rail dt{color:var(--ph-ink-3)}
.hero--photo .rail dd em{color:var(--ph-ink-2)}
/* ⭐ ux-build 3.5 — over a photograph there is no secondary text. Full ink and
   a shadow that travels with the glyphs, not a dimmed grey. */
.hero--photo :is(h1,.rail dt,.rail dd,.rail dd em){
  text-shadow:0 1px 2px rgba(0,0,0,.72), 0 2px 20px rgba(0,0,0,.55)}
/* ⚠️ the accent italic is the one glyph set that is neither white nor large-
   and-bold, and it sits at the END of the line where a left-weighted pool is
   weakest. --accent is #1F7A4D in light mode — unreadable here — so the hero
   pins its own green. */
/* ⭐ ux-build 3.7 — FOREGROUND FIRST. On the new, darker frame the accent
   measured 4.44:1 after the scrim was lightened for the picture: still over
   its floor of 3, but bought by taking the photograph back down, which is the
   trade that ends with a hero at mean luma 29. Lightening the GLYPHS raises
   the same ratio and costs the picture nothing. */
.hero--photo h1 .hl{color:#BFF3CE}
.hero--photo .rail{border-color:rgba(255,255,255,.24)}
/* ═══ GLASS DOORS ON THE PHOTOGRAPH — 23 Sep 2026, owner: "use glass buttons" ══
   ⛔ THE HARD PART, AND IT IS NOT THE LOOK. On a full-bleed photograph a glass
   button's label contrast is decided by the PICTURE, not by a token — so these
   are measured the same way the headline was: ON/OFF captures, glyph pixels
   only, worst tenth, composited photo → scrim → glass → label.
   ⛔ `backdrop-filter` is unsupported or switched off for real readers, and a
   button nobody can read is not a graceful degradation. So the BASE declaration
   is opaque enough to clear 4.5:1 on its own with no blur at all, and the blur
   sits behind @supports as the enhancement. Both branches are measured.
   ⭐ HIERARCHY SURVIVES THE GLASS: the primary is a tinted green at the higher
   opacity, the secondary is clearer and colourless. Two equal glass buttons are
   two secondary buttons and the hero loses its first door.
   ⭐ Depth is a hairline plus a light top edge plus the blur — not a drop
   shadow. A drop shadow on a photograph is a 2018 tell.
   ⭐ MEASURED at 1440 on real pixels, glyph-diff, worst tenth, floor 4.5:
        with blur    #F3FBF6 6.62:1   #FFFFFF 6.97:1
        NO blur      #F3FBF6 6.32:1   #FFFFFF 6.65:1   ← the branch that matters
   ⚠️ These were remeasured after the scrim was lightened twice for the swapped
   photograph. A glass button's number is only true for the picture behind it —
   change the frame or the scrim and this comment is a claim, not a measurement. */
.hero--photo .hero__act .btn{
  border-radius:.55rem; border:1px solid rgba(255,255,255,.30);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.34);
  transition:background-color .16s ease, border-color .16s ease}
.hero--photo .hero__act .btn:hover{transform:none}
/* PRIMARY — tinted glass, higher opacity. The fallback alpha is the one that
   has to carry the label on its own. */
.hero--photo .hero__act .btn--go{
  background:rgba(19,78,46,.70); color:#F3FBF6;
  border-color:rgba(184,238,201,.46)}
.hero--photo .hero__act .btn--go:hover{background:rgba(24,96,57,.80)}
/* SECONDARY — clearer, colourless glass */
.hero--photo .hero__act .btn--line{
  background:rgba(7,13,9,.46); color:#FFFFFF;
  border-color:rgba(255,255,255,.46)}
.hero--photo .hero__act .btn--line:hover{background:rgba(7,13,9,.60)}
@supports (backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px)){
  .hero--photo .hero__act .btn{
    -webkit-backdrop-filter:blur(16px) saturate(1.18);
    backdrop-filter:blur(16px) saturate(1.18)}
  .hero--photo .hero__act .btn--go{background:rgba(19,78,46,.54)}
  .hero--photo .hero__act .btn--line{background:rgba(7,13,9,.30)}
}
/* ⭐ a focus ring that survives being drawn on glass over a photograph: the
   accent alone disappears into foliage, so it carries a white outer keyline. */
.hero--photo .hero__act .btn:focus-visible{
  outline:2.5px solid #EAFBEF; outline-offset:3px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.34), 0 0 0 5.5px rgba(10,40,22,.85)}
/* the glow and hairline belonged to the photo card that is no longer there */
.hero--photo .hero__fig::before,.hero--photo .hero__fig::after{display:none}
@media (prefers-reduced-motion:no-preference){
  .hero--photo .hero__photo{will-change:transform;
    transform:translate3d(calc(var(--px,0) * -7px), calc(var(--sy,0) * 22px), 0) scale(1.06);
    transition:transform .45s cubic-bezier(.2,.8,.3,1)}
  .hero.tracking .hero__photo{transition:none}
}
.eyebrow{display:flex; align-items:center; gap:.6rem; margin-bottom:1.1rem; flex-wrap:wrap}
.eyebrow .dot{width:.5rem; height:.5rem; border-radius:50%; background:var(--pass); flex:none}
.hero h1{font-size:var(--step-4); font-weight:800; max-width:16ch; margin-bottom:1.1rem;
  overflow-wrap:break-word}
.hero h1 u{text-decoration:none; color:var(--accent)}
.hero__lede{font-size:var(--step-1); color:var(--ink-2); max-width:46ch; margin-bottom:1.75rem}
.hero__act{display:flex; gap:.75rem; flex-wrap:wrap; align-items:center}
.hero__note{font-size:.8125rem; color:var(--ink-3); max-width:36ch; margin-top:1rem}
.split{display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr); gap:clamp(1.75rem,4vw,3rem); align-items:start}
@media (max-width:56rem){ .split{grid-template-columns:minmax(0,1fr)} }
/* ⚠️ 23 Sep 2026 — the hero's text column is 2 blocks now (headline + doors),
   about 600px against a 990px figure column. `align-items:start` left ~390px of
   dead black under the buttons, which is what a stack looks like once you take
   the stack out. Centring is a hero-only override: every other `.split` on this
   site still has two columns of similar height and wants to top-align. */
@media (min-width:56.01rem){ .hero .split{align-items:center} }

/* page head for interior routes */
.phead{padding-block:clamp(2rem,4vw,3.25rem) clamp(1.5rem,3vw,2.25rem); border-bottom:1px solid var(--rule)}
.crumb{font-family:var(--f-mono); font-size:.75rem; color:var(--ink-3); margin-bottom:.9rem}
.crumb a{text-decoration:none}
.crumb a:hover{color:var(--accent)}
.phead h1{font-size:var(--step-3); font-weight:800; max-width:20ch; margin-bottom:.85rem}
.phead p{font-size:var(--step-1); color:var(--ink-2); max-width:52ch}

.ticket{background:var(--surface); border:1px solid var(--rule); border-radius:.4rem; box-shadow:var(--shadow); overflow:hidden}
.ticket__cap{display:flex; align-items:center; gap:.5rem; padding:.5rem 1.1rem; font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.11em; text-transform:uppercase}
.ticket__cap{background:var(--surface-2); color:var(--ink-2); border-bottom:1px solid var(--rule)}
.ticket__hd{display:flex; align-items:baseline; gap:.75rem; padding:.85rem 1.1rem; border-bottom:1px solid var(--rule); background:var(--surface-2); flex-wrap:wrap}
.ticket__no{font-family:var(--f-mono); font-weight:700; font-size:.8125rem}
.ticket__veh{font-size:.8125rem; color:var(--ink-2); margin-left:auto; font-family:var(--f-mono)}
.ticket__body{padding:1.1rem}
.line{display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:.6rem .9rem; align-items:baseline; padding-block:.7rem; border-bottom:1px dashed var(--rule-soft)}
.line:last-of-type{border-bottom:0}
.line__code{font-family:var(--f-mono); font-weight:700; font-size:.8125rem; color:var(--accent); background:var(--accent-soft); padding:.1rem .4rem; border-radius:.2rem; white-space:nowrap}
.line__what b{display:block; font-weight:600; font-size:.9375rem}
.line__what span{display:block; font-size:.8125rem; color:var(--ink-2); margin-top:.1rem}
.line__amt{font-family:var(--f-mono); font-weight:700; font-size:.9375rem; white-space:nowrap; font-variant-numeric:tabular-nums}
.line__amt em{display:block; font-style:normal; font-weight:400; font-size:.6875rem; color:var(--ink-3); text-align:right}
.ticket__ft{display:flex; align-items:center; gap:.75rem; padding:.9rem 1.1rem; border-top:1px solid var(--rule); background:var(--surface-2); flex-wrap:wrap}
.stamp{display:inline-flex; align-items:center; gap:.4rem; font-family:var(--f-mono); font-size:.75rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--pass); background:var(--pass-soft); border:1px solid color-mix(in srgb, var(--pass) 30%, transparent); padding:.28rem .55rem; border-radius:.2rem}
.ticket__tot{margin-left:auto; font-family:var(--f-mono); font-weight:700; font-size:1.0625rem}
.ticket__tot em{font-style:normal; font-size:.6875rem; color:var(--ink-3); font-weight:400; letter-spacing:.1em; text-transform:uppercase; margin-right:.4rem}

.sec{padding-block:clamp(2.5rem,5vw,4rem); border-bottom:1px solid var(--rule)}
.sec:last-child{border-bottom:0}
.sec__hd{max-width:var(--measure); margin-bottom:2rem}
.sec__hd h2{font-size:var(--step-3); font-weight:800; margin-block:.6rem .7rem}
.sec__hd p{color:var(--ink-2); font-size:var(--step-1)}
.prose{max-width:var(--measure); display:grid; gap:1.15rem}
.prose h3{font-size:1.25rem; font-weight:700; margin-top:.75rem}
.prose p{color:var(--ink-2)}
.prose ul{margin:0; padding-left:1.15rem; color:var(--ink-2); display:grid; gap:.5rem}
.prose strong{color:var(--ink); font-weight:600}

.steps{display:grid}
.step{display:grid; grid-template-columns:3.25rem minmax(0,1fr); gap:1.25rem; padding-block:1.35rem; border-top:1px solid var(--rule-soft); align-items:start}
.step:first-child{border-top:0; padding-top:0}
.step__n{font-family:var(--f-mono); font-weight:700; font-size:.8125rem; color:var(--n-col,var(--ink-3)); padding-top:.2rem}
.step h3{font-size:1.1875rem; font-weight:700; margin-bottom:.35rem}
.step p{color:var(--ink-2); max-width:58ch; font-size:.9375rem}
.step__meta{margin-top:.5rem; font-family:var(--f-mono); font-size:.75rem; color:var(--ink-3)}


/* ⭐ THE SPECIAL IS A WORK ORDER, NOT A COUPON. Every lawn company runs a
   first-month offer and every one of them prints the same starburst. This one
   is set as a torn-off ticket so it belongs to the same world as the water
   report in the hero — the page has one idea, not two. */
/* ⭐ A COUPON THAT IS ACTUALLY A COUPON. Perforated edge, tear-off stub, and it
   prints on its own — @media print hides the entire site and leaves this one card
   on the page, so "print" produces a coupon and not 26 pages of website.
   ⛔ The dashes are a repeating-linear-gradient border, not an image: it stays
   crisp at any zoom and costs nothing. */
/* ⭐ THE SPEC PLATE. Every service page gets a drawing instead of a stock photo,
   because we have three usable photographs and fifteen service pages — and the
   same borrowed garage repeated fifteen times is worse than no picture at all.
   Each plate is the service's own telltale on an engineering grid, with that
   service's real figures set as dimension callouts. It teaches something a stock
   image cannot, and it costs about 2 KB. */
.plate{position:relative; border:1px solid var(--rule); border-radius:.45rem;
  background:var(--surface); overflow:hidden; display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.05fr); align-items:center; gap:0}
@media (max-width:44rem){ .plate{grid-template-columns:minmax(0,1fr)} }
.plate__draw{position:relative; display:grid; place-items:center; padding:clamp(1.5rem,4vw,2.5rem);
  background:
    linear-gradient(to right, color-mix(in srgb,var(--rule) 45%,transparent) 1px, transparent 1px) 0 0/28px 28px,
    linear-gradient(to bottom, color-mix(in srgb,var(--rule) 45%,transparent) 1px, transparent 1px) 0 0/28px 28px,
    var(--surface-2)}
.plate__draw .sym{width:7.5rem; height:7.5rem; border-radius:.9rem;
  background:color-mix(in srgb, currentColor 12%, transparent);
  border:1px solid color-mix(in srgb, currentColor 34%, transparent)}
.plate__draw .sym svg{width:4.2rem; height:4.2rem; stroke-width:1.5}
.plate__specs{padding:clamp(1.25rem,3vw,2rem); display:grid; gap:0; align-content:center}
.plate__row{display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:baseline;
  gap:.6rem; padding-block:.7rem; border-bottom:1px dashed var(--rule-soft)}
.plate__row:last-child{border-bottom:0}
.plate__k{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.11em;
  text-transform:uppercase; color:var(--ink-3); white-space:nowrap}
.plate__lead{height:1px; background:repeating-linear-gradient(to right,
  color-mix(in srgb,var(--rule) 90%,transparent) 0 4px, transparent 4px 8px); align-self:center}
.plate__v{font-family:var(--f-mono); font-weight:700; font-size:.9375rem; white-space:nowrap}
.plate__cap{position:absolute; left:.7rem; top:.7rem; font-family:var(--f-mono);
  font-size:.625rem; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-3)}

.coupon{position:relative; background:var(--surface); color:var(--ink);
  border-radius:.5rem; box-shadow:var(--shadow); overflow:hidden;
  display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:stretch;
  border:2px dashed var(--rule)}
@media (max-width:46rem){ .coupon{grid-template-columns:minmax(0,1fr)} }
.coupon__body{padding:clamp(1.25rem,3vw,2rem)}
.coupon__tag{display:inline-flex; align-items:center; gap:.45rem; font-family:var(--f-mono);
  font-size:.6875rem; font-weight:700; letter-spacing:.13em; text-transform:uppercase;
  color:var(--accent); margin-bottom:.7rem}
.coupon h2{font-size:var(--step-2); font-weight:800; max-width:20ch; margin-bottom:.6rem}
.coupon__lede{color:var(--ink-2); max-width:46ch; margin-bottom:1rem}
.coupon__list{list-style:none; margin:0 0 1.1rem; padding:0; display:grid; gap:.4rem;
  font-size:.9375rem; color:var(--ink-2)}
.coupon__list li{display:grid; grid-template-columns:1.1rem minmax(0,1fr); gap:.55rem; align-items:start}
.coupon__list svg{width:1.1rem; height:1.1rem; stroke:var(--pass); fill:none; stroke-width:2;
  stroke-linecap:round; stroke-linejoin:round; margin-top:.25rem}
.coupon__fine{font-size:.75rem; color:var(--ink-3); max-width:54ch}

/* the tear-off stub, divided by a real perforation */
.coupon__stub{position:relative; display:grid; align-content:center; justify-items:center;
  gap:.2rem; text-align:center; padding:clamp(1.25rem,3vw,2rem) clamp(1.5rem,3vw,2.25rem);
  background:var(--surface-2); min-width:13rem}
.coupon__stub::before{content:""; position:absolute; left:0; top:.75rem; bottom:.75rem; width:2px;
  background:repeating-linear-gradient(to bottom, var(--rule) 0 6px, transparent 6px 12px)}
@media (max-width:46rem){
  .coupon__stub::before{left:.75rem; right:.75rem; top:0; bottom:auto; width:auto; height:2px;
    background:repeating-linear-gradient(to right, var(--rule) 0 6px, transparent 6px 12px)}
}
.coupon__price{font-family:var(--f-mono); font-weight:700; line-height:1;
  font-size:clamp(2.6rem,1.8rem + 3vw,3.75rem); color:var(--accent)}
.coupon__cap{font-family:var(--f-mono); font-size:.625rem; letter-spacing:.13em;
  text-transform:uppercase; color:var(--ink-3)}
/* ONE PRICE ON THIS STUB, not the auto build's two tiers.
   ⛔ Do not re-add a second tier without a second real price to put in it. */
.coupon__tiers{display:grid; gap:.7rem; width:100%}
.coupon__tier{display:grid; gap:.1rem; justify-items:center}
.coupon__tier + .coupon__tier{padding-top:.7rem;
  border-top:2px dashed color-mix(in srgb, var(--rule) 80%, transparent)}
.coupon__tier .coupon__price{font-size:clamp(2.1rem,1.5rem + 2.2vw,2.9rem)}
.coupon__act{display:flex; gap:.5rem; flex-wrap:wrap; justify-content:center; margin-top:.9rem}
.coupon__act .btn{padding:.5rem .85rem; min-height:2.5rem; font-size:.875rem}

@media print{
  body > *:not(main), .top, .foot, .draft, .launch, .chat, .rail, .sec:not(.coupon-sec){display:none!important}
  .coupon-sec, .coupon-sec *{visibility:visible}
  .coupon{box-shadow:none; border-color:#000; color:#000; background:#fff}
  .coupon__stub{background:#f4f4f4}
  .coupon__act{display:none!important}
  .coupon__price{color:#000}
  @page{margin:12mm}
}

.offer{display:grid; grid-template-columns:minmax(0,1fr) auto; gap:clamp(1.25rem,3vw,2.5rem);
  align-items:center; background:var(--surface); border:1px solid var(--rule);
  border-left:4px solid var(--accent); border-radius:.45rem; padding:clamp(1.25rem,3vw,2rem);
  box-shadow:var(--shadow); position:relative; overflow:hidden}
@media (max-width:46rem){ .offer{grid-template-columns:minmax(0,1fr)} }
.offer__tag{display:inline-flex; align-items:center; gap:.45rem; font-family:var(--f-mono);
  font-size:.6875rem; font-weight:700; letter-spacing:.13em; text-transform:uppercase;
  color:var(--accent); margin-bottom:.7rem}
.offer h2{font-size:var(--step-2); font-weight:800; max-width:18ch; margin-bottom:.65rem}
.offer__lede{color:var(--ink-2); max-width:48ch; margin-bottom:1.1rem}
.offer__list{list-style:none; margin:0 0 1.25rem; padding:0; display:grid; gap:.4rem;
  font-size:.9375rem; color:var(--ink-2)}
.offer__list li{display:grid; grid-template-columns:1.1rem minmax(0,1fr); gap:.55rem; align-items:start}
.offer__list svg{width:1.1rem; height:1.1rem; stroke:var(--pass); fill:none; stroke-width:2;
  stroke-linecap:round; stroke-linejoin:round; margin-top:.25rem}
.offer__fine{font-size:.75rem; color:var(--ink-3); max-width:52ch}
.offer__price{display:grid; justify-items:center; gap:.15rem; text-align:center;
  border:1px dashed var(--rule); border-radius:.4rem; padding:1.1rem 1.4rem;
  background:var(--surface-2); min-width:11rem}
.offer__price .amt{font-family:var(--f-mono); font-weight:700; font-size:clamp(2rem,1.4rem+2vw,2.75rem); line-height:1}
.offer__price .cap{font-family:var(--f-mono); font-size:.625rem; letter-spacing:.13em;
  text-transform:uppercase; color:var(--ink-3)}
.offer__act{margin-top:.9rem; display:flex; gap:.6rem; flex-wrap:wrap; justify-content:center}
.svc{display:grid; grid-template-columns:repeat(auto-fill,minmax(15.5rem,1fr)); gap:1px; background:var(--rule); border:1px solid var(--rule); border-radius:.4rem; overflow:hidden}
.svc a{background:var(--surface); padding:1.15rem; text-decoration:none; align-content:start; transition:background-color .12s ease}
.svc a:hover{background:var(--surface-2)}
.svc a{grid-template-columns:auto minmax(0,1fr); column-gap:.85rem}
.svc h3{font-size:1.0625rem; font-weight:700}
.sym{width:2.5rem; height:2.5rem; flex:none; border-radius:.45rem; display:grid; place-items:center;
     background:color-mix(in srgb, currentColor 13%, transparent);
     border:1px solid color-mix(in srgb, currentColor 30%, transparent)}
.sym svg{width:1.4rem; height:1.4rem; stroke:currentColor; fill:none; stroke-width:1.7;
     stroke-linecap:round; stroke-linejoin:round}
.svc a{display:flex; align-items:flex-start; gap:.9rem}
.svc .txt{display:grid; gap:.3rem; min-width:0}
.sym--lg{width:3.25rem; height:3.25rem; border-radius:.55rem}
.sym--lg svg{width:1.85rem; height:1.85rem}
.svc p{font-size:.875rem; color:var(--ink-2)}
.svc .spec{font-family:var(--f-mono); font-size:.6875rem; color:var(--ink-3); margin-top:.4rem}


/* ⭐ PHOTOGRAPHY BAND. A real photograph earns its place by showing the WORK, not
   by decorating the page. Sized and cropped here so a replacement drops in without
   the layout moving — the aspect ratio is fixed, the image covers it. */
.shots{display:grid; grid-template-columns:repeat(auto-fit,minmax(13rem,1fr)); gap:1px;
  background:var(--rule); border:1px solid var(--rule); border-radius:.45rem; overflow:hidden}
.shot{position:relative; background:var(--surface); display:grid}
.shot img{width:100%; aspect-ratio:5/4; object-fit:cover; display:block}
.shot figcaption{padding:.7rem .9rem .85rem; font-size:.8125rem; color:var(--ink-2)}
.shot figcaption b{display:block; color:var(--ink); font-weight:600; font-size:.875rem; margin-bottom:.15rem}
.shot__flag{position:absolute; top:.6rem; left:.6rem; font-family:var(--f-mono); font-size:.625rem;
  font-weight:700; letter-spacing:.11em; text-transform:uppercase; color:var(--todo);
  background:color-mix(in srgb, var(--todo-soft) 92%, transparent); border:1px solid var(--todo-line);
  padding:.25rem .5rem; border-radius:.2rem}
.shot--empty{display:grid; place-items:center; text-align:center; gap:.3rem; padding:1.5rem 1rem;
  aspect-ratio:5/4; border:1.5px dashed var(--rule); background:var(--surface-2)}
.shot--empty b{font-family:var(--f-mono); font-size:.75rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-2)}
.shot--empty span{font-size:.8125rem; color:var(--ink-3); max-width:26ch}

/* ⛔ THE DEMONSTRATION STRIP — see the comment in build.py's SHELL.
   Uses the accent so it reads as part of the design rather than as a browser
   warning, but it is the first thing on every page and it is never hidden. */
/* 🔴 TWO CHAT LAUNCHERS IN ONE CORNER (7 Sep 2026). This site is framed on
   botsonstaff.com/see-a-repair-site, and that page has its own fixed chat pill
   in the bottom-right. Measured in page coordinates: ours landed at x1292-1348
   / y1083-1139 and theirs at x1245-1380 / y822-880 — not overlapping, but two
   chat bubbles in the same corner region, one just inside a bordered frame and
   one just outside it. A visitor cannot tell which is the shop's.
   ⭐ SO WHEN FRAMED, THE SHOP'S CHAT MOVES LEFT. Both stay usable, the demo
   keeps the feature we are actually selling, and which is which becomes
   obvious. ⛔ Do not hide it instead: "booking and the chat both live" is what
   the page above the frame promises. */
html.framed .launch{right:auto; left:1rem}
html.framed .chat{right:auto; left:1rem}
/* ═══ THE WATER REPORT ════════════════════════════════════════════════════
   ⭐ THE HERO OF THIS SITE IS A DOCUMENT, not a photograph of a lawn. Every
   competitor shows a green rectangle and the word "beautiful"; none of them
   publishes a cut height, which is the number that actually separates one lawn
   company from another.
   ⚠️ The row that is OUT OF RANGE is colored, and the five that are fine are
   not. ⛔ Do not color all six — a readout where everything glows says
   nothing, and the whole point is that the eye lands on the one line that
   matters before a word has been read. */
.wr{position:relative; background:var(--surface); border:1px solid var(--rule);
  border-radius:.75rem; overflow:hidden; box-shadow:var(--shadow);
  font-family:var(--f-mono)}
.wr__hd{display:flex; justify-content:space-between; gap:1rem; align-items:baseline;
  padding:.75rem 1rem; border-bottom:1px solid var(--rule); background:var(--surface-2);
  font-size:.625rem; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-3)}
.wr__when{white-space:nowrap}
.wr__row{display:grid; grid-template-columns:3.1rem minmax(0,1fr) 4.5rem auto;
  grid-template-areas:"code name bar val" "code rng rng rng";
  gap:.15rem .7rem; align-items:center; padding:.6rem 1rem;
  border-bottom:1px dashed var(--rule-soft)}
.wr__code{grid-area:code; font-size:.6875rem; font-weight:700; letter-spacing:.06em;
  color:var(--ink-3)}
.wr__name{grid-area:name; font-family:var(--f-body); font-size:.875rem; color:var(--ink)}
.wr__bar{grid-area:bar; height:.3rem; border-radius:99px; background:var(--rule); overflow:hidden}
.wr__bar i{display:block; height:100%; border-radius:99px; background:var(--pass)}
.wr__val{grid-area:val; font-size:1rem; font-weight:700; text-align:right; white-space:nowrap}
.wr__val em{font-style:normal; font-size:.625rem; color:var(--ink-3); margin-left:.2rem}
.wr__rng{grid-area:rng; font-size:.625rem; letter-spacing:.08em; color:var(--ink-3)}
/* the one line that is wrong */
.wr__row--high{background:color-mix(in srgb, var(--todo) 9%, transparent)}
.wr__row--high .wr__bar i{background:var(--todo)}
.wr__row--high .wr__val{color:var(--todo)}
.wr__row--high .wr__code{color:var(--todo)}
.wr__note{padding:.85rem 1rem; font-family:var(--f-body); font-size:.8125rem;
  line-height:1.5; color:var(--ink-2); border-bottom:1px solid var(--rule);
  background:color-mix(in srgb, var(--todo-soft) 60%, transparent)}
.wr__note b{color:var(--ink)}

/* 🔴 THE REFUSAL IS THE LOUDEST THING ON THE CARD NOW — 23 Sep 2026.
   It used to be `.wr__chip`: a 10px uppercase pill in the card footer, set at
   the same size as the page furniture beside it and sharing a row with "next
   visit · zone 4 head reset". The single most persuasive element this site has
   was smaller than the field labels above it.
   ⭐ THERE IS NO PRICE ON IT AND THERE MUST NOT BE. Every other demo carries the
   idea as a $0.00 line, because every other demo's hero is an invoice or a
   repair order. This one is a YARD CARD — a record of what was done to a lawn,
   not a bill — and forcing a currency figure onto it would be a lie about what
   the document is. The VERDICT carries the weight instead: its own rule above
   and below, the largest type anywhere on the card, and ONE sentence of reason
   underneath that a stranger can go and check against the county's own rule.
   ⛔ GREEN, NOT AMBER. Amber on this card means a reading is out of range — that
   is the zone 4 row. A deliberate refusal is not a fault and must never borrow
   the colour of one, or the card starts saying two different things with one
   hue and the eye stops trusting either. */
.wr__no{border-top:2px solid var(--pass); border-bottom:2px solid var(--pass);
  background:color-mix(in srgb, var(--pass) 10%, transparent);
  padding:1rem 1rem 1.1rem; display:grid; gap:.35rem}
.wr__nok{font-size:.625rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3)}
.wr__nov{font-family:var(--f-display); font-weight:800; letter-spacing:-.025em; line-height:1;
  font-size:clamp(1.55rem,1.05rem + 2.1vw,2.3rem); color:var(--pass)}
.wr__nor{font-family:var(--f-body); font-size:.8125rem; line-height:1.5; color:var(--ink-2);
  max-width:46ch}
.wr__nor b{color:var(--ink)}
.wr__ft{display:flex; justify-content:space-between; align-items:center; gap:1rem;
  padding:.7rem 1rem; font-size:.625rem; letter-spacing:.11em; text-transform:uppercase;
  color:var(--ink-3); background:var(--surface-2)}
.wr__ft b{color:var(--ink)}
.wr__chip{color:var(--pass); border:1px solid color-mix(in srgb, var(--pass) 45%, transparent);
  background:color-mix(in srgb, var(--pass) 12%, transparent);
  padding:.25rem .5rem; border-radius:.25rem; font-weight:700}
@media (max-width:30rem){ .wr__row{grid-template-columns:2.8rem minmax(0,1fr) auto;
  grid-template-areas:"code name val" "code bar bar" "code rng rng"} }

.demostrip{display:flex; align-items:flex-start; gap:.6rem; padding:.6rem clamp(1rem,4vw,2rem);
  background:var(--accent-soft); border-bottom:1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  font-family:var(--f-mono); font-size:.75rem; line-height:1.45; color:var(--ink-2)}
.demostrip b{color:var(--ink); font-weight:700}
.demostrip a{color:var(--accent); font-weight:600}
.demostrip__dot{flex:none; width:.5rem; height:.5rem; margin-top:.4rem; border-radius:50%;
  background:var(--accent)}
@media print{.demostrip{display:none}}
.mapcard{display:flex; align-items:center; gap:.9rem; text-decoration:none;
  background:var(--surface); border:1px solid var(--rule); border-left:3px solid var(--accent);
  border-radius:.45rem; padding:1.1rem 1.25rem; min-height:9rem}
.mapcard:hover{background:var(--surface-2)}
.mapcard__pin{width:2.75rem; height:2.75rem; flex:none; border-radius:.45rem; display:grid; place-items:center;
  background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--accent)}
.mapcard__pin svg{width:1.5rem; height:1.5rem; fill:currentColor}
.mapcard b{display:block; font-family:var(--f-display); font-size:1.0625rem; font-weight:700}
.mapcard span{font-size:.9375rem; color:var(--ink-2)}
.mapcard em{display:block; font-style:normal; font-family:var(--f-mono); font-size:.6875rem;
  letter-spacing:.11em; text-transform:uppercase; color:var(--accent); margin-top:.35rem}
/* ⛔ `.mount` IS GONE — 23 Sep 2026. The rules were still here eight days after
   the comment below was written, and reviews.html was still mounting the box
   they styled: the CSS for the replacement had landed and the MARKUP never did.
   Deleted outright so nothing can render an empty grey slot again. If you find
   a `class="mount"` anywhere in this demo, it is older than this line. */

/* 🔴 THE REVIEW BLOCK THAT HAS NOTHING TO SHOW — 15 Sep 2026.
   It was a `.mount`: an empty grey box reading GOOGLE REVIEWS LOAD HERE, sitting
   exactly where a real client's site carries real reviews. Measured against
   Jose's live site, that box and a thin photo count were the entire gap.
   ⛔ THE FIX IS NOT TO TYPE SOME REVIEWS IN. A fictional company carrying
   testimonials is what 16 CFR 465.2 is written about, and it reaches the party
   that merely PROVIDED the means.
   ⭐ So the emptiness becomes the argument — the same move the rest of this site
   makes, where the best line on the yard card is the one where nothing was sold.
   Bordered in the accent rather than the hairline rule, because this is now a
   point being made and not a slot waiting to be filled. */
.revwhy{border:1px solid var(--accent); border-radius:.6rem;
  background:linear-gradient(180deg,var(--accent-soft),transparent 60%),var(--surface-2);
  padding:clamp(1.15rem,3vw,1.9rem)}
.revwhy__lbl{margin:0; font-family:var(--f-mono); font-size:.72rem; letter-spacing:.11em;
  text-transform:uppercase; color:var(--accent)}
.revwhy__lede{margin:.55rem 0 0; font-size:var(--step-1); line-height:1.45; color:var(--ink);
  max-width:56ch}
.revwhy__how{list-style:none; margin:1.35rem 0 0; padding:0; display:grid; gap:.9rem;
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}
.revwhy__how li{margin:0; padding:.9rem 1rem; border:1px solid var(--rule);
  border-radius:.45rem; background:var(--paper); font-size:.8125rem; line-height:1.5;
  color:var(--ink-2)}
.revwhy__how span{display:inline-grid; place-items:center; width:1.4rem; height:1.4rem;
  border-radius:50%; background:var(--accent); color:var(--accent-ink);
  font-family:var(--f-mono); font-size:.72rem; font-weight:700}
.revwhy__how b{display:block; margin:.45rem 0 .3rem; color:var(--ink); font-size:.875rem}
.revwhy__foot{margin:1.1rem 0 0; font-size:.78rem; color:var(--ink-3)}
.slot{border:1.5px dashed var(--rule); border-radius:.4rem; background:var(--surface-2); display:grid; place-items:center; text-align:center; padding:1.5rem 1rem; gap:.3rem; min-height:11rem}
.slot b{font-family:var(--f-mono); font-size:.75rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-2)}
.slot span{font-size:.8125rem; color:var(--ink-3); max-width:32ch}

.cta{color:var(--ink); border-radius:.5rem; padding:clamp(1.75rem,4vw,2.75rem); display:grid; gap:1.25rem}
.cta{background:var(--surface-2); border:1px solid var(--rule)}
.cta h2{font-size:var(--step-2); font-weight:800; max-width:22ch}
.cta p{max-width:52ch}
.cta p{color:var(--ink-2)}
.cta__row{display:flex; gap:.75rem; flex-wrap:wrap; align-items:center}

/* faq */
.faq{border-top:1px solid var(--rule-soft); max-width:var(--measure)}
.faq details{border-bottom:1px solid var(--rule-soft)}
.faq summary{cursor:pointer; padding:1rem 0; font-family:var(--f-display); font-weight:700; font-size:1.0625rem; list-style:none; display:flex; gap:.75rem; align-items:flex-start; min-height:2.75rem}
.faq summary::-webkit-details-marker{display:none}
.faq summary::before{content:"+"; font-family:var(--f-mono); color:var(--accent); font-weight:700; flex:none; line-height:1.4}
.faq details[open] summary::before{content:"\2212"}
.faq p{padding:0 0 1.1rem 1.6rem; color:var(--ink-2); max-width:58ch}

/* tables */
.tbl-wrap{overflow-x:auto; border:1px solid var(--rule); border-radius:.4rem; background:var(--surface)}
table{border-collapse:collapse; width:100%; min-width:34rem; font-size:.9375rem}
th,td{text-align:left; padding:.75rem .9rem; border-bottom:1px solid var(--rule-soft); vertical-align:top}
th{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-3); background:var(--surface-2)}
tr:last-child td{border-bottom:0}
td.num{font-family:var(--f-mono); font-variant-numeric:tabular-nums; white-space:nowrap}

.areas{display:flex; flex-wrap:wrap; gap:.5rem; list-style:none; margin:0; padding:0}
.areas li{font-family:var(--f-mono); font-size:.8125rem; border:1px solid var(--rule); border-radius:1.25rem; padding:.35rem .75rem; background:var(--surface)}


/* his own channels — ⛔ Yelp is deliberately absent, it is barred estate-wide */
.social{display:flex; gap:.5rem; flex-wrap:wrap; margin-top:.9rem}
.social a{display:inline-flex; align-items:center; gap:.4rem; text-decoration:none;
  border:1px solid var(--rule); border-radius:1.25rem; padding:.35rem .75rem;
  font-size:.8125rem; color:var(--ink-2); background:var(--surface)}
.social a:hover{border-color:var(--accent); color:var(--ink)}
.social svg{width:.95rem; height:.95rem; fill:currentColor}
.foot{padding-block:2.5rem 3rem; font-size:.875rem; color:var(--ink-2); border-top:1px solid var(--rule)}
.foot__grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(12rem,1fr)); gap:1.75rem; margin-bottom:2rem}
.foot h4{font-family:var(--f-mono); font-size:.6875rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3); margin-bottom:.6rem}
.foot ul{list-style:none; margin:0; padding:0; display:grid; gap:.35rem}
.foot a{text-decoration:none; color:var(--ink-2)}
.foot a:hover{color:var(--ink); text-decoration:underline}

/* ⭐ A CIRCLE, NOT A PILL. 3.5rem is comfortably over the 44px touch-target
   floor, and the label moves to aria-label so a screen reader still gets
   "Describe a symptom" while the corner stays quiet. The tooltip on hover
   keeps the meaning discoverable for everyone else. */
.launch{position:fixed; right:1.15rem; bottom:1.15rem; z-index:40;
  width:3.5rem; height:3.5rem; border-radius:50%; padding:0;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent); color:var(--accent-ink); border:0; cursor:pointer;
  box-shadow:0 6px 20px -4px rgba(0,0,0,.55), 0 0 0 1px color-mix(in srgb,var(--accent) 55%,transparent);
  transition:transform .15s cubic-bezier(.3,1.3,.5,1), box-shadow .15s ease}
.launch:hover{transform:scale(1.06)}
.launch:active{transform:scale(.96)}
.launch svg{width:1.55rem; height:1.55rem; fill:none; stroke:currentColor;
  stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round}
.launch::after{content:attr(data-label); position:absolute; right:calc(100% + .6rem);
  white-space:nowrap; background:var(--surface); color:var(--ink);
  border:1px solid var(--rule); border-radius:.3rem; padding:.35rem .6rem;
  font-family:var(--f-body); font-size:.8125rem; box-shadow:var(--shadow);
  opacity:0; pointer-events:none; transform:translateX(4px); transition:opacity .14s ease, transform .14s ease}
.launch:hover::after,.launch:focus-visible::after{opacity:1; transform:none}
@media (max-width:30rem){ .launch::after{display:none} }
.chat{position:fixed; right:1rem; bottom:1rem; z-index:41; width:min(23rem, calc(100vw - 2rem)); background:var(--surface); border:1px solid var(--rule); border-radius:.5rem; box-shadow:var(--shadow); overflow:hidden; display:grid; grid-template-rows:auto 1fr auto auto; max-height:min(32rem, calc(100vh - 2rem))}
.chat__hd{display:flex; align-items:center; gap:.6rem; padding:.75rem .9rem; border-bottom:1px solid var(--rule); background:var(--surface-2)}
.chat__hd b{font-family:var(--f-display); font-size:.9375rem}
.chat__hd .lbl{margin-left:auto}
.chat__x{background:none; border:0; cursor:pointer; color:var(--ink-2); font-size:1.15rem; line-height:1; padding:.2rem .3rem; min-height:2rem}
.chat__log{padding:.9rem; display:grid; gap:.7rem; overflow-y:auto; align-content:start}
.msg{max-width:85%; padding:.55rem .75rem; border-radius:.45rem; font-size:.875rem; line-height:1.45}
.msg--bot{background:var(--surface-2); border:1px solid var(--rule-soft); justify-self:start; border-bottom-left-radius:.1rem}
.msg--me{background:var(--accent); color:var(--accent-ink); justify-self:end; border-bottom-right-radius:.1rem}
.chat__opts{display:flex; flex-wrap:wrap; gap:.4rem; padding:0 .9rem .9rem}
.chip{background:transparent; border:1.5px solid var(--rule); color:var(--ink); border-radius:1.25rem; padding:.4rem .75rem; font-size:.8125rem; font-family:var(--f-body); cursor:pointer; min-height:2rem}
.chip:hover{border-color:var(--accent); color:var(--accent)}
.chat__ft{padding:.6rem .9rem .75rem; border-top:1px solid var(--rule); font-size:.6875rem; color:var(--ink-3); font-family:var(--f-mono)}
[hidden]{display:none!important}
@media (prefers-reduced-motion:reduce){ *{animation:none!important; transition:none!important} }


/* ══════════════════════════════════════════════════════════════════════════
   ⭐ THE PROFESSIONAL PASS — 11 Sep 2026.
   Measured against the reference client rebuild the owner holds this bar to,
   this demo was missing five things, and not one of them was a photograph:
     1. no brand MARK — the header carried a stacked logo IMAGE that needed a
        dark plate painted behind it just to survive the light theme
     2. no single decisive accent — the page borrowed six telltale colours
     3. no offer strip, so nothing said what the customer actually GETS
     4. the headline colour was the generic --accent, not a brand signal
     5. the primary button was the same green/blue as every chip on the page
   ⛔ Do NOT close this gap with a photograph or an AI image. These sites are
   drawn, not photographed, and that is the demo's honesty, not a limitation.

   ⚠️ THE GROUND IS BOTH. This site is dark-first and ALSO ships a light theme
   (prefers-color-scheme AND [data-theme="light"]). So the accent is declared
   as THREE values, not one: the brand hex, a lifted end for text on the dark
   ground, and a darker end for text on the light ground. --signal-text is the
   one that flips; everything that renders as TYPE uses it. The button fill is
   constant in both themes because it carries its own ink.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  --signal:#4C7A2C;           /* the accent, as specified */
  --signal-deep:#3B6222;        /* darker end — accent TYPE on the light ground */
  --signal-lift:#8CC96A;        /* lighter end — accent TYPE on the dark ground */
  --signal-fill:#4C7A2C;        /* the primary button */
  --signal-ink:#FFFFFF;          /* type ON the primary button */
  --signal-text:var(--signal-lift);
}
@media (prefers-color-scheme:light){
  :root:not([data-theme="dark"]){ --signal-text:var(--signal-deep); }
}
:root[data-theme="light"]{ --signal-text:var(--signal-deep); }

/* ── the mark ─────────────────────────────────────────────────────────── */
/* ⭐ Inline, in currentColor, so it inherits the theme instead of needing a
   plate painted behind it the way the old lockup image did.
   🔴 23 Sep 2026 — THIRTEEN COMPANIES WERE WEARING ONE IDEA IN THIRTEEN COLOURS.
   Every demo's mark was the same formula: a 28x28 viewBox, monoline currentColor
   strokes at 1.5–2.6 weight, no fill, no wordmark treatment. This one was a
   sprout: a ground line, a stem and two outlined leaves. Defensible on its own,
   and indefensible as the element that appears on every page of every site in
   the set — "a trade that gets the same page in a different colour has been sold
   a template", showing up in the logo.
   ⭐ THE FIX IS A DIFFERENT KIND OF MARK, not a different shape inside the same
   kind. This is now a SOLID TILE with the form knocked out of it: 34x34, rounded
   10, filled in the brand colour, a darker lower half for two-tone, and three
   white mow stripes that taper into the distance. ⛔ Not one stroke anywhere in
   it — that is the point. The tile keeps `currentColor` so the theme still
   drives it, and the knockouts are white, which holds on a saturated colour in
   either theme.
   ⚠️ What it must NOT become: a leaf outline at a different stroke width.
   ⚠️ The comment below refers to the trade and city being repeated in the hero
   eyebrow — that eyebrow came out on 23 Sep (ux-build 1.1, a label on a label).
   The city now sits in the hero rail instead, so the reasoning still holds. */
.brand{gap:.65rem}
.brand__mark{width:1.625rem; height:1.625rem; flex:none; display:block;
  color:var(--signal-text);
  transition:transform .18s cubic-bezier(.3,1.2,.5,1), color .18s ease}
.brand:hover .brand__mark{transform:translateY(-1px)}
.brand__txt{display:flex; flex-direction:column; gap:.1rem; min-width:0}
/* 🔴 A TEXT WORDMARK IS WIDER THAN THE 132px LOCKUP IT REPLACED, and .top__in is
   a WRAPPING flex row — so the phone and the button dropped to a second row at
   widths where they used to sit beside the nav. Measured, not guessed: at a
   1100px viewport the brand went 132px → 277px against a 267px budget, and
   .top__act moved from y=80 to y=125. ⛔ Never swap a logo for type in a
   wrapping header without re-measuring the row: nothing about it looks broken,
   it just quietly costs a row of height on every page.
   ⭐ The SECOND line is the widest part of the block, not the name — so it is
   the first thing to go, and the trade and city it repeats are both printed in
   the hero eyebrow a couple of inches below it. */
.brand{gap:.55rem}
.brand__nm{font-size:.9375rem}
.brand__sub{font-size:.5625rem; letter-spacing:.1em}
/* ⚠️ MEASURED AT 1024px, NOT GUESSED. With the second line already dropped, the
   header still needed 963px of a 942px content box on the longest of the four
   names — a 21px overrun that costs a whole extra row. The 35px comes off in
   four places rather than one so no single element gets conspicuously small:
   the row gap, the brand gap, the mark, and the wordmark. Below 60rem the nav
   collapses to the burger and none of this is load-bearing any more. */
@media (max-width:75rem){
  .top__in{gap:.875rem}
  .brand{gap:.45rem}
  .brand__mark{width:1.45rem; height:1.45rem}
  .brand__sub{display:none}
  .brand__nm{font-size:.8125rem}
}

/* ── the headline carries the colour ──────────────────────────────────── */
/* ⚠️ <em> so it still reads as emphasis with styles off; the slant comes off
   because the display face already leans on weight, not italics. */
h1 .hl{font-style:normal; color:var(--signal-text)}
.hero h1 u{color:var(--signal-text)}   /* the inner pages use <u>; same colour */

/* ── the offer strip ──────────────────────────────────────────────────── */
/* ⭐ The reference site's coupon bar, in this trade's own words, and every word of it is
   taken from copy already published elsewhere on this site.
   ⛔ It must never carry a price, a rating or a review count. */
.offerbar{background:var(--surface-2); border-bottom:1px solid var(--rule)}
.offerbar__in{display:flex; align-items:center; gap:.85rem; flex-wrap:wrap;
  padding-block:.55rem}
.offerbar__tag{flex:0 0 auto; font-family:var(--f-mono); font-size:.625rem;
  font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--signal-text); border-radius:999px; padding:.18rem .55rem;
  border:1px solid color-mix(in srgb, var(--signal-text) 42%, transparent)}
.offerbar__msg{margin:0; flex:1 1 22rem; min-width:0; font-size:.8125rem;
  line-height:1.45; color:var(--ink-2)}
.offerbar__msg b{color:var(--ink); font-weight:600}
.offerbar__go{flex:0 0 auto; font-family:var(--f-mono); font-size:.6875rem;
  font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--ink); text-decoration:none; white-space:nowrap; padding-bottom:2px;
  border-bottom:1px solid color-mix(in srgb, var(--signal-text) 60%, transparent)}
.offerbar__go:hover{color:var(--signal-text)}
@media (max-width:40rem){
  .offerbar__go{display:none}          /* the hero button is a screen below it */
  .offerbar__msg{font-size:.78125rem}
}

/* ── the primary button is the signal, not the generic accent ─────────── */
.btn--go{background:var(--signal-fill); color:var(--signal-ink);
  box-shadow:0 1px 0 color-mix(in srgb, #000 18%, transparent) inset,
             0 8px 22px -14px color-mix(in srgb, var(--signal-fill) 90%, #000)}
.btn--go:hover{background:color-mix(in srgb, var(--signal-fill) 86%, #fff);
  color:var(--signal-ink)}

/* 🔴 AND THE GHOST HAS TO BE RESTATED AFTER IT. `.btn--line` is declared ~500
   lines above. This stylesheet is unlayered, so a later rule of EQUAL
   specificity wins — the appended `.btn--go` block above is why this one has
   to exist below it rather than being left where it was. Two primaries is no
   primary. ⛔ Any future .btn rule appended past this point must restate the
   ghost again. */
.btn--line{background:transparent; border-color:var(--rule); color:var(--ink);
  box-shadow:none}
.btn--line:hover{background:var(--surface-2); border-color:var(--signal-text);
  color:var(--ink)}

/* ── depth on the drawn hero ──────────────────────────────────────────── */
/* ⭐ What separates the reference site's instrument from a flat vector is one light and a
   real shadow. These heroes had neither, so the art sat ON the page instead
   of above it. */
.hero__fig img{filter:drop-shadow(0 26px 44px rgba(0,0,0,.45)) contrast(1.05) saturate(1.04)}
.hero__fig .wr{box-shadow:0 1px 2px rgba(0,0,0,.32), 0 30px 56px -22px rgba(0,0,0,.72);
  filter:contrast(1.02)}
@media (prefers-color-scheme:light){
  :root:not([data-theme="dark"]) .hero__fig img{filter:drop-shadow(0 22px 36px rgba(19,26,33,.28)) contrast(1.04) saturate(1.03)}
  :root:not([data-theme="dark"]) .hero__fig .wr{box-shadow:0 1px 2px rgba(19,26,33,.08), 0 26px 48px -20px rgba(19,26,33,.34)}
}
:root[data-theme="light"] .hero__fig img{filter:drop-shadow(0 22px 36px rgba(19,26,33,.28)) contrast(1.04) saturate(1.03)}
:root[data-theme="light"] .hero__fig .wr{box-shadow:0 1px 2px rgba(19,26,33,.08), 0 26px 48px -20px rgba(19,26,33,.34)}
@media (prefers-reduced-motion:reduce){ .brand:hover .brand__mark{transform:none} }


/* ── 🗓 THE DAY, AS THE CUSTOMER HAS IT — 11 Sep 2026 ─────────────────────
   ⭐ THE HERO ABOVE STATES WHAT THIS COMPANY DOCUMENTS. That is our fact, not
   the customer's. What a person actually buys from a trade is the day they do
   not lose — so the headline names the trade and the town, the accent carries
   the hook, and this section walks the day beat by beat in THEIR time.
   ⛔ Every beat is something this company could really do. Nothing here claims
   a booking is held, confirmed or reserved, no price appears that is not
   already published elsewhere on this site, and ONE BEAT IS A REFUSAL — the
   beat a real customer repeats to somebody else.
   ⭐ It borrows the page's own tokens, so it is this trade's accent and this
   trade's rules. ⛔ Do not paste another demo's colour in here. */
.sec--day .day{list-style:none; margin:1.75rem 0 0; padding:0; display:grid; gap:0}
.day__i{display:grid; grid-template-columns:5.5rem minmax(0,1fr); gap:0 1.25rem;
  padding:1.15rem 0; border-top:1px solid var(--rule); align-items:start}
.day__i:first-child{border-top:0}
/* 🔴 THE THREE CHILDREN MUST BE PLACED, NOT AUTO-FLOWED. A grid with two columns
   and three children puts the THIRD one back in column 1 — so the paragraph lands
   under the timestamp and wraps at about ten characters, breaking words in half.
   It looks like a rendering fault and it is only visible once you actually open
   the page: the markup is correct, the CSS is short, and every gate passes.
   ⛔ Never leave these three rules implicit. */
.day__t{grid-column:1; grid-row:1 / span 2;
  font-family:var(--f-mono); font-size:.8125rem; font-weight:500; letter-spacing:.06em;
  text-transform:uppercase; color:var(--signal-text); padding-top:.2rem; white-space:nowrap}
.day__i h3{grid-column:2; grid-row:1; margin:0 0 .3rem; font-size:1.0625rem; font-weight:700; line-height:1.25}
.day__i p{grid-column:2; grid-row:2; margin:0; font-size:.9375rem; line-height:1.55; color:var(--ink-2); max-width:62ch}
/* ⭐ ONE beat is marked, and it is the one where the promise in the HEADLINE
   actually happens. ⛔ Never the sale — the sale is not the customer's story. */
.day__i--live .day__t::after{content:""; display:inline-block; width:6px; height:6px;
  border-radius:50%; background:var(--signal-text); margin-left:6px; vertical-align:middle}
.day__foot{margin:1.5rem 0 0; padding-top:1.15rem; border-top:1px solid var(--rule);
  font-size:.9375rem; line-height:1.6; color:var(--ink-2); max-width:62ch}
.day__foot b{color:var(--ink)}
@media (max-width:560px){
  .day__i{grid-template-columns:1fr; gap:.35rem}
  /* ⛔ and release the explicit placement with it, or every beat stacks in one cell */
  .day__t{grid-column:1; grid-row:auto; padding-top:0}
  .day__i h3,.day__i p{grid-column:1; grid-row:auto}
}


/* ── 📷 REVIEWED PHOTOGRAPHS ──────────────────────────────────────────────
   Fetched under commercial-use licences, culled BY EYE from a contact sheet,
   downloaded locally (the sync guard refuses an external <img>, and a remote
   image inside an iframe fails as a blank box with no error).
   ⚠️ Any CC-BY image carries its credit in the frame. Attribution is a licence
   term, not a courtesy. */
.photoband{position:relative;margin:0;line-height:0}
.photoband img{width:100%;height:clamp(190px,25vw,320px);object-fit:cover;display:block;filter:saturate(1.02) contrast(1.02)}
.photoband__cr{position:absolute;right:8px;bottom:8px;font-size:10px;line-height:1.4;color:rgba(255,255,255,.82);background:rgba(0,0,0,.55);padding:3px 7px;border-radius:3px}
.photoband__cr a{color:inherit}


/* ══ 🧭 THE HEADER, 2026 — 15 Sep 2026 ══════════════════════════════════════
   Owner: "the header need to be way better on each site look like proper 2026
   header trends", "make sure everyting looks as goos as outr customer sites".
   🔴 MEASURED FIRST, ALL TWELVE AT 1280: heights ran from 62px to 223px, only
   one was sticky, and eleven carried a telephone number that the new utility bar
   was already showing a few pixels above it.
   ⭐ This layer is keyed on the `header` ELEMENT, not on a class, because these
   demos call it .site, .top, .mast, .nav and .masthead — five names for one
   thing. Anything that needs a class name is done per demo.
   ⛔ It is deliberately visual-only: sticky, glass, a hairline that appears on
   scroll, and honest focus rings. It does not re-order anybody's markup. */
header{
  position:sticky; top:0; z-index:60;
  -webkit-backdrop-filter:saturate(1.3) blur(10px);
  backdrop-filter:saturate(1.3) blur(10px);
  transition:box-shadow .22s ease, border-color .22s ease;
}
/* ⭐ The rule under the header only appears once you have scrolled — a border
   that is always there is chrome, a border that arrives is depth. */
header::after{
  content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:currentColor; opacity:0; transition:opacity .22s ease;
  pointer-events:none;
}
html[data-scrolled] header::after{opacity:.16}
html[data-scrolled] header{box-shadow:0 10px 30px -22px rgba(0,0,0,.55)}

/* ⛔ 44px is not decoration. WCAG 2.2 §2.5.8, and on a phone this is the only
   navigation there is. */
@media (max-width:900px){
  header nav a{min-height:44px;display:flex;align-items:center}
}
/* a focus ring that is visible on both a light and a dark header */
header a:focus-visible, header button:focus-visible, header label:focus-visible{
  outline:2px solid currentColor; outline-offset:3px; border-radius:6px;
}


/* 🔴 STICKY WAS LOSING ON SPECIFICITY — 15 Sep 2026. `header{position:sticky}`
   is (0,0,1) and these demos set `.top{position:relative}` at (0,1,0), so five
   of twelve silently stayed put while the other seven pinned. Measured, not
   assumed: auto-detailing, drain-cleaning, mobile-repair, plumbing-hvac and
   rv-powersports all read `relative` after the layer went in.
   ⭐ `header[class]` is (0,1,1) and beats a single class without reaching for
   !important, which would then have to be fought later. */
header[class]{position:sticky;top:0;z-index:60}

/* 🔴 THE OFF-CANVAS MENU WAS WIDENING THE PAGE. Parked at translateX(102%) the
   panel still counted toward scrollWidth — 700px against a 371px viewport — so
   the whole site scrolled sideways on a phone with nothing visible to scroll to.
   ⛔ `overflow-x:hidden` would fix it and break `position:sticky` in the same
   stroke; `clip` does not create a scroll container, so the header still pins. */
html{overflow-x:clip}


/* 🔴 THE NAV WAS WRAPPING ONTO ITS OWN ROW — 15 Sep 2026, measured at 1280.
   These headers are flex rows that ran out of width, so the navigation dropped
   beneath the brand and the header doubled in height: 134, 138, 104, 223.
   A sticky header that tall eats a quarter of a laptop screen, and it is the
   clearest difference between these and the client sites.
   ⭐ One row, and the NAV is the part allowed to give — `min-width:0` lets it
   shrink, and if it still will not fit it scrolls sideways within itself rather
   than pushing the whole header down. ⛔ `scrollbar-width:none` hides the bar,
   not the ability to scroll; the items remain reachable by wheel, trackpad,
   touch and keyboard. */
@media (min-width:901px){
  header .top__in,
  header .masthead__row,
  header .brandline{flex-wrap:nowrap}
  header nav{min-width:0}
}
@media (min-width:901px){
  header nav ul{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
  header nav ul::-webkit-scrollbar{display:none}
  header nav a{white-space:nowrap}
}
