
/* ---------- typeface ----------
   Self-hosted Inter Variable, latin subset, 47KB.

   THE PROBLEM IT FIXES. The stack was system-only:
     -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial
   On a Mac or iPhone that resolves to SF Pro, which is variable, so the site's
   thirteen weights — 550, 640, 660, 670, 680, 720, 730, 750, 760, 770 — all
   render as genuinely different. On Android none of the first three exist and
   Inter is not installed, so it falls to Roboto, which ships static weights.
   Everything from 640 to 770 rounds to 700: h1, h2, h3, buttons and the logo
   all collapse to one weight and the whole hierarchy disappears. Measured in
   the browser before fixing — 550, 660 and 770 rendered at identical widths.

   Inter is first in the stack on purpose. The point is that every visitor sees
   the same thing, not that Apple users see something nicer.

   font-display:swap so text paints immediately in the fallback and swaps when
   the font arrives — never a blank page, which matters when the visitor is
   locked out. unicode-range keeps it to latin so nothing else is downloaded. */
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url('/assets/fonts/inter-var-latin.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{
  /* --ink-3 was #6b7683, which measured 4.38:1 on the wash background — just
     under the 4.5 floor, and it is the colour of every photo caption, every
     price footnote and the "Google review" attribution under the carousel.
     #646e79 is 4.92:1 on wash and 5.19:1 on white. Two shades darker; nothing
     else about the design moves. Found 13 Aug 2026 by measuring the rendered
     page rather than reading the palette. */
  --ink:#0f1419; --ink-2:#3d4852; --ink-3:#646e79; --ink-4:#94a0ab;
  --line:#e3e7eb; --line-2:#f0f3f6; --bg:#fff; --wash:#f7f9fb;
  --brand:#c8a04a; --brand-dk:#8a6a1f; --brand-wash:#fdf8ee; --brand-line:#eddcb8;
  /* Emergency only. Lockouts and the 24/7 call. Never a marketing CTA. */
  --emerg:#b42318; --emerg-dk:#8f1a12; --emerg-ink:#fff;
  --ok:#1a7f4b; --ok-wash:#eefaf3;
  --warn:#8a6100; --warn-wash:#fffbeb; --warn-line:#fde68a;
  --radius:14px;
  --shadow:0 1px 2px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.07);
  --maxw:900px;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font:16.5px/1.65 'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
  color:var(--ink);background:var(--bg);-webkit-font-smoothing:antialiased}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 22px}
.narrow{max-width:760px}
/* The header is not body text and should not be measured like it.
   Found 12 Aug 2026 by looking at the rendered page rather than the code: the
   nav was wrapping onto a second line on EVERY desktop page, orphaning "About"
   under the others and making the header 93px tall instead of ~60px.
   Not a narrow-window edge case — the arithmetic never fit:
       logo 112 + nav 538 + phone 109 + book 112 + 3 gaps = 925px
   against a --maxw of 900px. Short by 25px, always.
   The body column stays at 900/760 because that is a readable measure. The
   header just needs room, so it gets its own. */
header .wrap{max-width:1120px}
a{color:var(--brand-dk)}

/* ---------- header ---------- */
.topbar{background:var(--ink);color:#c9d1d9;font-size:13px;padding:7px 0}
.topbar .wrap{display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap}
.topbar b{color:#fff;font-weight:600}
.topbar a{color:var(--brand);text-decoration:none}
header{border-bottom:1px solid var(--line);background:#fff;position:sticky;top:0;z-index:50}
.hbar{display:flex;align-items:center;gap:18px;padding:13px 0}
.logo{font-weight:750;letter-spacing:.01em;font-size:17.5px;text-decoration:none;color:var(--ink);
  white-space:nowrap;display:flex;align-items:center}
.logo span{color:var(--brand-dk)}
.logo img{height:40px;width:auto;display:block}
@media(max-width:520px){.logo img{height:30px}}
nav.main{margin-left:auto;display:flex;gap:19px;align-items:center;flex-wrap:wrap}
nav.main a{color:var(--ink-2);text-decoration:none;font-size:14.5px;font-weight:550;white-space:nowrap}
nav.main a:hover{color:var(--brand-dk)}
.hcall{font-weight:700;color:var(--ink);text-decoration:none;white-space:nowrap;font-size:15px}
.hbook{background:var(--brand);color:#1a1205;padding:9px 15px;border-radius:9px;
  text-decoration:none;font-weight:680;font-size:14.5px;white-space:nowrap}
.hbook:hover{background:var(--brand-dk);color:#fff}
@media(max-width:860px){
  nav.main{order:10;width:100%;margin-left:0;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;
    border-top:1px solid var(--line);padding:9px 0 2px;flex-wrap:nowrap}
  nav.main a{white-space:nowrap;padding:5px 13px 5px 0;font-size:14px}
  .hbar{flex-wrap:wrap;padding:11px 0 4px}
  .hcall{margin-left:auto}
}
@media(max-width:520px){.hbook{padding:8px 12px;font-size:13.5px}.hcall{font-size:14px}}

/* ---------- type ---------- */
.crumb{font-size:13px;color:var(--ink-3);padding:20px 0 0}
.crumb a{color:var(--ink-3);text-decoration:none}
h1{font-size:40px;line-height:1.15;letter-spacing:-.03em;margin:14px 0 12px;font-weight:720}
h2{font-size:26px;line-height:1.25;letter-spacing:-.022em;margin:46px 0 13px;font-weight:680}
h3{font-size:18.5px;margin:30px 0 7px;font-weight:660;letter-spacing:-.01em}
h4{font-size:16px;margin:22px 0 5px;font-weight:660}
/* Size classes, so a heading can be the CORRECT LEVEL for the document outline
   and still look the way the page needs it to. Added 12 Aug 2026: the QA sweep
   found nine pages whose outline skipped a level (h1 straight to h3, h2 straight
   to h4) purely because the writer wanted a smaller heading. A screen-reader
   user navigating by heading falls through that gap. Change the tag, add the
   class, nothing moves visually. */
h2.sz3,h3.sz3{font-size:18.5px;margin:30px 0 7px;font-weight:660;letter-spacing:-.01em}
h3.sz4,h4.sz4{font-size:16px;margin:22px 0 5px;font-weight:660;letter-spacing:0}
p{margin:0 0 15px}
.lede{font-size:19.5px;line-height:1.55;color:var(--ink-2);margin:0 0 24px}
ul,ol{margin:0 0 15px 22px}
li{margin-bottom:6px}
strong{font-weight:660}
hr{border:none;border-top:1px solid var(--line);margin:44px 0}
@media(max-width:640px){h1{font-size:30px}h2{font-size:22px}.lede{font-size:17.5px}}

/* ---------- buttons ---------- */
.cta{display:flex;gap:11px;flex-wrap:wrap;margin:26px 0 10px}
/* ---------- motion ----------
   Sections fade and rise a few pixels as they arrive. This is the single
   cheapest thing that makes a page feel built rather than dumped: no library,
   about fifteen lines of JavaScript, nothing loaded.

   THREE RULES IT OBEYS, and they are the reason this is safe on a site people
   reach in an emergency:
     1. `prefers-reduced-motion` turns ALL of it off, not just some of it.
     2. Nothing is hidden until the JavaScript says so — the class is added to
        <html> by the observer script itself. With JS off, or before it runs,
        every section is simply visible. Content must never depend on motion.
     3. It only moves 14px. Enough to register, not enough to make anyone wait. */
/* RETIMED 12 Aug 2026, after watching it rather than reading it.
   At .55s, scrolling at normal speed outran the animation: whole viewports of
   near-invisible content — the stats band as ghost-grey numbers, client logos
   barely there. The effect meant to stop the site looking plain was making it
   look broken, and worst on a phone, where people flick-scroll fastest.
   .22s is still perceptible as motion but cannot be outrun. Paired with the
   positive rootMargin in the observer, which now starts the fade BEFORE the
   section reaches the screen, so it has usually finished by the time you see it. */
/* MADE ADDITIVE 13 Aug 2026. This is the better fix, and it came from an
   outside critique: "make the animation additive so a failure degrades to
   'no animation' rather than 'no content'."

   Before, the resting state was opacity:0 and JavaScript had to actively
   un-hide every section. Adding the .js-reveal class up front made that
   survivable for a total JS failure, and shortening the transition made the
   blank-viewport window small — but the architecture was still subtractive:
   ANY failure after the class landed (a thrown error in a later listener, an
   observer that never fires on some browser, a section that enters the
   viewport during the same frame it is created) left real content invisible
   with no way back.

   Now the resting state is VISIBLE. The animation is a keyframe that runs once
   on arrival, playing FROM the faded state TO the normal one. Nothing is ever
   hidden waiting for script: if `.in` is never added, the section simply sits
   there, fully readable, un-animated. The failure mode is a missing flourish
   instead of a missing page — which is the only acceptable trade on a site
   people reach when they are locked out. */
.js-reveal [data-reveal].in{animation:mkrise .28s cubic-bezier(.2,.7,.3,1) both}
@keyframes mkrise{from{opacity:0;transform:translateY(14px)}
                  to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){
  /* Now the reveal is a keyframe rather than a transition, this has to cancel
     the ANIMATION. The old transition:none no longer applied to anything. */
  .js-reveal [data-reveal],
  .js-reveal [data-reveal].in{opacity:1;transform:none;animation:none}
  .livedot i{animation:none}
  .stat:after{transition:none}
  .btn:hover{transform:none}
  .svc:hover{transform:none}
}

/* Buttons lift very slightly on hover. One pixel — enough that the page feels
   like it is listening, not enough to be a bounce. */
.btn:hover{transform:translateY(-1px);box-shadow:0 6px 18px rgba(16,24,40,.16)}
.btn:active{transform:translateY(0);box-shadow:none}
.btn{display:inline-block;padding:14px 22px;border-radius:10px;text-decoration:none;
  transition:background .16s,color .16s,border-color .16s,transform .14s,box-shadow .14s;
  font-weight:660;font-size:15.5px;border:1.5px solid transparent;cursor:pointer;font-family:inherit}
.btn-p{background:var(--brand);color:#1a1205}
.btn-p:hover{background:var(--brand-dk);color:#fff}
.btn-s{background:#fff;color:var(--ink);border-color:var(--line)}
/* .btn-e — the ONLY red on the site. Emergency call, lockouts. See core.py. */
.btn-e{background:var(--emerg);color:var(--emerg-ink);border-color:var(--emerg)}
.btn-e:hover{background:var(--emerg-dk);color:#fff}
.btn-s:hover{border-color:var(--ink-4)}
.btn-d{background:var(--ink);color:#fff}

/* ---------- trust ---------- */
.trust{display:flex;gap:9px;flex-wrap:wrap;list-style:none;margin:16px 0 0;padding:0}
.trust li{font-size:13px;color:var(--ink-2);background:var(--wash);border:1px solid var(--line);
  padding:5px 11px;border-radius:99px}

/* ---------- tables ---------- */
.tablewrap{overflow-x:auto;margin:16px 0}
table{width:100%;border-collapse:collapse;font-size:15.5px}
th,td{text-align:left;padding:13px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{font-size:12px;text-transform:uppercase;letter-spacing:.07em;color:var(--ink-3);font-weight:660;
  border-bottom:1.5px solid var(--line-2)}
td.price{white-space:nowrap;font-weight:680}
tr.hi td{background:var(--brand-wash)}
.sub{font-size:13.5px;color:var(--ink-3);display:block;margin-top:3px;font-weight:400}

/* ---------- boxes ---------- */
.box{background:var(--wash);border:1px solid var(--line);border-radius:var(--radius);
  padding:20px 22px;margin:28px 0}
.box>*:last-child{margin-bottom:0}
.box-b{background:var(--brand-wash);border-color:var(--brand-line)}
.box-d{background:var(--ink);color:#dbe3ea;border-color:var(--ink)}
.box-d strong,.box-d h3{color:#fff}
.tbc{background:var(--warn-wash);border:1px dashed #d9a441;color:var(--warn);
  border-radius:7px;padding:2px 8px;font-size:13px;font-weight:640;display:inline-block;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.placeholder-block{background:var(--warn-wash);border:1px dashed #d9a441;border-radius:10px;
  padding:16px 18px;margin:22px 0;color:var(--warn);font-size:14.5px}
.placeholder-block b{color:#5c430f}
.imgph{background:var(--wash);border:1px dashed var(--ink-4);border-radius:11px;
  padding:34px 18px;text-align:center;color:var(--ink-3);font-size:13.5px;margin:16px 0}

/* ---------- tiers ---------- */
.tiers{display:grid;gap:14px;margin:26px 0;grid-template-columns:1fr;align-items:stretch}
@media(min-width:820px){.tiers{grid-template-columns:repeat(3,1fr)}}
.tier{background:#fff;border:1.5px solid var(--line);border-radius:var(--radius);padding:22px 20px;
  position:relative;display:flex;flex-direction:column}
.tier.best{border-color:var(--brand);background:var(--brand-wash);box-shadow:var(--shadow)}
.ribbon{position:absolute;top:-11px;left:50%;transform:translateX(-50%);background:var(--brand);color:#1a1205;
  font-size:11px;font-weight:750;letter-spacing:.06em;text-transform:uppercase;padding:4px 11px;border-radius:6px;white-space:nowrap}
.tier .nm{font-size:18px;font-weight:680;margin-bottom:2px}
.tier .tag{font-size:13.5px;color:var(--ink-3);margin-bottom:12px}
.tier .pr{font-size:33px;font-weight:730;letter-spacing:-.03em;margin-bottom:2px}
.tier .prn{font-size:13px;color:var(--ink-3);margin-bottom:15px}
.tier ul{list-style:none;margin:0 0 17px;font-size:14.5px;color:var(--ink-2);flex:1}
.tier li{padding:5px 0 5px 21px;position:relative;margin:0}
.tier li:before{content:"";position:absolute;left:2px;top:13px;width:6px;height:6px;border-radius:99px;background:var(--brand)}
.tier .btn{width:100%;text-align:center;padding:12px;margin-top:auto;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}

/* ---------- faq ---------- */
details{border-bottom:1px solid var(--line);padding:15px 0}
summary{cursor:pointer;font-weight:640;font-size:16px;list-style:none}
summary::-webkit-details-marker{display:none}
summary:before{content:"+";float:right;color:var(--brand-dk);font-weight:700;font-size:19px;line-height:1.1}
details[open] summary:before{content:"−"}
details p{margin:11px 0 0;color:var(--ink-2);font-size:15.5px}
details p:last-child{margin-bottom:0}

/* ---------- grid ---------- */
.grid2{display:grid;gap:16px;grid-template-columns:1fr;margin:20px 0}
.grid3{display:grid;gap:14px;grid-template-columns:1fr;margin:20px 0}
@media(min-width:760px){.grid2{grid-template-columns:1fr 1fr}.grid3{grid-template-columns:repeat(3,1fr)}}
.cardl{display:block;background:#fff;border:1.5px solid var(--line);border-radius:var(--radius);
  padding:18px 19px;text-decoration:none;color:inherit;transition:border-color .16s,box-shadow .16s}
.cardl:hover{border-color:var(--brand);box-shadow:var(--shadow)}
.cardl b{display:block;font-size:16.5px;font-weight:660;margin-bottom:4px}
.cardl small{color:var(--ink-3);font-size:14px;line-height:1.5;display:block}
.cardl .pill{display:inline-block;margin-top:9px;font-size:12.5px;font-weight:660;color:var(--brand-dk)}

/* ---------- town list ---------- */
.towns{columns:2;column-gap:26px;font-size:15px;margin:16px 0}
@media(min-width:760px){.towns{columns:4}}
.towns a{display:block;padding:4px 0;text-decoration:none;color:var(--ink-2)}
.towns a:hover{color:var(--brand-dk)}

/* ---------- legal / footer ---------- */
.pricing-terms{border:1px solid var(--line);border-radius:11px;padding:16px 18px;margin:22px 0;
  font-size:13.5px;color:var(--ink-3);background:var(--wash)}
.pricing-terms b{color:var(--ink-2)}
.pricing-terms ul{margin:8px 0 0 19px}
.pricing-terms li{margin-bottom:4px}
footer{border-top:1px solid var(--line);margin-top:64px;padding:38px 0 56px;background:var(--wash);font-size:14.5px;color:var(--ink-2)}
.fgrid{display:grid;gap:26px;grid-template-columns:1fr;margin-bottom:30px}
@media(min-width:760px){.fgrid{grid-template-columns:1.4fr 1fr 1fr 1fr}}
footer h4{font-size:12.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--ink-3);margin:0 0 10px}
footer ul{list-style:none;margin:0}
footer li{margin-bottom:5px}
footer a{color:var(--ink-2);text-decoration:none}
footer a:hover{color:var(--brand-dk)}
.legal{border-top:1px solid var(--line);padding-top:22px;font-size:13px;color:var(--ink-3);line-height:1.6}
.legal .lic{font-weight:640;color:var(--ink-2)}

/* ---------- reviews ---------- */
.reviews{margin:44px 0}
.ratingbox{display:flex;gap:20px;align-items:center;flex-wrap:wrap;background:var(--brand-wash);
  border:1px solid var(--brand-line);border-radius:var(--radius);padding:20px 22px;margin:16px 0}
.rb-score{display:flex;align-items:baseline;gap:11px;flex:none}
.rb-stars{color:var(--brand-dk);letter-spacing:2px}
.rb-stars.big{font-size:23px}
.rb-num{font-size:38px;font-weight:730;letter-spacing:-.03em;line-height:1}
.rb-txt{font-size:15.5px;color:var(--ink-2);flex:1;min-width:210px}
.rb{display:inline-flex;align-items:center;gap:7px;font-size:14px;color:var(--ink-2);white-space:nowrap}
.rb .rb-stars{font-size:14px;letter-spacing:1px}
.revframe{width:100%;height:420px;border:1px solid var(--line);border-radius:var(--radius);
  margin-top:18px;display:block}
.herorate{display:flex;align-items:center;gap:13px;flex-wrap:wrap;margin:18px 0 0}
.herorate .rb-stars{font-size:19px}
.herorate b{font-size:19px;font-weight:730}
.herorate span.sub2{color:var(--ink-3);font-size:15px}



/* ---------- the new homeowner banner ----------
   Timmy, on the QR landing page: "congratulations on the new house — there's no
   image, there's no picture, it's just very plain... something behind it that
   celebrated them. It could even be confetti... watermarked, fading in the
   background."

   Confetti, generated at build time as an SVG tile in the brand palette and
   embedded as a data URI. No image request, ~8KB inside the stylesheet, sharp at
   any density.

   Two things keep it from being tacky. It sits at 22% opacity and is masked so it
   fades to nothing before it reaches the body text — texture, not clip art. And
   the headline sits in its own layer above it, so contrast is untouched: this is
   a page someone reaches by scanning a card in a realtor's folder, and it has to
   stay legible in a parked car. */
.celebrate{position:relative;overflow:hidden;isolation:isolate}
.celebrate:before{content:"";position:absolute;inset:0;z-index:-1;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22420%22%20height%3D%22420%22%20viewBox%3D%220%200%20420%20420%22%3E%3Cpath%20d%3D%22M136.0%2063.4%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%23c8a04a%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%2817%20136.0%2063.4%29%22%2F%3E%3Crect%20x%3D%2239.5%22%20y%3D%22244.8%22%20width%3D%229.9%22%20height%3D%223.5%22%20rx%3D%221%22%20fill%3D%22%233d4852%22%20transform%3D%22rotate%2877%2039.5%20244.8%29%22%2F%3E%3Crect%20x%3D%22231.4%22%20y%3D%2224.8%22%20width%3D%2211.4%22%20height%3D%224.9%22%20rx%3D%221%22%20fill%3D%22%233d4852%22%20transform%3D%22rotate%2845%20231.4%2024.8%29%22%2F%3E%3Cpath%20d%3D%22M242.4%20166.6%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%238a6a1f%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%2817%20242.4%20166.6%29%22%2F%3E%3Ccircle%20cx%3D%22121.6%22%20cy%3D%2260.6%22%20r%3D%222.8%22%20fill%3D%22%23c8a04a%22%2F%3E%3Ccircle%20cx%3D%2243.3%22%20cy%3D%22239.9%22%20r%3D%221.9%22%20fill%3D%22%238a6a1f%22%2F%3E%3Cpath%20d%3D%22M25.0%2086.5%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%23c8a04a%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28191%2025.0%2086.5%29%22%2F%3E%3Cpath%20d%3D%22M195.6%20387.8%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%231a7f4b%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28108%20195.6%20387.8%29%22%2F%3E%3Ccircle%20cx%3D%22293.6%22%20cy%3D%22102.5%22%20r%3D%222.3%22%20fill%3D%22%233d4852%22%2F%3E%3Crect%20x%3D%22188.5%22%20y%3D%22255.8%22%20width%3D%2212.3%22%20height%3D%223.3%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%2843%20188.5%20255.8%29%22%2F%3E%3Ccircle%20cx%3D%22205.4%22%20cy%3D%2216.5%22%20r%3D%222.9%22%20fill%3D%22%23c8a04a%22%2F%3E%3Ccircle%20cx%3D%22343.7%22%20cy%3D%22142.9%22%20r%3D%222.4%22%20fill%3D%22%231a7f4b%22%2F%3E%3Crect%20x%3D%22352.8%22%20y%3D%22396.8%22%20width%3D%2212.1%22%20height%3D%223.6%22%20rx%3D%221%22%20fill%3D%22%23b42318%22%20transform%3D%22rotate%28251%20352.8%20396.8%29%22%2F%3E%3Crect%20x%3D%22242.7%22%20y%3D%22286.1%22%20width%3D%2211.7%22%20height%3D%223.0%22%20rx%3D%221%22%20fill%3D%22%23b42318%22%20transform%3D%22rotate%28102%20242.7%20286.1%29%22%2F%3E%3Crect%20x%3D%22193.9%22%20y%3D%2270.6%22%20width%3D%229.0%22%20height%3D%224.5%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%28178%20193.9%2070.6%29%22%2F%3E%3Crect%20x%3D%22167.1%22%20y%3D%22385.1%22%20width%3D%2210.8%22%20height%3D%224.8%22%20rx%3D%221%22%20fill%3D%22%23b42318%22%20transform%3D%22rotate%2829%20167.1%20385.1%29%22%2F%3E%3Cpath%20d%3D%22M344.1%20362.9%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%231a7f4b%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28254%20344.1%20362.9%29%22%2F%3E%3Crect%20x%3D%22286.7%22%20y%3D%22159.8%22%20width%3D%228.6%22%20height%3D%223.5%22%20rx%3D%221%22%20fill%3D%22%238a6a1f%22%20transform%3D%22rotate%2854%20286.7%20159.8%29%22%2F%3E%3Crect%20x%3D%22203.7%22%20y%3D%22247.4%22%20width%3D%2210.7%22%20height%3D%224.2%22%20rx%3D%221%22%20fill%3D%22%231a7f4b%22%20transform%3D%22rotate%28101%20203.7%20247.4%29%22%2F%3E%3Ccircle%20cx%3D%22133.8%22%20cy%3D%2252.7%22%20r%3D%222.8%22%20fill%3D%22%233d4852%22%2F%3E%3Crect%20x%3D%22191.8%22%20y%3D%22365.8%22%20width%3D%229.8%22%20height%3D%223.2%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%28287%20191.8%20365.8%29%22%2F%3E%3Crect%20x%3D%22266.4%22%20y%3D%2226.1%22%20width%3D%227.8%22%20height%3D%224.2%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%28354%20266.4%2026.1%29%22%2F%3E%3Crect%20x%3D%2243.0%22%20y%3D%22238.0%22%20width%3D%227.2%22%20height%3D%224.7%22%20rx%3D%221%22%20fill%3D%22%233d4852%22%20transform%3D%22rotate%2837%2043.0%20238.0%29%22%2F%3E%3Ccircle%20cx%3D%22257.9%22%20cy%3D%2262.4%22%20r%3D%222.5%22%20fill%3D%22%231a7f4b%22%2F%3E%3Crect%20x%3D%2248.4%22%20y%3D%22205.0%22%20width%3D%228.0%22%20height%3D%224.5%22%20rx%3D%221%22%20fill%3D%22%23b42318%22%20transform%3D%22rotate%28173%2048.4%20205.0%29%22%2F%3E%3Crect%20x%3D%22310.9%22%20y%3D%22201.0%22%20width%3D%2213.7%22%20height%3D%224.1%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%2858%20310.9%20201.0%29%22%2F%3E%3Crect%20x%3D%2261.6%22%20y%3D%22228.1%22%20width%3D%2211.5%22%20height%3D%223.2%22%20rx%3D%221%22%20fill%3D%22%23c8a04a%22%20transform%3D%22rotate%28273%2061.6%20228.1%29%22%2F%3E%3Crect%20x%3D%22355.1%22%20y%3D%22217.7%22%20width%3D%2210.8%22%20height%3D%224.0%22%20rx%3D%221%22%20fill%3D%22%238a6a1f%22%20transform%3D%22rotate%28128%20355.1%20217.7%29%22%2F%3E%3Cpath%20d%3D%22M267.3%20257.6%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%238a6a1f%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28290%20267.3%20257.6%29%22%2F%3E%3Cpath%20d%3D%22M310.7%2095.2%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%233d4852%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28177%20310.7%2095.2%29%22%2F%3E%3Ccircle%20cx%3D%22415.6%22%20cy%3D%22331.8%22%20r%3D%223.1%22%20fill%3D%22%23b42318%22%2F%3E%3Crect%20x%3D%22187.8%22%20y%3D%22393.5%22%20width%3D%228.5%22%20height%3D%223.5%22%20rx%3D%221%22%20fill%3D%22%231a7f4b%22%20transform%3D%22rotate%28344%20187.8%20393.5%29%22%2F%3E%3Ccircle%20cx%3D%2282.6%22%20cy%3D%2285.8%22%20r%3D%221.8%22%20fill%3D%22%233d4852%22%2F%3E%3Ccircle%20cx%3D%22381.9%22%20cy%3D%22144.5%22%20r%3D%223.1%22%20fill%3D%22%23c8a04a%22%2F%3E%3Crect%20x%3D%22328.6%22%20y%3D%22315.1%22%20width%3D%2211.5%22%20height%3D%223.2%22%20rx%3D%221%22%20fill%3D%22%23b42318%22%20transform%3D%22rotate%28320%20328.6%20315.1%29%22%2F%3E%3Cpath%20d%3D%22M397.4%20303.2%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%23b42318%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28144%20397.4%20303.2%29%22%2F%3E%3Ccircle%20cx%3D%22304.4%22%20cy%3D%2271.4%22%20r%3D%222.5%22%20fill%3D%22%238a6a1f%22%2F%3E%3Ccircle%20cx%3D%22275.5%22%20cy%3D%22256.9%22%20r%3D%222.3%22%20fill%3D%22%233d4852%22%2F%3E%3Cpath%20d%3D%22M230.4%2055.0%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%23c8a04a%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28288%20230.4%2055.0%29%22%2F%3E%3Cpath%20d%3D%22M43.2%20314.8%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%238a6a1f%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28156%2043.2%20314.8%29%22%2F%3E%3Ccircle%20cx%3D%22347.0%22%20cy%3D%2288.6%22%20r%3D%222.9%22%20fill%3D%22%231a7f4b%22%2F%3E%3Cpath%20d%3D%22M136.9%20228.6%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%238a6a1f%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%2822%20136.9%20228.6%29%22%2F%3E%3Cpath%20d%3D%22M377.0%20278.2%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%233d4852%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28151%20377.0%20278.2%29%22%2F%3E%3Cpath%20d%3D%22M210.7%20223.4%20q%206%20-5%2012%200%20t%2012%200%22%20stroke%3D%22%233d4852%22%20stroke-width%3D%222%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%20transform%3D%22rotate%28184%20210.7%20223.4%29%22%2F%3E%3Ccircle%20cx%3D%22326.1%22%20cy%3D%22255.6%22%20r%3D%222.8%22%20fill%3D%22%238a6a1f%22%2F%3E%3Ccircle%20cx%3D%22233.7%22%20cy%3D%22136.9%22%20r%3D%222.9%22%20fill%3D%22%233d4852%22%2F%3E%3C%2Fsvg%3E");
  background-size:420px 420px;opacity:.22;
  -webkit-mask-image:radial-gradient(120% 100% at 50% 0%,#000 0%,#000 38%,transparent 78%);
          mask-image:radial-gradient(120% 100% at 50% 0%,#000 0%,#000 38%,transparent 78%)}
.celebrate>*{position:relative;z-index:1}
/* A very slow drift, so it feels alive rather than animated. Off entirely for
   anyone who has asked for reduced motion. */
@media(prefers-reduced-motion:no-preference){
  .celebrate:before{animation:confettiDrift 26s linear infinite}
}
@media(prefers-reduced-motion:reduce){.celebrate:before{animation:none}}
@keyframes confettiDrift{from{background-position:0 0}to{background-position:0 420px}}

/* ---------- full-bleed sections ---------- */
/* HERO, REWORKED 12 Aug 2026. Timmy: "This looks awfully plain."
   He was right, and the reason was mine rather than a constraint. I had been
   optimising hard for one reader — someone locked out, on a phone, on cell
   data, wanting a price — and treated "loads fast and states prices plainly"
   as if it were in tension with "looks like somebody cared". It is not.
   Everything below is CSS and one IntersectionObserver: no libraries, no web
   fonts, no images added, no measurable weight. */
.hero{background:var(--ink);color:#fff;padding:64px 0 58px;position:relative;overflow:hidden}
/* Depth, in three cheap layers instead of a photograph:
   1. a warm pool of brand light behind the headline, so the top-left corner
      where the eye lands is not flat black
   2. a cool one bottom-right to stop it reading as a vignette
   3. the original gold ring, kept — it is the one bit of the old hero that
      was doing something */
.hero:before{content:"";position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(560px 420px at 8% -10%, rgba(200,160,74,.20), transparent 62%),
    radial-gradient(700px 520px at 105% 115%, rgba(96,140,190,.16), transparent 60%)}
.hero:after{content:"";position:absolute;right:-90px;top:-70px;width:420px;height:420px;
  border:70px solid var(--brand);border-radius:50%;opacity:.10;pointer-events:none}
.hero .wrap{position:relative;z-index:1;max-width:1120px}
.hero h1{font-size:54px;line-height:1.02;letter-spacing:-.04em;margin:0 0 16px;font-weight:770;
  max-width:13ch;text-wrap:balance}
.hero .lede{color:#c2cdd8;font-size:20px;max-width:52ch;margin-bottom:26px}
/* The doorknob line, demoted from h1 to deck. It keeps the weight and the size
   of a statement rather than becoming body copy — it is still the best sentence
   on the site, it just no longer has to do the job of telling a stranger what
   this business is. */
.hero .lede-hook{font-size:26px;font-weight:680;color:#fff;letter-spacing:-.02em;
  margin:2px 0 14px;max-width:22ch}
/* The service line under the headline. Same block, smaller and lighter, so the
   h1 reads as one unit to a person and as a single heading to a crawler. */
.h1sub{display:block;font-size:18px;font-weight:600;letter-spacing:-.01em;
  color:#9fb0c0;margin-top:12px}
@media(max-width:700px){.hero .lede-hook{font-size:21px}.h1sub{font-size:16px}}
.hero .cta{margin:0 0 22px;gap:10px;flex-wrap:nowrap}
.hero .cta .btn{padding:14px 18px;font-size:15px;white-space:nowrap}
/* The hero photo was a bare rectangle sitting on the background. A hairline,
   a real corner radius and a lit edge make it read as a photograph that was
   placed rather than one that landed. */
.hero .ph{margin:0}
.hero .ph img{border-radius:16px;border:1px solid rgba(255,255,255,.14);
  box-shadow:0 24px 60px rgba(0,0,0,.45), 0 0 0 1px rgba(200,160,74,.16);
  display:block;width:100%;height:auto}
@media(max-width:900px){.hero .cta{flex-wrap:wrap}}
@media(max-width:700px){.hero{padding:38px 0 40px}.hero h1{font-size:36px}.hero .lede{font-size:17px}
  .hero:after{width:240px;height:240px;border-width:44px;right:-70px;top:-50px}}

/* An eyebrow above a heading. Small, and it does a real job: it tells you what
   kind of thing you are about to read before you read it. */
.eyebrow{display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:700;
  letter-spacing:.13em;text-transform:uppercase;color:var(--brand-dk);margin:0 0 10px}
.eyebrow:before{content:"";width:22px;height:2px;background:var(--brand);border-radius:2px}
/* CONTRAST FIX 13 Aug 2026, measured at 2.31:1 — half the 4.5 required.
   The bug is a collision between two rules that were each right alone. The base
   .eyebrow gives it a CREAM PILL background; this override then set brass text,
   written on the assumption the eyebrow sat directly on the dark hero. It does
   not — it sits on its own cream pill, so it was brass on cream.
   brand-dk on the same pill measures 4.77:1. The pill stays; only the ink
   changes, so nothing about the look shifts except that it can be read outdoors,
   which is where a locksmith's customers read it. */
.hero .eyebrow{color:var(--brand-dk)}
.band-dark .eyebrow{color:var(--brand)}

/* "We are actually awake" — a locksmith's single most valuable claim at 2am,
   and it was previously a line of grey text. The dot only pulses for people who
   have not asked for reduced motion. */
.livedot{display:inline-flex;align-items:center;gap:8px;font-size:13.5px;font-weight:640;
  color:#dfe7ee;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.13);
  padding:6px 12px 6px 10px;border-radius:99px}
.livedot i{width:8px;height:8px;border-radius:50%;background:#35d07f;flex:none;
  box-shadow:0 0 0 0 rgba(53,208,127,.55);animation:pulse 2.4s ease-out infinite}
@keyframes pulse{
  0%{box-shadow:0 0 0 0 rgba(53,208,127,.5)}
  70%{box-shadow:0 0 0 9px rgba(53,208,127,0)}
  100%{box-shadow:0 0 0 0 rgba(53,208,127,0)}}

.band{padding:56px 0;border-top:1px solid var(--line)}
.band-wash{background:var(--wash)}
.band-dark{background:var(--ink);color:#c9d3dd;border:none}
.band-dark>.wrap>.stats:first-child{margin-top:0}
.band-dark h2{color:#fff}
.band-dark p{color:#a9b6c3}
.band h2:first-child{margin-top:0}
.band-head{max-width:60ch;margin-bottom:26px}
.band-head h2{margin-top:0}
.band-head p{color:var(--ink-3);font-size:17.5px;margin:0}
@media(max-width:700px){.band{padding:38px 0}}

/* ---------- service cards with icons ---------- */
.svcgrid{display:grid;gap:14px;grid-template-columns:1fr}
@media(min-width:620px){.svcgrid{grid-template-columns:1fr 1fr}}
@media(min-width:900px){.svcgrid.three{grid-template-columns:repeat(3,1fr)}}
.svc{display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  padding:22px 20px 20px;text-decoration:none;color:inherit;position:relative;overflow:hidden;
  transition:border-color .16s,transform .16s,box-shadow .16s}
.svc:hover{border-color:var(--brand);transform:translateY(-3px);box-shadow:var(--shadow)}
/* A gold rule that runs across the top edge on hover. One line of CSS, and it
   makes a grid of cards feel responsive instead of static. */
.svc:before{content:"";position:absolute;left:0;top:0;height:3px;width:0;background:var(--brand);
  transition:width .28s ease}
.svc:hover:before,.svc:focus-visible:before{width:100%}
/* The icon sat naked on the card. In a soft brand disc it reads as deliberate,
   and it gives the eye somewhere to land before the words. */
.svc .ic{color:var(--brand-dk);margin-bottom:14px;display:inline-grid;place-items:center;
  width:52px;height:52px;border-radius:13px;background:var(--brand-wash);
  border:1px solid var(--brand-line);line-height:0;transition:background .16s}
.svc:hover .ic{background:#faf1de}
.svc b{display:block;font-size:17.5px;font-weight:670;margin-bottom:5px;letter-spacing:-.01em}
.svc p{font-size:14.5px;color:var(--ink-3);margin:0;line-height:1.55}
.svc .from{display:block;margin-top:auto;padding-top:13px;font-size:14px;font-weight:670;color:var(--ink)}
.svc .from span{color:var(--ink-3);font-weight:400}

/* ---------- stat strip ---------- */
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--line);
  border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;margin:0}
@media(min-width:760px){.stats{grid-template-columns:repeat(4,1fr)}}
.stat{background:#fff;padding:22px 18px;text-align:center;position:relative}
.stat b{display:block;font-size:33px;font-weight:760;letter-spacing:-.035em;line-height:1}
.stat span{display:block;font-size:13.5px;color:var(--ink-3);margin-top:7px}
.band-dark .stats{background:#25303b;border-color:#25303b}
.band-dark .stat{background:#161d25}
/* The numbers are the argument. Gold makes them the argument. */
.band-dark .stat b{color:var(--brand)}
.band-dark .stat span{color:#93a3b3}
/* A hairline that draws itself in as each stat arrives. Purely decorative, so
   it is the first thing to go under prefers-reduced-motion. */
.stat:after{content:"";position:absolute;left:50%;bottom:0;width:0;height:2px;
  background:var(--brand);transform:translateX(-50%);transition:width .5s ease .1s}
.stats.in .stat:after{width:34px}
.band-dark .stat span{color:#93a3b2}

/* ---------- steps ---------- */
.steps{counter-reset:s;display:grid;gap:16px;grid-template-columns:1fr;margin:24px 0 0}
@media(min-width:760px){.steps{grid-template-columns:repeat(3,1fr)}}
.step-c{counter-increment:s;position:relative;padding-top:44px}
.step-c:before{content:counter(s);position:absolute;top:0;left:0;width:34px;height:34px;
  border-radius:50%;background:var(--brand);color:#1a1205;display:grid;place-items:center;
  font-weight:750;font-size:15px}
.step-c b{display:block;font-size:16.5px;font-weight:670;margin-bottom:4px}
.step-c p{font-size:14.5px;color:var(--ink-3);margin:0}
.band-dark .step-c p{color:#a9b6c3}

/* ---------- photo slot ---------- */
.photo{background:linear-gradient(135deg,var(--wash) 25%,#eef2f6 25%,#eef2f6 50%,var(--wash) 50%,
  var(--wash) 75%,#eef2f6 75%);background-size:16px 16px;border:1px solid var(--line);
  border-radius:var(--radius);min-height:230px;display:grid;place-items:center;text-align:center;
  color:var(--ink-3);font-size:13.5px;padding:20px}
/* pull quote — for a line that should read slower than the paragraph around it */
.pullq{font-family:Georgia,'Times New Roman',serif;font-size:19px;line-height:1.5;
  color:var(--ink);border-left:3px solid var(--brand);padding:2px 0 2px 18px;margin:18px 0}
.pullq strong{font-family:inherit}
@media(max-width:560px){.pullq{font-size:17.5px;padding-left:14px}}
.revsplit{display:grid;gap:20px;grid-template-columns:1fr;align-items:center;
  background:var(--wash);border:1px solid var(--line);border-radius:var(--radius);padding:20px 22px;margin-top:16px}
@media(min-width:760px){.revsplit{grid-template-columns:1.6fr 1fr;gap:28px}}
.split{display:grid;gap:26px;grid-template-columns:1fr;align-items:center}
@media(min-width:820px){.split{grid-template-columns:1fr 1fr}.split.rev>*:first-child{order:2}}

/* ---------- section rhythm ----------
   Measured on the homepage 13 Aug 2026: thirteen sections, twelve of them
   identical — 900px wide, left aligned, 56px padding top and bottom. The
   background colours alternate, so there IS colour rhythm, but every section is
   the same SHAPE. That sameness is what reads as plain; it is not the palette
   and it is not the type.

   Two changes, both structural rather than decorative:

   1. MEDIA SECTIONS BREAK THE TEXT MEASURE. 900px is a good line length for
      reading and a bad one for a row of photographs — the pictures were being
      held to the width of a paragraph. Any band containing a photo row, the
      tier cards or the client logos now runs to 1120px. Done with :has() so it
      is automatic: a section qualifies by what is inside it, and nobody has to
      remember to tag one. Browsers without :has() simply keep 900px, which is
      the current behaviour, so the failure mode is "no change".

   2. PADDING SCALES WITH THE SECTION. A 164px band and a 1015px band both had
      exactly 56px of air, which makes the small one feel cramped and the big
      one feel unframed. Media sections get more room to sit in. */
.band:has(.phrow) .wrap,
.band:has(.tiers) .wrap,
.band:has(.clients) .wrap,
.band:has(.svcgrid) .wrap{max-width:1120px}
@media(min-width:900px){
  .band:has(.phrow),
  .band:has(.tiers),
  .band:has(.clients){padding-top:74px;padding-bottom:74px}
}

/* ---------- credentials bar ----------
   Sits directly under the hero. Deliberately quiet: this is not a sales band,
   it is the row of facts a suspicious person checks before they call a stranger
   to their house at night. Reads as one line on desktop, two columns on a
   phone, and the licence number is the only thing at full ink strength. */
.credbar{background:var(--wash);border-bottom:1px solid var(--line)}
.cr-row{display:grid;gap:14px 26px;grid-template-columns:1fr 1fr;padding:14px 0}
@media(min-width:820px){.cr-row{grid-template-columns:repeat(4,auto);
  justify-content:space-between;gap:26px}}
.cr-i{display:flex;flex-direction:column;line-height:1.3}
.cr-i b{font-size:13.5px;font-weight:700;color:var(--ink);letter-spacing:-.005em}
.cr-i span{font-size:12.5px;color:var(--ink-3)}

/* ---------- answer-first strip ---------- */
.answerfirst{display:grid;gap:16px;align-items:center;background:var(--wash);
  border:1px solid var(--line);border-left:3px solid var(--brand);
  border-radius:var(--radius);padding:18px 20px;margin:22px 0 8px}
@media(min-width:700px){.answerfirst{grid-template-columns:1fr auto}}
.af-facts{display:grid;gap:14px}
@media(min-width:520px){.af-facts{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}}
.af-i{display:flex;flex-direction:column;gap:2px}
.af-k{font-size:11.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3)}
.af-i b{font-size:19px;font-weight:730;letter-spacing:-.015em;color:var(--ink)}
.af-s{font-size:13px;color:var(--ink-3);line-height:1.4}
.af-cta{white-space:nowrap}

/* small label above a heading — used for the MK-1 spotlight.
   NOTE: this redeclares .eyebrow from the hero section above and wins on
   source order. 11.5px + uppercase + .09em tracking was the smallest text on
   the site; bumped to 12px, which is the floor worth shipping on a phone. */
.eyebrow{display:inline-block;font-size:12px;font-weight:750;letter-spacing:.09em;
  text-transform:uppercase;color:var(--brand-dk);background:var(--brand-wash);
  border:1px solid var(--brand-line);padding:4px 10px;border-radius:999px}

/* ---------- sticky mobile call bar ---------- */
.callbar{display:none}
@media(max-width:700px){
  .callbar{display:grid;grid-template-columns:1fr 1fr;gap:0;position:fixed;left:0;right:0;bottom:0;
    z-index:80;box-shadow:0 -2px 16px rgba(16,24,40,.16)}
  .callbar a{padding:15px 8px;text-align:center;font-weight:700;font-size:15.5px;
    text-decoration:none}
  .callbar .c1{background:var(--ink);color:#fff}
  .callbar .c2{background:var(--brand);color:#1a1205}
  body{padding-bottom:56px}
}

/* ---------- mobile tap targets ----------
   From the 390px audit (12 Aug 2026): 32 of 43 standalone controls were under
   the 44px minimum. Footer links measured 17px tall, the topbar phone link
   15px. Nothing overflowed and no page scrolled sideways — the geometry was
   fine, the targets were just small.

   Scoped to mobile ON PURPOSE. A 17px footer link is perfectly clickable with
   a mouse, and padding every link to 44px on desktop would stretch the footer
   for no gain. Phones get the padding; desktop keeps the tighter design. */
@media(max-width:860px){
  /* Footer nav — a stacked list of links is the classic mis-tap zone. */
  footer li{margin-bottom:0}
  /* 14px top and bottom on a 17px line clears 44px exactly. Measured, not
     guessed — 11px got it to 40 and 40 is still a mis-tap. */
  footer .fgrid li a{display:inline-block;padding:14px 0;line-height:1.25}
  /* Padding every footer link to 44px took the mobile footer from ~1.4k to
     1971px — five screens of footer. Two columns buys the tap targets back
     without the height. Only the link lists; the business-info block is a
     plain div and keeps the full width. */
  footer .fgrid ul{columns:2;column-gap:18px}
  footer .fgrid li{break-inside:avoid}

  /* Phone and email. These are the two highest-intent taps on a locksmith
     site and they were the two smallest things on the page. */
  .topbar a,footer a[href^="tel:"],footer a[href^="mailto:"]{
    display:inline-block;padding:13px 0}
  .hcall{display:inline-flex;align-items:center;min-height:44px}
  .hbook{display:inline-flex;align-items:center;min-height:44px}

  /* Header nav strip. Vertical padding for the thumb; the horizontal padding
     already existed for the scroll rhythm. */
  nav.main a{padding:11px 14px 11px 0}
}

/* The header nav is a horizontal scroll strip below 860px. At 390px, 118px of
   it sits past the right edge — "Commercial" and "About" are reachable only by
   swiping, and a part-cut word is a weak hint that there is more. This adds a
   fade at the right edge so it reads as scrollable. It is pinned to the header
   rather than the nav so it does not scroll away with the content. */
@media(max-width:860px){
  header .hbar{position:relative}
  header .hbar:after{content:"";position:absolute;right:0;bottom:0;
    width:34px;height:40px;pointer-events:none;
    background:linear-gradient(to right,rgba(255,255,255,0),#fff 72%)}
}


/* ---------- photographs ---------- */
/* MK product illustrations. Original line drawings, one visual system —
   see illustrations.py. Sized in ch-independent units so the three sit at
   identical scale wherever they appear; that consistency is the entire
   argument for drawing them rather than photographing three imported units. */
/* Rotating review carousel. Every slide stays in the DOM and is only visually
   hidden, so the text is indexable and reachable by a screen reader — a
   carousel that only exists once JavaScript runs is invisible to both. */
.rc{position:relative;margin:18px 0 6px;background:var(--wash);
    border:1px solid var(--line);border-radius:var(--radius);padding:20px 46px 40px}
/* THE GAP, FIXED 12 Aug 2026. Timmy: "it'll show one review, there's no
   carousel, and then there's this huge gap between that and whatever's next."
   Both halves of that were real and both were mine.

   The gap: every slide sat in the SAME grid cell (grid-area:1/1), so the track
   was always as tall as the LONGEST review. Show a two-line review and you get
   four lines of empty space under it. Now only the active slide is in flow —
   the inactive ones are taken out with position:absolute — so the block is
   exactly as tall as the review you are reading. Every slide is still in the
   DOM, which is what keeps the text indexable and reachable by a screen
   reader. */
.rc-track{list-style:none;margin:0;padding:0;position:relative}
.rc-slide{position:absolute;left:0;top:0;right:0;opacity:0;visibility:hidden;
  transition:opacity .35s ease}
.rc-slide.on{position:relative;opacity:1;visibility:visible}
.rc-stars{color:var(--brand-dk);letter-spacing:2px;font-size:15px;margin-bottom:8px}
.rc blockquote{margin:0;font-size:16.5px;line-height:1.6;color:var(--ink-2)}
/* The quote marks are LITERAL characters, not CSS escapes, and that is on
   purpose. This CSS lives inside a Python string, where "C" is not a CSS
   escape at all — Python reads  as an OCTAL escape (U+0081, an invisible
   control character) and leaves a bare "C" behind it. Every review on the site
   rendered as an invisible byte followed by a literal C, which is exactly what
   Timmy saw and called tacky. Same for D and its stray D. */
.rc blockquote:before{content:"“"}
.rc blockquote:after{content:"”"}
/* Reviewer identity block. Timmy's note, 12 Aug: a review without a face reads
   as something we typed. Google's own profile photo next to the name is what
   makes it look like a person. Photos are hotlinked from Google's CDN — the
   same thing every review widget does — and any that fail swap to an initial
   disc via the inline onerror, which is also what renders for reviewers who
   never set a photo. Nothing here can break the layout: the disc and the photo
   are the same 38px. */
.rc figcaption{margin-top:13px;display:flex;align-items:center;gap:10px;
               font-size:13.5px;font-weight:640;color:var(--ink)}
.rc figcaption .sub{display:block;font-weight:400;color:var(--ink-3);font-size:12px;margin-top:1px}
.rc-av{width:38px;height:38px;border-radius:50%;flex:0 0 38px;object-fit:cover;
       background:var(--line-2);border:1px solid var(--line)}
.rc-ini{width:38px;height:38px;border-radius:50%;flex:0 0 38px;display:grid;place-items:center;
        background:var(--brand-dk);color:#fff;font-size:16px;font-weight:700;line-height:1}
.rc-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;
        border:1px solid var(--line);background:#fff;border-radius:50%;cursor:pointer;
        font-size:19px;line-height:1;color:var(--ink-3);display:grid;place-items:center;padding:0}
.rc-nav:hover{color:var(--brand-dk);border-color:var(--brand-line)}
.rc-prev{left:8px}
.rc-next{right:8px}
/* The controls were too quiet to read as controls — 8px dots the colour of the
   background. If it does not LOOK like a carousel, being one does not help. */
.rc-dots{position:absolute;left:0;right:0;bottom:13px;display:flex;gap:9px;
         justify-content:center;align-items:center}
.rc-dot{width:9px;height:9px;padding:0;border-radius:50%;border:0;cursor:pointer;
        background:#d6dde4;box-shadow:inset 0 0 0 1px #c3ccd5;transition:all .2s;
        position:relative}
/* The dot is 9px because a big dot looks clumsy — but 9px is an impossible
   target with a thumb (measured 9x9 in the 390px audit, vs the 44px minimum).
   So the HIT AREA is inflated to 44px with a transparent overlay while the
   visible dot stays small. This is why .rc-dots has a fixed height and the
   overlay is not allowed to affect layout. */
.rc-dot:before{content:"";position:absolute;left:50%;top:50%;
        width:44px;height:44px;transform:translate(-50%,-50%)}
.rc-dot:hover{background:var(--brand)}
.rc-dot.on{background:var(--brand-dk);box-shadow:none;width:22px;border-radius:99px}
/* Trustindex host + our fallback. The host has a min-height so the page does
   not jump when the third-party widget paints, and [hidden] is forced because
   several resets override it. */
.ti-host{min-height:300px}
.ti-fallback[hidden],.ti-more[hidden],.ti-host[hidden]{display:none!important}
.ti-more{margin:16px 0 0;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.ti-more .fine{margin:0}
@media(max-width:560px){.ti-host{min-height:340px}}

.rc-count{position:absolute;right:16px;bottom:11px;font-size:12px;font-weight:640;
          color:var(--ink-4);letter-spacing:.02em}
/* The counter used to be hidden below 560px. That was the same mistake as the
   hover pause, in a different place: on a phone the arrows are hidden too, so
   dropping the counter left the dots as the ONLY hint that there is more than
   one review — on the devices most of this site's traffic uses. It stays. */
@media(max-width:560px){.rc{padding:18px 16px 38px}.rc-nav{display:none}}
@media(prefers-reduced-motion:reduce){.rc-slide{transition:none}}
.ill{margin:0;text-align:center}
.ill svg{width:100%;max-width:190px;height:auto;display:inline-block}
.ill figcaption{margin-top:8px;font-size:13px;color:var(--ink-3);line-height:1.4}
.ill-sm svg{max-width:132px}
/* The wide MK-3 drawing: outside unit, inside plate and chime together. */
.ill-wide{margin:24px 0}
.ill-wide svg{max-width:100%;width:100%}
.ill-row{display:grid;gap:18px;grid-template-columns:repeat(2,1fr);
         margin:26px 0;align-items:end}
@media(min-width:820px){.ill-row{grid-template-columns:repeat(3,1fr)}}
.tier .ill{margin:2px 0 12px}
.tier .ill svg{max-width:112px}
.ph{margin:0;border-radius:var(--radius);overflow:hidden;background:var(--wash)}
.ph img{display:block;width:100%;height:auto}
.ph figcaption{font-size:13px;color:var(--ink-3);padding:9px 2px 0}
.band-dark .ph figcaption{color:#93a3b2}
.ph.crop img{aspect-ratio:4/3;object-fit:cover}
.ph.tall img{aspect-ratio:3/4;object-fit:cover}
.ph.wide img{aspect-ratio:16/7;object-fit:cover}
/* Product shot inside a pricing card. White studio background, so no border or
   wash behind it — the card supplies those. Capped so the picture introduces the
   product without pushing the price and the buy button below the fold. */
.tier .ph.prod{margin:-4px 0 10px;background:none}
.tier .ph.prod img{max-height:190px;width:auto;max-width:100%;margin:0 auto;display:block}

/* Portrait guard. A phone photo is 1206x2622; at width:100% in a 900px column
   that is about 1,900px of one deadbolt, which is exactly what Timmy hit on the
   Mul-T-Lock page. Cap the height, centre it, keep the whole subject visible
   rather than cropping the top off a product shot. core.photo() applies this
   automatically to anything taller than it is wide. */
.ph.portrait{background:none}
.ph.portrait img{max-height:520px;width:auto;max-width:100%;margin:0 auto;border-radius:var(--radius)}
.ph.portrait figcaption{text-align:center}

/* Photos side by side. Tall product shots compare far better in a row than
   stacked, and it stops any one of them dominating the page. */
.phrow{display:grid;gap:14px;grid-template-columns:1fr;margin:20px 0}
@media(min-width:620px){
  .phrow-2{grid-template-columns:1fr 1fr}
  .phrow-3{grid-template-columns:repeat(3,1fr)}
}
/* object-fit:contain, NOT cover. Added as cover on 12 Aug and corrected the same
   day: cover crops to fill the box, which on tall product shots sliced the top
   and bottom off the myQ keypads — Timmy: "they don't allow the keypad to be
   fully seen." In a row of mixed aspect ratios the whole point is that you can
   see each thing, so the box gets a neutral fill and the product stays whole. */
.phrow .ph{background:var(--wash);display:flex;flex-direction:column}
.phrow .ph img{width:100%;height:300px;object-fit:contain;object-position:center;
               background:var(--wash);padding:6px}
@media(max-width:619px){.phrow .ph img{height:260px}}
.phrow .ph figcaption{font-size:12.5px;line-height:1.4}

.hero-split{display:grid;gap:30px;grid-template-columns:1fr;align-items:center}
/* Rebalanced 12 Aug 2026. Was 1.05fr .95fr, which rendered the photo at
   497x226 — a thumbnail next to a 54px headline. For a family trade business
   three people standing in front of a branded van IS the pitch: it proves there
   is a real crew, in real uniforms, with a real vehicle, before a word is read.
   It should not be the smallest thing in the hero.

   Measured, not guessed: .92fr 1.08fr only bought 12% more area (497x226 ->
   527x239), so it went further. .85fr 1.15fr is about +27%, and that is the
   limit — the h1 is capped at 13ch, which at 54px needs ~390px, and a narrower
   text column starts breaking "a doorknob." across lines badly. */
@media(min-width:900px){.hero-split{grid-template-columns:.85fr 1.15fr;gap:38px}}
/* Superseded 12 Aug 2026 by the hero block above — the border-radius here was
   on the FIGURE, not the image, so the photo's own square corners sat on top of
   it and nothing looked rounded. Kept only for the shadow fallback. */
.hero .ph{box-shadow:none}
.hero .ph img{aspect-ratio:auto;object-fit:contain}
.hero .ph figcaption{display:none}
@media(max-width:899px){.hero .ph{margin-top:24px}}

/* ---------- client logo strip ---------- */
.clients{margin:30px 0 0;background:var(--ink);border-radius:var(--radius);padding:26px 24px}
.band-dark .clients{background:#161d25}
.cl-head{font-size:12px;text-transform:uppercase;letter-spacing:.09em;color:#8fa0b0;
  font-weight:700;margin:0 0 18px;text-align:center}
.clogos{display:flex;flex-wrap:wrap;gap:16px 34px;align-items:center;justify-content:center}
.clogo{display:flex;align-items:center;height:30px}
.clogo img{display:block;height:25px;width:auto;opacity:.9}
.cl-note{font-size:13.5px;color:#93a3b2;text-align:center;margin:18px auto 0;max-width:62ch}
@media(max-width:560px){.clogo img{height:20px}.clogos{gap:14px 22px}.clients{padding:22px 16px}}

/* ---------- staging banner ---------- */
.staging{background:#7a1d1d;color:#fff;font-size:13px;font-weight:600;padding:9px 0;text-align:center;
  letter-spacing:.01em}
.staging span{opacity:.85;font-weight:400}
