/* ============================================================
   BENCHMARK DRAIN & SEWER — "THE CAMERA RUN"
   ------------------------------------------------------------
   The artefact this site is built on is a DEPTH-STAMPED CAMERA
   RUN: a sewer line read from the cleanout to the city main,
   with every observation pinned at the foot mark it was found
   at and the depth it was found under. So the page is a ruled
   FOOTAGE RAIL running down it, not a grid of cards.

   ⛔ IT MUST NOT LOOK LIKE THE OTHER TWELVE. Taken already: a
   field report, a paper notebook, a panel grid, an instrument
   cluster, a studio wall, a drawing sheet, a route log, a
   dispatch board, a water report, a yard card, a wash card and
   a repair order. A run log is measured in DISTANCE AND DEPTH
   rather than in time or in stages, and the closest cousin —
   the route log — reads left to right across a day. This one
   reads downward, into the ground.

   THE GROUND IS SOIL, not a screen. Four of the other demos are
   dark and every one of them is a cool dark — instrument navy,
   studio near-black, dispatch slate. This one is a warm brown
   black, because the whole page is a section through a front
   lawn and the pipe is under it.

   THE ACCENT IS LOCATE GREEN, #00843D. 🔴 It was tracer
   magenta until the owner said "no pink websites", 11 Sep 2026,
   and he was right twice over: it read pink, and the honest
   colour for this trade was sitting there unused. APWA uniform
   colour code — the one every locator, every 811 ticket and
   every paint stripe in the country runs on — makes GREEN the
   colour of sewers and drain lines. A drain company's locate
   flag IS green. Nothing else on the page may be green now,
   which is why "sound pipe, nothing found" became neutral
   stone (--clear): green had to mean one thing.

   ⛔ System fonts only. This file makes zero network requests —
   the site is served in an iframe on another domain, and a
   webfont that is slow or blocked renders as a page in the
   wrong typeface with no error anywhere.
   ============================================================ */

:root{
  --soil:#151210;        /* the ground the page is cut out of */
  --soil2:#0F0C0A;       /* deeper — the footer well */
  --panel:#1E1A16;       /* a raised surface */
  --panel2:#262119;
  --hair:#342C25;        /* the hairline that does the raising */
  --hair2:#493E35;
  --txt:#EFE7E0;
  --dim:#B4A69B;
  --dimmer:#9A8C82;

  --locate:#00843D;      /* APWA sewer green — FILL, carries white at 4.81:1 */
  --locate-hi:#12A855;   /* pressed / hover — carries near-black at 6.27:1 */
  --locate-tx:#3FD07A;   /* locate green AS TYPE on the soil ground, 9.34:1 */
  --locate-dk:#0A4D26;   /* the border under a green fill */

  --clear:#C4B9AE;       /* sound pipe, nothing found. Neutral on purpose —
                            green is the locate colour now and may not mean two things. */
  --water:#5E9DBA;       /* standing water, in the drawings only */

  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"DejaVu Sans Mono","Liberation Mono",monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;

  --ease:cubic-bezier(.2,.7,.3,1);
  --t:170ms var(--ease);
  --lip:rgba(255,236,224,.055);
  --sill:rgba(0,0,0,.45);
  --raise:inset 0 1px 0 var(--lip), inset 0 -1px 0 var(--sill);
}

*,*::before,*::after{box-sizing:border-box}
/* .chatbox and #bookmsg are toggled with the hidden attribute, and several
   rules below set display — which would otherwise beat the UA hidden rule. */
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%;overflow-x:hidden}
body{
  margin:0;
  background-color:var(--soil);
  /* ⭐ THE GROUND HAS STRATA. A horizontal band every 34px, very faint, so the
     page reads as a cut face of earth rather than a flat dark fill. It is not
     a grid: the bands are uneven on purpose, the way soil is. */
  background-image:
    repeating-linear-gradient(to bottom,
      rgba(255,226,200,.022) 0 1px,
      transparent 1px 13px,
      rgba(255,226,200,.012) 13px 14px,
      transparent 14px 34px);
  color:var(--txt);
  font-family:var(--sans);
  font-size:100%;
  line-height:1.62;
  overflow-x:hidden;
}
img,svg{max-width:100%}
a{color:var(--locate-tx);text-underline-offset:3px;transition:color var(--t)}
a:hover{color:#FFA3C6}
h1,h2,h3,h4{margin:0 0 .5em;line-height:1.14;font-weight:800;letter-spacing:-.028em}
h1{font-size:clamp(1.875rem,5.7vw,3.125rem)}
h2{font-size:clamp(1.375rem,3.5vw,2rem)}
h3{font-size:clamp(1.0625rem,2.2vw,1.25rem);font-weight:750;letter-spacing:-.018em}
h4{font-size:1rem}
p{margin:0 0 1.05em}
ul,ol{margin:0 0 1.1em;padding-left:1.15em}
li{margin:0 0 .45em}
hr{border:0;border-top:1px solid var(--hair);margin:30px 0}
:focus-visible{outline:2px solid var(--locate-hi);outline-offset:2px}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

.wrap{width:min(1140px,calc(100% - 32px));margin-inline:auto}
.narrow{width:min(740px,100%)}
.sec{padding:58px 0;position:relative;z-index:1}
.sec--tight{padding:32px 0}

/* the eyebrow. One class, one hue, currentColor, so the variants carry their
   own tick without a second rule for the tick. */
.eyebrow{
  position:relative;display:block;margin:0 0 11px;padding-left:18px;
  font-family:var(--mono);font-size:0.6875rem;letter-spacing:.17em;text-transform:uppercase;
  color:var(--dimmer);
}
.eyebrow::before{
  content:"";position:absolute;left:0;top:.62em;width:12px;height:1px;
  background:currentColor;opacity:.9;
}
.eyebrow--locate{color:var(--locate-tx)}
.eyebrow--clear{color:var(--clear)}

/* ============================================================
   DEMONSTRATION STRIP
   ⛔ Required, verbatim class name, first thing on EVERY page.
   These files are reachable on their own, outside the viewer
   that frames them, and standing alone this site reads as a
   real Casselberry drain company with a phone number on it. So
   it says what it is before anything else, every time.
   ============================================================ */
.demostrip{
  display:flex;gap:10px;align-items:flex-start;
  background:linear-gradient(180deg,#2A0E1B,#1C0A12);
  border-bottom:1px solid var(--locate-dk);
  color:#F6D3E0;
  font-family:var(--mono);font-size:0.7812rem;line-height:1.5;
  padding:9px max(16px,calc((100% - 1140px)/2));
  position:relative;z-index:2;
}
.demostrip b{color:#FFE2EC;font-weight:700}
.demostrip a{color:var(--locate-tx);font-weight:700;white-space:nowrap}
.demostrip__dot{
  flex:0 0 auto;width:8px;height:8px;margin-top:6px;border-radius:50%;
  background:var(--locate);box-shadow:0 0 0 3px rgba(0,132,61,.2);
}
@media(prefers-reduced-motion:no-preference){
  .demostrip__dot{animation:locpulse 2.8s ease-in-out infinite}
  @keyframes locpulse{0%,100%{opacity:1}50%{opacity:.4}}
}

/* ---------- header ---------- */
.top{
  background:var(--panel);border-bottom:1px solid var(--hair2);
  background-image:linear-gradient(180deg,rgba(255,232,214,.045),rgba(0,0,0,.18));
  box-shadow:0 1px 0 rgba(0,132,61,.22),0 6px 22px rgba(0,0,0,.45);
  position:relative;z-index:2;
}
.top__in{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:12px 0}
.brand{display:flex;align-items:center;gap:11px;text-decoration:none;color:inherit;margin-right:auto}
.brand__mark{
  flex:0 0 auto;width:38px;height:38px;border:1px solid var(--hair2);background:var(--soil);
  display:grid;place-items:center;border-radius:3px;color:var(--locate-tx);
  transition:border-color var(--t),color var(--t);
}
.brand__mark svg{display:block;width:24px;height:24px}
.brand:hover .brand__mark{border-color:var(--locate-dk);color:var(--locate-hi)}
.brand__nm{display:block;font-weight:800;font-size:0.9688rem;letter-spacing:-.02em;line-height:1.2}
.brand__sub{display:block;font-family:var(--mono);font-size:0.6562rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--dimmer);line-height:1.6}
.top__act{display:flex;align-items:center;gap:10px}
.tel{
  font-family:var(--mono);font-size:0.875rem;font-weight:700;letter-spacing:.02em;
  color:var(--txt);text-decoration:none;border:1px solid var(--hair2);
  padding:8px 12px;border-radius:3px;background:var(--panel2);white-space:nowrap;
  box-shadow:var(--raise);transition:border-color var(--t),color var(--t);
}
.tel:hover{border-color:var(--locate);color:#FFF}
.burger{display:none;background:var(--panel2);border:1px solid var(--hair2);border-radius:3px;
  width:40px;height:38px;cursor:pointer;padding:0;color:var(--txt)}
.burger i,.burger i::before,.burger i::after{display:block;height:1.5px;width:18px;background:currentColor;
  margin-inline:auto;position:relative}
.burger i::before{content:"";position:absolute;top:-6px}
.burger i::after{content:"";position:absolute;top:6px}

.nav{width:100%;border-top:1px solid var(--hair);margin-top:2px}
.nav ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap}
.nav a{
  display:block;text-decoration:none;color:var(--dim);
  font-family:var(--mono);font-size:0.8125rem;letter-spacing:.1em;text-transform:uppercase;
  padding:11px 13px 10px;border-bottom:2px solid transparent;
  transition:color var(--t),background-color var(--t),border-color var(--t);
}
.nav a:hover{color:var(--txt);background:var(--panel2)}
.nav a[aria-current="page"]{color:var(--locate-tx);border-bottom-color:var(--locate)}
@media(max-width:900px){
  .burger{display:block}
  .nav{display:none}
  .nav.open{display:block}
  .nav ul{flex-direction:column}
  .nav a{border-bottom:1px solid var(--hair);border-left:2px solid transparent;padding:12px 4px}
  .nav a[aria-current="page"]{border-bottom-color:var(--hair);border-left-color:var(--locate)}
}

/* ---------- the offer strip ----------
   ⭐ What the customer actually gets, in this site's own sentence, directly
   under the nav on every page. ⛔ Never a price, a rating or a count: this
   company has published none of those and has no customers to count. */
.offerbar{
  border-bottom:1px solid var(--hair);
  background-image:linear-gradient(180deg,var(--panel2),var(--panel));
  box-shadow:inset 0 1px 0 var(--lip);
  position:relative;z-index:2;
}
.offerbar__in{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 0}
.offerbar__tag{
  font-family:var(--mono);font-size:0.6562rem;text-transform:uppercase;letter-spacing:.15em;
  color:var(--locate-tx);border:1px solid rgba(0,132,61,.5);border-radius:2px;
  padding:3px 9px;flex:0 0 auto;
}
.offerbar__msg{margin:0;font-size:0.8438rem;line-height:1.45;color:var(--dim);flex:1 1 340px;min-width:0}
.offerbar__msg b{color:var(--txt);font-weight:700}
.offerbar__go{
  flex:0 0 auto;font-family:var(--mono);font-size:0.7188rem;text-transform:uppercase;
  letter-spacing:.09em;color:var(--txt);text-decoration:none;white-space:nowrap;
  border-bottom:1px solid rgba(0,132,61,.6);padding-bottom:2px;
  transition:color var(--t),border-bottom-color var(--t);
}
.offerbar__go:hover{color:var(--locate-tx);border-bottom-color:var(--locate)}
@media(max-width:660px){
  .offerbar__go{display:none}          /* the hero button is immediately below */
  .offerbar__msg{font-size:0.7812rem}
}

/* ---------- hero ---------- */
.hero{padding:44px 0 10px}
.hero__grid{display:grid;gap:26px;grid-template-columns:1fr}
@media(min-width:1020px){.hero__grid{grid-template-columns:1.02fr .98fr;gap:32px;align-items:start}}
.hero h1{margin-top:8px}
/* ⭐ ONE ACCENTED PHRASE, and the trade-and-town half deliberately steps back
   so the hook is the only thing carrying colour or weight. */
.h1__where{display:block;font-weight:400;font-size:.52em;letter-spacing:-.01em;color:var(--dim);margin-bottom:.24em}
h1 .hl{font-style:normal;color:var(--locate-tx)}
.hero__lede{font-size:clamp(1.0312rem,2vw,1.1875rem);line-height:1.6;color:var(--dim);max-width:60ch}
.hero__lede b{color:var(--txt);font-weight:650}

/* ---------- drawn figures ---------- */
.figure{
  background:var(--panel);border:1px solid var(--hair);border-radius:2px;padding:14px;margin:0;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05) 0%,rgba(255,232,214,.012) 34%,rgba(0,0,0,.17) 100%);
  box-shadow:var(--raise);transition:border-color var(--t);
}
.figure:hover{border-color:var(--hair2)}
.figure svg{display:block;width:100%;height:auto}
.figure__cap{
  font-family:var(--mono);font-size:0.6562rem;letter-spacing:.06em;line-height:1.66;color:var(--dimmer);
  margin:10px 0 0;border-top:1px solid var(--hair);padding-top:9px;
}
.hero .figure{box-shadow:var(--raise),0 30px 48px rgba(0,0,0,.55),0 3px 10px rgba(0,0,0,.4)}

/* ============================================================
   🎥 THE CAMERA RUN — THE STRUCTURE OF THIS SITE.
   A ruled footage rail. Every observation is pinned at the foot
   mark it was found at, with the depth under it, reading DOWN
   the page the way the camera reads down the pipe.
   ⛔ This is not a timeline and it must not become one: the
   left column is a DISTANCE, not a clock, and the run log and
   the customer-day timeline further down the page are two
   different things that must stay visually distinct.
   ============================================================ */
.run{list-style:none;margin:26px 0 0;padding:0;position:relative}
.run__i{
  position:relative;display:grid;grid-template-columns:96px 1fr;gap:0 22px;
  padding:18px 0 18px 0;border-top:1px solid var(--hair);align-items:start;
}
.run__i:first-child{border-top:0}
/* the rail itself: a hairline down the gutter with a tick at every entry */
.run::before{
  content:"";position:absolute;left:96px;top:6px;bottom:6px;width:1px;background:var(--hair2);
}
.run__i::before{
  content:"";position:absolute;left:92px;top:26px;width:9px;height:1px;background:var(--hair2);
}
.run__i::after{
  content:"";position:absolute;left:93.5px;top:23px;width:6px;height:6px;border-radius:50%;
  background:var(--soil);border:1px solid var(--hair2);
}
.run__ft{
  font-family:var(--mono);text-align:right;padding-right:18px;display:block;
  line-height:1;color:var(--txt);
}
.run__ft b{display:block;font-size:clamp(1.5rem,4.2vw,1.875rem);font-weight:700;letter-spacing:-.045em;
  font-variant-numeric:tabular-nums}
.run__ft i{display:block;font-style:normal;font-size:0.625rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--dimmer);margin-top:5px}
.run__b{min-width:0}
.run__d{
  display:block;font-family:var(--mono);font-size:0.6562rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--dimmer);margin-bottom:6px;
}
.run__b h3{margin:0 0 5px;max-width:60ch}
.run__b p{margin:0;font-size:0.9062rem;line-height:1.62;color:var(--dim);max-width:64ch}
.run__tag{
  display:inline-block;margin-top:10px;padding:2px 8px;border:1px solid var(--hair2);border-radius:2px;
  font-family:var(--mono);font-size:0.5938rem;letter-spacing:.17em;text-transform:uppercase;color:var(--dim);
}
/* ⭐ SOUND PIPE IS NEUTRAL STONE AND A DEFECT IS LOCATE GREEN. Two states, and an
   entry that is neither keeps the neutral rail — most of a run IS neutral,
   and colouring every line would make the two that matter invisible. */
.run__i--clear .run__ft b{color:var(--clear)}
.run__i--clear::after{background:var(--clear);border-color:var(--clear)}
.run__i--clear .run__tag{color:var(--clear);border-color:rgba(196,185,174,.45)}
.run__i--fault .run__ft b{color:var(--locate-tx)}
.run__i--fault::after{background:var(--locate);border-color:var(--locate-hi);box-shadow:0 0 0 3px rgba(0,132,61,.18)}
.run__i--fault .run__tag{color:var(--locate-tx);border-color:rgba(0,132,61,.5);background:rgba(0,132,61,.09)}
.run__i--fault .run__b h3{color:#FFF}
.run__i--fault{
  background:linear-gradient(90deg,rgba(0,132,61,.085),rgba(0,132,61,0) 62%);
}
@media(max-width:620px){
  .run::before{left:0}
  .run__i{grid-template-columns:1fr;gap:8px;padding-left:20px}
  .run__i::before{left:0;width:9px;top:20px}
  .run__i::after{left:-3px;top:17px}
  .run__ft{text-align:left;padding-right:0;display:flex;align-items:baseline;gap:8px}
  .run__ft b{font-size:1.5rem}
  .run__ft i{margin-top:0}
  .run__i--fault{background:linear-gradient(90deg,rgba(0,132,61,.09),rgba(0,132,61,0) 86%)}
}

/* ============================================================
   THE TICKET — the document the customer keeps.
   The best line on it is the one where nothing was sold.
   ============================================================ */
.ticket{
  background:var(--panel);border:1px solid var(--hair2);border-radius:2px;overflow:hidden;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05) 0%,rgba(255,232,214,.012) 34%,rgba(0,0,0,.17) 100%);
  box-shadow:var(--raise),0 1px 0 rgba(0,0,0,.4);
}
.ticket__hd{
  display:flex;flex-wrap:wrap;gap:6px 18px;align-items:baseline;
  padding:12px 16px;background:var(--panel2);border-top:1px solid var(--locate-dk);
  border-bottom:1px solid var(--hair2);
  background-image:linear-gradient(180deg,rgba(255,232,214,.06),rgba(0,0,0,.14));
  font-family:var(--mono);font-size:0.6875rem;letter-spacing:.12em;text-transform:uppercase;color:var(--dimmer);
}
.ticket__no{color:var(--locate-tx);font-weight:700;letter-spacing:.1em}
.ticket__meta{
  padding:12px 16px;border-bottom:1px solid var(--hair);font-family:var(--mono);
  font-size:0.75rem;color:var(--dim);line-height:1.85;
}
.ticket__meta span{color:var(--txt)}
.ticket__meta b{color:var(--dimmer);font-weight:400}
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.rows{width:100%;border-collapse:collapse;font-size:0.875rem}
.rows th{
  text-align:left;font-family:var(--mono);font-size:0.625rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--dimmer);font-weight:400;padding:10px 16px;border-bottom:1px solid var(--hair);
}
.rows th:last-child,.rows td:last-child{text-align:right;white-space:nowrap}
.rows td{padding:12px 16px;border-bottom:1px solid var(--hair);vertical-align:top}
.rows td:last-child{font-family:var(--mono);font-weight:700;font-size:0.875rem}
.rows tr:last-child td{border-bottom:0}
.rows .desc b{color:#FFF;font-weight:700}
.rows .desc small{display:block;color:var(--dim);font-size:0.7812rem;line-height:1.56;margin-top:3px}
.rows tbody tr{transition:background-color var(--t)}
.rows tbody tr:not(.tot):not(.row--zero):hover{background:rgba(255,232,214,.028)}
.row--zero{background:linear-gradient(90deg,rgba(0,132,61,.15),rgba(0,132,61,.03))}
.row--zero td{border-top:1px solid rgba(0,132,61,.45);border-bottom:1px solid rgba(0,132,61,.45)}
.row--zero td:first-child{box-shadow:inset 3px 0 0 rgba(0,132,61,.8)}
.row--zero td:last-child{color:var(--locate-tx)}
.row--zero .desc b{color:var(--locate-tx);font-family:var(--mono);letter-spacing:.02em;font-size:0.8438rem}
.rows .tot td{background:var(--panel2);font-weight:700;
  background-image:linear-gradient(180deg,rgba(255,232,214,.05),rgba(0,0,0,.16))}
.rows .tot td:last-child{color:var(--txt);font-size:1rem}
.asleft{
  font-family:var(--mono);font-size:0.7188rem;line-height:1.9;color:var(--dim);
  padding:12px 16px;background:var(--soil);border-top:1px solid var(--hair);
  background-image:linear-gradient(180deg,rgba(0,0,0,.3),rgba(255,232,214,.018));
}
.asleft b{color:var(--clear);font-weight:700}

/* the declined line, lit from its own edge */
.declined{
  border:1px solid rgba(0,132,61,.45);border-left:3px solid var(--locate);
  background:rgba(0,132,61,.06);padding:18px 18px 4px;border-radius:2px;margin:0 0 22px;
  background-image:linear-gradient(174deg,rgba(0,132,61,.10) 0%,rgba(0,132,61,.02) 60%,rgba(0,0,0,.14) 100%);
  box-shadow:inset 3px 0 22px -8px rgba(0,132,61,.8),inset 0 1px 0 rgba(255,190,214,.09);
}
.declined__k{
  font-family:var(--mono);font-size:0.75rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--locate-tx);display:block;margin-bottom:9px;
}
.declined p:last-child{margin-bottom:1.05em}
.declined strong{color:#FFC3D9}

/* ---------- callouts and panels ---------- */
.note{
  border:1px solid var(--hair);border-left:3px solid var(--locate);background:var(--panel);
  padding:16px 18px 4px;border-radius:2px;margin:0 0 22px;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05),rgba(0,0,0,.16));
  box-shadow:var(--raise),inset 3px 0 14px -6px rgba(0,132,61,.5);
  transition:border-color var(--t);
}
.note--clear{border-left-color:var(--clear);box-shadow:var(--raise),inset 3px 0 14px -6px rgba(196,185,174,.5)}
.note--plain{border-left-color:var(--hair2);box-shadow:var(--raise)}
.note:hover{border-color:var(--hair2)}
.note h3{margin-bottom:.35em;font-size:1.0312rem;color:#FFF}
.note p:last-child{margin-bottom:1.05em}

.panel{
  background:var(--panel);border:1px solid var(--hair);border-radius:2px;padding:20px 20px 6px;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05),rgba(0,0,0,.16));
  box-shadow:var(--raise);transition:border-color var(--t);
}
.panel:hover{border-color:var(--hair2)}
.panel h3{color:#FFF}
.panel--well{background:var(--soil2);
  background-image:linear-gradient(176deg,rgba(255,232,214,.035),rgba(0,0,0,.3))}

.cols{display:grid;gap:22px;grid-template-columns:1fr}
@media(min-width:820px){
  .cols--2{grid-template-columns:1fr 1fr}
  .cols--3{grid-template-columns:repeat(3,1fr)}
  /* 🔴 RAGGED COLUMNS. Where one child of a two-column block runs far longer
     than the other, `align-items:start` strands the short one at the top with
     dead space under it. The short column TRAVELS instead — stretching an
     empty card only makes a taller empty card, and sticky keeps the argument
     beside the document it is arguing about.
     ⚠️ `align-self:start` is required with it: a stretched item has no free
     space to move in, and sticky then silently does nothing. */
  .cols--2>*{align-self:start}
  .cols--2>*:last-child{position:sticky;top:24px}
}

/* ============================================================
   THE SERVICE INDEX — a run sheet, not marketing cards. Each
   entry is a code, a name and the one sentence that says what
   it is actually for.
   ============================================================ */
.index{display:grid;gap:8px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
.entry{
  display:grid;grid-template-columns:auto 1fr;gap:0 13px;align-items:start;
  text-decoration:none;color:inherit;background:var(--panel);
  border:1px solid var(--hair);border-radius:2px;padding:14px 14px 15px;min-width:0;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05),rgba(0,0,0,.16));
  box-shadow:var(--raise);
  transition:border-color var(--t),background-color var(--t),transform var(--t),box-shadow var(--t);
}
.entry__ic{
  grid-row:1 / span 3;display:grid;place-items:center;width:38px;height:38px;padding:5px;
  border:1px solid var(--hair2);border-radius:3px;background:var(--soil);color:var(--locate-tx);
  background-image:linear-gradient(176deg,rgba(255,232,214,.05),rgba(0,0,0,.22));
  transition:color var(--t),border-color var(--t),box-shadow var(--t);
}
.entry__ic svg{display:block;width:100%;height:100%}
.entry__k{font-family:var(--mono);font-size:0.5938rem;letter-spacing:.17em;color:var(--dimmer);
  display:block;margin-bottom:4px}
.entry__n{display:block;font-weight:700;font-size:0.9062rem;line-height:1.3;letter-spacing:-.015em;color:var(--txt);
  transition:color var(--t)}
.entry__d{display:block;font-size:0.7812rem;line-height:1.45;color:var(--dim);margin-top:5px}
.entry:hover{border-color:var(--hair2);background-color:var(--panel2);transform:translateY(-2px);
  box-shadow:var(--raise),0 7px 20px rgba(0,0,0,.42)}
.entry:hover .entry__ic{border-color:var(--locate);box-shadow:0 0 14px rgba(0,132,61,.24)}
.entry:hover .entry__n{color:#FFF}
.entry:hover .entry__k{color:var(--locate-tx)}
.entry__k{transition:color var(--t)}

/* ---------- a plain data table ---------- */
.spec{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:0.7812rem;min-width:460px}
.spec th{text-align:left;padding:9px 12px;border-bottom:1px solid var(--hair2);color:var(--dimmer);
  font-weight:400;font-size:0.625rem;letter-spacing:.15em;text-transform:uppercase}
.spec td{padding:9px 12px;border-bottom:1px solid var(--hair);color:var(--txt)}
/* 🔴 NO `white-space:nowrap` HERE, AND IT WAS THERE FOR AN HOUR. These two
   classes were written for short values and then used on whole sentences — "a
   line with a collapse or hard bends in the way" — and nowrap made the three
   column table 931px wide inside a 740px measure. It did not break the page,
   because `.tablewrap` scrolls: it just quietly hid the third column behind a
   scrollbar on a 1280px screen with room to spare.
   ⛔ A table that CAN wrap should wrap. The overflow container is the fallback
   for a phone, not the layout. */
.spec td.ok{color:var(--clear);font-weight:700}
.spec td.no{color:var(--locate-tx);font-weight:700}
.spec tbody tr{transition:background-color var(--t)}
.spec tbody tr:hover{background:rgba(255,232,214,.03)}

/* ---------- numbered steps ---------- */
.steps{list-style:none;margin:22px 0 0;padding:0;counter-reset:s}
.steps>li{counter-increment:s;position:relative;padding:0 0 26px 58px;margin:0;border-left:1px solid var(--hair)}
.steps>li:last-child{border-left-color:transparent;padding-bottom:0}
.steps>li::before{
  content:"0" counter(s);position:absolute;left:-19px;top:0;width:38px;height:38px;
  display:grid;place-items:center;background:var(--panel2);border:1px solid var(--hair2);
  font-family:var(--mono);font-size:0.8125rem;font-weight:700;color:var(--locate-tx);border-radius:2px;
  box-shadow:var(--raise);
}
.steps h3{margin-bottom:.35em}
.steps p{color:var(--dim)}

.qa{border-top:1px solid var(--hair);padding:18px 0 2px}
.qa h3{font-size:1.0625rem;color:#FFF;transition:color var(--t)}
.qa:hover h3{color:var(--locate-tx)}
.qa p{color:var(--dim)}

.deflist{margin:0}
.deflist dt{font-family:var(--mono);font-size:0.6875rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--locate-tx);margin:18px 0 5px}
.deflist dt:first-child{margin-top:0}
.deflist dd{margin:0;color:var(--dim)}

/* ---------- buttons ---------- */
.btn{
  display:inline-block;text-decoration:none;font-family:var(--mono);font-size:0.75rem;
  letter-spacing:.14em;text-transform:uppercase;font-weight:700;
  padding:13px 20px;border-radius:2px;border:1px solid var(--locate-dk);
  background-color:var(--locate);color:#FFF;
  background-image:linear-gradient(180deg,rgba(255,226,238,.22),rgba(0,0,0,.18));
  box-shadow:inset 0 1px 0 rgba(255,226,238,.3),0 1px 0 rgba(0,0,0,.4);
  transition:background-color var(--t),border-color var(--t),color var(--t),
             transform var(--t),box-shadow var(--t);
}
.btn:hover{
  background-color:var(--locate-hi);border-color:var(--locate-hi);color:#170A10;transform:translateY(-2px);
  box-shadow:inset 0 1px 0 rgba(255,236,244,.42),0 8px 20px rgba(0,132,61,.3);
}
.btn:active{transform:translateY(0)}
.btnrow{display:flex;flex-wrap:wrap;gap:10px;margin:22px 0 6px}

.cta{
  position:relative;overflow:hidden;border:1px solid var(--hair2);background:var(--panel);
  border-radius:2px;padding:26px 22px 22px;
  background-image:linear-gradient(176deg,rgba(255,232,214,.05),rgba(0,0,0,.18));
  box-shadow:var(--raise);
}
.cta h2{margin-bottom:.35em}
.cta::before,.cta::after{
  content:"";position:absolute;width:22px;height:22px;pointer-events:none;
  border-color:var(--locate);opacity:.72;
}
.cta::before{top:9px;left:9px;border-top:1px solid;border-left:1px solid}
.cta::after{bottom:9px;right:9px;border-bottom:1px solid;border-right:1px solid}

/* ---------- form ---------- */
.form{display:grid;gap:14px;grid-template-columns:1fr}
@media(min-width:700px){.form{grid-template-columns:1fr 1fr}.form .full{grid-column:1 / -1}}
.field label{display:block;font-family:var(--mono);font-size:0.6562rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--dimmer);margin-bottom:6px}
.field input,.field select,.field textarea{
  width:100%;background:var(--soil);border:1px solid var(--hair2);color:var(--txt);
  font-family:var(--sans);font-size:0.9375rem;padding:11px 12px;border-radius:2px;
  transition:border-color var(--t),box-shadow var(--t);
}
.field textarea{min-height:120px;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{
  border-color:var(--locate-hi);outline:none;box-shadow:0 0 0 3px rgba(0,132,61,.18)}

/* ---------- the reviews empty state ---------- */
.empty{
  border:1px dashed var(--hair2);background:var(--panel);border-radius:2px;
  padding:38px 22px;text-align:center;
  background-image:linear-gradient(176deg,rgba(255,232,214,.04),rgba(0,0,0,.16));
}
.empty .eyebrow{padding-left:0;margin-bottom:14px}
.empty .eyebrow::before{display:none}
.empty p{max-width:58ch;margin-inline:auto;color:var(--dim)}

/* ---------- footer ---------- */
.foot{
  background:var(--soil2);border-top:1px solid var(--locate-dk);margin-top:40px;padding:36px 0 30px;
  box-shadow:inset 0 1px 0 rgba(0,132,61,.16);position:relative;z-index:1;
}
.foot__grid{display:grid;gap:26px;grid-template-columns:1fr}
@media(min-width:760px){.foot__grid{grid-template-columns:1.3fr 1fr 1fr}}
.foot h4{font-family:var(--mono);font-size:0.6562rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--dimmer);margin:0 0 12px;font-weight:400;letter-spacing:.16em}
.foot ul{list-style:none;margin:0;padding:0}
.foot li{margin:0 0 7px}
.foot a{color:var(--dim);text-decoration:none;font-size:0.875rem;transition:color var(--t),padding-left var(--t)}
.foot li a:hover{color:var(--locate-tx);padding-left:4px}
.foot__addr{font-family:var(--mono);font-size:0.7812rem;line-height:1.85;color:var(--dim)}
.foot__addr b{color:var(--txt)}
.foot__fine{border-top:1px solid var(--hair);margin-top:28px;padding-top:18px;
  font-family:var(--mono);font-size:0.6875rem;line-height:1.75;color:var(--dimmer)}
.foot__fine a{font-size:0.6875rem;color:var(--locate-tx)}

/* ============================================================
   CHAT LAUNCHER — pinned bottom-LEFT.
   ⛔ The page is displayed inside an iframe on another domain
   whose own launcher sits bottom-right. Left, always, or the
   two overlap and the visitor presses ours by accident.
   ============================================================ */
.launch{
  position:fixed;left:16px;bottom:16px;z-index:60;
  display:flex;align-items:center;gap:9px;cursor:pointer;
  background:var(--panel2);border:1px solid var(--hair2);border-radius:3px;
  color:var(--txt);font-family:var(--mono);font-size:0.7188rem;letter-spacing:.12em;
  text-transform:uppercase;font-weight:700;padding:10px 14px;
  box-shadow:var(--raise),0 6px 18px rgba(0,0,0,.5);
  transition:border-color var(--t),color var(--t),transform var(--t);
}
.launch:hover{border-color:var(--locate);color:#FFF;transform:translateY(-2px)}
/* the brand mark rides the launcher too — it is the same company */
.launch__mark{display:block;width:18px;height:18px;color:var(--locate-tx);flex:0 0 auto}
.launch__mark svg{display:block;width:100%;height:100%}
.chatbox{
  position:fixed;left:16px;bottom:16px;z-index:61;
  width:min(348px,calc(100vw - 32px));max-height:min(520px,calc(100vh - 32px));
  display:flex;flex-direction:column;
  background:var(--panel);border:1px solid var(--hair2);border-radius:3px;
  box-shadow:0 10px 40px rgba(0,0,0,.6);
}
.chatbox__hd{display:flex;align-items:center;gap:9px;padding:10px 12px;background:var(--panel2);
  border-bottom:1px solid var(--hair2)}
.chatbox__t{font-family:var(--mono);font-size:0.6875rem;letter-spacing:.13em;text-transform:uppercase;
  color:var(--dim);margin-right:auto}
.chatbox__x{background:none;border:1px solid var(--hair2);color:var(--dim);cursor:pointer;
  width:26px;height:26px;border-radius:2px;font-size:0.9375rem;line-height:1;padding:0;
  transition:color var(--t),border-color var(--t)}
.chatbox__x:hover{color:#FFF;border-color:var(--locate)}
.log{flex:1 1 auto;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:9px;min-height:150px}
.msg{max-width:88%;padding:9px 11px;border-radius:3px;font-size:0.8438rem;line-height:1.5}
.msg--bot{background:var(--panel2);border:1px solid var(--hair);align-self:flex-start;color:var(--txt)}
.msg--me{background:rgba(0,132,61,.16);border:1px solid rgba(0,132,61,.4);align-self:flex-end;color:#FFD4E3}
.opts{display:flex;flex-wrap:wrap;gap:7px;padding:0 12px 12px}
.chip{
  background:var(--soil);border:1px solid var(--hair2);color:var(--txt);cursor:pointer;
  font-family:var(--sans);font-size:0.7812rem;padding:8px 11px;border-radius:2px;text-align:left;
  transition:border-color var(--t),color var(--t),transform var(--t);
}
.chip:hover{border-color:var(--locate);color:#FFF;transform:translateY(-1px)}
.chatbox__fine{font-family:var(--mono);font-size:0.5938rem;letter-spacing:.08em;color:var(--dimmer);
  padding:8px 12px;border-top:1px solid var(--hair);line-height:1.5}

/* ============================================================
   🕓 THE DAY, AS THE CUSTOMER HAS IT
   ⭐ The run log above is OUR fact. What a person actually buys
   is a Saturday that ends with the line open and the reason
   flagged in the lawn, so this block tells the same job from
   the householder's side.
   ⛔ It must not be confused with the run: the run is measured
   in FEET and is ruled; this is measured in CLOCK TIME and is
   flush. 08:40 is the REFUSAL and 11:20 is the marked beat —
   the moment the headline comes true.
   ⛔ Nothing here books, holds or confirms a time.
   ============================================================ */
.day{list-style:none;margin:24px 0 0;padding:0;display:grid;gap:0}
.day__i{
  display:grid;grid-template-columns:112px 1fr;gap:0 20px;
  padding:16px 0;border-top:1px solid var(--hair);align-items:start;
}
.day__i:first-child{border-top:0}
.day__t{
  font-family:var(--mono);font-size:0.7188rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--locate-tx);padding-top:3px;white-space:nowrap;font-variant-numeric:tabular-nums;
}
.day__i h3{margin:0 0 5px;font-size:clamp(1.0312rem,2.2vw,1.1875rem);line-height:1.25;color:var(--txt);max-width:62ch}
.day__i p{margin:0;font-size:0.9062rem;line-height:1.62;color:var(--dim);max-width:64ch}
.day__i--live .day__t::after{
  content:"";display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--locate-tx);margin-left:7px;vertical-align:middle;
}
/* 🔴 THE THREE-CHILD GRID BUG. `.day__i` holds THREE children — the timestamp,
   the h3 and the p — in a TWO-column grid, so auto-placement drops the
   paragraph back into column one at the width of a timestamp, rendering it in
   a 110px gutter and making the row four times as tall as it should be. Both
   text children are pinned to column two.
   ⛔ And it is reset inside the single-column breakpoint, or the `2` invents an
   implicit second column on a phone. */
.day__i>h3,.day__i>p{grid-column:2}
.day__foot{
  margin:22px 0 0;padding-top:16px;border-top:1px solid var(--hair);
  font-size:0.9062rem;line-height:1.66;color:var(--dim);max-width:68ch;
}
.day__foot b{color:var(--txt);font-weight:700}
@media(max-width:560px){
  .day__i{grid-template-columns:1fr;gap:4px}
  .day__t{padding-top:0}
  .day__i>h3,.day__i>p{grid-column:1}
}

/* ============================================================
   DESIGNED IMAGE SLOT
   A photograph belongs here and none has been taken, because
   this company is invented. So the slot is DRAWN — the right
   aspect ratio, line art in this site's own palette, and a
   caption naming the photograph that goes here. It must never
   be mistaken for a photograph and never read as broken. The
   class names are stable: a real photo drops straight in.
   ============================================================ */
.shot{
  position:relative;border:1px solid var(--hair2);border-radius:2px;overflow:hidden;
  background-color:var(--panel);margin:0;box-shadow:var(--raise);
  transition:border-color var(--t);
}
.shot:hover{border-color:var(--locate-dk)}
.shot__frame{
  display:block;position:relative;width:100%;
  background:linear-gradient(163deg,#241D18 0%,#171210 48%,#0C0907 100%);
  border-bottom:1px solid var(--hair);
}
.shot__frame svg{display:block;width:100%;height:auto}
.shot__badge{
  position:absolute;top:9px;left:9px;z-index:2;
  font-family:var(--mono);font-size:0.5312rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--locate-tx);background:rgba(12,9,7,.88);border:1px solid var(--locate-dk);
  padding:3px 7px;border-radius:2px;
}
.shot__cap{
  font-family:var(--mono);font-size:0.6562rem;letter-spacing:.06em;line-height:1.66;
  color:var(--dimmer);margin:0;padding:10px 13px 11px;
}
.shot__cap b{color:var(--dim);font-weight:400}

/* ============================================================
   MOTION
   ⭐ EVERYTHING BELOW IS AN ENHANCEMENT OF A PAGE THAT IS
   ALREADY COMPLETE AND CORRECT with scripting and animation
   both switched off. `.rv` and `.rvs` carry no styles of their
   own; delete the observer in the inline script and every
   element is already in the state the animation ends on.
   ⛔ Nothing here hides content waiting for a script.
   ============================================================ */
@media(prefers-reduced-motion:no-preference){
  @keyframes rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
  .rv.is-in{animation:rise .56s var(--ease) both}
  @keyframes stagger{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
  .rvs.is-in>*{animation:stagger .48s var(--ease) both}
  .rvs.is-in>*:nth-child(1){animation-delay:0s}
  .rvs.is-in>*:nth-child(2){animation-delay:.045s}
  .rvs.is-in>*:nth-child(3){animation-delay:.09s}
  .rvs.is-in>*:nth-child(4){animation-delay:.135s}
  .rvs.is-in>*:nth-child(5){animation-delay:.18s}
  .rvs.is-in>*:nth-child(6){animation-delay:.225s}
  .rvs.is-in>*:nth-child(7){animation-delay:.27s}
  .rvs.is-in>*:nth-child(8){animation-delay:.315s}
  .rvs.is-in>*:nth-child(n+9){animation-delay:.36s}

  /* the fault marker on the profile drawing breathes. Slow. Never a flash. */
  @keyframes dyeglow{
    0%,100%{opacity:.55}
    50%    {opacity:1}
  }
  .faultpin{animation:dyeglow 5.6s ease-in-out infinite}
}
@media(prefers-reduced-motion:reduce){
  /* sticky is motion-adjacent for anyone who finds movement uncomfortable, and
     it has no purpose at one column wide anyway. */
  .cols--2>*:last-child{position:static}
  /* ⭐ AND THE HOVER LIFTS GO TOO. The keyframe animations above are already
     gated by the no-preference block, but a `transform` on :hover is movement
     as well, and it is the movement a person is most often pointing at when
     they turn this setting on. The colour and border changes stay, so nothing
     loses its hover state — it just stops moving. */
  .btn:hover,.entry:hover,.chip:hover,.launch:hover{transform:none}
}

/* ---------- narrow screens ---------- */
@media(max-width:430px){
  .sec{padding:44px 0}
  .cta::before,.cta::after{width:16px;height:16px}
  .index{grid-template-columns:1fr}
}

/* 🔴 AND THE GHOST IS RESTATED AFTER THE PRIMARY, LAST IN THE FILE.
   This stylesheet is UNLAYERED, so a `.btn` rule appended below a
   `.btn--ghost` rule wins at equal specificity and takes both its colour and
   its gradient — and the two buttons render as one solid block. Two primaries
   is no primary. The secondary is therefore declared here, after the last
   `.btn{` in the file.
   ⛔ Any future `.btn` rule appended below this point must restate it again. */
.btn--ghost{
  background-color:transparent;color:var(--txt);border-color:var(--hair2);
  background-image:linear-gradient(180deg,rgba(255,232,214,.05),rgba(0,0,0,.16));
  box-shadow:var(--raise);
}
.btn--ghost:hover{
  background-color:var(--panel2);color:#FFF;border-color:var(--locate-hi);
  box-shadow:var(--raise),0 8px 20px rgba(0,0,0,.45);
}

/* ── 📷 PHOTOGRAPHS ───────────────────────────────────────────────────────
   🔴 11 Sep 2026. This site shipped with none, and every figure on it was a
   line drawing. Owner: "you went back to diagram stick figures that is far
   from good" and "need photos!!". He is right — a real drain company's site is
   photographs of pipe, and a page of drawings reads as a prototype.

   ⚠️ ONE THING HAD TO GO WITH THEM. Three captions said, in so many words,
   "there are no photographs on this site". That was TRUE when it was written
   and it is a lie the moment an <img> lands, so the claim was removed rather
   than left to rot. What stays true is the demo banner: the company is
   invented, and these are stock photographs of the trade, not of its work.

   ⛔ Every CC BY / CC BY-SA frame carries its credit IN the frame. Attribution
   is a licence term, not a courtesy, and "we'll add them later" is how a
   licence gets breached. */
.photo{position:relative;margin:0;line-height:0;border-radius:4px;overflow:hidden;
  border:1px solid var(--hair);background:var(--panel)}
.photo img{width:100%;display:block;object-fit:cover;
  filter:saturate(1.03) contrast(1.03)}
.photo--hero img{height:clamp(260px,34vw,430px)}
.photo--band img{height:clamp(190px,26vw,320px)}
.photo--tall img{height:clamp(240px,30vw,380px)}
/* the ground is warm and near-black; a cold photo sitting straight on it floats.
   A soil-tinted wash along the bottom sets it down and gives the credit a bed. */
.photo::after{content:"";position:absolute;inset:auto 0 0 0;height:38%;pointer-events:none;
  background:linear-gradient(to bottom,rgba(21,18,16,0),rgba(21,18,16,.62))}
.photo__cr{position:absolute;right:9px;bottom:8px;z-index:2;
  font-family:var(--mono);font-size:0.5938rem;letter-spacing:.06em;line-height:1.45;
  color:rgba(255,255,255,.72);background:rgba(0,0,0,.42);padding:3px 7px;border-radius:2px}
.photo__cr a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.photo__cap{line-height:1.55;font-size:0.7812rem;color:var(--dimmer);padding-top:9px}
.photo__cap b{color:var(--dim);font-weight:650}

/* ── 🚪 THE WAY OUT STAYS ON SCREEN ───────────────────────────────────────
   🔴 11 Sep 2026, owner: "need to be able and x out of all the demo sites if
   you view on mobile dont let people get stuck somewher with out easy exit."

   This banner is the only route from a demo back to us, and it sat at the top
   of the document as a static block. On a phone, two swipes down a service page
   and it is gone — a visitor who arrived on a shared link has no way back and
   no reason to think one exists. ⚠️ The link was always there and was always
   unreachable, which is the same shape as the embed's close button being parked
   off-screen above the iframe.

   ⭐ Sticky on phones only. On a desktop the banner is in view at the top of a
   page people can see all of, and pinning it would just spend vertical room. */
@media (max-width: 640px) {
  .demostrip {
    position: sticky;
    top: 0;
    z-index: 90;
  }
  /* ⚠️ AND THE LINK HAS TO BE HITTABLE, NOT MERELY PRESENT. It is inline text
     inside a sentence, so on a 375px screen it wrapped into two fragments 15px
     tall — measured, both genuinely tappable, and both far under the 44px a
     thumb needs. ⭐ Make it a control: one line, real padding, no wrap. */
  .demostrip a {
    display: inline-block;
    margin-top: 4px;
    padding: 11px 12px;
    white-space: nowrap;
    font-weight: 700;
  }
}


/* 🔴 INSIDE OUR OWN FRAME THE BANNER IS DUPLICATE — 11 Sep 2026. Owner, looking
   at the embed on the home page: "take all of this off this is awfull they know
   that". He is right about that surface: our own chrome prints a disclosure
   directly under the frame, in our voice, so the demo's banner made two stacked
   notices — which reads as nervous rather than as honest.
   ⛔ A STANDALONE PAGE KEEPS IT. There the banner is the ONLY notice, and these
   pages carry a live telephone line: an invented business with a working number
   and nothing saying so is a trap, not a demo.
   ⚠️ Fails in the honest direction — with JavaScript off, nothing is hidden.
   ⛔ If the note under the frame is ever removed, delete this rule first. */
html[data-embedded] .demostrip { display: none !important; }


/* ── 🧭 THE UTILITY BAR — 15 Sep 2026 ─────────────────────────────────
   Written by tools/demo-utilbar.mjs. See that file for why the disclosure
   banner became a chip and why hours are absent on the demos that publish none.
   ⛔ Self-contained colour on purpose: every demo means something different by
   --ink, and borrowing it painted this a different thing on each site. */
.utilbar{background:#0f1b2a;color:rgba(255,255,255,.92);
  border-bottom:1px solid rgba(255,255,255,.14);font-size:.78rem;line-height:1.4;
  font-family:system-ui,-apple-system,"Segoe UI",sans-serif}
.utilbar__in{display:flex;align-items:center;justify-content:space-between;
  gap:.9rem;min-height:38px;padding:.3rem 20px;flex-wrap:wrap;
  max-width:1180px;margin:0 auto}
.utilbar__hours{margin:0;display:flex;align-items:center;gap:.45rem;min-width:0}
.utilbar__hours b{font-weight:600;color:#fff}
.utilbar__sep{opacity:.45}
.utilbar__hours>span:last-child{opacity:.72}
.utilbar__right{display:flex;align-items:center;gap:.9rem;flex-wrap:wrap;min-width:0}
.utilbar__lnk{color:rgba(255,255,255,.88);text-decoration:none;
  border-bottom:1px solid rgba(255,255,255,.26);padding-bottom:1px}
.utilbar__lnk:hover{color:#fff;border-bottom-color:#fff}
/* ⭐ colour is never the only carrier — the words say Open or Closed outright */
.utilbar__dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:#7fe0a8}
.utilbar__hours[data-state="open"] .utilbar__dot{background:#7fe0a8;animation:utilpulse 3s ease-out infinite}
.utilbar__hours[data-state="soon"] .utilbar__dot{background:#ffc861}
.utilbar__hours[data-state="shut"] .utilbar__dot{background:#9aa6b1}
@keyframes utilpulse{0%{box-shadow:0 0 0 0 rgba(127,224,168,.5)}
  70%{box-shadow:0 0 0 6px rgba(127,224,168,0)}100%{box-shadow:0 0 0 0 rgba(127,224,168,0)}}
@media (prefers-reduced-motion:reduce){.utilbar__dot{animation:none !important}}
.utilbar .demostrip{display:inline-flex;align-items:center;gap:.4rem;
  padding:.16rem .55rem;border-radius:999px;background:rgba(255,255,255,.10);
  border:1px solid rgba(255,255,255,.22);font-size:.72rem;color:#fff;
  text-decoration:none;cursor:pointer}
.utilbar .demostrip:hover{background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.38)}
.utilbar .demostrip b{text-transform:uppercase;letter-spacing:.08em;font-size:.68rem;font-weight:700}
.utilbar .demostrip__dot{width:7px;height:7px;border-radius:50%;background:#ff6a55;display:block}
@media (max-width:860px){
  /* ⛔ the TAIL hides, never the notice — and the chip becomes the link to it */
  .utilbar__demotail{display:none}
  .utilbar__in{gap:.55rem}
  .utilbar__right{gap:.7rem}
  .utilbar .demostrip{min-height:30px}
}
@media (max-width:560px){
  .utilbar{font-size:.72rem}
  .utilbar__hours>span:last-child,.utilbar__sep{display:none}
  .utilbar .demostrip{min-height:34px;padding-inline:.7rem}
}
/* ⛔ Inside our own iframe the surrounding chrome carries the disclosure, so the
   chip is hidden there and ONLY there. A standalone page always keeps it. */
html[data-embedded] .utilbar .demostrip{display:none !important}

/* 🔴 NO STATUS LIGHT WITHOUT A STATUS — 15 Sep 2026. Six of these demos publish
   no hours at all, and one publishes "by appointment". The dot is a status
   light: green, beside a company name with nothing behind it, it silently says
   OPEN NOW about a business whose hours nobody ever wrote. It only appears once
   the script has decided a real state from real published hours.
   ⚠️ This shipped for one sync because the fix was written into a script that
   died on an assertion BEFORE the file was saved — the edit was reported and
   never landed. Verify the artefact, not the log line. */
.utilbar__hours:not([data-state]) .utilbar__dot{display:none}


/* ── 👥 THE SOCIAL MARKS — 15 Sep 2026. Owner: "put fake no link social on the
   headers", after twice being told there is nowhere honest to point them.
   ⛔ THEY ARE NOT LINKS, AND THAT IS THE POINT. These companies are invented,
   so a social icon could only lead to an invented profile, to somebody else's
   real account, or to nothing — and a control that goes nowhere is worse than
   no control. They are `<span>` marks, `aria-hidden`, not focusable and not
   clickable: an illustration of where a real client's profiles sit in the bar,
   on a page that already says it is a demonstration.
   ⛔ Do not turn these into <a> on a real build without real profiles behind
   them. Bots On Staff's own Facebook and Instagram have never published once. */
.utilbar__social{display:inline-flex;align-items:center;gap:.55rem;opacity:.62}
.utilbar__social svg{width:15px;height:15px;display:block}
@media (max-width:560px){ .utilbar__social{display:none} }


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

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


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

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


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

@media (min-width:901px){
  header .brand{flex:0 1 auto;min-width:0}
  header nav.nav{flex:1 1 auto;min-width:0}
  header .top__act{flex:0 0 auto}
}



/* ============================================================
   HAND-EDITED, 23 SEP 2026 - AND DELIBERATELY DIVERGED
   ------------------------------------------------------------
   The header of this site says it is generated from a source folder outside
   this repository. IT IS NO LONGER, AND IT MUST NOT BE REGENERATED. Those
   sources are untracked and unbacked, and at least one of them provably does
   not reproduce its own published copy - a rebuild loses photo bands, fonts
   and the town. public/demo is in git, so THIS file is the source of truth
   for this site now. Do not run tools/sync-demo.mjs or any build.py here.

   WHAT CHANGED
   1. EVERY font-size in this file was px and is now rem. A reader who has
      set a larger default text size in their browser got nothing from this
      site - which is WCAG 2.2 SC 1.4.4 (Resize Text), and it is also just
      the wrong way round: a size a reader can scale belongs in rem. The
      body no longer pins itself to 16px either; it takes 100% of whatever
      the reader asked for.
   2. The hero lost its 88-word single-paragraph lede and both of its
      eyebrows, and gained the refusal. A hero is one thing.
   3. Six <img> on this site declared 1800x1200 while the files on disk were
      1800x1350, 1800x1201 or 1800x1012. Those attributes are an ASPECT
      RATIO, not a size, and a wrong one is worse than none: the browser
      reserves the wrong box and the page jumps as each photograph lands.
   ============================================================ */

/* THE HERO HEADLINE IS A RATIO, NOT A NUMBER. Against the refusal's .8125rem
   this is 3.2x at 320px and 4.8x at 1440 - the same design at both ends.
   A heading that is 3x the body at one width and 1.9x at another is two
   different designs wearing one class name. */
/* ⚠️ `.hero--home` is GONE. The home hero is a full-bleed `.lanaihero` now
   and this rule sized a class no page carries. `.hero` is still the page
   head on fifteen inner pages, so nothing here may size a bare `.hero h1`. */

/* THE REFUSAL, in the hero. The whole site is a depth-stamped run log, so the
   refusal is set as a line lifted straight off the log: its own rules above
   and below, the figure larger than anything but the headline, and one
   sentence of reason that names the measurement it rests on - the open joint
   at 47.5 ft, which a second contractor can put his own camera on and either
   agree or disagree with. That is what makes it uncopyable. */
.declined-lead{
  margin:20px 0 0;padding:14px 0;max-width:34rem;
  border-top:2px solid var(--locate-tx);
  border-bottom:2px solid var(--locate-tx);
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 18px;
  align-items:baseline;
}
.declined-lead__k{
  grid-column:1 / -1;margin:0;font-family:var(--mono);font-size:0.6875rem;
  letter-spacing:.19em;text-transform:uppercase;color:var(--locate-tx);
}
.declined-lead__v{
  grid-column:1;margin:0;font-family:var(--mono);font-weight:700;
  font-size:clamp(1.9rem,1.1rem + 2.6vw,2.6rem);line-height:1;
  letter-spacing:-.03em;color:var(--txt);
}
.declined-lead__why{
  grid-column:2;margin:0;font-size:0.8125rem;line-height:1.5;color:var(--txt);
  max-width:34ch;
}
@media(max-width:560px){
  .declined-lead{grid-template-columns:minmax(0,1fr)}
  .declined-lead__v,.declined-lead__why{grid-column:1}
}


/* ============================================================
   THE FULL-PICTURE HERO - 23 Sep 2026
   Owner: "all need full picture hero done prodfessionaly".
   The photograph is the frame now: edge to edge, no card, no shadow, no
   margin. A picture inside a rounded box with a drop shadow is a picture OF
   a photograph; this is the photograph.
   AND IT IS NOT THE OTHER THREE DEMOS. They centre their type in the frame.
   This one sets it at GRADE - along the bottom edge - because the site is a
   section through a front lawn and everything it sells is under the line the
   words sit on. That also leaves the top half of the trench at full
   brightness, which is the point of putting a photograph there at all.
   ⚠️ The vertical stops are percentages, which ux-build warns about: a
   gradient percentage resolves against the ELEMENT, not the viewport, so on a
   hero taller than the screen a stop at 58% aims below the fold. It is safe
   HERE and only here because this hero is capped at 700px - about one screen.
   ⛔ If you raise that cap, convert the vertical stops to px first.
   ============================================================ */
.lanaihero{position:relative;isolation:isolate;display:flex;flex-direction:column;
  justify-content:flex-end;min-height:clamp(420px,72vh,700px);
  overflow:hidden;background:#0E0B09}
.lanaihero>img{position:absolute;inset:0;z-index:0;width:100%;height:100%;
  object-fit:cover;object-position:center 44%}
.lanaihero__scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
  /* 🔴 RE-TUNED AGAINST A MEASUREMENT, not a feeling. The first version of this
     scrim composited to band luma 43.4 against a floor of 60 and picture side
     43.2 against 80 - a dark rectangle that happened to contain a photograph.
     ⛔ The last stop is .00 and that number is the picture. If a contrast
     reading fails, fix the foreground or the near stops. Never the far end.
     ⚠️ 0deg/90deg and % stops only: tools/hero-photo-contrast.js and
     public/_luma.js both parse this rule and refuse any other angle. */
  background:
    linear-gradient(0deg,  rgba(14,11,9,.84) 0%,  rgba(14,11,9,.62) 20%,
                           rgba(14,11,9,.24) 42%, rgba(14,11,9,.04) 68%,
                           rgba(14,11,9,.00) 100%),
    linear-gradient(90deg, rgba(14,11,9,.72) 0%,  rgba(14,11,9,.42) 40%,
                           rgba(14,11,9,.12) 66%, rgba(14,11,9,.02) 84%,
                           rgba(14,11,9,.00) 100%)}
.lanaihero__in{position:relative;z-index:2;
  width:min(1140px,calc(100% - 32px));align-self:center;
  padding-block:0 clamp(26px,5vw,54px)}

/* THE HEADLINE IS A RATIO, NOT A NUMBER. Against the refusal's .8125rem this
   is 3.2x at 320px and 4.8x at 1440 - the same design at both ends. */
.lanaihero h1{font-size:clamp(2.6rem,1.3rem + 5.6vw,3.9rem);line-height:1.04;
  margin:0 0 .2em;max-width:19ch;color:#FFF;text-wrap:balance}
.lanaihero .h1__where{color:rgba(255,255,255,.90)}
.lanaihero h1 .hl{color:var(--locate-hi)}
/* Over a photograph there is no secondary text: full ink and a shadow that
   travels with the glyphs. */
.lanaihero :is(h1,.declined-lead__k,.declined-lead__v,.declined-lead__why){
  text-shadow:0 1px 2px rgba(0,0,0,.72), 0 2px 22px rgba(0,0,0,.55)}
.lanaihero .declined-lead{max-width:36rem}
.lanaihero .declined-lead__v,.lanaihero .declined-lead__why{color:#FFF}
.lanaihero .btnrow{margin-top:20px}

/* A WIDE PHOTOGRAPH WHERE THE DRAWING USED TO BE. Same measure as the run log
   under it, no card, one hairline, caption in the page's own mono. */
.shotwide{margin:22px 0 26px}
.shotwide img{display:block;width:100%;height:auto;aspect-ratio:3/2;
  object-fit:cover;border-radius:2px}
.shotwide__cap{margin-top:9px;font-family:var(--mono);font-size:0.6875rem;
  letter-spacing:.06em;line-height:1.66;color:var(--dimmer);max-width:74ch;
  border-top:1px solid var(--hair);padding-top:8px}


/* 🔴 THE ACCENT ITALIC IS THE ONE GLYPH SET THAT IS NEITHER WHITE NOR
   LARGE-AND-BOLD, and it is always at the END of a line - which is where a
   left-weighted pool is weakest. Measured on glyph pixels over the new
   photograph it came back at 1.02:1 against a floor of 3, and the little mono
   label above the refusal at 3.36 against 4.5. Nothing had changed about the
   colour; the backdrop under it had gone from near-black soil to a daylit
   sewer dig.
   ⛔ That is one selector over two different backdrops, which is a coin toss.
   --locate-tx (#3FD07A) is 9.34:1 on the page's own ground and is still right
   THERE. Over the photograph it needs a high-luminance sibling instead, so
   the green survives as an accent without pretending a mid-tone can sit on a
   white truck. Resolve by specificity, never by :not(). */
.lanaihero{--locate-ph:#D7F7E4}
.lanaihero h1 .hl{color:var(--locate-ph)}
.lanaihero .declined-lead__k{color:var(--locate-ph)}
.lanaihero .declined-lead{border-color:var(--locate-ph)}


/* ============================================================
   THE MARK — A WORDMARK ON A GRADE RULE. NO ICON. 23 Sep 2026.
   Owner: "the logos can be way more creative".
   🔴 Every demo on this estate wore one idea — a small monoline pictogram in
   a 24-28px viewBox, currentColor, no fill — and this site's was a circle
   inside a circle inside a bordered square. Thirteen trades, one mark.
   ⭐ This site has NO pictogram now. The type is the logo: the name in two
   weights, and under it a 2px APWA locate-green rule the width of the
   wordmark with the town beneath. That rule is grade. Everything this company
   sells is under it, which is the whole idea of the site and is not an idea
   any of the other twelve marks could borrow.
   ⛔ The green is --locate (#00843D), the fill green, not the type green. It
   is a graphic element here, not small type on a photograph.
   ⚠️ A type lockup is wider than a pictogram and the header row wraps. The
   wordmark is capped and the sub line is the first thing to go at 30rem.
   ============================================================ */
.brand--word{display:grid;gap:3px;align-content:center;text-decoration:none;
  color:inherit;margin-right:auto;max-width:15rem}
.brand--word .brand__nm{display:flex;align-items:baseline;gap:.5em;
  font-family:var(--sans);font-weight:800;font-size:1.0625rem;
  letter-spacing:-.035em;line-height:1;color:var(--txt);white-space:nowrap}
.brand--word .brand__nm em{font-style:normal;font-family:var(--mono);
  font-weight:400;font-size:.5em;letter-spacing:.2em;text-transform:uppercase;
  color:var(--locate-tx)}
.brand__markrule{display:block;height:2px;background:var(--locate);
  border-radius:1px;transition:background .18s ease}
.brand--word:hover .brand__markrule{background:var(--locate-hi)}
.brand--word .brand__sub{display:block;font-family:var(--mono);
  font-size:0.5625rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--dimmer);white-space:nowrap}
@media (max-width:560px){
  .brand--word{max-width:12rem}
  .brand--word .brand__nm{font-size:.9375rem}
  .brand--word .brand__sub{display:none}
}


/* 🔴 A PHONE IS NOT A NARROW DESKTOP. Measured at 390px the base scrim left
   three nodes of the headline under their floors, because the hero is shorter
   there and the type occupies a much larger share of the frame than it does
   at 1280. The pool has to be deeper and reach further up.
   ⚠️ This costs picture-side luma at phone width and that is the right trade:
   a contrast floor is a requirement and a luma floor is a target. The number
   is reported rather than hidden. */
@media (max-width:560px){
  .lanaihero{min-height:clamp(380px,66vh,560px)}
  .lanaihero__scrim{background:
    linear-gradient(0deg,  rgba(14,11,9,.92) 0%,  rgba(14,11,9,.84) 34%,
                           rgba(14,11,9,.46) 62%, rgba(14,11,9,.12) 88%,
                           rgba(14,11,9,.02) 100%),
    linear-gradient(90deg, rgba(14,11,9,.26) 0%,  rgba(14,11,9,.06) 100%)}
}


/* ============================================================
   GLASS CONTROLS — 23 Sep 2026. Owner: "use glass buttons all the newest
   elements".
   🔴 A glass button on a photographic hero has its label contrast decided by
   the PHOTOGRAPH. This hero now stands on a daylit sewer dig at 128 mean, and
   a white label on a translucent green sitting over a white vacuum truck is
   exactly the case that fails.
   ⛔ SO THE BASE DECLARATION CARRIES THE CONTRAST WITH NO BLUR. The
   @supports block adds blur and saturation and changes no alpha.
   backdrop-filter is unsupported or switched off for real readers; a fill
   that only clears 4.5:1 once blurred does not clear 4.5:1.
   ⛔ FORM: A LOCATE FLAG. Green glass with a bright green 2px bottom edge —
   the stripe a locator sprays on a lawn — at the same 2px radius the rest of
   this site uses. It is not the other three: auto-repair is square glass,
   pool-service a capsule, pressure-washing a weighted badge edge.
   ⚠️ THE GRADIENT HAS TO GO. The old button carried
   `background-image:linear-gradient(...)` over its fill; a gradient on top of
   glass is an opaque lid on the thing that is supposed to be transparent.
   ⛔ AND THIS FILE IS UNLAYERED — a `.btn{` rule appended below a
   `.btn--ghost{` rule wins at equal specificity and both buttons render as
   one solid block. Two primaries is no primary. The ghost is therefore
   RESTATED at the bottom of this block, after the last `.btn{`, and
   check-demo-polish refuses the build if that order is ever broken.
   ============================================================ */
.btn{
  /* 🔴 --locate-glass, NOT --locate. Measured over the WORST WINDOW a button
     this size can occupy on this hero — the white flank of the vacuum truck,
     which is where the photograph crops to at another viewport — white on 86%
     of #00843D came back at 3.95 against a floor of 4.5. It read 5.63 where
     the button happens to sit at 1280, and "it passes where it happens to
     sit" is not a property of the design.
     ⭐ A quarter-stop darker green at 88% reads 6.4 and shows MORE of the
     photograph through it than 93% of the lighter green would have. Darkening
     the tint buys translucency; raising the alpha spends it. */
  --locate-glass:#05672F;
  background-color:color-mix(in srgb, var(--locate-glass) 88%, transparent);
  background-image:none;
  border:1px solid color-mix(in srgb, #FFF 28%, transparent);
  border-bottom:2px solid var(--locate-hi);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.26);
  color:#FFF}
.btn:hover{
  background-color:color-mix(in srgb, var(--locate-hi) 92%, transparent);
  border-color:color-mix(in srgb, #FFF 48%, transparent);
  border-bottom-color:#FFF; color:#FFF;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.38)}
@supports ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){
  .btn,.launch{
    -webkit-backdrop-filter:blur(13px) saturate(1.22);
    backdrop-filter:blur(13px) saturate(1.22)}
}
.btn:focus-visible,.launch:focus-visible{
  outline:2px solid color-mix(in srgb, var(--locate-tx) 80%, #FFF);
  outline-offset:3px}
/* ⛔ RESTATED BELOW THE LAST `.btn{`. Do not move it above one. */
.btn--ghost{
  background-color:rgba(14,11,9,.56);
  background-image:none;
  color:var(--txt);
  border:1px solid rgba(255,255,255,.34);
  border-bottom:2px solid rgba(255,255,255,.44);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.14)}
.btn--ghost:hover{
  background-color:rgba(14,11,9,.74); color:#FFF;
  border-color:rgba(255,255,255,.55); border-bottom-color:var(--locate-tx)}
.lanaihero .btn--ghost{color:#FFF}


/* ============================================================
   THE SHEEN — A SHAPE BOUND TO THE TYPE, NOT A SHEET OVER THE PICTURE
   23 Sep 2026. Owner: "i think the picture should be full hero then shene out
   where you want the text", and "evry hero needs to be perfecto".
   🔴 WHAT WAS WRONG WITH THE SCRIM, and the tell was in my own numbers. A
   scrim on the SECTION is one sheet doing two jobs: it darkens the words and
   it darkens the two thirds of the frame that carry no words at all. That is
   exactly why the band luma and the picture-side luma had to be chased
   against each other, and why at phone width the picture side fell to 51-69
   against a target of 80 — the wash covered everything, so the only way to
   keep the type legible was to dim the photograph.
   ⭐ THE DARKENING IS LOCAL NOW. It lives on the ::before of the block that
   holds the type, so it moves with the words at every width and at every one
   of the four compositions. type sits AT GRADE across a wide measure, so the sheen is wide and shallow
   ⛔ SOFT EDGES ONLY. The ellipse is sized 50% 50% of its own box, so it
   reaches alpha 0 exactly at that box's edge — there is no step anywhere, and
   no boundary a reader could see. A visible edge where a sheen stops is worse
   than the flat wash it replaced.
   ⛔ AND THE FLOOR DOES NOT MOVE: 4.5:1 on every run of type, 3:1 on the
   display, measured over the composited result at the brightest region the
   type actually occupies. A local sheen has less area to work in, so it is
   harder than a sheet, not easier.
   ⚠️ What is left on .lanaihero__scrim is an EDGE FLOOR only — it keeps the
   extreme top and near edges from clipping against the header and the page
   ground. It is not doing the reading work any more.
   ============================================================ */
.lanaihero__scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(0deg,  rgba(14,11,9,.24) 0%, rgba(14,11,9,.07) 14%,
                           rgba(14,11,9,.00) 32%, rgba(14,11,9,.00) 100%),
    linear-gradient(90deg, rgba(14,11,9,.12) 0%, rgba(14,11,9,.03) 26%,
                           rgba(14,11,9,.00) 54%, rgba(14,11,9,.00) 100%)}
.lanaihero .sheen{position:relative;display:grid;max-width:46rem;z-index:0}
.lanaihero .sheen::before{content:"";position:absolute;z-index:-1;pointer-events:none;
  top:-10rem;bottom:-11rem;left:-13rem;right:-12rem;
  /* 🔴 THE DIRECTIONAL LAYER HAD TO GO, AND THE NUMBER THAT KILLED IT WAS
     0.60 OF ALPHA IN ONE PIXEL. A 90deg linear varies only with x, so it
     ends abruptly at the top and bottom of its own box — on three of these
     four heroes that edge happened to fall off-frame and nobody would have
     seen it; on drain-cleaning it landed at y=109 as a hard horizontal line
     across the whole picture. ⛔ It also darkened the frame ABOVE the type
     just as much as the type itself, which is the wash it was meant to
     replace. An ellipse falls to zero in every direction; a linear does not.
     ⭐ So it is one ellipse, with a FLAT CORE wide enough that the block's
     own corners — where a headline's last line ends — are still inside it,
     and a long tail so the steepest step is under 0.004 of alpha per pixel.
     The inflation is what buys that: the box is the type block plus enough
     margin that its corners sit at about t=0.83 of the ellipse, not 0.90. */
  /* 🔴 THE RADII MUST FIT THE BOX FOR THE CENTRE YOU CHOSE. `50% 50% at 48%
     54%` pushes the ellipse 4% past the box's far side, so it is CUT there
     rather than fading out — alpha stepped from .30 to 0 in one pixel and
     the edge check caught it at 0.0124/px. ⛔ radius = min(centre, 1-centre)
     on each axis, or the shape you drew is not the shape that paints. */
  background:radial-gradient(48% 46% at 48% 54%,
    rgba(14,11,9,.88) 0%, rgba(14,11,9,.88) 68%, rgba(14,11,9,.68) 82%,
    rgba(14,11,9,.30) 92%, rgba(14,11,9,.08) 97%, rgba(14,11,9,.00) 100%)}
@media (max-width:640px){
  /* ⚠️ A PHONE IS NOT A NARROW DESKTOP. The block is most of the frame here,
     so the inflation that keeps the falloff soft has to shrink with it or the
     sheen simply becomes the wash again. */
  /* ⚠️ MEASURED AT 320: the type block is ~83% of the frame here, so a sheen
     that spills 5rem past it in every direction covers the photograph
     entirely — picture-outside fell to 3% of the frame. A local sheen
     cannot give much back when the type IS the frame, but the falloff
     belongs inside the viewport rather than off the edge of it. */
  /* 🔴 AT 320 THE TYPE IS THE FRAME, AND THAT IS THE WHOLE FINDING. The block
     here is ~280x500 of a 320x605 hero. An ellipse inscribed in a box can never
     reach that box's CORNERS — measured, the headline's top corner sits at
     t=1.05, outside the shape entirely — so tightening the inflation to return
     picture took the h1 from 3.21 to 2.33 and then to 1.19 against a floor of 3.
     ⛔ THE FLOOR DOES NOT MOVE, so the inflation stays wide enough that the
     falloff happens off-frame and the corners stay in the core.
     ⚠️ THE COST, PRINTED RATHER THAN HIDDEN: at 320 only about 3% of the frame
     is outside the sheen. A sheen bound to the type cannot give the picture
     back when the type occupies 83% of the frame — that is a property of the
     content at that width, not of the technique, and the honest lever for it
     is a shorter headline or a taller hero, neither of which is mine to take.
     From 641px up the ellipse does exactly what it was built to do: 32-47% of
     the frame sits outside the sheen at its own photographic luminance. */
  .lanaihero .sheen::before{top:-5rem;bottom:-5rem;left:-5rem;right:-5rem;
    /* 🔴 THE PHONE BRANCH HAD FORGOTTEN THE COMPOSITION. All four shared
       `at 50% 50%` here, which is right for the one hero whose type is
       centred and wrong for the three that anchor top or bottom — and it
       only showed at ONE width: pool-service passed at 320 (3.34) and at
       390, and failed at 375 (2.32 against 3), because that is where its
       hero grew tall enough to push the headline into the ellipse's top
       corner. ⛔ A sheen bound to the type has to follow the type at
       every width, not just the ones that were spot-checked.
       ⚠️ radius = min(centre, 1-centre) per axis, or the shape is cut. */
    /* 🔴 AND THE ANCHOR IS THE BLOCK'S POSITION IN THE FRAME, NOT THE TYPE'S
       POSITION IN THE BLOCK. I moved this ellipse to 58% because the
       composition sets its type AT GRADE — and at 375 the headline went to
       1.47 against a floor of 3, visibly sitting on bright sky, because the
       h1 is at the TOP of a tall block that is itself at the bottom of the
       frame. ⛔ Centre the sheen on the block's own mass; the frame anchor
       is already expressed by where the block is. Back to 50%. */
    background:radial-gradient(50% 50% at 50% 50%,
      rgba(14,11,9,.91) 0%, rgba(14,11,9,.91) 74%, rgba(14,11,9,.70) 85%,
      rgba(14,11,9,.30) 93%, rgba(14,11,9,.08) 97%, rgba(14,11,9,.00) 100%)}
}

/* 🔴 THE TABLET GAP — MEASURED, ATTEMPTED, AND REVERTED. At 741 the desktop
   sheen leaves only 1% of the frame outside it on auto-repair against 32% at
   1280, because the inflation is 12-14rem a side and that is the whole frame
   at 741. I capped the margin in vw to get the picture back: picture-outside
   went to 20-31% and the HEADLINE fell to 2.17 and 1.63 against a floor of 3,
   with pressure-washing's refusal reason at 2.80 against 4.5.
   ⛔ THE FLOOR DOES NOT MOVE, so the vw cap is gone and the rem inflation
   stands. ⭐ THE REASON IS GEOMETRY, NOT TUNING: an ellipse inscribed in a box
   cannot cover that box's corners, so the margin has to be about 0.37 of the
   block on each axis to keep a headline's last line inside the core — roughly
   240px for a 660px measure. At 741 the block is already the full column, so
   block + margin exceeds the frame however the margin is written.
   ⚠️ SO THE HONEST NUMBER IS PRINTED INSTEAD: 641-1023px returns 1-18% of the
   frame to the photograph, and 1024 and up returns 32-47%. Below 641 it is
   3-6%. A sheen bound to the type gives the picture back exactly in proportion
   to how much of the frame the type is NOT using. */
