/* NOTE: the font-variation-settings:"wdth" declarations further down are
   inert under Chakra Petch, which is a static face. They were cut for
   Saira, which carried a real width axis. Harmless where they sit, and
   they are the tuning to restore if the display face ever goes variable
   again. Do not read them as doing anything today. */
:root{
  --night:#0B0E0D; --coal:#131816; --gun:#1B2220; --steel:#2B3532; --steel2:#3C4844;
  --tracer:#AFD03E; --tracer2:#8BA82F; --tracerdim:rgb(175 208 62 / 14%);
  --flare:#FF7A18;
  --chalk:#E8F0EB; --chalk2:#A9BBB1; --chalk3:#7C8F86;
  --disp:"Chakra Petch",system-ui,sans-serif;
  --body:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",helvetica,sans-serif;
  --r:4px;
}
*{box-sizing:border-box}
html{background:var(--night);scroll-behavior:smooth}

/* ===== texture on the flat dark sections =====
   Flat black reads as a hole in the page. Four very low contrast layers give
   it a surface: a fine diagonal hatch for grain, a 72px drafting grid for the
   plane, and a soft lift off the top edge so the section feels raised rather
   than cut out. Pure CSS, so no extra request and none of the banding a
   compressed noise tile gets on a near black field. */
:root{
  --grain:
    repeating-linear-gradient(135deg,rgb(255 255 255 / 1.5%) 0 1px,rgb(0 0 0 / 0%) 1px 6px),
    repeating-linear-gradient(0deg,rgb(255 255 255 / 1.9%) 0 1px,rgb(0 0 0 / 0%) 1px 72px),
    repeating-linear-gradient(90deg,rgb(255 255 255 / 1.9%) 0 1px,rgb(0 0 0 / 0%) 1px 72px),
    radial-gradient(135% 104% at 50% 0%,rgb(255 255 255 / 3%),rgb(255 255 255 / 0%) 62%);
}
body{background:var(--night);color:var(--chalk);font-family:var(--body);font-size:18px;line-height:1.65;margin:0;-webkit-font-smoothing:antialiased}
.w{max-width:1200px;margin:0 auto;padding:0 28px}
.wn{max-width:860px;margin:0 auto;padding:0 28px}
h1,h2,h3,.disp{font-family:var(--disp);font-variation-settings:"wdth" 112;font-weight:700;letter-spacing:-.016em;line-height:1.04}
h1{font-size:clamp(38px,5.4vw,64px);font-weight:700;text-transform:uppercase;letter-spacing:-.018em;margin:0 0 20px;color:#fff}
h2{font-size:clamp(30px,3.9vw,46px);font-weight:700;margin:0 0 18px;color:#fff}
h3{font-size:23px;margin:0 0 10px;color:#fff;font-weight:700;letter-spacing:-.012em}
p{margin:0 0 17px}
a{color:var(--tracer)}
.kicker{font-family:var(--disp);font-variation-settings:"wdth" 110;font-size:13px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--tracer);margin:0 0 14px;display:block}
.lede{font-size:21px;line-height:1.56;color:var(--chalk2)}
.sec{padding:96px 0;background-image:var(--grain)}
.sec.tight{padding:72px 0}
.sec.coal{background-color:var(--coal);background-image:var(--grain)}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:9px;font-family:var(--disp);font-variation-settings:"wdth" 108;font-size:17px;font-weight:700;letter-spacing:-.004em;padding:16px 28px;text-decoration:none;border-radius:var(--r);border:2px solid transparent;transition:transform .12s ease}
.btn:hover{transform:translateY(-1px)}
.btn-call{background:var(--tracer);color:#16200A;border-color:var(--tracer);box-shadow:0 6px 28px rgb(175 208 62 / 24%)}
.btn-em{background:var(--flare);color:#200C00;border-color:var(--flare);box-shadow:0 6px 28px rgb(255 122 24 / 22%)}
.btn-gh{background:transparent;color:#fff;border-color:var(--steel2)}
.btn-gh:hover{border-color:var(--tracer);color:var(--tracer)}
.btn-sm{font-size:16px;padding:12px 20px}

/* header */
header{position:sticky;top:0;z-index:50;background:rgb(11 14 13 / 93%);backdrop-filter:blur(10px);border-bottom:1px solid var(--steel)}
.hdr{display:flex;align-items:center;gap:22px;padding:14px 0}
.brand{display:flex;align-items:center;gap:12px;text-decoration:none;flex:none}
.brand .mark{display:block;width:46px;height:auto;flex:none}
.brand .nm{font-family:var(--disp);font-variation-settings:"wdth" 112;font-weight:700;text-transform:uppercase;letter-spacing:-.01em;line-height:.92}
.brand .nm b{display:block;font-size:23px;color:#fff;font-weight:700}
.brand .nm i{display:block;font-size:12px;letter-spacing:.22em;color:var(--tracer);font-style:normal;font-weight:600}
nav.main{display:flex;gap:21px;margin-left:8px;flex-wrap:nowrap}
nav.main a{font-family:var(--disp);font-size:15.5px;font-weight:600;letter-spacing:-.004em;color:var(--chalk2);text-decoration:none;white-space:nowrap}
nav.main a:hover{color:#fff}
.hdr-cta{margin-left:auto;display:flex;gap:12px;align-items:center}
.hdr-ph{font-family:var(--disp);font-size:23px;white-space:nowrap;font-weight:700;color:#fff;text-decoration:none;letter-spacing:.01em}

@media(width <=1180px){nav.main a:nth-child(6){display:none}}

@media(width <=1060px){nav.main a:nth-child(5){display:none}}

@media(width <=980px){nav.main{display:none}}

@media(width <=620px){.hdr-ph{display:none}}

/* ===== image first system =====
   Every major section is a photograph that bleeds edge to edge.
   Text sits on it, small, in a corner. Accent appears on CTAs only. */

/* full bleed photographic band */
.band-img{position:relative;width:100%;min-height:clamp(560px,78vh,860px);display:flex;overflow:hidden;background:#07090A}
.band-img>.bg{position:absolute;inset:0;background:#07090A center/cover no-repeat;will-change:transform}
.band-img>video.bg{width:100%;height:100%;object-fit:cover}
.band-img>.veil{position:absolute;inset:0}

/* scrim variants, chosen by where the photo is empty */
.v-left  >.veil{background:linear-gradient(95deg,rgb(7 9 10 / 94%) 0%,rgb(7 9 10 / 80%) 34%,rgb(7 9 10 / 30%) 62%,rgb(7 9 10 / 10%) 100%)}
.v-right >.veil{background:linear-gradient(265deg,rgb(7 9 10 / 94%) 0%,rgb(7 9 10 / 80%) 34%,rgb(7 9 10 / 30%) 62%,rgb(7 9 10 / 10%) 100%)}
.v-bottom>.veil{background:linear-gradient(0deg,rgb(7 9 10 / 95%) 0%,rgb(7 9 10 / 62%) 32%,rgb(7 9 10 / 10%) 70%,transparent 100%)}
.v-top   >.veil{background:linear-gradient(180deg,rgb(7 9 10 / 92%) 0%,rgb(7 9 10 / 45%) 42%,transparent 78%)}
.band-in{position:relative;z-index:2;width:100%;max-width:1320px;margin:0 auto;padding:clamp(44px,6vw,92px) 28px;display:flex;align-items:flex-end}
.band-in.mid{align-items:center}
.band-cp{max-width:40ch}
.band-cp.right{margin-left:auto;text-align:right}
.band-cp.center{margin:0 auto;text-align:center;max-width:34ch}
.eyebrow{display:block;font-family:var(--disp);font-variation-settings:"wdth" 110;font-size:12px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:var(--tracer);margin-bottom:16px}
.band-cp h1,.band-cp h2{font-size:clamp(34px,4.6vw,62px);line-height:1;margin:0 0 16px;text-transform:uppercase;letter-spacing:-.02em;font-weight:700}
.band-cp p{font-size:clamp(16px,1.3vw,19px);color:var(--chalk2);margin:0 0 26px;max-width:42ch}
.band-cp.right p{margin-left:auto}
.band-cp.center p{margin:0 auto 26px}

/* corner brackets, the tactical framing device */
.brk{position:relative;display:inline-block;padding:22px 26px}
.brk::before,.brk::after{content:"";position:absolute;width:26px;height:26px;border:2px solid var(--tracer)}
.brk::before{top:0;left:0;border-right:0;border-bottom:0}
.brk::after{bottom:0;right:0;border-left:0;border-top:0}

/* spec strip, the DiMarzio data bar */
.spec{display:flex;flex-wrap:wrap;gap:0;margin:0 0 26px;border:1px solid rgb(232 240 235 / 18%);background:rgb(7 9 10 / 55%);backdrop-filter:blur(3px)}
.spec div{padding:11px 18px;border-right:1px solid rgb(232 240 235 / 14%);min-width:92px}
.spec div:last-child{border-right:0}
.spec .k{display:block;font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--chalk3);margin-bottom:3px}
.spec .v{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:19px;font-weight:700;color:#fff;letter-spacing:-.015em}
.spec .v.hot{color:var(--tracer)}

/* the sparse black band between photographs */
.slab{background-color:#07090A;background-image:var(--grain);padding:clamp(56px,7vw,104px) 0;border-top:1px solid #171D1B;border-bottom:1px solid #171D1B}
.slab-h{text-align:center;margin-bottom:clamp(38px,5vw,64px)}
.slab-h .eyebrow{text-align:center}
.slab-h h2{font-size:clamp(26px,3vw,40px);text-transform:uppercase;letter-spacing:-.016em;margin:0}

/* line art category grid, SigSauer featured categories */
.cats{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:0;max-width:1320px;margin:0 auto;padding:0 28px}

@media(width <=1000px){.cats{grid-template-columns:repeat(3,minmax(0,1fr))}}

@media(width <=560px){.cats{grid-template-columns:repeat(2,minmax(0,1fr))}}
.cat{display:flex;flex-direction:column;align-items:center;gap:16px;padding:30px 12px;text-decoration:none;transition:background .16s}
.cat:hover{background:#0E1413}
.cat svg{width:62px;height:46px;stroke:#D7E4DE;fill:none;stroke-width:1.4;transition:stroke .16s}
.cat:hover svg{stroke:var(--tracer)}
.cat span{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--chalk2);text-align:center;display:flex;align-items:center;gap:7px}
.cat span i{width:15px;height:15px;border-radius:50%;background:var(--tracer);display:grid;place-items:center;flex:none}
.cat span i::before{content:"";border-left:4px solid #07090A;border-top:3px solid transparent;border-bottom:3px solid transparent;margin-left:1px}

/* trust bar, thin and quiet */
.tbar{background:#07090A;border-bottom:1px solid #171D1B}
.tbar-in{max-width:1320px;margin:0 auto;padding:16px 28px;display:flex;flex-wrap:wrap;gap:clamp(20px,3vw,54px);align-items:center;justify-content:center}
.tbar b{font-family:var(--disp);font-size:14px;font-weight:700;color:#fff;letter-spacing:.01em}
.tbar span{font-size:13px;color:var(--chalk3);margin-left:7px}
.tbar .dot{width:4px;height:4px;border-radius:50%;background:var(--tracer);flex:none}
.stars{color:var(--tracer);letter-spacing:2px;font-size:13px}

/* emergency strip */
.estrip{position:relative;border-top:1px solid #4A2408;border-bottom:1px solid #4A2408;background:#140800 center 40%/cover no-repeat}
.estrip::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgb(20 8 0 / 96%) 0%,rgb(20 8 0 / 86%) 46%,rgb(20 8 0 / 52%) 76%,rgb(20 8 0 / 86%) 100%)}
.estrip .w{position:relative;z-index:2}
.estrip-in{display:flex;flex-wrap:wrap;gap:22px;align-items:center;justify-content:space-between;padding:26px 0}
.estrip h3{color:var(--flare);margin:0 0 4px;font-size:22px;text-transform:uppercase;letter-spacing:-.01em}
.estrip p{margin:0;color:#E8C8A6;font-size:16.5px}

/* value-first help */
.help{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,246px),1fr));gap:1px;margin-top:0;background:#171D1B;border:1px solid #171D1B;max-width:1320px;margin-left:auto;margin-right:auto}
.help>div{background:#0B100F;padding:34px 30px}
.help .n{font-family:var(--disp);font-size:12px;font-weight:700;color:var(--tracer);letter-spacing:.14em;text-transform:uppercase;margin-bottom:14px}
.help h3{font-size:21px;text-transform:uppercase;letter-spacing:-.012em}
.help p{font-size:16.5px;color:var(--chalk2);margin:0}

/* steps as photographs */
.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:1px;background:#171D1B;max-width:1320px;margin:0 auto;counter-reset:s}
.step{position:relative;background:#07090A;padding:0 0 32px}
.step .shot{aspect-ratio:4/3;margin:0 0 26px;background:#0E1413 center/cover no-repeat;filter:saturate(.9) contrast(1.06)}

.step::before{counter-increment:s;content:"0" counter(s);position:absolute;top:0;left:0;z-index:2;
  font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:15px;font-weight:700;color:#07090A;
  background:var(--tracer);padding:7px 13px;letter-spacing:.08em}
.step h3{font-size:22px;margin:0 0 10px;padding:0 30px;text-transform:uppercase;letter-spacing:-.014em}
.step p{color:var(--chalk2);font-size:16.5px;margin:0;padding:0 30px}

/* crew */
.band{margin:40px 0 0;border-radius:var(--r);overflow:hidden;border:1px solid var(--steel);aspect-ratio:36/11;background:#111614 center/cover no-repeat;filter:saturate(.94) contrast(1.03)}
.band.tall{aspect-ratio:24/10}
.exp{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:22px;margin-top:40px}
.exp figure{margin:0;background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);overflow:hidden}
.exp .ph{aspect-ratio:4/3;background:#161C1A center/cover no-repeat;filter:saturate(.93) contrast(1.03)}
.exp figcaption{padding:20px 22px;font-size:16.5px;color:var(--chalk2)}
.exp figcaption b{display:block;font-family:var(--disp);font-size:20px;color:#fff;margin-bottom:5px;font-weight:700}

/* six people, so three across divides evenly. auto-fit was giving four in the
   first row and leaving two orphans beside a block of empty background. */
.crew{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(22px,2.6vw,38px);margin-top:46px}

@media(width <=880px){.crew{grid-template-columns:repeat(2,1fr)}}

@media(width <=520px){.crew{grid-template-columns:1fr}}
.mug{aspect-ratio:3/4;border-radius:var(--r);position:relative;overflow:hidden;background:#161C1A center top/cover no-repeat;filter:saturate(.95)}
.mug::before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgb(11 14 13 / 0%) 55%,rgb(11 14 13 / 50%) 100%)}
.crew h3{margin:18px 0 2px;font-size:23px}
.crew .role{font-family:var(--disp);font-size:13px;text-transform:uppercase;letter-spacing:.11em;color:var(--tracer);font-weight:600}
.crew p{font-size:16px;color:var(--chalk2);margin:10px 0 0}

/* objections */
.obj{margin-top:42px;border-top:1px solid var(--steel)}
.obj details{border-bottom:1px solid var(--steel)}
.obj summary{cursor:pointer;list-style:none;padding:24px 0;font-family:var(--disp);font-size:21px;font-weight:700;letter-spacing:-.012em;color:#fff;display:flex;justify-content:space-between;gap:20px;align-items:center}
.obj summary::-webkit-details-marker{display:none}
.obj summary::after{content:"+";color:var(--tracer);font-size:30px;line-height:1;flex:none}
.obj details[open] summary::after{content:"\2212"}
.obj .ans{padding:0 0 26px;color:var(--chalk2);font-size:17.5px;max-width:74ch}

/* reviews */
.revs{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:22px;margin-top:42px}
.rev{background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:28px}
.rev .st{color:var(--tracer);letter-spacing:3px;margin-bottom:14px;font-size:16px}
.rev q{display:block;font-size:17.5px;line-height:1.6;color:var(--chalk);quotes:none}
.rev .who{margin-top:18px;font-family:var(--disp);font-size:13px;text-transform:uppercase;letter-spacing:.11em;color:var(--chalk3)}

/* tool */

/* poly-B scorecard */
.sc{background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);margin-top:44px;overflow:hidden}
.sc-h{padding:24px 32px;border-bottom:1px solid var(--steel);background:linear-gradient(120deg,rgb(175 208 62 / 10%),transparent 62%);display:flex;flex-wrap:wrap;gap:16px;align-items:center;justify-content:space-between}
.sc-h h3{margin:0;font-size:24px}
.sc-h .tag{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--tracer)}
.rail{display:flex;gap:4px;padding:0 32px;margin-top:22px}
.rail i{flex:1;height:3px;border-radius:99px;background:var(--steel);transition:background .3s}
.rail i.on{background:var(--tracer)}
.sc-b{padding:28px 32px 32px}
.stepn{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--chalk3);margin-bottom:12px}
.step-q{display:none}
.step-q.on{display:block;animation:fade .26s ease}

@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.step-q h4{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:clamp(23px,2.6vw,30px);font-weight:700;letter-spacing:-.016em;color:#fff;margin:0 0 8px;line-height:1.12}
.why{font-size:16.5px;color:var(--chalk3);margin:0 0 22px;max-width:60ch}
.ops{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:11px}
.op{text-align:left;font-family:var(--body);font-size:17px;font-weight:500;padding:17px 20px;border:1.5px solid var(--steel2);background:transparent;color:var(--chalk);border-radius:var(--r);cursor:pointer;transition:all .14s}
.op:hover{border-color:var(--tracer);color:#fff;transform:translateY(-1px)}
.op small{display:block;font-size:14.5px;color:var(--chalk3);margin-top:3px}
.row{display:flex;gap:11px;flex-wrap:wrap;align-items:center}
.ti{font-family:var(--body);font-size:18px;background:var(--night);border:1.5px solid var(--steel2);color:#fff;padding:15px 18px;border-radius:var(--r);flex:1;min-width:240px}
.ti:focus{outline:none;border-color:var(--tracer)}
.found{margin-top:16px;padding:15px 18px;border-radius:var(--r);background:rgb(175 208 62 / 8%);border:1px solid rgb(175 208 62 / 30%);font-size:16px;color:var(--chalk2)}
.found b{color:#fff}
.found .src{display:block;margin-top:5px;font-size:13.5px;color:var(--chalk3)}
.drop{border:1.5px dashed var(--steel2);border-radius:var(--r);padding:30px;text-align:center;cursor:pointer;transition:all .14s}
.drop:hover{border-color:var(--tracer)}
.drop b{display:block;font-family:var(--disp);font-size:18px;color:#fff;margin-bottom:5px}
.drop span{font-size:15.5px;color:var(--chalk3)}
.shotwrap{display:flex;gap:18px;align-items:flex-start;margin-top:18px}
.shotwrap img{width:150px;border-radius:var(--r);border:1px solid var(--steel2)}
.vision{flex:1;font-size:16px;color:var(--chalk2)}
.vision b{display:block;color:#fff;font-family:var(--disp);font-size:17px;margin-bottom:4px}
.est{display:inline-block;margin-top:9px;font-size:12.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--flare);font-family:var(--disp);font-weight:700}
.back{margin-top:22px;background:none;border:none;color:var(--chalk3);font-family:var(--body);font-size:15.5px;cursor:pointer;padding:0}
.back:hover{color:var(--tracer)}

/* the card itself */
.card{display:none}
.card.on{display:block;animation:fade .3s ease}
.card-top{display:flex;flex-wrap:wrap;gap:26px;align-items:center;padding-bottom:26px;border-bottom:1px solid var(--steel)}
.dial{position:relative;width:132px;height:132px;flex:none}
.dial svg{transform:rotate(-90deg)}
.dial .num{position:absolute;inset:0;display:grid;place-items:center;font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:40px;font-weight:700;color:#fff;letter-spacing:-.03em}
.grade{flex:1;min-width:220px}

/* .band is also the photo band at the top of this sheet, so the result
   grade has to shed that border, fill and aspect ratio */
.grade .band{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:clamp(28px,3.4vw,40px);font-weight:700;letter-spacing:-.02em;line-height:1;margin:0 0 9px;border:0;border-radius:0;background:none;aspect-ratio:auto;overflow:visible;filter:none}
.grade p{margin:0;font-size:17px;color:var(--chalk2)}
.tbl{width:100%;border-collapse:collapse;margin-top:24px;font-size:16px}
.tbl th{text-align:left;font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--chalk3);padding:0 0 10px;border-bottom:1px solid var(--steel)}
.tbl th:last-child,.tbl td:last-child{text-align:right;white-space:nowrap}

/* A routing card has no Points column, so its single cell must not inherit the
   right alignment meant for the number. */
.tbl.noscore th:last-child,.tbl.noscore td:last-child{text-align:left;white-space:normal}
.tbl td{padding:13px 0;border-bottom:1px solid var(--steel);color:var(--chalk2);vertical-align:top}
.tbl td b{color:#fff;display:block;font-weight:600}
.tbl td small{color:var(--chalk3);font-size:14.5px}
.tbl .pts{font-family:var(--disp);font-weight:700;color:var(--tracer);font-size:18px}
.tbl .pts.zero{color:var(--chalk3)}
.sowhat{margin-top:26px;padding:24px;border-radius:var(--r);background:var(--night);border-left:3px solid var(--flare)}
.sowhat b{display:block;font-family:var(--disp);font-size:19px;color:#fff;margin-bottom:7px}
.sowhat p{margin:0;font-size:16.5px;color:var(--chalk2)}
.sc-cta{display:flex;gap:12px;flex-wrap:wrap;margin-top:26px;align-items:center}
.redo{background:none;border:none;color:var(--chalk3);font-family:var(--body);font-size:15.5px;cursor:pointer}
.redo:hover{color:var(--tracer)}

/* final cta */
.final{background:linear-gradient(135deg,#16211C,#0B0E0D);border-top:1px solid var(--steel)}
.final h2{margin-bottom:14px}

footer{background:var(--night);border-top:1px solid var(--steel);padding:46px 0;font-size:15.5px;color:var(--chalk3)}
footer b{color:var(--chalk)}

/* mockup notes */
.note{background:#1C1407;border-left:3px solid var(--flare);padding:16px 20px;margin:0 0 30px;font-size:15.5px;color:#EBD2B2;border-radius:0 var(--r) var(--r) 0}
.note b{font-family:var(--disp);text-transform:uppercase;letter-spacing:.11em;font-size:12px;color:var(--flare);display:block;margin-bottom:4px}
.logos{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:22px;margin-top:40px}
.lg{background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:36px 24px;text-align:center}
.lg .cap{margin-top:20px;font-size:15px;color:var(--chalk3);line-height:1.55}
.lg .cap b{color:#fff;font-family:var(--disp);font-size:16px;font-weight:700;display:block;margin-bottom:5px}

@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* nav active state */
nav.main a.on{color:var(--tracer)}

/* footer */
.ft{border-top:1px solid var(--steel);background:var(--coal);padding:62px 0 30px;margin-top:0}
.ft-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:38px}

@media(width <=860px){.ft-grid{grid-template-columns:1fr 1fr}}

@media(width <=520px){.ft-grid{grid-template-columns:1fr}}
.ft-grid b{display:block;font-family:var(--disp);font-size:13px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--chalk3);margin-bottom:14px}
.ft-grid a{display:block;color:var(--chalk2);text-decoration:none;font-size:16px;margin-bottom:9px}
.ft-grid a:hover{color:var(--tracer)}
.ft .brand{display:flex}
.ft-nap{font-size:15.5px;color:var(--chalk3);line-height:1.75;margin:0}
.ft-nap a{display:inline;color:var(--tracer)}
.ft-cities{font-size:15.5px;color:var(--chalk3);line-height:2;margin:0}
.ft-cities a{display:inline;color:var(--chalk2)}
.ft-base{display:flex;flex-wrap:wrap;gap:16px;justify-content:space-between;margin-top:44px;padding-top:22px;border-top:1px solid var(--steel);font-size:14.5px;color:var(--chalk3)}

/* breadcrumbs */
.crumb{font-size:14.5px;color:var(--chalk3);padding:22px 0 0}
.crumb a{color:var(--chalk2);text-decoration:none}
.crumb a:hover{color:var(--tracer)}
.crumb span{margin:0 8px;opacity:.5}

/* page hero, lighter than the home hero */
.phero{position:relative;border-bottom:1px solid var(--steel);overflow:hidden}
.phero .plate{position:absolute;inset:0;background:#0B0E0D center 45%/cover no-repeat;opacity:.5}
.phero .scrim{position:absolute;inset:0;background:linear-gradient(95deg,#0B0E0D 4%,rgb(11 14 13 / 90%) 42%,rgb(11 14 13 / 55%) 78%,rgb(11 14 13 / 82%) 100%)}
.phero .in{position:relative;z-index:2;padding:62px 0 70px}
.phero h1{max-width:19ch}
.phero .lede{max-width:56ch}

/* generic prose + two column */
.prose{max-width:70ch}
.prose h3{margin-top:36px}
.prose ul{padding-left:22px;color:var(--chalk2);font-size:17px;line-height:1.7}
.prose li{margin-bottom:10px}
.split{display:grid;grid-template-columns:1.15fr .85fr;gap:52px;align-items:start}

@media(width <=900px){.split{grid-template-columns:1fr;gap:34px}}
.aside{background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:28px;position:sticky;top:96px}
.aside b{display:block;font-family:var(--disp);font-size:19px;color:#fff;margin-bottom:9px}
.aside p{font-size:16px;color:var(--chalk2)}

/* next step rail, the JSEO funnel router */
.nextup{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));gap:20px;margin-top:44px}
.nextup a{display:block;background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:26px;text-decoration:none;transition:all .14s}
.nextup a:hover{border-color:var(--tracer);transform:translateY(-2px)}
.nextup .k{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--tracer);display:block;margin-bottom:9px}
.nextup b{display:block;font-family:var(--disp);font-size:21px;color:#fff;margin-bottom:6px;letter-spacing:-.012em}
.nextup p{margin:0;font-size:16px;color:var(--chalk2)}

/* city grid */
.citygrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,246px),1fr));gap:18px;margin-top:38px}
.citygrid a{display:block;background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:22px 24px;text-decoration:none;transition:all .14s}
.citygrid a:hover{border-color:var(--tracer)}
.citygrid b{display:block;font-family:var(--disp);font-size:20px;color:#fff;letter-spacing:-.012em}
.citygrid span{display:block;font-size:14.5px;color:var(--chalk3);margin-top:4px}

/* hero video plate */
.phero video,.phero>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.55}

/* reduced motion hides the hero video, so a still plate marked .vfall stands in
   for it. Without this the hero loses its background image entirely. */
.phero .plate.vfall{display:none}

@media(prefers-reduced-motion:reduce){
  .phero video{display:none}
  .phero .plate.vfall{display:block}
}

/* ===== 3D review carousel ===== */
.stage{position:relative;height:clamp(420px,52vw,560px);perspective:1600px;perspective-origin:50% 48%;max-width:1320px;margin:0 auto}
.ring{position:absolute;inset:0;transform-style:preserve-3d;will-change:transform}

.rev3{position:absolute;top:50%;left:50%;width:clamp(260px,24vw,330px);margin:0;
  transform-style:preserve-3d;background:#0E1413;border:1px solid #1F2725;overflow:hidden;
  box-shadow:0 30px 70px rgb(0 0 0 / 60%)}
.rev3 .ph{aspect-ratio:4/3;background:#131A18 center/cover no-repeat;filter:saturate(.9) contrast(1.05)}
.rev3 .bd{padding:22px 22px 24px}
.rev3 .st{color:var(--tracer);letter-spacing:3px;font-size:13px;margin-bottom:11px}
.rev3 q{display:block;font-size:16.5px;line-height:1.55;color:var(--chalk);quotes:none}
.rev3 .who{margin-top:14px;font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--chalk3)}
.ringnav{display:flex;gap:10px;justify-content:center;align-items:center;margin-top:clamp(26px,4vw,44px)}
.ringnav button{width:42px;height:42px;border-radius:50%;border:1px solid #2B3532;background:transparent;color:var(--chalk2);cursor:pointer;font-size:17px;line-height:1;transition:all .15s}
.ringnav button:hover{border-color:var(--tracer);color:var(--tracer)}
.ringnav .pips{display:flex;gap:7px;margin:0 12px}
.ringnav .pips i{width:7px;height:7px;border-radius:50%;background:#2B3532;transition:background .3s}
.ringnav .pips i.on{background:var(--tracer)}

@media(prefers-reduced-motion:reduce){.ring{transform:none!important}
  .stage{height:auto;perspective:none}
  .rev3{position:static;transform:none!important;width:auto;margin:0 0 18px}}

/* ===== GSAP reveal primitives ===== */
.rv{opacity:0;transform:translateY(26px)}
.rv-on{opacity:1;transform:none}
.kenburns{transform:scale(1.08)}

@media(prefers-reduced-motion:reduce){.rv{opacity:1;transform:none}.kenburns{transform:none}}

/* counter row */
.nums{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1px;background:#171D1B;max-width:1320px;margin:0 auto;border:1px solid #171D1B}
.nums div{background:#07090A;padding:34px 26px;text-align:center}
.nums .n{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:clamp(38px,4.4vw,58px);font-weight:700;color:var(--tracer);line-height:1;letter-spacing:-.03em}
.nums .l{display:block;margin-top:10px;font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--chalk3)}

/* =====================================================   FLOW SYSTEM
   Full viewport media sections, pill chrome, scroll driven.
   Reference grammar: relats top tier, terminal industries.
   ============================================================ */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto!important}

/* floating pill nav */
.pill-nav{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:200;
  display:flex;align-items:center;gap:6px;padding:7px 7px 7px 18px;border-radius:999px;
  background:rgb(14 18 17 / 62%);backdrop-filter:blur(18px) saturate(1.4);
  border:1px solid rgb(232 240 235 / 12%);box-shadow:0 12px 40px rgb(0 0 0 / 45%);
  transition:transform .4s cubic-bezier(.2,.8,.2,1), opacity .3s}
.pill-nav.hide{transform:translateX(-50%) translateY(-130%);opacity:0}
.pill-nav .pb{display:flex;align-items:center;gap:9px;text-decoration:none;margin-right:8px}
.pill-nav .pb img{width:30px;height:auto;display:block}

.pill-nav .pb b{font-family:var(--disp);font-variation-settings:"wdth" 112;font-weight:700;
  font-size:15px;color:#fff;letter-spacing:-.01em;text-transform:uppercase;white-space:nowrap}
.pill-nav nav{display:flex;gap:2px}

.pill-nav nav a{font-family:var(--body);font-size:14.5px;font-weight:500;color:var(--chalk2);
  text-decoration:none;padding:9px 14px;border-radius:999px;white-space:nowrap;transition:all .16s}
.pill-nav nav a:hover{background:rgb(232 240 235 / 9%);color:#fff}
.pill-nav nav a.on{background:rgb(232 240 235 / 13%);color:#fff}
.pill-nav .np{display:flex;align-items:center;gap:6px;margin-left:6px}

.np .ghost,.np .solid{font-family:var(--disp);font-size:12.5px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;padding:11px 17px;border-radius:999px;text-decoration:none;white-space:nowrap;transition:all .16s}
.np .ghost{color:#fff;border:1px solid rgb(232 240 235 / 26%)}
.np .ghost:hover{border-color:var(--tracer);color:var(--tracer)}
.np .solid{background:var(--tracer);color:#0C1206}
.np .solid:hover{filter:brightness(1.08)}

@media(width <=1120px){.pill-nav nav{display:none}}

/* On a phone the number used to be the thing that got dropped, which on an
   emergency plumber site removes the single most valuable action on the page.
   The wordmark goes instead: the logo still links home, so nothing is lost. */
@media(width <=560px){.pill-nav{left:10px;right:10px;transform:none;padding:7px 7px 7px 10px;gap:6px}
  .pill-nav.hide{transform:translateY(-130%)}
  .pill-nav .pb{margin-right:0}
  .pill-nav .pb b{display:none}
  .pill-nav .np{margin-left:auto;gap:6px}
  .np .ghost,.np .solid{font-size:12px;padding:9px 13px;letter-spacing:.04em}}

@media(width <=380px){.np .solid{padding:9px 11px}}

/* ===== mobile menu =====
   Under 1120px the inline nav is hidden and, until now, nothing replaced
   it: a phone had the number and Book Now and no way to reach Services,
   Poly-B, How It Works or the city pages. The button opens a full screen
   drawer carrying the same links, the two actions, and the language
   switch, which leaves the pill at phone width with room for the number. */
.mbtn{display:none;width:44px;height:44px;flex:0 0 44px;border:0;background:transparent;border-radius:999px;
  padding:0;cursor:pointer;flex-direction:column;align-items:center;justify-content:center;gap:5px;margin-left:2px}
.mbtn span{display:block;width:20px;height:2px;background:#fff;border-radius:2px;transition:transform .25s ease,opacity .2s ease}
.mbtn[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.mbtn[aria-expanded="true"] span:nth-child(2){opacity:0}
.mbtn[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
.mnav{position:fixed;inset:0;z-index:199;background:rgb(11 14 13 / 96%);backdrop-filter:blur(18px) saturate(1.3);
  padding:92px 22px 32px;display:flex;flex-direction:column;justify-content:space-between;gap:24px;
  overflow:auto;-webkit-overflow-scrolling:touch}
.mnav[hidden]{display:none}
.mnav nav{display:flex;flex-direction:column;gap:2px}
.mnav nav a{font-family:var(--disp);font-size:27px;font-weight:700;color:#fff;text-decoration:none;
  padding:11px 10px;border-radius:12px;letter-spacing:-.01em;min-height:44px;display:flex;align-items:center}
.mnav nav a.on{color:var(--tracer)}
.mnav-act{display:grid;gap:10px}
.mnav-act a{font-family:var(--disp);font-weight:700;text-transform:uppercase;letter-spacing:.06em;font-size:14px;
  text-align:center;padding:15px 16px;border-radius:999px;text-decoration:none;min-height:52px;
  display:flex;align-items:center;justify-content:center}
.mnav-act .solid{background:var(--tracer);color:#0C1206}
.mnav-act .ghost{color:#fff;border:1px solid rgb(232 240 235 / 30%)}
.mnav-act .lang{color:var(--chalk2);font-size:13px;min-height:44px}
html.menu-open{overflow:hidden}
html.menu-open .pill-nav{transform:translateX(-50%);opacity:1}
@media(width <=1120px){.mbtn{display:flex}}
@media(width <=560px){html.menu-open .pill-nav{transform:none}
  .pill-nav .np .lang{display:none}}
@media(width <=360px){.np .ghost{padding:9px 10px;font-size:11.5px}.mbtn{width:38px;flex-basis:38px}}

/* full viewport media scene */
.scene{position:relative;min-height:100svh;display:grid;place-items:center;overflow:hidden;background:#07090A}
.scene .media{position:absolute;inset:0;overflow:hidden}
.scene .media>img,.scene .media>video{width:100%;height:100%;object-fit:cover;display:block;will-change:transform}

/* ===== keep heads out of the crop =====
   A wide short .scene box crops a landscape photo VERTICALLY under cover: a
   1800x760 image (2.37) in a 2000x648 box (3.09) scales to width and loses
   ~98px off the top and the bottom. On a photo where the faces sit in the
   upper third that takes the tops of their heads off, which is what kept
   happening. .faces biases the crop upward so the heads survive and the
   ground is what gets lost instead, which nobody misses. */
.scene .media>img.faces{object-position:center 22%}
.scene .fade{position:absolute;inset:0;pointer-events:none}
.f-vignette .fade{background:radial-gradient(130% 92% at 50% 52%,transparent 34%,rgb(7 9 10 / 66%) 82%,rgb(7 9 10 / 92%) 100%)}
.f-base     .fade{background:linear-gradient(0deg,rgb(7 9 10 / 94%) 0%,rgb(7 9 10 / 55%) 26%,transparent 62%)}
.f-sideL    .fade{background:linear-gradient(95deg,rgb(7 9 10 / 93%) 2%,rgb(7 9 10 / 66%) 38%,rgb(7 9 10 / 8%) 76%)}
.f-sideR    .fade{background:linear-gradient(265deg,rgb(7 9 10 / 93%) 2%,rgb(7 9 10 / 66%) 38%,rgb(7 9 10 / 8%) 76%)}

/* The storm loop over a short scene. f-base is cut for a full height hero,
   where the copy sits in the bottom quarter and the gradient has already
   reached .94 by the time it gets there. The home page emergency band is
   62svh, so object-fit:cover crops street-night.mp4 to the white body of the
   van rather than to the road, and the headline lands on it at the height
   where f-base has run out. Measured in the browser on the two lightning
   frames at 2.6s and 7.3s, the worst pixel behind the headline was 3.10:1 at
   1440 and the worst behind the sub was 4.50:1 at 390, which is the AA line
   with nothing left over. Same gradient, pulled upward: 7.5:1 behind the
   headline and 9.8:1 behind the sub at every width from 390 to 1920, with
   the rain and the flashes still reading at the top of the frame. */
.scene.f-base.storm .fade{background:linear-gradient(0deg,rgb(7 9 10 / 95%) 0%,
  rgb(7 9 10 / 76%) 34%,rgb(7 9 10 / 44%) 66%,rgb(7 9 10 / 16%) 100%)}
.scene-in{position:relative;z-index:3;width:100%;max-width:1240px;padding:0 28px;text-align:center}
.scene-in.low{align-self:end;padding-bottom:clamp(76px,11vh,140px)}
.scene-in.left{text-align:left;margin-right:auto}

/* the huge light statement, the relats voice */
.statement{font-family:var(--disp);font-variation-settings:"wdth" 100;font-weight:600;
  font-size:clamp(40px,7.4vw,112px);line-height:.98;letter-spacing:-.035em;color:#fff;margin:0 0 26px}
.statement em{font-style:normal;color:var(--tracer)}
.statement.tight{font-size:clamp(34px,5.4vw,78px)}
.sub{font-size:clamp(16px,1.45vw,21px);line-height:1.5;color:var(--chalk2);max-width:54ch;margin:0 auto 34px}

/* a second, quieter line under the main sub: the reason to take the next step,
   sized so it reads as a note rather than competing with the statement */
.sub.small{font-size:clamp(14.5px,1.12vw,16.5px);color:var(--chalk3);max-width:50ch;margin-top:-22px;margin-bottom:28px}
.scene-in.left .sub{margin-left:0}

.tag-s{display:inline-block;font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.22em;
  text-transform:uppercase;color:var(--tracer);margin-bottom:24px}

/* pill CTA with a thumbnail, the relats button */
.pcta{display:inline-flex;align-items:center;gap:13px;padding:8px 24px 8px 8px;border-radius:999px;
  background:rgb(14 18 17 / 66%);backdrop-filter:blur(14px);border:1px solid rgb(232 240 235 / 18%);
  text-decoration:none;transition:all .18s;font-family:var(--body);font-size:16px;font-weight:500;color:#fff}
.pcta:hover{background:rgb(232 240 235 / 14%);border-color:var(--tracer);transform:translateY(-2px)}
.pcta .thumb{width:40px;height:40px;border-radius:50%;background:#1B2220 center/cover no-repeat;flex:none}
.pcta .arr{width:40px;height:40px;border-radius:50%;background:var(--tracer);display:grid;place-items:center;flex:none;color:#0C1206;font-size:17px}
.pcta.em{background:var(--flare);border-color:var(--flare);color:#200C00;font-weight:600}
.pcta.em .arr{background:rgb(0 0 0 / 22%);color:#200C00}
.pcta.em:hover{background:var(--flare);filter:brightness(1.08)}
.ctarow{display:flex;gap:13px;flex-wrap:wrap;justify-content:center}
.scene-in.left .ctarow{justify-content:flex-start}

/* scroll affordance */
.scrollcue{position:absolute;right:30px;bottom:26px;z-index:4;display:flex;align-items:center;gap:10px;
  font-family:var(--body);font-size:13.5px;color:var(--chalk2);letter-spacing:.02em}
.scrollcue i{width:19px;height:30px;border-radius:999px;border:1.5px solid rgb(232 240 235 / 45%);position:relative;display:block}

.scrollcue i::after{content:"";position:absolute;left:50%;top:6px;width:3px;height:6px;border-radius:2px;
  background:var(--chalk2);transform:translateX(-50%);animation:cue 1.7s ease-in-out infinite}

@keyframes cue{0%{opacity:0;transform:translate(-50%,0)}35%{opacity:1}100%{opacity:0;transform:translate(-50%,11px)}}

@media(prefers-reduced-motion:reduce){.scrollcue i::after{animation:none}}

/* big stat scene */
.statnum{font-family:var(--disp);font-variation-settings:"wdth" 100;font-weight:400;
  font-size:clamp(70px,16vw,230px);line-height:.84;letter-spacing:-.05em;color:#fff;margin:0}

.statcap{font-family:var(--disp);font-size:13px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--tracer);margin-top:22px;display:block}

/* word by word reveal */
.wr{display:inline-block;overflow:hidden;vertical-align:bottom}
.wr>span{display:inline-block;will-change:transform}

/* --- flow corrections --- */
.statement{text-transform:none!important}
.scene-in h1,.scene-in h2{text-transform:none}

/* Readable text needs its own floor, independent of the art direction fade.
 *
 * This floor used to be a viewport-scale wash: 92vh tall, anchored 16vh BELOW
 * the fold, so its dense end sat off-screen and the copy landed in the thin
 * part. Measured against the photographs it runs on, the band where a
 * paragraph actually sits was carrying about 27% — and the brightest 5% of
 * van-day (the white van panel and the sky, rgb 252,251,246) put --chalk2 body
 * copy at 1.03:1 against it. That is not dim, it is invisible.
 *
 * So the floor now hugs the TEXT BOX rather than the viewport, which is what
 * lets it be dense enough to work without veiling the whole photograph. It is
 * elliptical so it fades on every side instead of showing the vertical seam
 * the old full-bleed version did, and it is centred low because that is where
 * the small type lives: the fine print needs 74% over the worst pixels, the
 * sub needs 59%, and the big white statement only needs 43%. Reading those off
 * one gradient costs the photo far less than flattening everything to 74%.
 *
 * Numbers from the real images, not by eye. Re-derive them if the art changes.
 */
.scene-in.low::before{content:"";position:absolute;inset:-7vh -9vw -5vh;
  background:radial-gradient(124% 116% at 50% 64%,
    rgb(7 9 10 / 82%) 0%,rgb(7 9 10 / 78%) 38%,rgb(7 9 10 / 62%) 62%,
    rgb(7 9 10 / 34%) 82%,rgb(7 9 10 / 10%) 93%,transparent 100%);
  z-index:-1;pointer-events:none}
.scene-in.left::before{content:"";position:absolute;inset:auto -10vw -16vh;height:92vh;
  background:linear-gradient(0deg,rgb(7 9 10 / 92%) 6%,rgb(7 9 10 / 74%) 34%,rgb(7 9 10 / 34%) 64%,transparent 100%);
  z-index:-1;pointer-events:none}
.scene-in{position:relative;isolation:isolate}

.scene-in.left::before{inset:auto auto -16vh -14vw;width:86vw;
  background:linear-gradient(72deg,rgb(7 9 10 / 94%) 18%,rgb(7 9 10 / 72%) 48%,transparent 82%)}
.sub{text-shadow:0 1px 26px rgb(7 9 10 / 85%)}
.statement{text-shadow:0 2px 40px rgb(7 9 10 / 55%)}

/* Over a photograph the body tokens are one step too dark to survive.
 *
 * --chalk2 is a mid tone (relative luminance .471), which reads beautifully on
 * the flat --night sections and collapses the moment a sunlit van or a bright
 * sky is behind it. Lifting scene copy one step — sub to --chalk (.855), the
 * fine print to --chalk2 — is what lets the scrim above stay at a strength
 * that keeps the photograph visible. Both together clear WCAG AA 4.5:1 against
 * the worst 5% of pixels in the images these scenes actually use.
 *
 * Scoped to .scene on purpose: off a photograph the original hierarchy is
 * correct and nothing here should touch it. */
.scene .sub{color:var(--chalk)}
.scene .sub.small{color:var(--chalk2)}
.scene .src{color:var(--chalk2)}
.scene .src a{color:var(--chalk)}

/* the breadcrumb and kicker sit higher in the frame than the headline, which
   on a scene like the night van puts them over the brightest part of the
   photograph rather than the scrim. They need the same lift the sub gets. */
.scene .crumb,.scene .crumb a{text-shadow:0 1px 18px rgb(7 9 10 / 95%),0 0 40px rgb(7 9 10 / 80%)}
.scene .tag-s,.scene .kicker{text-shadow:0 1px 18px rgb(7 9 10 / 95%),0 0 40px rgb(7 9 10 / 80%)}
.scene-in.low{max-width:1140px}
.scene-in.low .statement{font-size:clamp(38px,6.4vw,96px)}

/* ===== scroll scrubbed x-ray, the terminal industries pattern ===== */
.scrub{position:relative}
.scrub-pin{position:sticky;top:0;height:100svh;overflow:hidden;background:#07090A;display:grid;place-items:center}
.scrub-pin video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* while the file is still arriving the poster stands in for the video; the
   element itself paints black once play() has been called */
.scrub.loading .scrub-pin video{opacity:0}
.scrub.loading .scrub-pin::before{content:"";position:absolute;inset:0;z-index:1;
  background:var(--poster) center/cover no-repeat #07090A}

.scrub-pin .vig{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(118% 86% at 50% 48%,transparent 40%,rgb(7 9 10 / 62%) 84%,rgb(7 9 10 / 94%) 100%)}
.scrub-cap{position:absolute;left:0;right:0;bottom:clamp(54px,9vh,120px);z-index:4;text-align:center;padding:0 28px;pointer-events:none}
.scrub-step{position:absolute;left:0;right:0;bottom:0;opacity:0;padding:0 28px}

.scrub-step h3{font-family:var(--disp);font-variation-settings:"wdth" 100;font-weight:400;
  font-size:clamp(28px,4.4vw,62px);line-height:1.02;letter-spacing:-.03em;color:#fff;margin:0 0 14px;text-transform:none}
.scrub-step p{font-size:clamp(15px,1.3vw,19px);color:var(--chalk2);max-width:52ch;margin:0 auto}

.scrub-key{display:inline-flex;gap:22px;flex-wrap:wrap;justify-content:center;margin-top:22px;
  padding:12px 22px;border-radius:999px;background:rgb(7 9 10 / 60%);backdrop-filter:blur(10px);
  border:1px solid rgb(232 240 235 / 14%)}

.scrub-key span{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--chalk2);
  font-family:var(--disp);font-weight:600;letter-spacing:.06em;text-transform:uppercase}
.scrub-key i{width:11px;height:11px;border-radius:50%;flex:none}
.scrub-prog{position:absolute;left:50%;transform:translateX(-50%);bottom:22px;z-index:5;width:min(360px,52vw);height:2px;background:rgb(232 240 235 / 18%);border-radius:99px}
.scrub-prog i{display:block;height:100%;width:0;background:var(--tracer);border-radius:99px}

@media(prefers-reduced-motion:reduce){
  .scrub-pin{position:relative;height:auto;min-height:0}
  .scrub-pin video{position:relative;height:auto}
  .scrub-step{position:relative;opacity:1;margin-bottom:22px}
  .scrub-cap{position:relative;bottom:auto;padding:30px 28px}}

/* captions and legend were stacking on top of each other */
.scrub-cap{bottom:auto;top:50%;transform:translateY(-50%);height:0;display:grid;place-items:center}
.scrub-step{position:absolute;inset:50% auto auto 50%;transform:translate(-50%,-50%);width:min(900px,88vw);text-align:center}

.scrub-key{position:absolute;left:50%;transform:translateX(-50%);bottom:clamp(52px,8vh,96px);
  z-index:6;margin:0;pointer-events:none}
.scrub-step h3{text-shadow:0 2px 40px rgb(7 9 10 / 90%),0 0 90px rgb(7 9 10 / 80%)}
.scrub-step p{text-shadow:0 1px 26px rgb(7 9 10 / 95%)}

.scrub-pin .floor{position:absolute;left:0;right:0;bottom:0;height:46vh;z-index:2;pointer-events:none;
  background:linear-gradient(0deg,rgb(7 9 10 / 88%) 0%,rgb(7 9 10 / 45%) 46%,transparent 100%)}

/* stack every caption in one grid cell instead of absolute positioning
   inside a zero height box, which pushed them off screen */
.scrub-cap{position:absolute!important;inset:0!important;top:auto!important;
  transform:none!important;height:auto!important;
  display:grid!important;place-items:center;padding:0 28px;z-index:4}

.scrub-step{position:static!important;grid-area:1/1;left:auto!important;top:auto!important;
  transform:none!important;width:min(880px,86vw);text-align:center;
  transition:opacity .35s ease}

/* top:auto was cancelling inset:0 and bottom anchoring the caption block */
.scrub-cap{top:0!important;bottom:0!important;align-content:center;
  padding-bottom:clamp(120px,18vh,210px)}

/* the house x-ray plate is light, so its type and scrims must invert.
   Scoped to #xray: other scrub sections sit on black and keep the dark base. */
#xray .scrub-pin{background:#E8E6E2}
#xray .scrub-pin .vig{background:radial-gradient(120% 92% at 50% 46%,transparent 46%,rgb(232 230 226 / 50%) 86%,rgb(232 230 226 / 90%) 100%)}
#xray .scrub-pin .floor{background:linear-gradient(0deg,rgb(236 234 230 / 96%) 0%,rgb(236 234 230 / 70%) 38%,transparent 100%)}
#xray .scrub-step h3{color:#11161A;text-shadow:0 1px 30px rgb(255 255 255 / 95%),0 0 80px rgb(255 255 255 / 90%)}
#xray .scrub-step p{color:#39434A;text-shadow:0 1px 20px rgb(255 255 255 / 98%)}
#xray .scrub-key{background:rgb(17 22 26 / 86%);border-color:rgb(255 255 255 / 14%)}
#xray .scrub-prog{background:rgb(17 22 26 / 20%)}
#xray .scrub-prog i{background:#11161A}

/* ---- heater x-ray: black plate, contain not cover, facts under the caption ----
   Two sections share this treatment and the same render: #heaterxray, the six
   caption cutaway on the service page, and #heaterpeek, the three caption teaser
   on the home page. :is() keeps id specificity, so the order of these rules
   against the #xray block below is unchanged. ---- */
:is(#heaterxray,#heaterpeek) .scrub-pin{background:#000}
:is(#heaterxray,#heaterpeek) .scrub-pin video{object-fit:contain}
:is(#heaterxray,#heaterpeek).loading .scrub-pin::before{background-size:contain}

@media(width <=700px){
  /* Measured: the tank's subject centre is at 72.8% of frame width, so contain
     parks it hard right with dead space down the left. At this box aspect
     (393x250 against a 1920x1080 source) cover scales to HEIGHT and crops only
     ~11% horizontally, so the relief valve and supply lines at the top of the
     tank survive, which is what contain was protecting in the first place. */
  :is(#heaterxray,#heaterpeek) .scrub-pin video{object-fit:cover;object-position:73% center}
}
:is(#heaterxray,#heaterpeek) .scrub-pin .vig{background:radial-gradient(120% 92% at 68% 50%,transparent 52%,rgb(0 0 0 / 50%) 88%,rgb(0 0 0 / 90%) 100%)}
:is(#heaterxray,#heaterpeek) .scrub-pin .floor{background:linear-gradient(0deg,rgb(0 0 0 / 92%) 0%,rgb(0 0 0 / 50%) 40%,transparent 100%)}
:is(#heaterxray,#heaterpeek) .scrub-cap{padding-bottom:clamp(140px,20vh,230px)}

:is(#heaterxray,#heaterpeek) .scrub-step{text-align:left;width:min(440px,42vw);
  margin-right:auto;margin-left:clamp(28px,6vw,110px);grid-area:1/1;justify-self:start}
:is(#heaterxray,#heaterpeek) .scrub-step h3{font-size:clamp(25px,2.9vw,42px);margin:0 0 12px}
:is(#heaterxray,#heaterpeek) .scrub-step p{max-width:34ch;margin:0}

.scrub-ix{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--tracer);margin-bottom:11px}

.scrub-spec{display:flex;gap:24px;flex-wrap:wrap;margin-top:18px;padding-top:15px;
  border-top:1px solid rgb(232 240 235 / 16%)}
.scrub-spec div{min-width:96px}

.scrub-spec .lb{display:block;font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:var(--chalk3);margin-bottom:4px}

.scrub-spec .vl{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:21px;
  font-weight:700;color:var(--tracer);letter-spacing:-.015em}

@media(width <=900px){
  :is(#heaterxray,#heaterpeek) .scrub-step{width:auto;margin:0 auto;text-align:center}
  :is(#heaterxray,#heaterpeek) .scrub-step p{margin:0 auto}
  :is(#heaterxray,#heaterpeek) .scrub-spec{justify-content:center}}

/* ===== crew at work gallery, real photographs =====
   These are real job photos at two aspects: 4:3 landscape and 3:4 portrait.
   Cover crops whatever the cell does not match, and a short wide cell crops
   vertically, which is exactly where the heads are. So every cell here is
   either 1.45 (for the landscape shots) or 0.71 (for the portrait ones), and
   nothing is allowed to be a letterbox. A bare .w2 was 3:1 and decapitated
   everyone in it, so .w2 now always pairs with .h2 in the markup. */
.gal{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:210px;gap:12px;margin-top:44px}
.gal.g3{grid-template-columns:repeat(3,1fr)}

@media(width <=1000px){.gal,.gal.g3{grid-template-columns:repeat(2,1fr);grid-auto-rows:240px}}

@media(width <=560px){
  .gal,.gal.g3{grid-template-columns:1fr;grid-auto-rows:260px}
  .gal .w2,.gal .h2{grid-column:auto;grid-row:auto}}

.gal figure{margin:0;position:relative;overflow:hidden;border-radius:var(--r);
  background:#11161A center 28% / cover no-repeat;border:1px solid var(--steel)}

.gal figure::after{content:attr(data-cap);position:absolute;left:0;right:0;bottom:0;padding:26px 16px 12px;
  font-size:12.5px;letter-spacing:.04em;color:var(--chalk2);
  background:linear-gradient(0deg,rgb(7 9 10 / 88%),transparent);opacity:0;transition:opacity .22s}
.gal figure:hover::after{opacity:1}
.gal .w2{grid-column:span 2}
.gal .h2{grid-row:span 2}

/* ===== service cards: image first, minimal text =====
   These were lost when the image first system replaced the old block.
   services.html and every city page still use them. */
.svcs{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:14px;margin-top:40px}

.svc{position:relative;overflow:hidden;border-radius:var(--r);background:#0E1413;border:1px solid var(--steel);
  min-height:360px;display:flex;flex-direction:column;justify-content:flex-end;
  text-decoration:none;transition:transform .18s ease,border-color .18s ease}
.svc:hover{transform:translateY(-3px);border-color:var(--tracer)}

.svc .img{position:absolute;inset:0;background:#0E1413 center/cover no-repeat;
  filter:saturate(.92) contrast(1.05);transition:transform .5s ease}
.svc:hover .img{transform:scale(1.05)}

.svc .img::before{content:"";position:absolute;inset:0;
  background:linear-gradient(0deg,rgb(7 9 10 / 96%) 8%,rgb(7 9 10 / 72%) 36%,rgb(7 9 10 / 12%) 72%,transparent 100%)}
.svc .bd{position:relative;z-index:2;padding:26px 24px 22px}
.svc h3{font-size:24px;margin:0 0 7px;text-transform:none;letter-spacing:-.016em;color:#fff}
.svc p{font-size:16px;color:var(--chalk2);margin:0 0 16px;max-width:34ch;line-height:1.45}

.svc .ft{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding-top:14px;border-top:1px solid rgb(232 240 235 / 16%)}
.svc .pr{font-family:var(--disp);font-size:17px;color:var(--tracer);font-weight:700;letter-spacing:-.01em}

.svc .go{font-family:var(--disp);font-size:12.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.11em;color:#fff;text-decoration:none;display:inline-flex;align-items:center;gap:7px}

.svc .go::after{content:"";width:17px;height:17px;border-radius:50%;background:var(--tracer);
  display:inline-grid;place-items:center}

/* review stars are gold, not brand green. Green reads as a UI accent,
   gold reads as a rating. */

/* .tbar span is (0,1,1) and beat the bare .stars rule at (0,1,0), so the star
   row under the hero was rendering in --chalk3 grey while the carousel stars
   were gold. Same class, two different colors on one page. */
.stars,.rev3 .st,.rev .st,.tbar .stars,.tbar span.stars{color:#F6B21B}

/* ===== requested by the city page build ===== */

/* 50/50 local fact block, lighter than .split which assumes a long article */
.local{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,4vw,56px);align-items:start;margin-top:40px}

@media(width <=820px){.local{grid-template-columns:1fr;gap:28px}}

/* label/value pairs so coverage data reads as data, not prose */
.fact{display:grid;grid-template-columns:auto 1fr;gap:10px 22px;margin:0;font-size:16px}

.fact dt{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--chalk3);align-self:center}
.fact dd{margin:0;color:var(--chalk);font-weight:500}

/* thin full width urgency strip, repeatable at the foot of any page */
.urgent{background:linear-gradient(90deg,#2A1203,#140800);border-top:1px solid #4A2408;border-bottom:1px solid #4A2408}

.urgent-in{max-width:1320px;margin:0 auto;padding:20px 28px;display:flex;flex-wrap:wrap;gap:18px;
  align-items:center;justify-content:center;text-align:center}

.urgent b{font-family:var(--disp);font-variation-settings:"wdth" 112;font-size:clamp(20px,2.4vw,30px);
  font-weight:700;color:var(--flare);letter-spacing:-.018em}
.urgent span{font-size:16.5px;color:#E8C8A6}

.urgent a{font-family:var(--disp);font-size:15px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  background:var(--flare);color:#200C00;padding:13px 24px;border-radius:999px;text-decoration:none;white-space:nowrap}
.urgent a:hover{filter:brightness(1.08)}

/* very short headlines need to scale up, not sit in a size built for ten words */
.statement.short{font-size:clamp(46px,8.6vw,132px);letter-spacing:-.045em;line-height:.94}

/* ===== coverflow review carousel =====
   Reference behaviour: every card visible at once, the centre one upright and
   full size, the siders angled back and scaled down. Replaces the ring, where
   only the front card was readable. */
.stage{height:auto;min-height:clamp(430px,46vw,540px);perspective:1500px;perspective-origin:50% 50%;
  display:grid;place-items:center;overflow:hidden}
.ring{position:relative;width:100%;height:clamp(430px,46vw,540px);transform-style:preserve-3d;transform:none!important}

.rev3{position:absolute;top:50%;left:50%;width:clamp(250px,23vw,310px);
  transform-origin:50% 50%;transition:transform .55s cubic-bezier(.22,.8,.26,1),opacity .4s,filter .4s;
  background:#0E1413;border:1px solid #1F2725;border-radius:var(--r);overflow:hidden;
  box-shadow:0 26px 60px rgb(0 0 0 / 55%)}
.rev3 .ph{aspect-ratio:4/3}
.rev3.c0{z-index:50}
.rev3 q{font-size:16px}
.rev3 .bd{padding:20px 20px 22px}

@media(width <=700px){
  .stage{min-height:420px}.ring{height:420px}
  .rev3{width:min(300px,78vw)}}

/* mirror a scene's media so the subject clears the text.
   Applied to .media, not the img/video, because GSAP owns that transform. */
.scene.flip .media{transform:scaleX(-1)}

/* ===== third party credibility marquee =====
   The badges are other people's marks, so they get room and a quiet label
   rather than a headline. The strip scrolls continuously because there are
   more badges than fit a row, and the track holds two identical copies so
   the wrap has no seam. Edges fade into the page instead of being cut. */
.cred{background-color:#07090A;background-image:var(--grain);
  border-top:1px solid #171D1B;border-bottom:1px solid #171D1B;
  padding:clamp(28px,3.4vw,46px) 0;overflow:hidden}

.cred-lb{text-align:center;font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.2em;text-transform:uppercase;color:var(--chalk3);margin-bottom:clamp(20px,2.4vw,30px)}

.cred-vp{position:relative;overflow:hidden;
  mask-image:linear-gradient(90deg,transparent 0,#000 13%,#000 87%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 13%,#000 87%,transparent 100%);
  mask-size:100% 100%;mask-size:100% 100%;
  mask-repeat:no-repeat;mask-repeat:no-repeat}

.cred-track{display:flex;align-items:center;width:max-content;
  animation:credroll 42s linear infinite}
.cred:hover .cred-track,.cred:focus-within .cred-track{animation-play-state:paused}
.cred-dup{display:flex;align-items:center}
.cred-i{flex:none;display:grid;place-items:center;padding:0 clamp(26px,3.4vw,54px)}

.cred-i img{display:block;height:clamp(52px,5.2vw,74px);width:auto;

  /* these marks were drawn for white backgrounds. A touch of lift keeps the
     dark ones, BBB especially, from sinking into the page. */
  filter:brightness(1.06) drop-shadow(0 0 16px rgb(255 255 255 / 10%));
  transition:filter .16s ease,transform .16s ease}

/* Badges that link to their record are clickable, so they have to look it.
   The strip already pauses on hover, which is what makes a moving target
   catchable in the first place. */
a.cred-i{text-decoration:none;border-radius:4px}

a.cred-i:hover img,a.cred-i:focus-visible img{
  filter:brightness(1.22) drop-shadow(0 0 20px rgb(175 208 62 / 35%));
  transform:translateY(-2px)}
a.cred-i:focus-visible{outline:2px solid var(--tracer);outline-offset:4px}

@keyframes credroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* no animation: let it be a plain swipeable row instead of a frozen one */
@media(prefers-reduced-motion:reduce){
  .cred-track{animation:none;width:auto;overflow-x:auto;scrollbar-width:none}
  .cred-track::-webkit-scrollbar{display:none}
  .cred-dup{display:none}}

/* a citation line under a claim. Quiet by design: the claim carries the page,
   the source is there for the one reader who checks, and for the client who
   has to defend it. */
.src{font-size:13.5px;line-height:1.55;color:var(--chalk3);max-width:58ch;margin:-18px 0 30px}
.src a{color:var(--chalk2);text-decoration:underline;text-underline-offset:2px}
.src a:hover{color:var(--tracer)}
.scene .src{text-shadow:0 1px 20px rgb(7 9 10 / 90%)}

/* ===== reusable modal overlay =====
   Markup is <div class="mdl" id="NAME-modal"> and any element carrying
   data-modal="NAME" opens it. Progressive enhancement: the inline script in
   the head puts .js on <html>, so with JS off the block never becomes an
   overlay, it just stays in the page as an ordinary section. */
.mdl{position:relative;background:var(--coal);padding:0 0 clamp(56px,7vw,92px)}
.mdl .mdl-veil,.mdl .mdl-x{display:none}
.mdl-box{width:100%;max-width:980px;margin:0 auto;padding:0 28px}

html.js .mdl{display:none;position:fixed;inset:0;z-index:900;padding:0;background:none}
html.js .mdl.open{display:block}
html.js .mdl:not(.in){pointer-events:none}
html.js .mdl.in{pointer-events:auto}

html.js .mdl .mdl-veil{display:block;position:absolute;inset:0;background:rgb(7 9 10 / 74%);
  backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;transition:opacity .26s ease}
html.js .mdl.in .mdl-veil{opacity:1}

/* the scroll layer fills the overlay so tall content scrolls inside it,
   never the page behind. margin:auto on the box centres short content
   without the overflow clipping that align-items:center causes. */
html.js .mdl-scroll{position:absolute;inset:0;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;display:flex;padding:clamp(66px,9vh,92px) 16px clamp(28px,6vh,60px)}

html.js .mdl-box{max-width:980px;margin:auto;padding:0;opacity:0;transform:translateY(20px) scale(.986);
  transition:opacity .26s ease,transform .34s cubic-bezier(.2,.8,.2,1)}
html.js .mdl.in .mdl-box{opacity:1;transform:none}
html.js .mdl-box:focus{outline:none}
html.js .mdl-box .sc{margin-top:0}

html.js .mdl .mdl-x{display:grid;place-items:center;position:absolute;top:14px;right:14px;z-index:3;
  width:46px;height:46px;padding:0;border-radius:999px;background:rgb(14 18 17 / 82%);
  backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border:1px solid rgb(232 240 235 / 22%);color:#fff;cursor:pointer;transition:all .16s}
html.js .mdl .mdl-x:hover{border-color:var(--tracer);color:var(--tracer)}
html.js .mdl .mdl-x svg{display:block;width:20px;height:20px;fill:none;stroke:currentcolor;stroke-width:2}

/* lenis is told to stand down in JS; this is the lock for wheel, touch and
   keys, and the only lock at all when lenis never loaded */
html.mdl-lock{overflow:hidden;scrollbar-gutter:stable}
html.mdl-lock body{overflow:hidden}

@media(prefers-reduced-motion:reduce){
  html.js .mdl .mdl-veil,html.js .mdl-box{opacity:1;transform:none}}

@media(width <=560px){
  html.js .mdl-scroll{padding:58px 10px 24px}
  html.js .mdl .mdl-x{top:9px;right:9px;width:42px;height:42px}
  .mdl-box{padding:0 16px}
  .sc-h{padding:20px 18px}
  .rail{padding:0 18px}
  .sc-b{padding:22px 18px 26px}}

/* compact image led band, the scorecard launcher */
.cband{position:relative;overflow:hidden;padding:clamp(54px,6.5vw,84px) 0;
  border-top:1px solid var(--steel);border-bottom:1px solid var(--steel)}
.cband .plate{position:absolute;inset:0;background:#0B0E0D center 48%/cover no-repeat;opacity:.46}

.cband .scrim{position:absolute;inset:0;
  background:linear-gradient(95deg,#0B0E0D 2%,rgb(11 14 13 / 90%) 40%,rgb(11 14 13 / 52%) 76%,rgb(11 14 13 / 80%) 100%)}
.cband .in{position:relative;z-index:2}
.cband .statement{font-size:clamp(30px,4vw,54px);margin:0 0 14px}
.cband .ctarow{justify-content:flex-start}

/* in the CTA band this line IS the lead, so it is bigger than the quiet
   .sub.small note used under a statement elsewhere. */
.cband .sub.small{font-size:clamp(15.5px,1.2vw,18px);color:var(--chalk2);max-width:50ch;margin:0 0 26px}
.cband .sub{margin-left:0}
.phero .ctarow{justify-content:flex-start;margin-top:26px}
button.pcta{cursor:pointer;text-align:left}

/* ===== proof in the hero =====
   Two marks only, above the CTA row, so a first time visitor sees who has
   already vetted this company before they read anything. Full color on
   purpose: these are recognized at a glance and desaturating them throws
   away the only thing that makes them work. */

/* a plate behind them, because a hero photograph is never a reliable
   background: over the bright dust and van in the trench shot the Google
   wordmark simply disappeared. */
.hero-cred{display:inline-flex;align-items:center;gap:clamp(16px,2vw,28px);margin:0 0 28px;
  padding:11px clamp(18px,2vw,26px);border-radius:999px;
  background:rgb(7 9 10 / 62%);border:1px solid rgb(232 240 235 / 14%);
  backdrop-filter:blur(7px);backdrop-filter:blur(7px)}
.scene-in .hero-cred{margin-left:auto;margin-right:auto}
.scene-in.left .hero-cred{margin-left:0}

.hero-cred img{display:block;height:clamp(34px,3.6vw,48px);width:auto;
  filter:drop-shadow(0 2px 16px rgb(0 0 0 / 80%))}
.hero-cred i{width:1px;height:clamp(24px,2.6vw,34px);background:rgb(232 240 235 / 30%);flex:none}

@media(width <=560px){.hero-cred{gap:14px;margin-bottom:22px}}

/* keyboard users land on the nav first on every page; this lets them past it.
   Visible only while focused, so it costs the design nothing. */
.skip{position:absolute;left:-9999px;top:0;z-index:999;
  background:var(--tracer);color:#16200A;font-family:var(--disp);font-weight:700;
  font-size:15px;padding:12px 20px;border-radius:0 0 var(--r) 0;text-decoration:none}
.skip:focus{left:0}

/* a label that exists for screen readers only. The visible question above the
   field is a heading, which does not associate with the input. */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ===== isolated subject shots on a phone =====
   A product style frame (the heater on black, the x-ray house on its plate)
   puts the subject on one side with empty ground on the other, composed for a
   wide screen. object-fit:cover on a 390x844 viewport scales to fill the
   height and keeps only a narrow centre strip, so a subject sitting off to one
   side falls outside the crop completely: the water heater page showed a bare
   sliver of the tank at the edge and nothing else.

   contain shows the whole subject, and because the ground behind it is flat
   there is no visible letterbox. The parallax scale has to come off too or it
   crops back in by 12%. */
@media(width <=700px){
  /* cover, not contain, with the crop window moved to wherever the subject
     actually sits (--subj-x). contain left the tank marooned at the right of
     the frame with dead space beside it; this fills the box with the subject
     centred instead. The parallax scale still has to come off or it eats 12%. */

  /* contain, so the whole subject survives. cover was tried first and clipped
     the relief valve and supply lines off the top of the tank, which is the
     one part the page is about. The box is sized so the letterbox reads as
     deliberate padding rather than dead space, and the ground behind is flat
     black so no seam shows. */
  .scene.subject .media img,
  .scene.subject .media video{object-fit:contain!important;
    object-position:var(--subj-x,50%) var(--subj-y,50%)!important;transform:none!important}
  .scene.subject{min-height:auto}
  .scene.subject .media{position:relative;height:38svh}
  .scene.subject .media .fade,.scene.subject>.fade{display:none}
  .scene.subject .scene-in{padding-top:22px}
}

/* ===== a landscape clip whose subject sits off centre =====
   object-fit:cover on a 393x852 phone keeps only ~26% of a 1600px wide frame
   (411px of it), the centre strip x 37-63%. Measured 2026-10-11, camera-loop
   puts its technician at 34.6% of frame width, so that strip lands in the gap
   between him and the pipe: a centre crop keeps 22% of the clip's content and
   the man is simply not in shot. The other three background clips measure
   44.5%, 53.1% and 38.2% and keep 45-52%, so they are fine and are left
   alone. contain was tried and is worse here: it letterboxes an environmental
   shot down to a thin band, unlike the product shots where contain is right. */
@media(width <=700px){
  .scene.subj-left .media>video{object-position:28% center}

  /* Bringing the technician back into frame put him BEHIND the copy, and the
     f-sideL / f-sideR scrims cannot cover that: they are directional, built
     to darken the one side the copy sits on in a two column desktop layout.
     On a phone the copy spans the full width, so on cast-iron (f-sideR, dark
     on the right) the body text ran across the left where the scrim is only
     8% opaque, straight over his hi-vis shirt, and the last two lines stopped
     reading. A full width wash replaces the directional one here only. Tuned
     against three strengths: lighter still showed green through the text,
     heavier put the section back to the near black it started as. */
  .scene.subj-left>.fade{
    background:linear-gradient(180deg,rgb(7 9 10 / 82%) 0%,rgb(7 9 10 / 62%) 45%,rgb(7 9 10 / 88%) 100%)}
}

/* ===== scrub sections on a phone =====
   On a wide screen the caption sits in the empty part of the frame. On 390x844
   there is no empty part: the video fills the screen and the caption lands on
   top of the subject. The house x-ray body copy vanished into the linework,
   and the heater card covered the very part it was describing.

   So on a phone the two stop overlapping and stack instead. The video takes
   the top half at contain, so the whole subject is visible rather than a
   centre slice of it, and the caption gets the bottom half to itself. The key
   moves up under the video so it stops landing on the text. */
@media(width <=700px){
  .scrub-pin{display:flex;flex-direction:column;place-items:stretch}

  /* 46svh plus a 367px caption card came to 670px against a 659px viewport,
     so the spec row at the bottom of the card was cut off at every step.
     38svh leaves ~42px of headroom. Measured on iPhone 15. */
  .scrub-pin video{position:relative;inset:auto;flex:none;
    width:100%;height:38svh;object-fit:contain}
  .scrub-pin .vig,.scrub-pin .floor{display:none}

  .scrub-cap{position:relative!important;inset:auto!important;transform:none!important;height:auto!important;
    flex:1;display:grid;place-items:center;padding:0 14px 92px;text-align:left}

  .scrub-step{position:static!important;grid-area:1/1;left:auto!important;top:auto!important;
    bottom:auto!important;transform:none!important;width:100%!important;max-width:none;
    padding:16px 18px 18px;border-radius:var(--r);align-self:center}
  .scrub-step h3{font-size:clamp(21px,5.6vw,27px);margin:0 0 8px}
  .scrub-step p{font-size:15.5px;line-height:1.45;max-width:none;margin:0}

  /* light plate keeps dark type, dark plate keeps light type */
  #xray .scrub-step{background:rgb(236 234 230 / 94%);border:1px solid rgb(17 22 26 / 12%)}

  :is(#heaterxray,#heaterpeek) .scrub-step{background:rgb(7 9 10 / 86%);border:1px solid rgb(232 240 235 / 16%);
    margin:0;text-align:left}

  #xray .scrub-step h3,#xray .scrub-step p,
  :is(#heaterxray,#heaterpeek) .scrub-step h3,:is(#heaterxray,#heaterpeek) .scrub-step p{text-shadow:none}
  :is(#heaterxray,#heaterpeek) .scrub-step{width:100%!important;margin-left:0;margin-right:0}
  :is(#heaterxray,#heaterpeek) .scrub-spec{margin-top:12px;padding-top:11px;gap:14px}
  :is(#heaterxray,#heaterpeek) .scrub-spec div{min-width:84px}
  :is(#heaterxray,#heaterpeek) .scrub-spec .vl{font-size:17px}

  .scrub-key{position:static!important;transform:none!important;
    align-self:center;margin:10px auto 0;font-size:12px;gap:14px;padding:7px 14px}
}

/* ===== scrub caption plate, all sizes =====
   The old renders had an empty third of frame to drop bare text into. These
   fill the frame with detail from the first second, so a caption with nothing
   behind it lands on linework or on a corroded fitting and stops being
   readable. The plate is tinted to whichever ground the section uses and sits
   low and left, clear of the subject the camera is centred on. */
.scrub-step{width:min(560px,46vw);justify-self:start;margin:0 0 0 clamp(28px,6vw,96px);text-align:left;
  padding:20px 24px 22px;border-radius:var(--r);
  backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
.scrub-cap{text-align:left;justify-items:start}
#xray .scrub-step{background:rgb(238 236 232 / 90%);border:1px solid rgb(17 22 26 / 12%)}
:is(#heaterxray,#heaterpeek) .scrub-step{background:rgb(7 9 10 / 80%);border:1px solid rgb(232 240 235 / 16%)}

#xray .scrub-step h3,#xray .scrub-step p,
:is(#heaterxray,#heaterpeek) .scrub-step h3,:is(#heaterxray,#heaterpeek) .scrub-step p{text-shadow:none}

/* the house orbit keeps its subject near the middle of frame, so the caption
   drops to the lower left where it stops covering the thing it describes */
.scrub-step h3,.scrub-step p{text-align:left}
#xray .scrub-cap{align-content:end;padding-bottom:clamp(96px,14vh,170px)}

@media(width <=700px){#xray .scrub-cap{align-content:center;padding-bottom:92px}}

/* ===== descenders, and why they were being cut off =====
   The headline reveal wraps every word in .wr with overflow:hidden so the
   word can slide up from below its own box. That box is the line box, so any
   glyph that drops below the baseline (g, y, p, j, q) gets clipped by it.
   Archivo's descenders were shallow enough to get away with it. Saira's are
   not, which is why "photographs" lost the tail of its g.

   Padding opens the box downward and the matching negative margin keeps the
   line spacing exactly where it was, so nothing else on the page moves. */

/* Sized from the face's real metrics, not by eye: Chakra Petch's deepest
   descender is 0.221em (lowercase g, measured from the TTF), against
   Saira's 0.206em. The padding and the negative margin move together, so
   this cancels for layout and only enlarges the overflow box. */
.wr{padding-bottom:.24em;margin-bottom:-.24em}

/* the hero marks are links now, so they need to feel like it */
.hero-cred a{display:inline-flex;border-radius:6px;transition:opacity .15s ease}
.hero-cred a:hover{opacity:.78}
.hero-cred a:focus-visible{outline:2px solid var(--tracer);outline-offset:4px}

/* ===== social profiles, hero and footer =====
   Inline SVG, so the strip costs no extra request and no icon font. The links
   were missing from the rebuild entirely; the client asked for them in both
   places. Hit area is 44px on touch even though the glyph is smaller, because
   these sit next to a tel: link and a mis-tap there is a wasted call. */
.soc{display:flex;align-items:center;gap:8px;flex-wrap:wrap}

.soc a{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  color:var(--chalk2);border:1px solid var(--steel);background:rgb(7 9 10 / 42%);
  text-decoration:none;transition:color .14s,border-color .14s,transform .14s,background .14s}

.soc a:hover,.soc a:focus-visible{color:var(--tracer);border-color:var(--tracer);
  background:var(--tracerdim);transform:translateY(-2px)}
.soc a:focus-visible{outline:2px solid var(--tracer);outline-offset:2px}
.soc svg{width:19px;height:19px;display:block;fill:currentcolor}

/* in a photographic hero the icons sit over an image, so they get the same
   backdrop treatment the .pcta buttons do rather than a flat border */
.soc-hero{justify-content:center;margin-top:26px}
.scene-in.left .soc-hero{justify-content:flex-start}

.soc-hero a{background:rgb(7 9 10 / 55%);border-color:rgb(232 240 235 / 26%);
  backdrop-filter:blur(3px);color:#fff}
.soc-hero a:hover,.soc-hero a:focus-visible{border-color:var(--tracer);color:var(--tracer)}

.soc-ft{margin-top:20px}
.soc-ft a{width:40px;height:40px;background:transparent}
.soc-ft svg{width:17px;height:17px}

@media(width <=560px){.soc-hero{gap:6px;margin-top:20px}}

/* ===== static maps =====
   The map is a flat image (build/make_maps.py bakes it from OpenStreetMap
   tiles), not an embed, so it behaves like every other photograph on the
   page: one local request, lazy, no third party contact until the reader
   clicks through for directions. */
.mapfig{margin:0;border:1px solid var(--steel);border-radius:var(--r);
  overflow:hidden;background:#0E1413}
.mapfig img{display:block;width:100%;height:auto}

.mapfig figcaption{padding:14px 18px;font-size:14px;line-height:1.55;
  color:var(--chalk3);border-top:1px solid var(--steel);background:var(--gun)}
.mapfig figcaption a{color:var(--chalk2);text-decoration:underline;text-underline-offset:2px}
.mapfig figcaption a:hover{color:var(--tracer)}
.mapfig figcaption b{color:var(--chalk);font-weight:600}
.mapfig.wide{margin:40px 0 0}
.mapfig .key{display:inline-flex;align-items:center;gap:7px;margin-right:16px;white-space:nowrap}
.mapfig .key i{width:11px;height:11px;border-radius:50%;flex:none;border:1px solid #0C1206}

/* a <picture> swaps in a tighter map crop on phones; the <img> inside it
   still needs the fill rule the bare .mapfig img had */
.mapfig picture{display:block}
.mapfig picture img{display:block;width:100%;height:auto}

/* ===== spec strip on a narrow screen =====
   The three cell strip (diagnostic / repair price / invoice) wraps 2 + 1 at
   390px, which leaves the second row half empty with no divider beside it and
   reads as a missing cell rather than a wrap. Below 560 the strip stacks one
   cell per row and the dividers turn horizontal, so it stays a strip. */
@media(width <=560px){
  .spec div{flex:1 1 100%;border-right:0;border-bottom:1px solid rgb(232 240 235 / 14%)}
  .spec div:last-child{border-bottom:0}
}

/* ===== legal pages =====
   Carried over from the old site so nothing that was published disappears in
   the rebuild. These are read, not scanned, so the display face steps down to
   a size that can hold a paragraph without shouting, and .prose finally gets
   a paragraph rule of its own (it only ever styled lists). */

/* clears the fixed pill nav, which floats over the top of the page */
.legal{padding-top:clamp(104px,10vw,128px)}
.legal .kicker{margin-top:16px}
.legal h1{font-size:clamp(32px,4.2vw,46px);text-transform:none;letter-spacing:-.014em;margin-bottom:10px}

.legal .upd{font-size:14.5px;letter-spacing:.04em;text-transform:uppercase;
  font-family:var(--disp);font-weight:600;color:var(--chalk3);margin:0 0 30px}
.legal .prose{max-width:74ch}
.legal .prose p{font-size:17px;line-height:1.72;color:var(--chalk2)}

.legal .prose .lede{font-size:20px;line-height:1.6;color:var(--chalk);
  padding-bottom:6px;margin-bottom:30px;border-bottom:1px solid var(--steel)}

.legal .prose h2{font-size:clamp(20px,2.1vw,25px);text-transform:none;
  letter-spacing:-.008em;margin:40px 0 12px}
.legal .prose ul{margin:0 0 17px}
.legal .prose li b{color:var(--chalk)}

.legal .prose .addr{background:var(--gun);border:1px solid var(--steel);
  border-radius:var(--r);padding:20px 24px;line-height:1.85}

/* the legal row in the footer base, so these pages are reachable from every
   page the way they were on the old site */
.ft-legal{display:flex;gap:16px;flex-wrap:wrap}
.ft-legal a{color:var(--chalk3);text-decoration:none;font-size:14px}
.ft-legal a:hover{color:var(--tracer)}

/* ===== lead in hero on the water heater page =====
   The page used to open with a static studio shot of a heater and then run
   the scrubbed cutaway of the same heater directly underneath it, so the
   subject appeared twice in a row. The cutaway is now the first thing the
   visitor sees moving, and this text band carries the breadcrumb, kicker,
   headline, facts and calls to action that used to sit on the photograph.
   No second heater. */
.scene.lead{min-height:auto;place-items:start center;background-image:var(--grain);
  padding:clamp(86px,12vh,138px) 0 clamp(34px,5vh,60px)}
.scene.lead .scene-in{text-align:left}

.lead-foot{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;
  margin-top:clamp(24px,4vh,44px);padding-top:16px;
  border-top:1px solid rgb(232 240 235 / 14%)}

.lead-foot b{font-family:var(--disp);font-variation-settings:"wdth" 112;font-weight:700;
  font-size:clamp(19px,2vw,26px);color:#fff;letter-spacing:-.015em}

@media(width <=700px){.scene.lead{padding-top:clamp(70px,10vh,100px)}}

/* ===== callout markers: a dot on the part, a dashed leader, a glass card =====
   The treatment the client pointed at on relats.com. The caption plate is the
   card, a white dot sits on the part under discussion, and a thin dashed line
   joins the two.

   The dot is anchored to the VIDEO FRAME, never to the element box. These two
   sections letterbox differently (#heaterxray is contain, #xray is cover) and
   the fit changes again at 700px, so a marker placed in percent of the
   element would walk off the subject as soon as the frame is cropped.
   flow.js measures the drawn frame rect from videoWidth/videoHeight and the
   computed object-fit, then places the dot inside that rect. */
.scrub-fx{position:absolute;inset:0;z-index:3;pointer-events:none;opacity:0;
  transition:opacity .3s ease}
.scrub-lead{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.scrub-lead line{stroke:var(--tracer);stroke-width:1;stroke-dasharray:3 5;opacity:.9}

.scrub-dot{position:absolute;left:0;top:0;width:10px;height:10px;margin:-5px 0 0 -5px;
  border-radius:50%;background:#fff;
  box-shadow:0 0 0 1.5px var(--tracer),0 0 0 7px rgb(175 208 62 / 18%),0 2px 12px rgb(7 9 10 / 70%)}
#xray .scrub-lead line{stroke:#11161A;opacity:.55}
#xray .scrub-dot{box-shadow:0 0 0 1.5px #11161A,0 0 0 7px rgb(17 22 26 / 12%),0 2px 12px rgb(17 22 26 / 40%)}

@media(prefers-reduced-motion:reduce){.scrub-fx{display:none}}

/* the caption plate becomes the frosted card: translucent, blurred, rounded */
.scrub-step{border-radius:12px;padding:17px 22px 19px;
  backdrop-filter:blur(16px) saturate(135%);backdrop-filter:blur(16px) saturate(135%)}

:is(#heaterxray,#heaterpeek) .scrub-step{background:rgb(7 9 10 / 58%);border:1px solid rgb(232 240 235 / 22%);
  box-shadow:0 20px 54px rgb(7 9 10 / 50%)}

#xray .scrub-step{background:rgb(238 236 232 / 66%);border:1px solid rgb(17 22 26 / 16%);
  box-shadow:0 20px 54px rgb(17 22 26 / 16%)}
#xray .scrub-ix{color:#11161A}
#xray .scrub-spec{border-top-color:rgb(17 22 26 / 18%)}
#xray .scrub-spec .lb{color:#5B666D}
#xray .scrub-spec .vl{color:#11161A}
#xray .scrub-step h3{font-size:clamp(24px,2.8vw,40px);margin:0 0 10px}
#xray .scrub-step p{max-width:34ch;margin:0}

/* the card parks in one of four corners of the pin, so it never lands on the
   part its own dot is pointing at. The grid row has to fill the pin for
   align-self to mean anything, hence the stretch. */
.scrub-cap{align-content:stretch!important;grid-template-rows:1fr;
  padding-top:clamp(82px,12vh,124px)}
.scrub-step{align-self:center}
#xray .scrub-step{align-self:end}
.scrub-step[data-pos="tl"]{place-self:start start!important;margin:0 auto 0 clamp(28px,6vw,96px)!important}
.scrub-step[data-pos="tr"]{place-self:start end!important;margin:0 clamp(28px,6vw,96px) 0 auto!important}
.scrub-step[data-pos="bl"]{place-self:end start!important;margin:0 auto 0 clamp(28px,6vw,96px)!important}
.scrub-step[data-pos="br"]{place-self:end end!important;margin:0 clamp(28px,6vw,96px) 0 auto!important}
.scrub-step[data-pos="cr"]{place-self:center end!important;margin:0 clamp(28px,6vw,96px) 0 auto!important}

/* 390px: the card still sits UNDER the video rather than on it, because a
   card over the subject covers the thing it describes. The dot stays on the
   frame above and the leader runs down into the top of the card, which is
   what makes the stack read as one callout instead of two separate pieces. */
@media(width <=700px){
  .scrub-cap{align-content:center!important;grid-template-rows:auto;padding-top:0}

  .scrub-step,.scrub-step[data-pos]{place-self:center stretch!important;
    margin:0!important;width:100%!important}
  :is(#heaterxray,#heaterpeek) .scrub-step{background:rgb(7 9 10 / 86%)}
  #xray .scrub-step{background:rgb(236 234 230 / 94%)}
  .scrub-dot{width:9px;height:9px;margin:-4.5px 0 0 -4.5px}
}

/* The house card needs the same narrow left column the heater card already
   has. Without this it inherits the old full width centred caption, which on
   a 1440 screen runs 880px across the middle of the frame and buries the very
   thing its own dot is pointing at. */
#xray .scrub-step{width:min(560px,46vw);justify-self:start;text-align:left;
  margin:0 auto 0 clamp(28px,6vw,96px)}
#xray .scrub-step h3,#xray .scrub-step p,#xray .scrub-ix{text-align:left}

@media(width <=900px){#xray .scrub-step{width:min(560px,62vw)}}

@media(width <=700px){#xray .scrub-step{width:100%!important;margin:0!important}}

/* The floor scrim existed to make bare caption text readable over the bottom
   of the frame. The caption now sits on its own frosted plate, so the scrim
   only has to seat the subject on a ground. At the old strength it erased the
   bottom fifth of the picture, which is exactly where the slab leak and the
   drain valve are, and a marker pointing into it pointed at nothing. */
#xray .scrub-pin .floor{background:linear-gradient(0deg,rgb(236 234 230 / 72%) 0%,rgb(236 234 230 / 30%) 40%,transparent 100%)}
:is(#heaterxray,#heaterpeek) .scrub-pin .floor{background:linear-gradient(0deg,rgb(0 0 0 / 62%) 0%,rgb(0 0 0 / 24%) 42%,transparent 100%)}
#xray .scrub-pin .vig{background:radial-gradient(124% 96% at 50% 46%,transparent 56%,rgb(232 230 226 / 34%) 88%,rgb(232 230 226 / 72%) 100%)}

/* lead hero: same gutter as the rest of the page, and a headline sized for a
   band rather than a full bleed photograph, so the cutaway starts sooner */
.scene.lead .scene-in{max-width:1240px;margin:0 auto;justify-self:center}
.scene.lead .crumb{padding-top:0;margin-bottom:16px}
.scene.lead .statement{font-size:clamp(34px,5.2vw,74px)}
.scene.lead .sub{margin-bottom:26px}

@media(width <=700px){.scene.lead .statement{font-size:clamp(31px,8vw,40px)}}

/* ===== the full service area list =====
   The rebuild had shrunk the coverage to the six cities that have their own
   page. Their own service areas page names more than twenty, so the six keep
   their cards and the rest are listed by county underneath. County is public
   civic fact; no zips here because their page publishes none and guessing
   them would be inventing. */
.arealist{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));
  gap:28px 36px;margin-top:4px}
.arealist>div{padding-top:16px;border-top:1px solid var(--steel)}

.arealist b{display:block;font-family:var(--disp);font-variation-settings:"wdth" 110;
  font-size:13px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--tracer);margin-bottom:9px}
.arealist p{margin:0;font-size:16.5px;line-height:1.7;color:var(--chalk2)}

.arealist a{color:#fff;text-decoration:underline;text-decoration-color:var(--steel2);
  text-underline-offset:3px}
.arealist a:hover{color:var(--tracer);text-decoration-color:var(--tracer)}

/* ===== fine print inside a centred scene =====
   .scene-in centres its text, but .src caps itself at 58ch with no auto side
   margin, so the capped block sat hard against the left edge of the scene
   while its text centred inside itself. Reads as misalignment, because it is.
   Left aligned scenes keep the old behavior. */
.scene-in:not(.left) .src{margin-left:auto;margin-right:auto}

/* ===== financing partner mark =====
   The Synchrony wordmark is dark slate and yellow, which disappears on our
   ground, so it sits on its own light plate. That also gives the mark the
   clear space a partner logo is supposed to keep. */
.partner{display:inline-flex;align-items:center;gap:14px;margin-top:26px;
  padding:11px 18px 11px 16px;border-radius:var(--r);
  background:#F4F6F5;border:1px solid rgb(255 255 255 / 18%)}

.partner span{font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:#5A6069;white-space:nowrap}
.partner img{display:block;height:26px;width:auto}
.scene-in.left .partner{margin-right:auto}

@media(width <=560px){
  /* The caption was 10.5px here so the strip fitted beside its logo on a
     phone. 12px is the legibility floor, and at 12px the box ran 20px past
     a 320 viewport and the logo was being clipped by .scene's overflow-x.
     Letting the caption wrap buys the width back without shrinking the type:
     the strip becomes two lines on a narrow phone instead of one cut one. */
  .partner{gap:10px;padding:9px 14px;flex-wrap:wrap;max-width:100%}
  .partner img{height:22px}
  .partner span{font-size:12px;white-space:normal}
}

/* The five neighborhoods the client names on their own home page. An earlier
   pass stripped every named subdivision as invented, which was right about the
   ones we had made up and wrong about these: they come straight off
   blackopsplumbing.com. Presented the way they present them, as a list, not
   assigned to a city we have not confirmed they sit in. */
.hoods{margin-top:34px;padding-top:20px;border-top:1px solid var(--steel)}

.hoods b{display:block;font-family:var(--disp);font-size:13px;font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;color:var(--tracer);margin-bottom:10px}
.hoods p{margin:0;font-size:17px;line-height:1.75;color:var(--chalk2)}

/* ===== scrub sections with motion turned off =====
   Every caption shares grid-area 1/1 so the driver can cross fade them in
   place. With prefers-reduced-motion the driver never runs, so all six or
   seven sat on top of each other and only the last one was legible. Reading
   the captions should not require the animation, so here they stop being a
   stack and become a list under the frame, the same shape the narrow
   viewport already uses. Specificity is deliberate: the data-pos corner rules
   carry !important at (0,2,0), so these have to reach (0,3,0) to win. */
@media(prefers-reduced-motion:reduce){
  .scrub .scrub-pin{position:relative;height:auto;min-height:0;display:block}

  .scrub .scrub-cap{position:relative!important;inset:auto!important;transform:none!important;height:auto!important;
    display:block!important;padding:26px 28px 8px;pointer-events:auto}

  .scrub .scrub-cap .scrub-step{position:static!important;grid-area:auto!important;
    opacity:1!important;transform:none!important;place-self:auto auto!important;width:min(720px,100%)!important;
    margin:0 auto 18px!important}
  .scrub .scrub-prog{display:none}

  .scrub .scrub-key{position:static!important;transform:none!important;
    margin:12px auto 0;align-self:center}
}

/* Fine print sits at the bottom of a photographic hero, which is exactly
   where the scrim is weakest, and .src is deliberately dim. Over a bright
   subject it stopped being readable at 390. A shadow costs nothing and keeps
   the disclosure legible without lightening the type and shouting. */
.scene-in .src{text-shadow:0 1px 10px rgb(7 9 10 / 92%),0 0 26px rgb(7 9 10 / 68%)}

/* ===== maintenance plans: three published tiers =====
   The client publishes three annual plans with a price and an inclusion list
   each. One card per tier, in the order they publish them, and the price gets
   the same weight on all three so none of them reads as the one being pushed.

   Three columns down to 1040px and then one, with the column capped. The
   in between step is a two plus one wrap, which strands the third tier on a
   row of its own and reads as a dropped cell rather than a third option. */
.plans{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;align-items:start}

.plan{display:flex;flex-direction:column;background:var(--gun);
  border:1px solid var(--steel);border-radius:var(--r);padding:30px 28px}

.plan-n{display:block;font-family:var(--disp);font-variation-settings:"wdth" 112;
  font-size:23px;font-weight:700;color:#fff;letter-spacing:-.015em;line-height:1.1}

.plan-t{display:block;font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.2em;text-transform:uppercase;color:var(--tracer);margin-top:7px}

.plan-price{display:flex;align-items:baseline;gap:11px;flex-wrap:wrap;
  margin:20px 0 0;padding-top:20px;border-top:1px solid var(--steel)}

.plan-price b{font-family:var(--disp);font-variation-settings:"wdth" 112;
  font-size:clamp(38px,3.4vw,46px);font-weight:700;color:#fff;
  letter-spacing:-.03em;line-height:1}

.plan-price span{font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;color:var(--chalk3)}
.plan-say{margin:15px 0 0;font-size:16.5px;line-height:1.5;color:var(--chalk2)}

.plan-l{list-style:none;margin:22px 0 0;padding:20px 0 0;
  border-top:1px solid var(--steel)}

/* the marker is absolute, not a grid or flex child: a list item that becomes a
   grid puts its own ::before in the first auto placed cell and the text lands
   underneath it rather than beside it */
.plan-l li{position:relative;padding:0 0 0 25px;margin:0 0 17px;
  font-size:16px;line-height:1.55;color:var(--chalk2)}
.plan-l li:last-child{margin-bottom:0}

.plan-l li::before{content:"";position:absolute;left:0;top:.52em;
  width:9px;height:9px;border-radius:50%;background:var(--tracer)}
.plan-l b{color:#fff;font-weight:600}

.plan-l .eg{display:block;margin-top:6px;font-size:14.5px;
  line-height:1.5;color:var(--chalk3)}

@media(width <=1040px){
  .plans{grid-template-columns:1fr;max-width:620px;margin-left:auto;margin-right:auto}
}

/* the six reasons the client publishes for joining, in their words. Six cells
   divide evenly into three columns and into one, so there is no orphan at any
   width the grid actually reaches. */
.rsn{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,290px),1fr));
  gap:1px;margin:0;padding:0;background:var(--steel);border:1px solid var(--steel)}

.rsn li{background:var(--night);padding:23px 24px;font-size:17px;line-height:1.45;
  color:var(--chalk);display:flex;align-items:flex-start;gap:13px}

.rsn li::before{content:"";flex:none;width:15px;height:15px;margin-top:5px;
  border-radius:50%;background:var(--tracerdim);border:1px solid var(--tracer)}

/* ===== a four card service grid =====
   .svcs defaults to three columns at full width, which leaves a four card set
   alone on its second row. These four pair up two and two instead, and fall to
   one column on a phone. */
.svcs.pair{grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr))}

/* a paired card is wider, so its paragraph sits higher up the frame than it
   does in the three column grid, which puts the first line above the point
   where the default scrim has taken hold. Same gradient, pulled upward. */
.svcs.pair .svc .img::before{background:linear-gradient(0deg,rgb(7 9 10 / 97%) 10%,
  rgb(7 9 10 / 86%) 46%,rgb(7 9 10 / 36%) 78%,transparent 100%)}

/* ===== breadcrumb under the floating nav =====
   The pill nav is position:fixed at top:18px, so it sits over the hero rather
   than above it. A photographic hero centres its content, and on a narrow
   screen that centring put the breadcrumb underneath the nav capsule: on the
   financing page the crumb's top was 70px against a nav bottom of 74px, so
   the first line of the page was behind the navigation. Below the width where
   the nav collapses to logo plus phone, the hero content starts clear of it. */
@media(width <=1120px){
  .scene .scene-in:not(.low){padding-top:92px}
}

/* .scene-in.low pins hero copy to the bottom of the frame, which works while
   the copy is shorter than the frame. On a phone it is not: the block grows
   upward past the top of the scene and the breadcrumb ends up at viewport 0,
   behind the floating nav. Below the nav collapse width the block stops being
   bottom pinned and centres like every other hero, with the same clearance. */
@media(width <=1120px){
  .scene .scene-in.low{align-self:center;padding-top:92px;
    padding-bottom:clamp(36px,6vh,64px)}
}

/* Between the nav collapse width and roughly 1400px the nav is still the full
   capsule and the hero is already short enough to drift under it: measured on
   financing at 1280, crumb top 75 against nav bottom 79. A lighter clearance
   is enough here; the full 92px below 1120 would shove the hero off balance
   on a laptop. */
@media(width >=1121px) and (width <=1400px){
  .scene .scene-in:not(.low){padding-top:52px}
}

/* ===== the phone gallery was dropping photographs =====
   The 560px rule that returns .w2 and .h2 to a single cell is written above
   .gal .w2{grid-column:span 2}, and at equal specificity the later rule wins,
   so the spans survived their own breakpoint. With one explicit column a span
   of two invents a second, auto sized, empty column, and every figure that
   auto placed into it came out two pixels wide: on about.html that silently
   deleted "Cutting in a new line" and "Slab leak, framing out", on
   drain-cleaning.html "Open cut where a line has failed".

   Re stating the reset here, after both span rules, is what actually turns
   them off. It is the rule the stylesheet already intended, moved to where
   the cascade can reach it. */
@media(width <=560px){
  .gal .w2,.gal .h2,
  .gal.g3 .w2,.gal.g3 .h2{grid-column:auto;grid-row:auto}
}

/* ===== the scrub video stops looking like a video in a frame =====
   On a phone the video is letterboxed: a 1920x1080 file painted 390x219
   inside a 390x388 box, leaving 84px of flat page colour above and below it,
   and the vignette that feathers the edges on desktop is switched off here.
   So the render sat as a hard rectangle on a background that did not match.

   It could not be made to match with a colour. These are lit studio
   backdrops, so the background is a gradient and it moves: the top edge of
   the house frame travels from #D0D0D2 to #8A8C95 over the scrub, up to
   94/255 away from the page. A feather was not available either, because
   from about fourteen seconds the camera is inside the house and the frame
   is full bleed detail to all four edges.

   So flow.js samples the real top and bottom edge of each frame and
   publishes --sv-top and --sv-bot. The bars and the hand off to the caption
   paint themselves with those, which means the backdrop continues past the
   video at every point in the scrub rather than meeting a line. The
   fallbacks are the old static colours, so with JS off nothing changes. */
@media(width <=700px){
  /* two layers: the top edge rebuilt as a horizontal gradient pinned to the
     top half, the bottom edge to the bottom half. Constant vertically inside
     each half, so the colour meeting the frame IS the frame's own edge. */
  .scrub-pin video{
    background-image:
      linear-gradient(to right,var(--sv-t0,#E8E6E2),var(--sv-t1,#E8E6E2),var(--sv-t2,#E8E6E2),var(--sv-t3,#E8E6E2)),
      linear-gradient(to right,var(--sv-b0,#E8E6E2),var(--sv-b1,#E8E6E2),var(--sv-b2,#E8E6E2),var(--sv-b3,#E8E6E2));
    background-size:100% 50%,100% 50%;
    background-position:top,bottom;
    background-repeat:no-repeat,no-repeat}

  :is(#heaterxray,#heaterpeek) .scrub-pin video{
    background-image:
      linear-gradient(to right,var(--sv-t0,#000),var(--sv-t1,#000),var(--sv-t2,#000),var(--sv-t3,#000)),
      linear-gradient(to right,var(--sv-b0,#000),var(--sv-b1,#000),var(--sv-b2,#000),var(--sv-b3,#000))}

  /* carry the frame's bottom edge a little way into the caption area so the
     band dissolves instead of ending */
  #xray .scrub-cap{
    background:linear-gradient(to bottom,var(--sv-b1,#E8E6E2) 0,#E8E6E2 84px)}

  :is(#heaterxray,#heaterpeek) .scrub-cap{
    background:linear-gradient(to bottom,var(--sv-b1,#000) 0,#000 84px)}
}

/* ===== plan offer, placed where the argument already lives =====
   The service pages spend real effort explaining why a part fails, then used
   to stop. This is the bridge from that explanation to the plan that prevents
   it. Two cards, because a single option is a decision and two is a choice,
   and the second one is marked as the one that covers what the page taught. */
.planlink{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,290px),1fr));gap:20px;margin-top:34px}

.planlink a{display:block;background:var(--gun);border:1px solid var(--steel);
  border-radius:var(--r);padding:26px;text-decoration:none;transition:all .14s}
.planlink a:hover{border-color:var(--tracer);transform:translateY(-2px)}

.planlink .k{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--tracer);display:block;margin-bottom:9px}

.planlink b{display:block;font-family:var(--disp);font-size:21px;color:#fff;
  margin-bottom:8px;letter-spacing:-.012em;line-height:1.15}
.planlink p{margin:0;font-size:16px;color:var(--chalk2);line-height:1.6}
.planlink a.pick{border-color:var(--tracer);background:linear-gradient(180deg,var(--tracerdim),var(--gun) 60%)}
.planlink a.pick:hover{transform:translateY(-2px)}

/* the which-plan rows reuse the .nextup grid but are not links: the choice
   is explained here and the only action is the phone */
.pickrow{background:var(--gun);border:1px solid var(--steel);border-radius:var(--r);padding:26px}

.pickrow .k{font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--tracer);display:block;margin-bottom:9px}
.pickrow b{display:block;font-family:var(--disp);font-size:21px;color:#fff;margin-bottom:8px;letter-spacing:-.012em}
.pickrow p{margin:0;font-size:16px;color:var(--chalk2);line-height:1.62}

/* ===== FAQ question headings =====
   The question inside each <details> moved into an <h3>. A <summary> is a
   control, not a heading, so before this every answered question on the site
   was absent from the document outline, which is the structure an answer
   engine reads when it decides which span of text answers which question.
   The h3 inherits everything from the summary, so nothing moves: the only
   change is semantic. min-width:0 keeps a long question wrapping inside the
   flex row instead of pushing the +/- marker off the end. */
.obj summary h3{
  font:inherit;color:inherit;letter-spacing:inherit;text-transform:none;
  margin:0;flex:1 1 auto;min-width:0}

/* Citation links inside an FAQ answer.
   The answers now carry links out to the primary sources they quote: a
   Citizens bulletin, a statute, a building code section, a manufacturer's
   manual. Site-wide, a link is signalled by colour alone (a{color:var(--tracer)}),
   which inside a paragraph of running text fails WCAG 2.1 SC 1.4.1 Use of
   Colour, and this site publishes a WCAG 2.1 commitment on its own ADA page.
   Underlining them only here keeps the rest of the design as it was. */
.obj .ans a{
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:2px}
.obj .ans a:hover{color:#fff}

/* ===== scorecards: the chooser, the selected option, the booking panel =====
   Appended at the end on purpose. Everything above this line already styles
   the question steps, the dial, the result table and the overlay, and none of
   it needed changing to carry five question sets instead of one. What is new
   is only what is new: the five way chooser that replaced the single generic
   "the scorecard is free" button, a visible selected state on an answered
   option, and the panel that turns a result into a booked window. */

/* --- the chooser ----------------------------------------------------
   Five named doors. Each one carries both an href and data-modal, so on a
   page that does not hold the matching overlay the link navigates and the
   #hash opens it on arrival, which is behaviour flow.js already has. */

/* Five columns, not auto-fit. auto-fit fitted four and left the fifth alone
   on the next row, which reads as a dropped cell rather than the end of a
   list. Below that it goes to three (three and two still reads as a list)
   and then straight to one, because two columns strands the fifth again. */
.scpick{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-top:30px}

@media(width <=1080px){.scpick{grid-template-columns:repeat(3,1fr)}}

@media(width <=720px){.scpick{grid-template-columns:1fr}}

.scpick-i{
  display:flex;flex-direction:column;position:relative;border-radius:var(--r);
  border:1.5px solid var(--steel2);background:var(--gun);overflow:hidden;
  text-decoration:none;transition:border-color .14s,transform .14s,background .14s}
.scpick-i:hover{border-color:var(--tracer);transform:translateY(-2px);background:#1F2725}

/* a strip, not a speck. A 44px crop of a job photograph reads as noise. */
.scpick-i .thumb{
  display:block;width:100%;height:104px;
  background:#0E1211 center/cover no-repeat;
  border-bottom:1px solid var(--steel2);filter:saturate(.92) contrast(1.04)}
.scpick-i:hover .thumb{filter:none}
.scpick-i b,.scpick-i .sub{padding:0 18px}
.scpick-i b{margin-top:16px}
.scpick-i .sub{padding-bottom:18px}

.scpick-i b{
  display:block;font-family:var(--disp);font-variation-settings:"wdth" 112;
  font-size:20px;font-weight:700;letter-spacing:-.015em;color:#fff;line-height:1.1}
.scpick-i .sub{display:block;margin-top:5px;font-size:15.5px;color:var(--chalk2);line-height:1.45}
.scpick-i .sub{margin-bottom:auto}
.scpick-i:hover .sub{color:var(--chalk)}

/* --- an answered option stays answered -----------------------------
   The steps can be walked back through, and without this the option you
   chose last time looked identical to the three you did not. */
.op.on{border-color:var(--tracer);background:var(--tracerdim);color:#fff}
.op.on small{color:var(--chalk2)}

/* --- what got flagged for the shop ---------------------------------
   Shown to the customer as well as sent, because "we told the technician
   about the gas smell" is only reassuring if you can see that we did. */
.sowhat .flags{margin-top:18px;padding-top:16px;border-top:1px solid var(--steel)}
.sowhat .flags b{font-size:12px;letter-spacing:.11em;text-transform:uppercase;color:var(--flare);margin-bottom:8px}
.sowhat .flags ul{margin:0;padding-left:20px}
.sowhat .flags li{font-size:16px;color:var(--chalk2);margin-bottom:4px}

/* --- the booking panel ---------------------------------------------- */
.bk{margin-top:30px;padding-top:28px;border-top:1px solid var(--steel)}

.bk h4{
  font-family:var(--disp);font-variation-settings:"wdth" 112;
  font-size:clamp(22px,2.4vw,28px);font-weight:700;letter-spacing:-.016em;
  color:#fff;margin:0 0 8px;line-height:1.12}

.bk h5{
  font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--chalk3);margin:0 0 8px}

/* Twelve columns, because auto-fit put the email on a row of its own and
   stranded the ZIP under it. A form reads as a form or it reads as a pile. */
.bk-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px;margin-top:20px}
.bk-grid label{display:flex;flex-direction:column;gap:6px;min-width:0}
.bk-grid label.c12{grid-column:span 12}
.bk-grid label.c6{grid-column:span 6}
.bk-grid label.c3{grid-column:span 3}

.bk-grid label>span{
  font-family:var(--disp);font-size:12px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--chalk3)}
.bk-grid .ti{min-width:0;width:100%;flex:none}
.bk-grid textarea.ti{resize:vertical;line-height:1.55;font-size:17px}

.bk-when{margin-top:26px}
.bk-when .why{margin:0 0 14px}
.bk-days{display:flex;flex-wrap:wrap;gap:8px}

.bk-day,.bk-slot{
  font-family:var(--body);font-size:16px;font-weight:500;
  padding:11px 16px;border:1.5px solid var(--steel2);background:transparent;
  color:var(--chalk);border-radius:var(--r);cursor:pointer;transition:all .14s}
.bk-day:hover,.bk-slot:hover{border-color:var(--tracer);color:#fff}
.bk-day.on,.bk-slot.on{border-color:var(--tracer);background:var(--tracerdim);color:#fff}

.bk-slots{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));
  gap:8px;margin-top:10px}

.bk-go{display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin-top:26px}

/* The one place a confirmation is ever printed, and the one place a failure
   is. Both are loud, because the difference between them is the whole point
   of wiring this to the real schedule instead of to an inbox. */
.bk-msg{margin-top:22px;padding:20px 22px;border-radius:var(--r);background:var(--night);border-left:3px solid var(--steel2)}
.bk-msg.good{border-left-color:var(--tracer)}
.bk-msg.bad{border-left-color:var(--flare)}
.bk-msg b{display:block;font-family:var(--disp);font-size:19px;color:#fff;margin-bottom:7px}
.bk-msg p{margin:0 0 10px;font-size:16.5px;color:var(--chalk2)}
.bk-msg p:last-child{margin-bottom:0}
.bk-msg a{color:var(--tracer)}
.bk-msg:focus{outline:none}

@media(width <=620px){
  /* on a phone everything is full width except the state and ZIP pair,
     which still belong on one line */
  .bk-grid label.c12,.bk-grid label.c6{grid-column:span 12}
  .bk-grid label.c3{grid-column:span 6}
  .bk-slots{grid-template-columns:1fr}
}

/* ===== pull quote: one real review, on the page it belongs to =====
   The proof used to live entirely in the home page carousel, which meant a
   visitor who landed on the water heater page from a search had no evidence
   in front of them at all. Each topic, city and support page now carries one
   review that is about that page's subject.

   Built out of the parts already in the stylesheet rather than a new look:
   the .aside / .plan card (--gun on --steel, --r corner), the .kicker label
   in --tracer, and the gold stars of .tbar. The only new shape is the rule
   down the left edge, which is what tells a reader at a glance that this is
   somebody else talking and not the page's own copy.

   The quote text sets in the body face, not Saira. The display face is for
   the page's own voice; a review in it would read as a headline the company
   wrote about itself. */
.proofsec{background-image:var(--grain)}

.proof{margin:0;background:var(--gun);border:1px solid var(--steel);
  border-left:3px solid var(--tracer);border-radius:var(--r);
  padding:clamp(24px,3vw,34px) clamp(22px,3vw,38px)}

.proof-k{display:block;font-family:var(--disp);font-variation-settings:"wdth" 110;
  font-size:12px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  color:var(--tracer);margin:0 0 14px}
.proof-q{margin:0;quotes:none}

/* The opening mark hangs into the gutter so the first word still lines up
   with the label above it. A quote mark that pushes the text in by its own
   width reads as a broken indent on the first line only. */
.proof-q p{margin:0 0 14px;font-size:clamp(17.5px,1.55vw,20.5px);line-height:1.55;
  color:var(--chalk)}
.proof-q p:first-child{text-indent:-.42em}
.proof-q p:first-child::before{content:"\201C"}
.proof-q p:last-child{margin-bottom:0}
.proof-q p:last-child::after{content:"\201D"}

.proof-by{display:flex;align-items:center;flex-wrap:wrap;gap:8px 16px;
  margin-top:20px;padding-top:17px;border-top:1px solid var(--steel)}
.proof-st{color:#F6B21B;letter-spacing:2.5px;font-size:13px;line-height:1}

.proof-who{font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.13em;text-transform:uppercase;color:var(--chalk2)}

.proof-go{margin-left:auto;font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:var(--chalk3);
  text-decoration:none;border-bottom:1px solid transparent;transition:color .14s,border-color .14s}
.proof-go:hover,.proof-go:focus-visible{color:var(--tracer);border-bottom-color:var(--tracer)}
.proof-go:focus-visible{outline:2px solid var(--tracer);outline-offset:3px}

@media(width <=560px){
  /* at 390 the name and the link will not sit on one line without the link
     wrapping mid word, so the link drops to its own row and goes left */
  .proof-go{margin-left:0;flex-basis:100%}
}

/* ===== carousel cards link through to the review they quote =====
   The overlay covers the card, so the whole card is the link rather than a
   small target inside a card that already responds to a click. The chip sits
   over the photograph and not over the body text, which is the one region of
   the card with nothing to read underneath it.

   .rev3 is already position:absolute while the coverflow runs, so inset:0
   resolves against the card and nothing here needs to touch it. Do NOT add a
   bare .rev3{position:relative}: this file is appended to, so that rule wins
   over the coverflow's own position:absolute further up, the cards drop out
   of the ring and stack down the page in document order. It looked like a
   clipping bug in a screenshot and was a broken carousel.

   Only the two flat layouts need the card positioned: reduced motion below,
   and the no-ScrollTrigger path, which flow.js sets inline. */
.rev3-go{position:absolute;inset:0;display:flex;align-items:flex-start;
  justify-content:flex-end;padding:12px;text-decoration:none;
  border-radius:var(--r);z-index:2}

.rev3-go span{font-family:var(--disp);font-size:12px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:var(--chalk2);
  background:rgb(11 14 13 / 82%);border:1px solid var(--steel);
  border-radius:var(--r);padding:5px 8px;line-height:1.1;
  transition:color .14s,border-color .14s,background .14s}

.rev3:hover .rev3-go span,.rev3-go:focus-visible span{color:var(--tracer);
  border-color:var(--tracer);background:rgb(11 14 13 / 94%)}
.rev3-go:focus-visible{outline:2px solid var(--tracer);outline-offset:-2px}

@media(prefers-reduced-motion:reduce){.rev3{position:relative}}

/* ===== BLUEPRINT GROUND =====
   The site used to be black end to end, which read as gloomy rather than
   tactical. Sections carrying photography or video keep the dark ground
   because the media needs it. Sections carrying text, proof and pricing sit
   on warm sand, because that is where someone is reading and deciding.
   The tactical character lives in the type, the hairlines and the accent.

   .slab.lite and .slab.mid are assigned in build/site.py. CSS cannot do the
   alternation alone: :nth-of-type counts <section> siblings rather than
   .slab siblings, and there is no :nth-of-class.
   ===================================================================== */

/* The light ground is sand, not steel. Cold gray read as unwelcoming, and
   sand is an authentic tactical colorway that happens to be warm, so the
   theme gets friendlier without going soft. Anchored on Tan 499 (Federal
   Std 595C 20180, ~#C8A882 as rendered) and tinted up to paper weight:
   hue sits at 38 degrees with red leading green leading blue, which reads
   as tan rather than cream, and the lightness is held near the old steel
   so the light slabs are still a real break from the dark ones.
   Every pair below is calculated, not eyeballed. Ratios against --lite:
   ink 13.96, ink2 7.77, accent 6.63. Against --lite2: 15.55, 8.66, 7.38.
   AA wants 4.5 for body and 3 for large, so the thinnest of those clears it
   by a wide margin. 343 text and background pairs on the built pages were
   measured in the browser after this change and none of them failed. */
:root{
  --lite:#EAE2D4;            /* the light ground: Tan 499 tinted to paper */
  --lite2:#F4EEE3;          /* a second light step, for cards and stripes */
  --lite-ink:#1B1611;         /* text on light: warm near black, 13.96:1 */
  --lite-ink2:#4A4136;       /* secondary text on light, 7.77:1 */
  --lite-rule:#D6C9B3;       /* hairlines on light, 1.27:1, same as before */
  --lite-accent:#45512A;   /* Ranger Green 498 taken down two steps, 6.63:1 */
  --mid:#1F1A13;              /* the mid tone that breaks two darks apart */
  --mid-ink:#EFE9DF;
}

/* ---- alternate the text slabs: 1st light, 2nd mid, 3rd light ... ---- */
.slab.lite{background-color:var(--lite);background-image:none;
  border-top:1px solid var(--lite-rule);border-bottom:1px solid var(--lite-rule)}
.slab.mid{background-color:var(--mid);background-image:var(--grain)}

/* ---- text on a light slab ---- */
.slab.lite,
.slab.lite p,
.slab.lite .sub{color:var(--lite-ink2)}

.slab.lite h1,
.slab.lite h2,
.slab.lite h3,
.slab.lite .statement,
.slab.lite b,
.slab.lite strong{color:var(--lite-ink)}

.slab.lite .eyebrow,
.slab.lite .kicker,
.slab.lite .help .n{color:var(--lite-accent)}
.slab.lite .cat span{color:var(--lite-ink)}
.slab.lite .cat svg{stroke:#40382E}
.slab.lite .cat:hover{background:var(--lite2)}
.slab.lite .cat:hover svg{stroke:var(--lite-accent)}

.slab.lite .cats,
.slab.lite .cat{border-color:var(--lite-rule)}

/* cards, steps, numbers and review blocks sitting on the light ground */
.slab.lite .help,
.slab.lite .step,
.slab.lite .rev3,
.slab.lite .stage,
.slab.lite .nums{background:var(--lite2);border-color:var(--lite-rule)}
.slab.lite .nums .n{color:var(--lite-ink)}
.slab.lite .nums .l{color:var(--lite-ink2)}
.slab.lite .spec{background:var(--lite2);border-color:var(--lite-rule)}
.slab.lite .spec div{border-right-color:var(--lite-rule)}
.slab.lite .spec .k{color:var(--lite-ink2)}
.slab.lite .spec .v{color:var(--lite-ink)}

/* ---- cells and cards that bring their own background ----
   The ink rules above are specific enough to beat the component's own text
   color, so anything that keeps a dark background has to be answered here or
   its text lands dark on dark. Found by walking every text and background
   pair on a light slab, not by eye.

   .help, .nums and .steps paint only the 1px grid gaps: the cells are
   separate elements, so the cells take the light card tone and the gap keeps
   the hairline tone. */
.slab.lite .help,
.slab.lite .nums,
.slab.lite .steps{background:var(--lite-rule)}

.slab.lite .help>div,
.slab.lite .nums div{background:var(--lite2)}

/* the photographic cards stay dark on purpose, because they carry a
   photograph, so their text stays light: #fff on the card ground is
   17.9:1 at its worst and --chalk2 is 9.25:1 */
.slab.lite .svc h3,
.slab.lite .citygrid b{color:#fff}

/* the lite link rule is .slab.lite a:not(.pcta):not(.cat), which counts as
   four classes and an element, so matching that count is not enough: at equal
   specificity the later rule wins and the link rule sits below this one.
   Naming .ft as well puts this one ahead on count. */
.slab.lite .svc .ft a.go{color:#fff}
.slab.lite .svc p{color:var(--chalk2)}

/* the review cards do take the light card tone, so their text goes dark and
   the gold of a rating is deepened until it carries on paper, 4.61:1 */
.slab.lite .rev3 q{color:var(--lite-ink)}
.slab.lite .rev3 .who{color:var(--lite-ink2)}

.slab.lite .rev3 .st,
.slab.lite .stars{color:#8A5A00}

/* links and buttons keep their job, but the ghost button needs an outline
   that exists against paper */
.slab.lite .pcta:not(.em){border-color:var(--lite-ink);color:var(--lite-ink)}
.slab.lite .pcta:not(.em):hover{background:var(--lite-ink);color:var(--lite)}
.slab.lite a:not(.pcta, .cat){color:var(--lite-accent)}

/* ---- the credibility strip reads better on paper than on black ---- */
.cred{background-color:var(--lite);background-image:none;
  border-top:1px solid var(--lite-rule);border-bottom:1px solid var(--lite-rule)}
.cred-lb{color:var(--lite-ink2)}

.cred-vp{mask-image:linear-gradient(90deg,transparent 0,#000 13%,#000 87%,transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 13%,#000 87%,transparent 100%)}

/* these marks were cut for white, so on paper they need no lift at all */
.cred-i img{filter:none}

a.cred-i:hover img,a.cred-i:focus-visible img{
  filter:brightness(1.04) drop-shadow(0 2px 10px rgb(0 0 0 / 22%));transform:translateY(-2px)}

/* ---- scenes and scrubs keep the dark ground on purpose ---- */

/* .scene and .scrub are untouched: they carry photography and video. */

/* ===== TAP TARGETS =====
   Measured against the live site with Playwright on nine real device
   profiles. Every link named below came back under 44x44 CSS px, which is
   the WCAG 2.5.5 / platform minimum, and the worst of them was the footer
   phone number at 107x19. On a business whose only conversion is a phone
   call that is the most expensive defect on the page.

   This block is in two halves.

   The FIRST half is layout neutral and applies everywhere. Where growing a
   hit box would move something, the growth is cancelled with a negative
   margin the same size as the padding: the box grows, the margin box does
   not, so nothing on the page shifts. That is why these rules come in
   matched padding/margin pairs.

   The SECOND half is inside @media(hover:none),(pointer:coarse) because it
   genuinely makes things taller, and a 44px minimum is a THUMB
   specification. A mouse does not need it, and the desktop composition was
   drawn deliberately, so on a mouse the footer, the NAP block and the city
   lists stay exactly as they are and every touch device gets the bigger
   target. A touch laptop that reports a fine pointer keeps the mouse
   layout, which is the right default for a device that has a trackpad.

   One class of link is deliberately NOT given a 44px box anywhere:
   CITATION links inside running prose (statutes, carrier bulletins, spec
   sheets, FAQ answers, map attribution). WCAG 2.5.5 and 2.5.8 both exempt a
   target "in a sentence or whose size is constrained by the line-height of
   non-target text", and the measurement shows why: a 40px box inside a 21px
   line box overlapped the neighbouring citation by 19px on the water heater
   and poly-B pages and by 26px in one FAQ answer, which converts "hard to
   hit" into "opens the wrong reference". So those are padded to the height
   of their own line box instead: 24-29px of reach instead of 16-21px, zero
   overlap, and the paragraph exactly where it was.

   No font size, colour or family is changed anywhere in this block. */

/* ---------- layout neutral, everywhere ---------- */

/* floating nav logo. Below 560px the wordmark is hidden by design (the phone
   number is worth more than the name on a phone), which left a bare 30x30
   logo as the link home. padding:7px with margin:-7px gives it a 44x44
   target while the capsule keeps the exact height it has now. */
.pill-nav .pb{min-width:44px;min-height:44px;justify-content:center;
  padding:7px;margin:-7px 1px -7px -7px}

@media(width <=560px){.pill-nav .pb{margin-right:-7px}}

/* the Book Now button in the floating nav. The ghost button beside it is the
   phone number and is handled further down. At 561px and up this one already
   measures 42.6px inside a capsule whose height is set by the taller ghost
   button, so the desktop nav is pixel identical; below 561px the smaller
   type had dropped it to 37.8px and the capsule grows 4px on a phone.
   The nav word links (1121px and up, mouse only) are left at 41.9px on
   purpose: they are not a touch target and growing them would change the
   hover pill. */
.np .solid{display:inline-flex;align-items:center;justify-content:center;min-height:44px}

/* breadcrumb "Home", 41x18. Inline inside the hero text block, so the
   negative margin keeps the inline-block's margin box the height of a normal
   line and the crumb row does not move; the horizontal pull keeps the
   separator where it was. 41x18 becomes 55x46. */
.crumb a{display:inline-block;padding:11px 7px;margin:-11px -7px}

/* service card CTAs, "See more" at 106x21 and "Call" at 70x21. .svc .ft is a
   flex row with the price on the left, so the CTA grows to 44px and is
   pulled back 10px top and bottom: the row ends up 24px instead of 20.6px
   and the card composition is unchanged. */
.svc .go{min-height:44px;margin-top:-10px;margin-bottom:-10px}

/* "Read it on Google" under a pull quote, 21px tall. The underline is part
   of the treatment, so this cannot become a 44px box with the rule floating
   12px below the text. Padding with a negative margin keeps the border where
   it is and gives the link 44px of reach. */
.proof-go{display:inline-block;padding:12px 4px;margin:-12px -4px}

/* the "Read on Google" chip on a carousel card. The link is already the
   whole card (inset:0) and measures 105x170, so only the visible chip was
   small. It is the chip, not the target, so it only needs enough padding to
   read as a button. */
.rev3-go span{padding:7px 9px}

/* credibility badge links in the hero, 71x34. */
.hero-cred a{min-height:44px;min-width:44px;align-items:center;justify-content:center}

/* inline citations, 16-21px tall. Padded to the height of their own line
   box: no overlap with the citation above or below, no change to the
   paragraph, 24-29px of reach. See the note at the top for why these do not
   get 44px. */
.src a,
.obj .ans a,
.mapfig figcaption a,
.legal .prose p > a{padding:4px 3px;margin:0 -3px}

/* ---------- touch pointers only ---------- */
@media(hover:none),(pointer:coarse){
  /* the conversion: every tel: and mailto: link on the site. One rule rather
     than a list of places, because a phone number is never a citation and
     must never be the small target on a page. Most of these are already
     buttons bigger than 44px and nothing changes for them. The ones that
     were not: the footer NAP phone at 107x19 and the email at 237x19, the
     .addr block on the legal pages, the phone in a .fact list, the number in
     the sticky header, the ghost button in the floating nav below 561px, and
     a mid-sentence "Call ..." in a couple of source notes.
     z-index:1 settles hit testing in the phone number's favour wherever a
     neighbouring citation box reaches into the same line. */
  a[href^="tel:"],a[href^="mailto:"]{display:inline-flex;align-items:center;
    min-height:44px;position:relative;z-index:1}

  /* footer service and start-here columns: full column width by 26px. They
     are already one per line, so the padding both fills the 44px and closes
     the gap, making adjacent rows touch instead of leaving a 9px dead strip
     between two links, which is what a list of links should do. Direct
     children only, so the NAP paragraph, the city run and the social icons
     in the same grid are not caught by it. */
  .ft-grid > div > a:not(.brand){min-height:44px;padding-top:9px;padding-bottom:9px;
    margin-bottom:0;display:flex;align-items:center}

  /* footer legal row: Terms, Privacy, ADA at 129x23. */
  .ft-legal a{display:inline-flex;align-items:center;min-height:44px}
  .ft-base{align-items:center}

  /* footer city run, 33x20 per name. The 44px boxes on consecutive lines
     would overlap at line-height 2, and two overlapping city links means a
     tap can open the wrong city, so the leading pays for the target. The
     words do not move inside their own line, the block just breathes.
     "Lutz" and "Odessa" stay under 44px WIDE on purpose: these sit in a
     middot run and widening a word would space the whole run out, and
     vertical reach is the axis a thumb actually misses. */
  .ft-cities{line-height:2.9}
  .ft-cities a{padding:14px 3px;margin:0 -3px}

  /* the county lists on the service areas page, same case and same reason:
     these are links to city pages, so a mis-tap lands on the wrong city. */
  .arealist p{line-height:2.6}
  .arealist p a{padding:12px 3px;margin:0 -3px}

  /* the social icons in the footer. .soc a is 44x44 everywhere else on the
     site; the footer override took them to 40x40, which was the last
     standalone link under the bar. The glyph inside stays 17px, so this is
     4px of ring, not a bigger mark. */
  .soc-ft a{width:44px;height:44px}
}

/* ===== language toggle =====
   Sits in the pill nav beside the phone number. Quieter than .ghost on
   purpose: it is a preference, not a conversion. The coarse-pointer block
   buys the 44px target without moving the desktop composition, which is the
   same split the rest of the tap-target work uses. */
.np .lang{font-family:var(--disp);font-size:12.5px;font-weight:600;letter-spacing:.07em;
  text-transform:uppercase;padding:11px 12px;border-radius:999px;text-decoration:none;
  white-space:nowrap;color:var(--chalk2);border:1px solid transparent;transition:color .18s,border-color .18s}
.np .lang:hover,.np .lang:focus-visible{color:var(--tracer);border-color:var(--steel)}
.slab.lite .np .lang{color:var(--lite-ink2)}
.slab.lite .np .lang:hover{color:var(--lite-accent)}
@media(hover:none),(pointer:coarse){
  .np .lang{display:inline-flex;align-items:center;min-height:44px}
}
@media(width <=520px){.np .lang{padding:11px 8px;font-size:11.5px}}

/* The pill nav has no room for a word at 320. Two labels in the markup and a
   swap here, rather than a second nav or a hidden toggle: the language has to
   stay reachable on the narrowest phone, which is where the Spanish visitor
   this was built for is most likely to be. */
.np .lang .l-short{display:none}
@media(width <=430px){
  .np .lang .l-long{display:none}
  .np .lang .l-short{display:inline}
  .np .lang{padding:11px 9px;letter-spacing:.04em}
}

/* A 1fr track floors at min-content, so a chip label that cannot wrap makes
   the track wider than the grid and the document scrolls sideways. English
   words were short enough to hide it; Spanish is not. minmax(0,1fr) lets the
   track shrink and this lets the label use the second line it needs. */
.cat span{flex-wrap:wrap;justify-content:center}
.cat span,.cat span *{min-width:0}
