/* ==========================================================================
   Tampa Trucks Unlimited
   One stylesheet. Every selector appears exactly ONCE; a duplicate lets the
   last one win silently and that is the dominant bug class in a hand-written
   sheet. Before editing a rule: grep -n '^\.name{' site.css
   ========================================================================== */

/* ---------- tokens ------------------------------------------------------ */
:root{--eyebrow:var(--accent-ink);--focus:var(--accent-ink);--dk-1:#E8EDF1;--dk-2:#C6CDD4;--dk-3:#A9B3BC;
  /* The shop's own palette: tire black, bone, and the lime of the bay door
     (median of the door pixels in IMG_7131/7262/8324). The lime NEVER carries
     body text on a light ground (2.3:1 on paper). It fills, rules, and marks
     state; --accent-ink is its text-safe partner, 5.9:1 on paper and 5.4:1 on
     paper-2. */
  --ink:#14171A;
  --ink-2:#1D2227;
  --ink-3:#2A3137;
  --paper:#F5F4F1;
  --paper-2:#EBE9E4;
  --accent:#91AE37;
  --accent-ink:#4D660F;
  --steel:#4A545E;
  --line:#D7D4CD;
  --line-dk:#333B42;

  --f-display:'Archivo','Archivo Fallback',system-ui,sans-serif;
  --f-body:'IBM Plex Sans','IBM Plex Sans Fallback',system-ui,sans-serif;
  --f-mono:'IBM Plex Mono','IBM Plex Mono Fallback',ui-monospace,monospace;

  /* a 6-step ramp, nothing between the steps */
  --t-xs:.875rem;
  --t-sm:1rem;
  --t-md:1.125rem;
  --t-lg:clamp(1.35rem,1.1rem + .9vw,1.75rem);
  --t-xl:clamp(1.9rem,1.4rem + 2.1vw,3rem);
  --t-2xl:clamp(2.6rem,1.7rem + 3.9vw,5rem);

  --wrap:1240px;
  --nav-h:64px;
  --util-h:34px;
  --r:4px;                       /* one radius. this is a workshop, not a spa */
  --ease:cubic-bezier(.22,.61,.36,1);
  --ghost:var(--ink);
  --ghost-ink:var(--paper);
}

/* ---------- reset ------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;
  scroll-padding-top:calc(var(--nav-h) + 18px)}
html{scrollbar-gutter:stable}  /* reserve the scrollbar gutter always, or the
   viewport widens while the preloader locks scrolling and every line re-wraps
   the instant the curtain lifts. Measured: 15px of shift. */

body{
  margin:0;
  /* clip, never hidden: hidden makes body a scroll container and kills
     position:sticky on iOS Safari while looking perfect in desktop Chrome */
  overflow-x:clip;
  overflow-y:visible;
  /* INK, not paper. iOS rubber-band overscroll paints the body background, and
     both ends of this page are dark (hero at the top, footer at the bottom), so
     a paper body flashed a light band past the footer that reads exactly like
     scrolling off the end of the page. The paper lives on <main> instead. */
  background:var(--ink);
  color:var(--ink);
  font:400 var(--t-md)/1.65 var(--f-body);
  font-synthesis-weight:none;
  -webkit-font-smoothing:antialiased;
}
img,svg,video{display:block;max-width:100%}
img{height:auto}
/* Global size for the inline Phosphor icons. Without this the only sizing rule
   was scoped to .btn, so every icon outside a button rendered at its intrinsic
   size and blew the phone pill up to three lines. */
svg.ic{width:19px;height:19px;flex:none}
picture{display:contents}
a{color:inherit}
button,input,select,textarea{font:inherit;color:inherit}
h1,h2,h3,h4{margin:0;font-family:var(--f-display);font-weight:700;line-height:1.04;letter-spacing:-.02em}
p,ul,ol{margin:0}
/* The focus ring, like the eyebrow, reads its colour from the surface: the lime is
   7.1:1 on ink and 2.3:1 on paper, so paper gets the ink green (5.9:1). */
:focus-visible{outline:3px solid var(--focus);outline-offset:3px;border-radius:2px}
::selection{background:var(--accent);color:var(--ink)}

/* ---------- layout ------------------------------------------------------ */
.wrap{width:min(100% - 2.5rem,var(--wrap));margin-inline:auto}
.wrap-n{width:min(100% - 2.5rem,940px);margin-inline:auto}
main{position:relative;background:var(--paper)}
main > section{position:relative}          /* NO z-index: keeps the stack sane */
.band{padding-block:clamp(3.75rem,7vw,7rem)}
.band-t{padding-block:clamp(5rem,10vw,9.5rem)}   /* tall */
.band-s{padding-block:clamp(2.75rem,4.5vw,4rem)} /* short */
.dark{background:var(--ink);color:var(--paper);--ghost:var(--paper);--ghost-ink:var(--ink)}
.dark,.hero,.phero,.film,.hero-card,.sheet,.map-p,.stat,.statband,.co-tag,.pre{--eyebrow:var(--accent);--focus:var(--accent)}
.tint{background:var(--paper-2)}
/* The eyebrow reads its colour from a token the SURFACE sets, the same way the
   ghost button already does. The old rule defaulted to --accent-ink
   (for light grounds) and only .dark overrode it, so every dark surface that is
   not literally .dark got the light-ground token: the hero eyebrow measured
   3.11:1, under the floor. A surface that sets a dark background now has to
   state the colours it overrides, rather than each text style guessing. */
.eyebrow{
  font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.10em;text-transform:uppercase;
  color:var(--eyebrow);display:flex;align-items:center;gap:.6rem;margin-bottom:1rem;
}
.eyebrow::before{content:"";width:26px;height:2px;background:currentColor;flex:none}
.lead{font-size:var(--t-lg);line-height:1.45;color:var(--steel);max-width:62ch}
.dark .lead{color:var(--dk-2)}
.h-xl{font-size:var(--t-2xl)}
.h-lg{font-size:var(--t-xl)}
.h-md{font-size:var(--t-lg)}
.muted{color:var(--steel)}
.dark .muted{color:var(--dk-3)}
.center{text-align:center}
.mt-s{margin-top:1rem}
.mt-m{margin-top:1.75rem}
.mt-l{margin-top:3rem}

/* ---------- buttons ----------------------------------------------------- */
/* .btn sets background/font/appearance explicitly because it lands on real
   <button> elements too, which would otherwise pick up the UA buttonface fill
   and ship white-on-white. */
.btn{
  -webkit-appearance:none;appearance:none;
  display:inline-flex;align-items:center;justify-content:center;gap:.55rem;
  min-height:48px;padding:.8rem 1.5rem;border:2px solid transparent;border-radius:var(--r);
  background:var(--accent);color:var(--ink);
  font:700 1rem/1 var(--f-display);letter-spacing:.01em;text-decoration:none;
  cursor:pointer;position:relative;
  transition:background .18s var(--ease),transform .1s var(--ease),color .18s var(--ease),filter .18s var(--ease);
}
.btn:hover{filter:brightness(1.09)}
.btn:active{transform:translateY(1px)}
.btn-out{background:transparent;color:var(--ghost);border-color:currentColor}
.btn-out:hover{background:var(--ghost);color:var(--ghost-ink)}
.btn-dk{background:var(--ink);color:var(--paper)}
.btn-dk:hover{background:var(--ink-3)}
.btn-row{display:flex;flex-wrap:wrap;gap:.85rem}
.btn .ic{width:19px;height:19px;flex:none}
/* Its own element. .prog carries the scroll position and is rewritten on the
   next scroll frame, so a loading width set on it is wiped immediately. */
.navload{position:fixed;inset:0 0 auto;height:3px;z-index:130;pointer-events:none;
  opacity:0;transition:opacity .2s ease}
.navload i{display:block;height:100%;width:100%;background:var(--accent);
  transform:scaleX(0);transform-origin:left}
.navload.on{opacity:1}
.navload.on i{animation:navload 8s cubic-bezier(.12,.72,.26,1) forwards}
@keyframes navload{
  0%{transform:scaleX(0)} 12%{transform:scaleX(.32)} 40%{transform:scaleX(.62)}
  75%{transform:scaleX(.84)} 100%{transform:scaleX(.94)}
}
@media (prefers-reduced-motion:reduce){.navload.on i{animation:none;transform:scaleX(.6)}}

a[data-busy]:not(.btn){opacity:.55;pointer-events:none}
.btn[data-busy]{pointer-events:none;color:transparent}
.btn[data-busy]::after{
  content:"";position:absolute;inset:0;margin:auto;width:19px;height:19px;
  border:2.5px solid rgba(20,23,26,.28);border-top-color:var(--ink);border-radius:50%;
  animation:spin .62s linear infinite;
}
.btn-out[data-busy]::after,.btn-dk[data-busy]::after{border-color:rgba(245,244,241,.3);border-top-color:var(--paper)}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- the build sheet (the site's signature device) --------------- */
/* A workshop job card: mono rows, hairline rules, a chamfered corner. Used for
   the hero spec, service specs and the gallery captions. */
.sheet{
  background:var(--ink-2);color:var(--paper);border:1px solid var(--line-dk);
  clip-path:polygon(0 0,calc(100% - 18px) 0,100% 18px,100% 100%,0 100%);
  padding:1.15rem 1.35rem;
}
.sheet-h{
  font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent);padding-bottom:.7rem;border-bottom:1px solid var(--line-dk);
}
.sheet dl{display:grid;grid-template-columns:auto 1fr;gap:.55rem 1.25rem;margin:.85rem 0 0;
  font-family:var(--f-mono);font-size:var(--t-sm)}
.sheet dt{color:var(--dk-3);font-size:var(--t-xs);letter-spacing:.06em;text-transform:uppercase;align-self:center}
.sheet dd{margin:0;text-align:right;font-weight:500}

/* ---------- header ------------------------------------------------------ */
.hdr{
  position:fixed;inset:0 0 auto;z-index:60;
  transition:background .3s var(--ease),box-shadow .3s var(--ease);
}
.hdr::before{      /* local pool: solves nav contrast without washing the photo */
  content:"";position:absolute;inset:0 0 -72px;pointer-events:none;
  background:linear-gradient(to bottom,rgba(10,12,14,.80) 0,rgba(10,12,14,.74) 58%,rgba(10,12,14,0));
  transition:opacity .3s var(--ease);
}
.hdr.solid{background:var(--ink);box-shadow:0 1px 0 var(--line-dk)}
.hdr.solid::before{opacity:0}
.hdr > *{position:relative}
.util{
  display:grid;grid-template-rows:1fr;
  transition:grid-template-rows .3s var(--ease),opacity .25s var(--ease);
  color:var(--dk-2);font-family:var(--f-mono);font-size:var(--t-xs);letter-spacing:.03em;
}
.hdr.solid .util{grid-template-rows:0fr;opacity:0}
.util-in{overflow:hidden;min-height:0}
.util-row{display:flex;align-items:center;justify-content:flex-end;gap:1.5rem;height:var(--util-h)}
.util-row > span,.util a{display:inline-flex;align-items:center;gap:.5rem}
.util a{text-decoration:none}
.util a:hover{color:var(--accent)}
.util .sep{opacity:.4}
.m-act{display:none}   /* the mobile menu's action block; only exists inside the open panel */
.nav{display:flex;align-items:center;gap:1.5rem;height:var(--nav-h);
  transition:height .3s var(--ease)}
.hdr.solid .nav{height:72px}
.brand{display:flex;align-items:center;gap:.7rem;text-decoration:none;color:var(--paper);margin-right:auto}
.brand svg{width:38px;height:38px;flex:none}
.brand b{font:800 1.16rem/1 var(--f-display);letter-spacing:-.015em;display:block;color:var(--paper)}
.brand i{font:600 var(--t-xs)/1 var(--f-mono);font-style:normal;letter-spacing:.11em;text-transform:uppercase;
  color:var(--accent);display:block;margin-top:4px;white-space:nowrap}
/* Over a photograph the lime measured 1.43:1 at 390 (mid-grey concrete under the
   crop). The transparent header reads its sub-label from the surface: paper. */
.hdr:not(.solid) .brand i{color:var(--dk-1)}

.menu{display:flex;align-items:center;gap:.25rem;list-style:none;margin:0;padding:0}
.menu > li{position:relative}
.menu > li > a{
  display:flex;align-items:center;gap:.3rem;padding:.7rem .78rem;min-height:44px;
  text-decoration:none;color:var(--paper);font:600 .945rem/1 var(--f-body);white-space:nowrap;
}
.menu > li > a::after{     /* growing underline, works on both header states */
  content:"";position:absolute;left:.78rem;right:.78rem;bottom:.42rem;height:2px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform .26s var(--ease);
}
.menu > li > a:hover::after,.menu > li > a[aria-current]::after,.menu > li.open > a::after{transform:scaleX(1)}
.menu .cv{width:11px;height:11px;transition:transform .24s var(--ease)}
.menu > li.open .cv{transform:rotate(180deg)}
.sub{
  position:absolute;top:calc(100% - 2px);left:0;min-width:242px;
  background:var(--ink-2);border:1px solid var(--line-dk);border-radius:var(--r);
  list-style:none;margin:0;padding:.4rem;
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .2s var(--ease),transform .2s var(--ease),visibility .2s;
  box-shadow:0 20px 44px rgba(0,0,0,.42);
}
.menu > li:hover .sub,.menu > li.open .sub{opacity:1;visibility:visible;transform:none}
.sub a{display:block;padding:.62rem .8rem;min-height:44px;text-decoration:none;
  color:var(--dk-1);font-size:.945rem;border-radius:3px;line-height:1.5;
  transition:background .16s var(--ease),color .16s var(--ease)}
.sub a:hover{background:var(--ink-3);color:var(--accent)}
.nav-cta{display:flex;align-items:center;gap:.6rem}
.phpill{
  display:inline-flex;align-items:center;justify-content:center;min-height:44px;min-width:44px;padding:.6rem .7rem;
  border:2px solid rgba(245,244,241,.34);border-radius:var(--r);text-decoration:none;
  color:var(--paper);font:700 .95rem/1 var(--f-mono);letter-spacing:.01em;white-space:nowrap;
  transition:border-color .18s var(--ease),background .18s var(--ease);
}
.phpill:hover{border-color:var(--accent);background:rgba(145,174,55,.12)}
.burger{display:none;width:46px;height:46px;padding:0;border:0;background:transparent;cursor:pointer;
  position:relative;border-radius:var(--r)}
.burger i{position:absolute;left:11px;width:24px;height:2.5px;background:var(--paper);border-radius:2px;
  transition:transform .28s var(--ease),opacity .2s var(--ease),background .2s var(--ease)}
.burger i:nth-child(1){top:15px}
.burger i:nth-child(2){top:22px}
.burger i:nth-child(3){top:29px}
.burger[aria-expanded="true"] i:nth-child(1){transform:translateY(7px) rotate(45deg)}
.burger[aria-expanded="true"] i:nth-child(2){opacity:0}
.burger[aria-expanded="true"] i:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

/* ---------- scroll progress + back to top ------------------------------- */
.prog{position:fixed;top:0;left:0;height:3px;background:var(--accent);z-index:80;width:0}
/* no transition on width: site.js already sets it once per scroll frame, and a
   width transition on top of that is a second layout stream for nothing */
.totop{
  position:fixed;right:clamp(1rem,3vw,2rem);bottom:clamp(1rem,3vw,2rem);z-index:55;
  width:52px;height:52px;border:0;border-radius:50%;background:var(--ink);color:var(--paper);
  cursor:pointer;display:grid;place-items:center;
  opacity:0;visibility:hidden;transform:translateY(10px);
  transition:opacity .25s var(--ease),transform .25s var(--ease),visibility .25s,background .18s var(--ease);
  box-shadow:0 10px 30px rgba(0,0,0,.28);
}
.totop.on{opacity:1;visibility:visible;transform:none}
.totop:hover{background:var(--accent);color:var(--ink)}
.totop svg{width:21px;height:21px}
.totop .ring{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg)}
.totop .ring circle{fill:none;stroke:var(--accent);stroke-width:2.5;
  stroke-dasharray:0 999;transition:stroke-dasharray .08s linear}

/* ---------- hero -------------------------------------------------------- */
.hero{position:relative;z-index:2;min-height:min(92vh,860px);min-height:min(92dvh,860px);display:flex;align-items:flex-end;
  --ghost:var(--paper);--ghost-ink:var(--ink);
  padding-top:calc(var(--nav-h) + var(--util-h) + 2rem);padding-bottom:clamp(2rem,5vw,4.5rem);
  overflow:hidden;background:var(--ink)}
.hero-media{position:absolute;inset:0;z-index:0}
.hero-media img{width:100%;height:100%;object-fit:cover;object-position:50% 46%}
.hero-media::after{content:"";position:absolute;inset:0;background:rgba(10,12,14,.18)}
.hero .wrap{position:relative;z-index:2;width:min(100% - 2.5rem,var(--wrap))}
/* The card sits RIGHT so the truck's front three quarter, which is the whole
   reason this photograph was chosen, is not hidden behind it. DOM order keeps
   the h1 first for reading order; only the visual order swaps. */
.hero-grid{display:grid;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(1.5rem,3vw,3rem);align-items:end}
.hero .sheet{order:1;align-self:end;max-width:440px}
/* The headline sits on an opaque plate, not a scrim. A white truck on bright
   asphalt needs 0.64 flat scrim to reach 4.5:1, which destroys the photograph.
   An opaque plate is 100% contrast control and costs the photo nothing. */
.hero-card{order:2;background:rgba(20,23,26,.94);border:1px solid var(--line-dk);
  clip-path:polygon(0 0,calc(100% - 26px) 0,100% 26px,100% 100%,0 100%);
  padding:clamp(1.6rem,3vw,2.6rem);color:var(--paper);backdrop-filter:blur(3px)}
.hero-card h1{font-size:clamp(2.1rem,1.3rem + 2.5vw,3.45rem);font-weight:800;letter-spacing:-.03em}
.hero-card h1 em{font-style:normal;color:var(--accent);display:block}
.hero-sub{margin-top:1rem;font-size:var(--t-md);color:var(--dk-2);max-width:46ch}

/* hero spec callouts: his own numbers for this exact truck, on opaque tags so
   contrast never depends on what is behind them */
.callouts{position:absolute;inset:0;z-index:1;pointer-events:none}
.co{position:absolute;pointer-events:auto;display:flex;align-items:center;gap:0;
  transition:opacity .5s var(--ease),transform .5s var(--ease)}
.js .co{opacity:0;transform:translateY(6px)}
.js .co.in{opacity:1;transform:none}
.co-dot{width:13px;height:13px;border-radius:50%;background:var(--accent);flex:none;
  border:2.5px solid var(--ink);position:relative}
.co-line{height:2px;background:var(--accent);width:56px;flex:none}
.co-tag{background:var(--ink);color:var(--paper);border:1px solid var(--accent);
  padding:.42rem .7rem;font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.04em;white-space:nowrap;
  transition:background .18s var(--ease),color .18s var(--ease)}
.co-tag b{color:var(--accent);font-weight:600}
.co:hover .co-tag,.co:focus-within .co-tag{background:var(--accent);color:var(--ink)}
.co:hover .co-tag b,.co:focus-within .co-tag b{color:var(--ink)}
.co.flip{flex-direction:row-reverse}

/* ---------- page header (every inner page) ------------------------------ */
.phero{position:relative;z-index:2;min-height:clamp(340px,44vh,460px);min-height:clamp(340px,44dvh,460px);display:flex;align-items:flex-end;
  padding-top:calc(var(--nav-h) + var(--util-h) + 2rem);padding-bottom:clamp(2.5rem,5vw,4rem);
  overflow:hidden;background:var(--ink);color:var(--paper);--ghost:var(--paper);--ghost-ink:var(--ink)}
.phero-media{position:absolute;inset:0;z-index:0}
.phero-media img{width:100%;height:100%;object-fit:cover}
.phero-media::after{content:"";position:absolute;inset:0;
  background:linear-gradient(to top,rgba(10,12,14,.88) 0,rgba(10,12,14,.85) 55%,rgba(10,12,14,0) 100%),
             linear-gradient(100deg,rgba(10,12,14,.9) 0%,rgba(10,12,14,.74) 46%,rgba(10,12,14,.42) 100%)}
.phero .wrap{position:relative;z-index:2}
.phero h1{font-size:var(--t-xl);max-width:19ch;margin-top:.6rem}
.phero .lead{margin-top:1rem;color:var(--dk-2);max-width:56ch}
.crumbs{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.1em;text-transform:uppercase;color:var(--dk-2)}
.crumbs a{text-decoration:none}
.crumbs a:hover{color:var(--accent)}
.crumbs span{opacity:.5;margin:0 .5rem}

/* ---------- dividers: this project's own shapes ------------------------- */
/* An angled cut and a stepped notch read as sheet metal, which is the subject.
   One shallow curve for relief. Values are unique to this build. */
.dv{position:absolute;left:0;right:0;line-height:0;pointer-events:none;z-index:3}
.dv-b{bottom:-1px}
.dv-t{top:-1px}
.dv-t svg{transform:scaleY(-1)}
.dv svg{width:100%;height:clamp(38px,5.2vw,76px);display:block}

/* ---------- cards & grids ----------------------------------------------- */
.grid{display:grid;gap:clamp(1rem,2vw,1.6rem)}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g4{grid-template-columns:repeat(4,minmax(0,1fr))}

/* every card in a card grid carries an image. a text-only card is a text
   section wearing a card costume. */
.svc{position:relative;display:block;text-decoration:none;color:inherit;overflow:hidden;
  border-radius:var(--r);background:var(--ink);isolation:isolate;
  transition:transform .32s var(--ease)}
.svc:hover{transform:translateY(-4px)}
.svc-img{aspect-ratio:4/3;overflow:hidden}
.svc-img img{width:100%;height:100%;object-fit:cover;transition:transform .55s var(--ease)}
.svc:hover .svc-img img{transform:scale(1.045)}
.svc-body{position:absolute;inset:auto 0 0;padding:1.4rem 1.3rem 1.25rem;
  background:linear-gradient(to top,rgba(10,12,14,.94) 24%,rgba(10,12,14,.62) 62%,transparent);
  color:var(--paper)}
.svc-body h3{font-size:1.32rem;letter-spacing:-.015em}
.svc-body p{margin-top:.4rem;font-size:.98rem;color:var(--dk-2);line-height:1.5;
  padding-right:3.4rem}   /* reserve the arrow's corner */
.svc-go{position:absolute;right:1.15rem;bottom:1.3rem;width:40px;height:40px;border-radius:50%;
  border:2px solid var(--accent);display:grid;place-items:center;color:var(--accent);
  transition:background .2s var(--ease),color .2s var(--ease)}
.svc:hover .svc-go{background:var(--accent);color:var(--ink)}
.svc-go svg{width:16px;height:16px}

/* split: photo one side, content the other. .flip reverses.
   NOT .rev, which collides with the review-card class and the collision is
   invisible until every reversed split renders as a white card. */
.split{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(1.6rem,4vw,4rem);align-items:center}
.split.flip .split-media{order:2}
.split-media{position:relative}
.split-media img{width:100%;height:100%;object-fit:cover}

/* mask shapes: chamfer, circle, arch. Rotated across the site so the eye has
   something to catch. Each caps its width on mobile or a full-width circle
   becomes an enormous disc. */
.m-chamfer{clip-path:polygon(0 0,calc(100% - 46px) 0,100% 46px,100% 100%,46px 100%,0 calc(100% - 46px));
  aspect-ratio:4/3;overflow:hidden}
.m-circle{border-radius:50%;aspect-ratio:1;overflow:hidden;max-width:min(460px,80vw);margin-inline:auto}
.m-arch{border-radius:50% 50% var(--r) var(--r)/38% 38% var(--r) var(--r);aspect-ratio:4/5;overflow:hidden;
  max-width:min(440px,80vw);margin-inline:auto}
.m-plate{clip-path:polygon(0 0,100% 0,100% calc(100% - 40px),calc(100% - 40px) 100%,0 100%);
  aspect-ratio:3/2;overflow:hidden}

/* the deliberate overlap. no overlap reads as a stack of divs. */
.ovl{position:absolute;z-index:4;max-width:min(290px,72vw)}
.ovl-br{right:-16px;bottom:-22px}
.ovl-bl{left:-16px;bottom:-22px}

/* ---------- stats (count-up) -------------------------------------------- */
.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--line-dk)}
.stat{background:var(--ink);padding:clamp(1.4rem,3vw,2.4rem) 1.2rem;text-align:center}
.stat b{display:block;font:800 clamp(2.2rem,4vw,3.4rem)/1 var(--f-display);color:var(--accent);
  letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.stat span{display:block;margin-top:.55rem;font:500 var(--t-xs)/1.4 var(--f-mono);
  letter-spacing:.1em;text-transform:uppercase;color:var(--dk-3)}

/* ---------- rails: one engine for the carousel and the marquee ---------- */
.rail{position:relative}
/* .rev lifts 3px on hover and this viewport clips vertically, so the lift was
   cut off along the top edge of every card. Headroom INSIDE the clip, pulled
   back out with a negative margin so nothing shifts in the layout. The card
   border is 1px and the lift is 3px, so 6px covers it with room to spare. */
.rail-vp{padding-block:6px;margin-block:-6px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;
  cursor:grab;overscroll-behavior-x:contain}
.rail-vp::-webkit-scrollbar{display:none}
.rail-vp.drag{cursor:grabbing;scroll-behavior:auto}
.rail-tr{display:flex;gap:1rem;width:max-content;will-change:transform}
.rail-nav{display:flex;gap:.6rem}
.rail-btn{width:46px;height:46px;border:2px solid var(--line);border-radius:50%;background:transparent;
  cursor:pointer;display:grid;place-items:center;color:var(--ink);
  transition:background .18s var(--ease),color .18s var(--ease),border-color .18s var(--ease)}
.rail-btn:hover{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.dark .rail-btn{border-color:var(--line-dk);color:var(--paper)}
.dark .rail-btn:hover{background:var(--accent);border-color:var(--accent);color:var(--ink)}
.rail-btn svg{width:16px;height:16px}

/* review card */
.rev{flex:0 0 min(400px,84vw);background:var(--paper);border:1px solid var(--line);
  border-radius:var(--r);padding:1.5rem 1.4rem;display:flex;flex-direction:column;
  text-decoration:none;color:inherit;transition:border-color .2s var(--ease),transform .2s var(--ease)}
.rev:hover{border-color:var(--ink);transform:translateY(-3px)}
.dark .rev{background:var(--ink-2);border-color:var(--line-dk)}
.stars{display:flex;gap:2px;color:var(--eyebrow)}   /* graphic, 3:1 floor: lime is 2.3:1 on the paper card, so read the surface token */
.stars svg{width:17px;height:17px}
.rev p{margin-top:.9rem;font-size:1.02rem;line-height:1.62;flex:1}
.rev footer{margin-top:1.15rem;padding-top:.9rem;border-top:1px solid var(--line);
  display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  font:500 var(--t-xs)/1.4 var(--f-mono);letter-spacing:.05em}
.dark .rev footer{border-top-color:var(--line-dk)}
.rev cite{font-style:normal;font-weight:600;text-transform:uppercase;letter-spacing:.08em}
.rev .src{color:var(--steel);display:inline-flex;align-items:center;gap:.35rem;white-space:nowrap}
.dark .rev .src{color:var(--dk-3)}
.rev .src svg{width:12px;height:12px}

/* marquee: same engine, drift, no buttons. ~0.9px/frame reads as calm and
   visible; 0.35 reads as static. */
.mq{background:var(--accent);color:var(--ink);padding:.95rem 0;overflow:hidden}
.mq .rail-tr{gap:0;align-items:center}
.mq span{display:inline-flex;align-items:center;gap:1.6rem;padding-right:1.6rem;
  font:700 1.02rem/1 var(--f-display);letter-spacing:.02em;white-space:nowrap;text-transform:uppercase}
.mq span::after{content:"";width:7px;height:7px;background:var(--ink);transform:rotate(45deg);flex:none}

/* ---------- accordion (FAQ) --------------------------------------------- */
/* grid-template-rows 0fr -> 1fr, never max-height. The collapsing child needs
   align-items:stretch on the parent and min-height:0 on itself, or it sizes to
   its own content and overflows a 0px track while devtools shows 0fr. */
.faq{border-top:1px solid var(--line)}
.dark .faq{border-top-color:var(--line-dk)}
.faq-i{display:grid;grid-template-rows:auto 0fr;align-items:stretch;
  border-bottom:1px solid var(--line);transition:grid-template-rows .3s var(--ease)}
.dark .faq-i{border-bottom-color:var(--line-dk)}
.faq-i.open{grid-template-rows:auto 1fr}
.faq-q{-webkit-appearance:none;appearance:none;background:transparent;border:0;width:100%;
  display:flex;align-items:center;justify-content:space-between;gap:1.5rem;
  padding:1.35rem 0;min-height:56px;cursor:pointer;text-align:left;
  font:700 1.12rem/1.4 var(--f-display);letter-spacing:-.01em;color:inherit}
.faq-q .pm{width:22px;height:22px;flex:none;position:relative}
.faq-q .pm::before,.faq-q .pm::after{content:"";position:absolute;background:var(--eyebrow);
  border-radius:2px;transition:transform .3s var(--ease)}
.faq-q .pm::before{inset:10px 0 10px 0;height:2.5px}
.faq-q .pm::after{inset:0 10px 0 10px;width:2.5px}
.faq-i.open .pm::after{transform:scaleY(0)}
.faq-a{overflow:hidden;min-height:0}
.faq-a p{padding:0 0 1.5rem;max-width:70ch;color:var(--steel)}
.dark .faq-a p{color:var(--dk-2)}

/* ---------- gallery + lightbox ------------------------------------------ */
/* uniform grid + aspect-ratio + object-fit. never column-count on real client
   photos: mixed aspect ratios give ragged columns and a dead zone. */
.gal{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(258px,100%),1fr));gap:.85rem}
.gal button{padding:0;border:0;background:var(--ink-2);cursor:pointer;overflow:hidden;
  border-radius:var(--r);position:relative;aspect-ratio:1;display:block}
.gal img{width:100%;height:100%;object-fit:cover;transition:transform .5s var(--ease)}
.gal button:hover img{transform:scale(1.05)}
.gal figcaption{position:absolute;inset:auto 0 0;padding:2.2rem .9rem .75rem;
  background:linear-gradient(to top,rgba(10,12,14,.9),transparent);
  color:var(--paper);font:500 var(--t-xs)/1.35 var(--f-mono);letter-spacing:.03em;text-align:left;
  opacity:0;transition:opacity .25s var(--ease)}
.gal button:hover figcaption,.gal button:focus-visible figcaption{opacity:1}

.lb{position:fixed;inset:0;z-index:100;background:rgba(8,10,12,.96);
  display:grid;grid-template-rows:auto 1fr auto;gap:1rem;padding:1rem;
  opacity:0;visibility:hidden;transition:opacity .25s var(--ease),visibility .25s}
.lb.on{opacity:1;visibility:visible}
.lb-bar{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  color:var(--dk-3);font:500 var(--t-xs)/1 var(--f-mono);letter-spacing:.1em}
/* .lb-img is position:relative so the img can be absolutely centred. That makes
   it a positioned sibling: without an explicit z-index on .lb-x, .lb-x paints
   UNDER it in DOM order and the close button becomes unclickable while still
   passing element.click() (which skips hit-testing entirely). */
.lb-img{position:relative;overflow:hidden;min-height:0}
.lb-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
.lb-x{width:48px;height:48px;border:2px solid #4A545D;border-radius:50%;background:transparent;
  color:var(--paper);cursor:pointer;display:grid;place-items:center;position:relative;z-index:3;
  transition:background .18s var(--ease),color .18s var(--ease),border-color .18s var(--ease)}
.lb-x:hover{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.lb-x svg{width:18px;height:18px}
.lb-ft{display:flex;align-items:center;justify-content:center;gap:1.5rem;color:var(--paper)}
.lb-cap{font:500 .92rem/1.4 var(--f-mono);text-align:center;max-width:60ch;color:var(--dk-2)}

/* ---------- service-area map -------------------------------------------- */
.map-wrap{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:clamp(1.5rem,3vw,2.5rem);
  align-items:start}
.map-c{position:relative}
.map-c svg{width:100%;height:auto;display:block}
.mpin{cursor:pointer}
.mpin circle{transition:r .2s var(--ease),fill .2s var(--ease)}
.mpin text{font:600 12px var(--f-mono);letter-spacing:.04em;fill:var(--dk-2);
  paint-order:stroke;stroke:#14171A;stroke-width:4px;stroke-linejoin:round;pointer-events:none}
.mpin:hover circle,.mpin.on circle{fill:var(--accent)}
.mpin:hover text,.mpin.on text{fill:var(--paper)}
.map-p{background:var(--ink-2);border:1px solid var(--line-dk);border-radius:var(--r);
  padding:1.5rem 1.4rem;min-height:210px}
.map-p h3{font-size:1.4rem;color:var(--paper)}
.map-p .dist{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.1em;text-transform:uppercase;
  color:var(--accent);margin-top:.5rem;display:block}
.map-p p{margin-top:.9rem;color:var(--dk-2);font-size:1rem}

/* ---------- forms ------------------------------------------------------- */
.form{display:grid;gap:1.1rem}
.f2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.1rem}
.fld{display:grid;gap:.45rem}
.fld > span{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.1em;text-transform:uppercase;color:var(--steel)}
.dark .fld > span{color:var(--dk-3)}
/* 16px MINIMUM on inputs. Below it iOS Safari zooms on focus and never zooms
   back, stranding the user at 1.4x for the rest of the session. */
.fld input,.fld textarea,.fld select{
  -webkit-appearance:none;appearance:none;width:100%;font-size:16px;line-height:1.5;
  padding:.85rem 1rem;min-height:50px;border:2px solid var(--line);border-radius:var(--r);
  background:var(--paper);color:var(--ink);font-family:var(--f-body);
  transition:border-color .18s var(--ease)}
.dark .fld input,.dark .fld textarea,.dark .fld select{background:var(--ink-2);border-color:var(--line-dk);color:var(--paper)}
.fld input:focus,.fld textarea:focus,.fld select:focus{border-color:var(--focus);outline:none}
.fld textarea{min-height:132px;resize:vertical}
.sel{position:relative}
.sel select{padding-right:2.8rem;cursor:pointer}
.sel::after{content:"";position:absolute;right:1.1rem;top:50%;width:11px;height:11px;
  margin-top:-8px;border-right:2.5px solid var(--accent);border-bottom:2.5px solid var(--accent);
  transform:rotate(45deg);pointer-events:none}
/* the UA file button cannot be themed and looks a decade old. hide it, drive
   the real input from a styled label. */
.file input{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden;clip:rect(0 0 0 0)}
.file label{display:flex;align-items:center;gap:.8rem;min-height:56px;padding:.9rem 1.1rem;
  border:2px dashed var(--line);border-radius:var(--r);cursor:pointer;font-size:1rem;
  color:var(--steel);transition:border-color .18s var(--ease),color .18s var(--ease)}
.dark .file label{border-color:var(--line-dk);color:var(--dk-3)}
.file label:hover,.file input:focus-visible + label{border-color:var(--focus);color:var(--ink)}
.dark .file label:hover,.dark .file input:focus-visible + label{color:var(--paper)}
.file label svg{width:20px;height:20px;flex:none;color:var(--eyebrow)}
.file.has label{border-style:solid;border-color:var(--focus);color:var(--ink)}
.dark .file.has label{color:var(--paper)}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* ---------- footer ------------------------------------------------------ */
.ft{background:var(--ink);color:var(--dk-2);padding-block:clamp(3rem,6vw,5rem) 0}
.ft-grid{display:grid;grid-template-columns:1.5fr repeat(3,1fr);gap:clamp(1.75rem,3vw,3rem)}
.ft h3{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent);margin-bottom:1.1rem}
.ft ul{list-style:none;margin:0;padding:0;display:grid;gap:.65rem}
.ft ul a{text-decoration:none;position:relative;display:inline-block;font-size:.985rem}
.ft ul a::after{content:"";position:absolute;left:0;right:0;bottom:-3px;height:1.5px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .26s var(--ease)}
.ft ul a:hover{color:var(--paper)}
.ft ul a:hover::after{transform:scaleX(1)}
.ft-brand p{margin-top:1rem;font-size:.985rem;max-width:38ch}
.social{display:flex;gap:.7rem;margin-top:1.4rem}
.social a{width:44px;height:44px;border:1.5px solid var(--line-dk);border-radius:50%;
  display:grid;place-items:center;color:var(--dk-2);
  transition:border-color .18s var(--ease),color .18s var(--ease),background .18s var(--ease)}
.social a:hover{border-color:var(--accent);color:var(--ink);background:var(--accent)}
.social svg{width:19px;height:19px}
.ft-bot{margin-top:clamp(2.5rem,5vw,4rem);border-top:1px solid var(--line-dk);
  padding-block:1.5rem;display:flex;flex-wrap:wrap;gap:1rem 2rem;align-items:center;
  justify-content:space-between;font-size:.9rem}
.demo{background:var(--ink-3);border-left:1px solid var(--dk-3);padding:1rem 1.2rem;
  margin-top:2.25rem;font-size:.92rem;line-height:1.6;color:var(--dk-2);border-radius:0 var(--r) var(--r) 0}
.demo b{color:var(--paper)}

/* ---------- sticky mobile action bar ------------------------------------ */
/* body reserves its height; the bar's own background matches its buttons so the
   iOS safe-area inset never paints a coloured band under them. */
.actbar{display:none}

/* ---------- reveal ------------------------------------------------------ */
/* scoped to .js so a script failure leaves the page visible rather than blank */
.js .rv{opacity:0;transform:translateY(18px);transition:opacity .6s var(--ease),transform .6s var(--ease)}
.js .rv.in{opacity:1;transform:none}

/* ---------- diagrams ---------------------------------------------------- */
.dgm{background:var(--paper-2);border:1px solid var(--line);border-radius:var(--r);
  padding:clamp(1.25rem,2.5vw,2rem)}
.dark .dgm{background:var(--ink-2);border-color:var(--line-dk)}
.dgm svg{width:100%;height:auto;display:block}
.dgm figcaption{margin-top:1.1rem;font-size:.95rem;color:var(--steel);line-height:1.6}
.dark .dgm figcaption{color:var(--dk-2)}
.key{display:flex;flex-wrap:wrap;gap:.6rem 1.4rem;margin-top:1rem;
  font:500 var(--t-xs)/1 var(--f-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--steel)}
.dark .key{color:var(--dk-3)}
.key i{display:inline-flex;align-items:center;gap:.45rem;font-style:normal}
.key i::before{content:"";width:13px;height:13px;background:var(--kc,var(--eyebrow));border-radius:2px;flex:none}

/* ---------- misc content blocks ----------------------------------------- */
.prose p + p{margin-top:1.1rem}
.prose h2{margin-top:2.5rem;font-size:var(--t-lg)}
.prose h2:first-child{margin-top:0}
.ticks{list-style:none;margin:1.4rem 0 0;padding:0;display:grid;gap:.8rem}
.ticks li{position:relative;padding-left:2.15rem;font-size:1.03rem;line-height:1.6}
.ticks li::before{content:"";position:absolute;left:0;top:.42em;width:20px;height:20px;
  background:var(--accent);clip-path:polygon(0 52%,38% 100%,100% 12%,88% 0,37% 74%,10% 42%)}
.ticks li b{color:inherit}
.pricenote{border-left:1px solid var(--line-dk);padding:.85rem 0 .85rem 1.2rem;font-size:1rem;color:var(--steel)}
.dark .pricenote{color:var(--dk-2);border-color:var(--dk-3)}

/* ---------- responsive -------------------------------------------------- */
@media (max-width:1080px){
  .ft-grid{grid-template-columns:1fr 1fr}
  .g4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .stats{grid-template-columns:repeat(2,minmax(0,1fr))}
  .hero-grid{grid-template-columns:1fr}
  .hero-card{order:1}
  .hero .sheet{order:2}
  .callouts{display:none}          /* the leader lines need the desktop frame */
}
@media (max-width:900px){
  /* one breakpoint for BOTH fixed corner elements. a bar at 800 and a button at
     760 collide in a 40px window. */
  .burger{display:block}
  .nav-cta .phpill{display:none}
  /* A header CHILD at top:100% of the nav row, which is the header's last child,
     so the panel rides every frame of the height transition with a 0px gap.
     The nav is a .wrap with a 1.25rem gutter each side; the negative insets
     bring the panel back to the viewport edges. */
  .menu{
    position:absolute;top:100%;left:-1.25rem;right:-1.25rem;z-index:5;
    max-height:calc(100vh - var(--hdr-h,98px));max-height:calc(100dvh - var(--hdr-h,98px));overflow-y:auto;
    /* fill to the bottom of the viewport so the panel ends on purpose (an action block),
       not on the last link with the hero showing underneath */
    min-height:calc(100vh - var(--hdr-h,98px));min-height:calc(100dvh - var(--hdr-h,98px));
    --ghost:var(--paper);--ghost-ink:var(--ink);
    background:var(--ink);border-top:1px solid var(--line-dk);
    flex-direction:column;align-items:stretch;gap:0;padding:.5rem 1.25rem 1.5rem;
    transform:translateY(-8px);opacity:0;visibility:hidden;
    transition:opacity .24s var(--ease),transform .24s var(--ease),visibility .24s;
  }
  .menu.open{opacity:1;visibility:visible;transform:none}
  /* align-items:stretch is load-bearing: a centre-aligned grid item sizes to its
     own content, overflows the 0px track, and every submenu sits permanently
     open on top of the link below it. */
  .menu > li{display:grid;grid-template-rows:auto 0fr;align-items:stretch;
    border-bottom:1px solid var(--line-dk);transition:grid-template-rows .28s var(--ease)}
  .menu > li.open{grid-template-rows:auto 1fr}
  .menu > li > a{padding:1rem .25rem;min-height:56px;font-size:1.06rem}
  .menu > li > a::after{display:none}
  .menu > li.m-act{display:grid;margin-top:auto;border-bottom:0;padding:1.25rem 0 .25rem}
  .m-act-in{display:grid;gap:.8rem}
  .m-act .btn{justify-content:center;width:100%}
  .m-act .qch{margin-top:0}
  .m-act .qch a{flex:1 1 auto;justify-content:center;padding-inline:.7rem;min-width:0}
  .m-act .qch .lbl{overflow:visible;text-overflow:clip;white-space:nowrap}
  .m-addr{margin:.25rem 0 0;display:flex;align-items:center;gap:.5rem;font:500 var(--t-xs)/1.3 var(--f-mono);letter-spacing:.03em;color:var(--dk-2)}
  .m-hours{margin:.15rem 0 0;display:flex;align-items:center;gap:.5rem;font:500 var(--t-xs)/1.3 var(--f-mono);letter-spacing:.03em;color:var(--dk-3)}
  /* No VERTICAL padding here. With border-box a grid item cannot be shorter
     than its own padding, so a closed 0fr track still left a 12px gap under
     every collapsed submenu. Spacing comes from the links instead. */
  .sub{position:static;opacity:1;visibility:visible;transform:none;overflow:hidden;min-height:0;
    background:transparent;border:0;box-shadow:none;padding:0 0 0 .9rem}
  .sub li:last-child a{margin-bottom:.7rem}
  .split,.f2,.map-wrap{grid-template-columns:1fr}
  .split.flip .split-media{order:0}
  .g3{grid-template-columns:1fr}
  .totop{bottom:calc(var(--cta-h,61px) + .75rem)}
  /* the bar background matches its buttons, so the safe-area inset cannot paint
     a contrasting band. dividers come from borders, never a grid gap. */
  .actbar{display:grid;grid-template-columns:1fr 1fr;align-items:start;
    position:fixed;inset:auto 0 0;z-index:58;
    background:var(--ink);border-top:1px solid var(--line-dk);
    min-height:calc(60px + env(safe-area-inset-bottom,0px))}
  .actbar a{display:flex;align-items:center;justify-content:center;gap:.5rem;min-height:60px;
    text-decoration:none;font:700 1rem/1 var(--f-display);color:var(--paper)}
  .actbar a + a{border-left:1px solid var(--line-dk);background:var(--accent);color:var(--ink)}
  .actbar svg{width:19px;height:19px}
  /* The space behind the fixed action bar is reserved on the FOOTER, not the
     body. On the body it was paper-coloured, so a 60px light strip appeared
     under the dark footer and read as scrolling past the end of the page. */
  .ft{padding-bottom:calc(var(--cta-h,61px) + .7rem)}
}
@media (max-width:680px){
  .nav-cta > .btn{display:none}
  .g2,.g4,.stats,.ft-grid{grid-template-columns:1fr}
  .util-row{justify-content:center;gap:.9rem}
  .util .hide-s{display:none}
  .util a,.util-row > span{white-space:nowrap}   /* one line or nothing: a wrapped util bar collided with the brand row on a phone */
  .hero{min-height:auto;padding-bottom:2.25rem}
  .m-chamfer{clip-path:polygon(0 0,calc(100% - 28px) 0,100% 28px,100% 100%,28px 100%,0 calc(100% - 28px))}
  .ovl{position:static;max-width:100%;margin-top:1rem}
  .lb{padding:.6rem}
}

/* declared AFTER the base rules it overrides: a media query does NOT beat a
   later declaration at equal specificity. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
  .rv{opacity:1;transform:none}
  .co{opacity:1;transform:none}
}

/* ---------- film ---------------------------------------------------------- */
/* The chamfer is TOP-RIGHT only. A bottom-left chamfer clips the corner where
   the native play/pause button lives, and clip-path clips the hit region too,
   so the first control anyone reaches for was dead. The video looked playable
   and felt locked. Never chamfer a corner of a element that owns controls. */
.film{position:relative;background:var(--ink);border-radius:var(--r);overflow:hidden;
  clip-path:polygon(0 0,calc(100% - 34px) 0,100% 34px,100% 100%,0 100%)}
.film video{width:100%;height:auto;display:block;aspect-ratio:16/9;background:var(--ink)}
/* The sound toggle sits at the TOP so it can never cover the native control
   strip. A play overlay at inset:0 makes the whole video unclickable, which
   reads as "the controls are frozen" rather than "there is an overlay". */
.film-snd{position:absolute;top:14px;right:14px;z-index:2;width:46px;height:46px;
  border:0;border-radius:50%;background:rgba(20,23,26,.78);color:var(--paper);
  cursor:pointer;display:grid;place-items:center;
  transition:background .18s var(--ease),color .18s var(--ease)}
.film-snd:hover{background:var(--accent);color:var(--ink)}
.film-snd svg{width:21px;height:21px;grid-area:1/1}
.film-snd .on{display:none}
.film-snd[aria-pressed="true"] .on{display:block}
.film-snd[aria-pressed="true"] .off{display:none}
.film-cap{margin-top:1rem;font:600 var(--t-xs)/1.5 var(--f-mono);letter-spacing:.06em;
  text-transform:uppercase;color:var(--steel)}

/* the info panel carried no image: it is the one place on the page that says
   "here is what you are driving to", so it gets the bay */
.map-pic{aspect-ratio:16/10;overflow:hidden;border-radius:var(--r);margin-bottom:1.15rem;
  clip-path:polygon(0 0,calc(100% - 22px) 0,100% 22px,100% 100%,0 100%)}
.map-pic img{width:100%;height:100%;object-fit:cover}

/* legend entry for a line, not a fill */
.key .key-dash::before{background:transparent;border-left:2px dashed var(--steel);
  width:2px;height:15px;border-radius:0}
.dark .key .key-dash::before{border-left-color:var(--dk-3)}

/* iOS Safari sizes an iframe to its CONTENT, ignoring width/height and CSS.
   Absolutely positioning it removes it from flow entirely, so it can never
   inflate the document height no matter what the embed reports. */
/* chamfer the TOP-right only: the bottom corners are where the embed's own
   controls sit, and clip-path clips the hit region as well as the pixels */
.map-embed{position:relative;aspect-ratio:4/3;overflow:hidden;border-radius:var(--r);
  clip-path:polygon(0 0,calc(100% - 30px) 0,100% 30px,100% 100%,0 100%)}
.map-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}


/* ---------- preloader (home only) ------------------------------------------
   House pattern: mark, wordmark, real progress bar, spinner, then the panel
   SWIPES UP off the hero rather than cross-fading into it.
   The scroll lock is tied to the element existing, not to JS state, so removing
   the panel releases it even if something threw. With JS off, a <noscript> rule
   in the head hides it. A 0s-duration CSS animation was tried as a failsafe and
   applied its END state immediately, so the panel never painted: don't. */
html:has(.pre){overflow:hidden}
.pre{position:fixed;inset:0;z-index:300;background:#14171A;
  display:grid;place-items:center;transform:translateY(0);
  transition:transform .44s cubic-bezier(.76,0,.24,1)}
.pre.up{transform:translateY(-101%)}
.pre-in{display:flex;flex-direction:column;align-items:center;gap:1rem;
  padding:0 24px;text-align:center;
  transition:opacity .2s ease,transform .2s ease}
.pre.done .pre-in{opacity:0;transform:translateY(-10px)}
.pre-in .pre-mark{width:clamp(74px,10vw,104px);height:auto;display:block;border-radius:12px}
.pre-mono{font-family:inherit;font-weight:900;font-size:clamp(2.2rem,5vw,3rem);
  letter-spacing:.04em;color:#91AE37;line-height:1}
.pre-name{font-weight:800;font-size:clamp(1.4rem,3.2vw,2rem);letter-spacing:.03em;
  text-transform:uppercase;color:#fff;line-height:1}
.pre-sub{font-size:var(--t-xs);letter-spacing:.11em;text-transform:uppercase;
  color:#91AE37;font-weight:700}
.pre-bar{width:min(260px,60vw);height:3px;background:rgba(255,255,255,.18);border-radius:2px;
  overflow:hidden;margin-top:.3rem}
.pre-bar i{display:block;height:100%;width:100%;background:#91AE37;
  transform:translateX(-100%);transition:transform .32s ease}
.pre-spin{width:18px;height:18px;border:2.5px solid rgba(255,255,255,.26);
  border-top-color:#91AE37;border-radius:50%;animation:preSpin .68s linear infinite}
@keyframes preSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.pre{display:none}}

/* ---------- TOUCH TARGETS (retrofit 2026-08) ---------------------------
   Craft floor: 44px for any standalone control, verified at a real 390x844
   touch viewport. Scoped to (pointer: coarse) so the desktop look is
   unchanged: a mouse does not need a 44px target. Height is the binding
   constraint; a short text link may stay narrow. */
@media (pointer: coarse) {
  /* min-height carries !important deliberately. This is an accessibility floor,
     not styling: component rules elsewhere set min-height:24px and, being more
     specific, silently beat an appended override. A floor that any component can
     out-specify is not a floor. Scoped to coarse pointers, so the desktop
     cascade is untouched.
     Container list came from MEASURING at 390x844, not from guessing. Not a
     blanket rule on every <a>: that inflates the line box of inline prose links. */
  .crumb a, .crumbs a, .u-r a, .util a, .lede a, .socs a, .proof a, .who a,
  .foot a, footer a, nav a, .rev a, .card a, .chips a,
  header .wrap > a, footer .wrap > a{
    display:inline-flex;align-items:center;min-height:44px !important}
  .socs a{min-width:44px;justify-content:center}
  .snav,.totop,.burger{min-width:44px !important;min-height:44px !important}
  .chip,.gf,.sndbtn,.pill{min-height:44px !important}
}

/* Footer brand marks. Official artwork (Facebook 2019 mark, Google four-colour G),
   so they carry their own colour and must NOT sit inside the generic bordered disc:
   a ring round the Facebook circle is a circle inside a circle. The 44px anchor stays
   as the tap target. Sized per mark for equal optical weight -- a filled disc reads
   heavier than an open glyph at the same height. Placed last so it beats .social a:hover. */
.social a.bm,.social a.bm:hover,.social a.bm:focus-visible{border-color:transparent;background:transparent}
.social a.bm svg{width:26px;height:26px}
.social a.bm-g svg{width:27px;height:27px}
/* Optical weight, not measured height: Instagram is a FILLED rounded square, so at
   an identical 26px it reads heavier than Facebook's filled circle (a circle covers
   ~79% of its bounding box) and much heavier than Google's open glyph, which is why
   the G is already up at 27. Sized down to 24 and checked against both at footer
   scale. */
.social a.bm-ig svg{width:24px;height:24px}
/* Marketplace is a filled disc like Facebook's, so it takes Facebook's 26px, not the G's 27. */
.social a.bm-mp svg{width:26px;height:26px}
.social a.bm:hover svg,.social a.bm:focus-visible svg{transform:translateY(-1px)}
.social a.bm svg{transition:transform .18s var(--ease)}

/* Map pin tap targets. A 6.5-unit dot is a mouse-only control, so each pin carries an
   invisible r=30 disc behind it. It is a <circle>, and .mpin:hover circle sets fill,
   so it MUST be excluded by a more specific selector or it renders as an accent blob
   covering its neighbours. */
.mpin .mpin-hit,.mpin:hover .mpin-hit,.mpin.on .mpin-hit,.mpin:focus .mpin-hit{fill:transparent;transition:none}

/* Service-area panel. All ten town blocks ship in the markup stacked in one
   grid cell; visibility:hidden keeps the inactive ones in the layout, so the
   cell sizes to the tallest block at any width and the section cannot change
   height as you move across the map. No fixed pixel height to go stale. */
.mp-in{display:grid}
.mp-slot{grid-area:1/1;visibility:hidden}
.mp-slot[data-on]{visibility:visible}

/* The town list is buttons: it responds to a press. The map responds to hover.
   Two controls driving one selection should not behave the same way, and
   hover-select on a list also fires once per button as the pointer travels
   down it. */
.mp-towns{display:flex;flex-wrap:wrap;gap:.5rem;list-style:none;padding:0;margin:0}
.mp-town{min-height:44px;padding:.55rem 1rem;border:1.5px solid var(--line-dk);
  background:transparent;color:var(--dk-2);border-radius:3px;cursor:pointer;
  font:600 var(--t-xs) var(--f-mono);letter-spacing:.04em;
  transition:border-color .18s var(--ease),background .18s var(--ease),color .18s var(--ease)}
.mp-town:hover{border-color:var(--accent);color:var(--paper)}
.mp-town[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:var(--ink)}
.mp-town:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The map on a phone. Measured at a real 390px viewport: the SVG renders 350px
   wide, so its 720-unit viewBox scales by 0.486 and the 12px town labels come
   out at 5.8px. That is not small text, it is unreadable, and 6px dots are not
   a tap target either. So on a phone the map shows the shape of the place and
   where the ten towns sit, and the town list underneath does the naming and the
   choosing -- the list is the 44px control set, not a waiver.
   Ring labels go too: the panel already states the distance for whichever town
   is selected, so at 5.8px they were redundant as well as illegible. */
/* Map label styling lives here, not inline on each <text>. An inline style wins
   over every stylesheet rule, so with the font set inline the mobile override
   below could never apply -- it measured 6.8px on a phone while the rule said 26. */
.map-c .ringlab{font:600 14px var(--f-mono);fill:var(--dk-3);paint-order:stroke;
  stroke:#252C35;stroke-width:5px;stroke-linejoin:round}
.map-c .shoplab{font:700 14px var(--f-mono);fill:var(--accent);paint-order:stroke;
  stroke:#14171A;stroke-width:5px;stroke-linejoin:round}
.map-c .complab{font:600 14px var(--f-mono);fill:var(--dk-3)}

@media (max-width:700px){
  .mpin text,.map-c .ringlab,.map-c .complab{display:none}
  .map-c .shoplab{font-size:26px}
  .mpin circle:not(.mpin-hit){r:11}
  /* labels are gone, so the discs can grow. 34 is the ceiling: the closest pin
     pair is 76 units apart, so anything over 38 would overlap its neighbour. */
  .mpin .mpin-hit{r:34}
}

/* ---------- quote channels (added 2026-09-02) ----------------------------
   Gerson asked for email, SMS and WhatsApp as the ways to reach him for a
   quote. Three plain anchors, one group, generated once in chrome.py. The pill
   reads its colours from the surface tokens the ghost button already uses, so
   a new dark band opts in automatically. Labels are single words: flex-wrap
   cannot break one item, and an ellipsis cannot reach a bare text node inside
   inline-flex, so the label sits in its own .lbl span. */
.qch{display:flex;flex-wrap:wrap;align-items:center;gap:.55rem;margin-top:1rem}
.qch-l{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.04em;text-transform:uppercase;
  color:var(--steel);margin-right:.2rem}
.hero .qch-l,.phero .qch-l,.dark .qch-l{color:var(--dk-3)}
.qch a{display:inline-flex;align-items:center;gap:.45rem;min-height:44px;padding:.55rem .95rem;
  border:1.5px solid var(--line);border-radius:var(--r);text-decoration:none;
  color:var(--ghost);font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.04em;text-transform:uppercase;
  max-width:100%;min-width:0;
  transition:background .18s var(--ease),color .18s var(--ease),border-color .18s var(--ease)}
.hero .qch a,.phero .qch a,.dark .qch a{border-color:rgba(245,244,241,.34)}
.qch a .lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.qch a:hover,.qch a:focus-visible{background:var(--ghost);color:var(--ghost-ink);border-color:var(--ghost)}
.qch a:active{transform:translateY(1px)}
.qch .ic{width:17px;height:17px;flex:none}

/* The contact page's primary quote block: one card per channel, the build-sheet
   device (ink plate, chamfer) so it reads as part of the same system. */
.qcards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(.8rem,2vw,1.2rem)}
.qcard{display:flex;align-items:center;gap:1rem;min-height:84px;min-width:0;padding:1.1rem 1.2rem;
  background:var(--ink-2);color:var(--paper);border:1px solid var(--line-dk);border-radius:var(--r);
  clip-path:polygon(0 0,calc(100% - 18px) 0,100% 18px,100% 100%,0 100%);text-decoration:none;
  transition:background .18s var(--ease),border-color .18s var(--ease)}
.qcard:hover,.qcard:focus-visible{background:var(--ink-3);border-color:var(--accent)}
.qcard .ic{width:28px;height:28px;flex:none;color:var(--accent)}
.qcard > div{min-width:0}
.qcard b{display:block;font:700 1.12rem/1.1 var(--f-display);letter-spacing:-.01em}
.qcard span{display:block;margin-top:.3rem;font:500 var(--t-xs)/1.3 var(--f-mono);color:var(--dk-2);overflow-wrap:anywhere}
@media (max-width:900px){.qcards{grid-template-columns:1fr}}

/* 404: the fuzzy did-you-mean list. Hidden until site.js finds a match. */
.dym{margin-top:1.4rem}
.dym-l{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--dk-3)}
.dym-list{list-style:none;margin:.6rem 0 0;padding:0;display:flex;flex-wrap:wrap;gap:.5rem}
.dym-list a{display:inline-flex;align-items:center;min-height:44px;padding:.55rem 1rem;
  border:2px solid rgba(245,244,241,.34);border-radius:var(--r);color:var(--paper);text-decoration:none;
  font:700 .95rem/1 var(--f-mono);transition:border-color .18s var(--ease),background .18s var(--ease)}
.dym-list a:hover,.dym-list a:focus-visible{background:var(--paper);color:var(--ink);border-color:var(--paper)}

/* Footer column headings moved from h3 to h2: pages end at h2, so an h3 in the
   footer landmark is a skipped level for anyone navigating by heading. */
.ft h2{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent);margin-bottom:1.1rem}

/* A 1.7 owner frame in a split slot. The two-truck photograph (6530) has a
   truck at each edge, and a 4:3 or 3:2 mask would cut one of them; this keeps
   the whole frame with the same bottom-right cut the plate uses. */
.m-wide{aspect-ratio:17/10;overflow:hidden;
  clip-path:polygon(0 0,100% 0,100% calc(100% - 34px),calc(100% - 34px) 100%,0 100%)}
.split-media figcaption{margin-top:.75rem;font:500 var(--t-xs)/1.5 var(--f-mono);color:var(--steel)}
.dark .split-media figcaption{color:var(--dk-3)}

/* The sticky bar carries the same four ways to reach the shop the hero does:
   call first and filled, then email, text and WhatsApp. Four cells at 390px
   are 97px each, so the icon sits over a single-word label instead of beside
   it, and every cell keeps the 60px height. Declared AFTER the base .actbar
   rules on purpose: cascade order beats a media query at equal specificity. */
@media (max-width:900px){
  .actbar{transition:transform .24s var(--ease)}
  .actbar.act4{grid-template-columns:repeat(4,minmax(0,1fr))}
  .actbar.act4 a{flex-direction:column;gap:.3rem;padding:.5rem .2rem;min-height:60px;min-width:0;
    font:600 var(--t-xs)/1 var(--f-display);letter-spacing:.01em}
  .actbar.act4 a span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .actbar.act4 a + a{background:var(--ink);color:var(--paper)}
  .actbar.act4 a:first-child{background:var(--accent);color:var(--ink)}
  .actbar.act4 svg{width:20px;height:20px}
  /* out of the way while the menu is open, or it covers the menu's last links */
  body:has(.menu.open) .actbar{transform:translateY(110%)}
}

/* Links inside the contact sheet rows become the whole row's control on a
   phone: an inline link alone in a block is a standalone target. */
@media (pointer: coarse){
  .sheet dd a{display:inline-flex;align-items:center;min-height:44px}
}

/* The hero quote row needs 314px for three pills at their natural width; the label
   adds 81px more and never fits inline on a phone. Measured: from a 414px viewport
   the three pills alone make one clean row, so the label moves to its own line
   THERE ONLY. Below 414 nothing changes, because a third row would be worse than
   today's wrap, and equal-width pills truncate "WhatsApp" at every phone width. */
@media (min-width:414px) and (max-width:680px){
  .hero .qch{gap:.5rem}
  .hero .qch-l{flex:1 0 100%}
  .hero .qch a{padding-inline:.7rem}
}
@media (max-width:400px){
  .util .u-rev,.util .u-rev + .sep{display:none}   /* SE-width phones: the phone number alone */
}

/* ---------- the brand wall ---------------------------------------------- */
/* Ten manufacturer marks, each one the manufacturer's own artwork flattened to
   a single flat white tone so the wall reads as one system instead of ten
   palettes. Display height is per-mark and OPTICAL (see parts.BRAND_MARKS):
   sizing a 15.7:1 wordmark and a 1:1 badge to one height makes the badge
   disappear. Nothing sits under a mark. */
.logowall{list-style:none;margin-inline:0;padding:0;display:flex;flex-wrap:wrap;
  align-items:center;justify-content:flex-start;
  gap:clamp(1.6rem,3.4vw,2.6rem) clamp(1.9rem,4.2vw,3.5rem)}
.logowall li{display:flex;align-items:center;max-width:100%}
/* max-width + object-fit: on a narrow phone the widest wordmark is boxed by the
   wrap instead of overflowing it, and contain scales it down keeping aspect.
   A plain max-width with a fixed height would stretch the mark. */
.logowall img{display:block;height:var(--bh);width:auto;max-width:100%;
  object-fit:contain;object-position:center;opacity:.92}

/* Gallery filters. His own services are the categories, so someone who came for one
   thing can see only that. Counts sit on the buttons because a filter that leads to an
   empty grid is worse than no filter. */
.gfils{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.1rem}
.gfil{display:inline-flex;align-items:center;gap:.45rem;min-height:44px;padding:.5rem .95rem;
  background:transparent;color:var(--ink-2);border:1px solid var(--line);border-radius:999px;
  font:600 .95rem/1 var(--f-display);cursor:pointer;
  transition:background .18s var(--ease),border-color .18s var(--ease),color .18s var(--ease)}
.gfil span{font:600 var(--t-xs)/1 var(--f-mono);color:var(--steel)}
.gfil:hover{border-color:var(--steel)}
/* The gallery sits on the paper band, so the resting state is INK. A light-ground token
   here rendered the five inactive filters at roughly 1.2:1 and they read as disabled.
   The dark override is kept in case the band tone ever flips. */
.dark .gfil{color:var(--dk-1);border-color:var(--line-dk)}
.dark .gfil span{color:var(--dk-3)}
.dark .gfil:hover{border-color:var(--dk-3)}
.gfil[aria-pressed="true"]{background:var(--accent);color:var(--ink);border-color:var(--accent)}
.gfil[aria-pressed="true"] span{color:var(--ink);opacity:.7}
.gcount{margin:0 0 1rem;font:500 var(--t-xs)/1.4 var(--f-mono);letter-spacing:.03em}
.gal button[hidden]{display:none}

/* Gallery sections. A flat grid of 44 photos hides its own organisation; the filter was
   the only way to see that the work splits by service. Headings make that visible while
   scrolling, and the filter then narrows to one section instead of reshuffling a wall. */
.galsec + .galsec{margin-top:clamp(2.6rem,5vw,4rem)}
.galsec-h{display:flex;align-items:baseline;gap:.6rem;margin:0 0 .3rem}
.galsec-h span{font:600 var(--t-xs)/1 var(--f-mono);letter-spacing:.06em;color:var(--steel)}
.galsec-l{margin:0 0 1.15rem;max-width:52ch}
.galsec[hidden]{display:none}
