@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700;800&family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap');

:root{
  --bg:#F8F7F3;
  --fg:#242B33;
  --navy:#102D54;
  --navy-2:#081C33;
  --slate:#5F6B78;
  --accent:#075ED1;
  --accent-strong:#075ED1;
  --cta:#075ED1;
  --cta-dark:#0875E1;
  --cream:#F8F7F3;
  --ice:#EEF1F4;
  --muted:#5F6B78;
  --border:#D9E0E7;
  --card:#fff;
  --safety:#F4B41A;
  --radius:4px;
  --maxw:1180px;
  --font-head:"Barlow Condensed",system-ui,sans-serif;
  --font-body:"Inter",system-ui,sans-serif;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--font-body);font-size:17px;line-height:1.6;-webkit-font-smoothing:antialiased}
h1,h2,h3,h4{font-family:var(--font-head);font-weight:700;letter-spacing:-0.01em;color:var(--navy);line-height:1.05}
a{color:var(--accent-strong);text-decoration:none}
a:hover{text-decoration:underline}
img{max-width:100%;display:block}
.container{max-width:var(--maxw);margin:0 auto;padding:0 24px}
.eyebrow{display:inline-block;font-family:var(--font-head);font-size:15px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;color:var(--cta);border-left:4px solid var(--cta);padding-left:10px;line-height:1.3;margin-bottom:14px}
.btn{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-head);font-weight:700;font-size:18px;letter-spacing:.045em;text-transform:uppercase;padding:16px 26px;min-height:56px;border-radius:3px;cursor:pointer;transition:background .2s,border-color .2s,transform .2s,box-shadow .2s;border:2px solid transparent}
.btn-primary,.btn-yellow{background:var(--cta);color:#fff;border-color:var(--cta);box-shadow:0 5px 0 #043B88}
.btn-primary:hover,.btn-yellow:hover{transform:translateY(-2px);text-decoration:none;box-shadow:0 7px 0 #043B88;background:var(--cta-dark);border-color:var(--cta-dark);color:#fff}
.btn-primary:active,.btn-yellow:active{transform:translateY(3px);box-shadow:0 2px 0 #043B88}
.btn-ghost{background:transparent;color:#fff;border:2px solid hsla(0,0%,100%,.5)}
.btn-ghost:hover{border-color:#fff;background:hsla(0,0%,100%,.08);color:#fff;text-decoration:none}
.btn-ghost-light{background:transparent;color:var(--navy);border:2px solid var(--border)}
.btn-ghost-light:hover{border-color:var(--navy);text-decoration:none}
.btn-light{background:#fff;color:var(--navy);border:2px solid #fff;box-shadow:0 5px 0 rgba(0,0,0,.30)}
.btn-light:hover{background:var(--ice);color:var(--cta);transform:translateY(-2px);text-decoration:none}
.btn-sm{padding:9px 16px;font-size:14px;min-height:0}

/* header */
header.site{position:sticky;top:0;z-index:50}
.util-bar{background:var(--navy);color:hsla(0,0%,100%,.85)}
.util-bar .ub-inner{display:flex;align-items:center;justify-content:flex-end;gap:22px;padding:5px 24px;font-size:12.5px;font-weight:600}
.ub-item{display:flex;align-items:center;gap:6px;white-space:nowrap}
.ub-item svg{width:14px;height:14px;color:var(--cta);flex:none}
.ub-phone{display:flex;align-items:center;gap:6px;color:#fff;font-weight:700;white-space:nowrap}
.ub-phone svg{width:13px;height:13px;flex:none}
.ub-phone:hover{color:#fff;text-decoration:underline}
@media(max-width:760px){.util-bar .ub-item:first-child{display:none}}
@media(max-width:560px){.util-bar{display:none}}
/* Trust-badge cluster (BBB, years-experience, financing, warranty), sitting
   in the white header row between the logo and the nav links — sized close
   to the "Free Estimate" button's height so it reads as a credibility row,
   not a second logo competing with the real one. */
.header-badges{display:flex;align-items:center;gap:16px;flex:none;padding-left:16px;margin-left:4px;border-left:1px solid var(--border)}
.header-badges img{height:50px;width:auto;display:block}
.header-badges a{display:block;line-height:0;transition:opacity .15s}
.header-badges a:hover{opacity:.82}
/* All badges (BBB, 25 Years, Financing, Lifetime Warranty) now share one
   consistent 50px height — the earlier 62px override on just 3 of the 4
   badges made the row read as uneven/mismatched (per Kris's rebrand
   feedback); removed so every badge sits at the same size. */
.header-badges img.trust-badge{height:50px}
@media(max-width:1600px){.header-badges{display:none}}
.row-wrap{background:#fff;border-bottom:1px solid var(--border)}
/* Full-bleed header row (unlike the centered .container everywhere else) so the
   logo sits at the true left edge of the browser window, matching the Ram Jack
   reference, instead of stopping at the 1180px centered column's left edge. */
.row-wrap .container{max-width:none;padding:0 28px}
header.site .row{display:flex;align-items:center;justify-content:space-between;height:102px;gap:20px}
.logo{display:flex;align-items:center;flex:none}
.logo img{height:96px;width:auto}
@media(max-width:760px){header.site .row{height:88px}.logo img{height:66px}}
@media(max-width:480px){header.site .row{height:76px}.logo img{height:52px}}
nav.main{display:flex;align-items:center;gap:14px;flex:none}
.navlinks{display:flex;align-items:center;gap:15px}
nav.main a{color:var(--slate);font-family:var(--font-body);font-weight:600;font-size:15px;white-space:nowrap}
nav.main a:hover{color:var(--cta);text-decoration:none}
nav.main a[aria-current="page"]{color:var(--cta);box-shadow:0 3px 0 var(--cta);padding-bottom:3px}
/* The new primary-button spec (18px text, 16px/26px padding, 56px min-height)
   is sized for a roomy hero/CTA-band button — at the header's normal inline
   nav-row width that combination, plus the longer "FREE EVALUATION" label,
   overflowed the header row past the viewport at common desktop widths
   (measured: pushed ~155px past 1440px). Scoped back down for this one
   inline context only; every other .btn-primary/.btn-yellow on the site
   (hero, forms, CTA bands, mobile drawer) keeps the full spec size. */
nav.main .btn{margin-left:4px;white-space:nowrap;padding:10px 14px;min-height:0;font-size:13px}
nav.main .btn-primary,nav.main .btn-yellow{box-shadow:0 3px 0 #043B88}
nav.main .btn-primary:hover,nav.main .btn-yellow:hover{box-shadow:0 4px 0 #043B88}
nav.main .btn-primary:active,nav.main .btn-yellow:active{box-shadow:0 1px 0 #043B88}
nav.main a.btn-primary,nav.main a.btn-primary:hover,nav.main a.btn-yellow,nav.main a.btn-yellow:hover{color:#fff}
.header-phone{display:flex;align-items:center;gap:7px;background:var(--cta);color:#fff;font-family:var(--font-head);font-weight:700;font-size:15px;text-transform:uppercase;letter-spacing:.02em;padding:10px 16px;border-radius:3px;white-space:nowrap}
.header-phone svg{width:16px;height:16px;color:#fff}
.header-phone:hover{color:#fff;text-decoration:none;background:var(--cta-dark)}
/* the phone number is already shown in the navy utility bar above at every
   width, so drop the redundant copy here first to make room for the trust
   badges before losing the nav links themselves */
@media(max-width:1700px){.header-phone{display:none}}
@media(max-width:1040px){.navlinks{display:none}}
/* "Services" nav dropdown — added when Crawl Space Repair became the first
   service to get its own real page (per Kris, "attach it to the menu").
   .navlinks is display:none below 1040px — on desktop widths this dropdown
   only needs to work as a hover menu, no click/tap toggle required (mobile
   gets its own click-to-expand accordion version in the hamburger drawer
   below). Menu sits flush against the toggle link's own box (no gap) so
   :hover never breaks moving the cursor from the link down into the menu. */
.nav-dropdown{position:relative}
.nav-dropdown-toggle{display:inline-flex;align-items:center;gap:4px}
.nav-dropdown-toggle .caret{transition:transform .15s}
.nav-dropdown:hover .nav-dropdown-toggle .caret{transform:rotate(180deg)}
.nav-dropdown-menu{position:absolute;top:100%;left:-14px;min-width:230px;background:#fff;
  border-radius:12px;box-shadow:0 18px 40px hsla(216,60%,15%,.18);border:1px solid var(--border);
  padding:8px;opacity:0;visibility:hidden;transform:translateY(4px);
  transition:opacity .15s,transform .15s,visibility .15s;z-index:60}
.nav-dropdown:hover .nav-dropdown-menu,.nav-dropdown-menu:hover{opacity:1;visibility:visible;transform:translateY(0)}
.nav-dropdown-menu a{display:block;padding:10px 14px;border-radius:8px;font-size:14.5px;white-space:nowrap}
.nav-dropdown-menu a:hover{background:var(--ice,#EEF1F4);color:var(--navy);text-decoration:none}

/* ---------- mobile nav (hamburger drawer) ----------
   Hidden entirely on desktop; only shows up once .navlinks itself disappears
   at 1040px, same breakpoint used throughout this header. A right-side
   drawer + dimmed backdrop, not a full-screen takeover, so it reads as a
   panel sliding in rather than a jarring full-page swap. */
.nav-toggle{display:none;background:none;border:none;padding:10px;margin-left:2px;cursor:pointer;
  flex-direction:column;align-items:center;justify-content:center;gap:5px;flex:none}
.nav-toggle-bar{display:block;width:24px;height:2.5px;border-radius:2px;background:var(--navy)}
@media(max-width:1040px){.nav-toggle{display:inline-flex}}
@media(min-width:1041px){.nav-toggle,.mobile-nav,.mobile-nav-backdrop{display:none!important}}
.mobile-nav-backdrop{position:fixed;inset:0;background:hsla(216,60%,10%,.5);opacity:0;
  transition:opacity .28s ease;z-index:300}
.mobile-nav-backdrop.is-open{opacity:1}
.mobile-nav{position:fixed;top:0;right:0;bottom:0;width:min(340px,86vw);background:#fff;z-index:301;
  display:flex;flex-direction:column;transform:translateX(100%);transition:transform .28s ease;
  box-shadow:-12px 0 40px hsla(216,60%,10%,.25);overflow-y:auto}
.mobile-nav.is-open{transform:translateX(0)}
.mobile-nav-head{display:flex;align-items:center;justify-content:space-between;
  padding:16px 18px;border-bottom:1px solid var(--border);flex:none}
.mobile-nav-logo{display:block;line-height:0}
.mobile-nav-logo img{height:40px;width:auto;display:block}
.mobile-nav-close{background:none;border:none;font-size:30px;line-height:1;color:var(--slate);
  cursor:pointer;padding:2px 8px}
.mobile-nav-links{display:flex;flex-direction:column;padding:6px 10px;flex:1}
.mobile-nav-links>a{display:block;padding:17px 12px;font-weight:700;font-size:16px;
  color:var(--navy);border-bottom:1px solid var(--border)}
.mobile-nav-links>a:active,.mobile-nav-links>a:hover{background:var(--ice,#EEF1F4);text-decoration:none}
.mobile-nav-group{border-bottom:1px solid var(--border)}
.mobile-nav-group-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;
  background:none;border:none;text-align:left;padding:17px 12px;font-weight:700;font-size:16px;
  color:var(--navy);cursor:pointer;font-family:inherit}
.mobile-nav-group-toggle .caret{transition:transform .2s;flex:none}
.mobile-nav-group.is-open .mobile-nav-group-toggle .caret{transform:rotate(180deg)}
.mobile-nav-submenu{max-height:0;overflow:hidden;transition:max-height .25s ease}
.mobile-nav-group.is-open .mobile-nav-submenu{max-height:640px}
.mobile-nav-submenu a{display:block;padding:13px 12px 13px 26px;font-size:14.5px;font-weight:600;
  color:var(--slate);border-top:1px solid var(--border)}
.mobile-nav-submenu a:active,.mobile-nav-submenu a:hover{background:var(--ice,#EEF1F4);color:var(--navy);text-decoration:none}
.mobile-nav-foot{padding:18px 18px 22px;border-top:1px solid var(--border);
  display:flex;flex-direction:column;gap:12px;flex:none}
.mobile-nav-cta{width:100%;justify-content:center;text-align:center;padding:13px}
.mobile-nav-phone{display:flex;align-items:center;justify-content:center;gap:8px;
  font-weight:700;color:var(--navy);font-size:16px}
.mobile-nav-phone svg{width:18px;height:18px;color:var(--cta)}

/* "Services" mega-menu — a wider, richer panel in place of the plain link
   list above, for the one dropdown with enough real pages behind it (8
   services) to be worth the extra real estate. Reuses .nav-dropdown-menu's
   own position/hover/reveal mechanics (see above); .mega just widens it and
   lays out two columns instead of a single stacked link list. */
.nav-dropdown-menu.mega{width:min(760px,calc(100vw - 48px));min-width:0;left:auto;right:-24px;
  transform:translateY(4px);padding:22px;display:flex;gap:26px;white-space:normal}
.nav-dropdown:hover .nav-dropdown-menu.mega{transform:translateY(0)}
.mega-grid{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:6px;align-content:start}
.mega-card{display:flex;gap:12px;align-items:flex-start;padding:12px;border-radius:10px}
.mega-card:hover{background:var(--ice,#EEF1F4)}
.mega-card-icon{flex:none;width:38px;height:38px;border-radius:10px;background:hsla(214,55%,38%,.1);
  color:var(--accent-strong);display:flex;align-items:center;justify-content:center}
.mega-card-icon svg{width:20px;height:20px}
.mega-card-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
.mega-card-title{font-weight:700;color:var(--navy);font-size:14.5px;white-space:normal}
.mega-card-desc{font-size:12.5px;color:var(--muted);line-height:1.45;white-space:normal}
.mega-side{flex:none;width:210px;background:var(--navy);border-radius:12px;padding:16px;
  display:flex;flex-direction:column;gap:14px}
.mega-side-photo{border-radius:8px;overflow:hidden;aspect-ratio:4/3}
.mega-side-photo img{width:100%;height:100%;object-fit:cover}
.mega-side-links{display:flex;flex-direction:column}
.mega-side-links h4{color:hsla(0,0%,100%,.55);font-size:11px;text-transform:uppercase;
  letter-spacing:.06em;font-weight:700;margin:0 0 8px}
.mega-side-links a{color:hsla(0,0%,100%,.92);font-size:13.5px;font-weight:600;
  padding:6px 0;white-space:normal}
.mega-side-links a:hover{color:#fff;text-decoration:underline}
.mega-cta{width:100%;justify-content:center;text-align:center;font-size:13.5px;padding:11px 10px}
@media(max-width:1040px){.nav-megawrap{display:none}}

/* hero (home) — split panel: solid navy copy panel on the left (fixed ~38%
   width), real looping video filling the right side. Mirrors ramjack.com's
   hero layout (dark panel + copy on the left, video bleeding the rest of the
   width) using Solid Foundations' own navy/orange instead of black/red. */
.hero{position:relative;overflow:hidden;background:var(--navy)}
.hero-grid{position:relative;display:grid;grid-template-columns:minmax(340px,42%) 1fr;min-height:78vh}
/* Kris's blueprint-style banner (same asset as the "What We Repair" grid
   background further down — public/media/blueprint-bg.jpg/.webp) behind the
   hero's text/form panel, replacing the old plain concrete-texture.jpg.
   The panel is narrow and tall (~42% width x ~78vh) while the source image
   is a wide banner (2400x1174) with its illustrated details — the helical
   pier/auger — near the LEFT edge and an open foggy gradient in the middle.
   background-position:center (the original setting) centered the "cover"
   crop on that empty middle, so none of the illustration ever showed.
   Shifted to left,left so the crop window lands on the auger artwork
   instead. Per Kris, the dark overlay is removed entirely (was .5/.6,
   before that .86/.9) — the raw image shows at full brightness now. Text
   contrast note: the headline/eyebrow are white and sit over the image's
   darker left-edge band, so they're still readable without the overlay;
   worth a live check after deploy in case any word lands on a lighter spot. */
.hero-panel{position:relative;z-index:2;display:flex;align-items:center;justify-content:center;padding:22px 40px;
  background-image:url(/media/blueprint-bg.jpg);
  background-size:cover;
  background-position:left;
  background-color:var(--navy)}
.hero-copy{max-width:540px}
/* No background overlay behind this text anymore (per Kris — the blueprint
   image shows at full brightness, uncovered). Its right side runs into the
   image's bright foggy center, so the white eyebrow/h1/tagline pick up a
   dark halo here instead — same technique as .vt-copy's light halo further
   down, just inverted for white-on-light instead of dark-on-light. Keeps
   contrast solid wherever the text happens to land, without dimming the
   image itself. The estimate form isn't included — it sits on its own
   solid card background and already has full contrast. */
.hero-copy>.eyebrow,.hero-copy h1,.hero-copy .hero-tagline{
  text-shadow:0 1px 3px hsla(214,70%,6%,.85),0 0 14px hsla(214,70%,6%,.6)}
/* same concrete+navy treatment as .hero-panel, so the brief gap before the
   video is ready reads as an intentional matching backdrop, not a blank void
   or a flash of the old crumbling-house photo */
/* Plain, un-textured backdrop for the video panel — this is a video "stage,"
   not a decorative surface, so it should read as an empty player about to
   play (like a normal <video>/iframe's idle state) rather than a patterned
   panel that then gets swapped out from under the video when it starts. The
   concrete texture stays on .hero-panel (the text side) only, per Kris. */
.hero-media{position:relative;overflow:hidden;background:#05090f}
.hero-media picture,.hero-media img{position:absolute;inset:0;width:100%;height:100%}
#heroFallbackPic{transition:opacity .3s ease}
.hero-media img{object-fit:cover;object-position:center center;transform-origin:48% 68%;
  animation:heroSettle 1.9s cubic-bezier(.31,1.32,.35,1) both}
@keyframes heroSettle{
  0%{transform:scale(1.02) rotate(0deg) translateY(-6px)}
  50%{transform:scale(1.02) rotate(-2.6deg) translateY(3px)}
  72%{transform:scale(1.02) rotate(-1.3deg) translateY(1px)}
  100%{transform:scale(1.02) rotate(-1.9deg) translateY(2px)}
}
@media (prefers-reduced-motion: reduce){.hero-media img{animation:none;transform:scale(1.02) rotate(-1.9deg) translateY(2px)}}
/* soft seam where the panel meets the video, like ramjack.com's edge — a
   white fade (per Kris, replacing the earlier yellow version) instead of
   navy */
.hero-media::before{content:"";position:absolute;inset:0;left:0;width:90px;z-index:2;pointer-events:none;
  background:linear-gradient(90deg,hsl(0 0% 100%) 0%,hsla(0,0%,100%,0) 100%)}
/* looping hero background video (real Solid Foundations jobsite footage) —
   desktop/tablet only; mobile and reduced-motion visitors keep the still photo
   above (also the fallback while the video is loading, and if it fails).
   Sized in JS against the actual media panel's box (not the viewport), since
   this panel is only part of the page width, not full-bleed. */
.yt-bg{position:absolute;inset:0;opacity:0;transition:opacity .8s ease}
.yt-bg.is-active{opacity:1}
.yt-bg iframe{position:absolute;top:50%;left:50%;border:0;pointer-events:none;transform:translate(-50%,-50%)}
.yt-bg video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;pointer-events:none}
.hero .eyebrow{color:#fff;margin-bottom:8px}
.hero h1{color:#fff;font-family:var(--font-head);font-size:clamp(38px,5.4vw,72px);font-weight:800;margin:0 0 6px;line-height:.98;
  letter-spacing:-.025em;max-width:680px;text-shadow:0 3px 16px rgba(0,0,0,.55)}
/* "cracking" — a clipped duplicate of the word renders a crack-line pattern only
   inside the letter shapes, then wipes in left-to-right like the fracture spreading */
.crack-word{position:relative;display:inline-block}
.crack-base{color:inherit}
.crack-fx{position:absolute;inset:0;left:0;top:0;color:transparent;
  background-image:url(/brand/crack-pattern.svg);background-size:100% 100%;background-repeat:no-repeat;
  -webkit-background-clip:text;background-clip:text;
  clip-path:inset(0 100% 0 0);animation:crackReveal .7s cubic-bezier(.2,.7,.3,1) 1.05s forwards}
@keyframes crackReveal{
  0%{clip-path:inset(0 100% 0 0)}
  85%{clip-path:inset(0 -2% 0 0)}
  100%{clip-path:inset(0 0% 0 0)}
}
@media (prefers-reduced-motion: reduce){.crack-fx{animation:none;clip-path:inset(0 0% 0 0)}}
/* .hero .sub is unused in any page's markup (checked sitewide) — left as
   dead CSS, harmless, restyled only for consistency in case a future page
   adds it back. .hero-tagline is the real supporting line under the H1
   (a short credentials string, e.g. "Family-Owned & Operated..."), so the
   rebrand's "hero paragraph" spec (Inter, larger, not all-caps) is applied
   there — sized a bit smaller than the literal 18px paragraph spec since
   this is a compact one-line credential string, not a full sentence. */
.hero .sub{font-family:var(--font-body);font-size:18px;font-weight:400;color:#EEF1F4;margin:0 0 26px;line-height:1.65;max-width:590px;text-shadow:0 2px 10px rgba(0,0,0,.4)}
.hero-tagline{color:#EEF1F4;font-family:var(--font-body);font-size:16px;font-weight:500;line-height:1.5;margin:0 0 22px;max-width:590px}
/* cracked-cement texture (public/brand/cracked-concrete.svg) with a blue
   gradient "overlay" multiply-blended on top — blue pulled from the real
   logo's ring color (sampled ~hsl(215 88% 51%)), not the site's orange accent,
   per Kris. Multiply-blend lets the concrete cracks read through the color
   instead of a flat solid card. */
/* Rebrand (Sept 2026): .ef-glass and .ef-frost used to be two different
   translucent "glass" treatments (a blue cracked-cement gradient card here,
   a frosted blur-over-photo card for the Orlando/geo hero forms). Per the
   rebrand brief both are consolidated into ONE consistent solid design —
   the class names stay (830+ pages already reference ef-frost, 2 reference
   ef-glass) but both now point at identical rules, aliased alongside the
   plain unmodified .estimate-card so every form on the site — hero, geo-page
   hero, and the plain in-page FAQ/contact forms — renders the same solid
   Deep Structural Navy card instead of 3 different looks. No backdrop-filter
   glass/blur anywhere any more. */
.estimate-card,.estimate-card.ef-glass,.estimate-card.ef-frost{
  background:rgba(8,28,51,.96);
  background-image:none;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  border:1px solid rgba(255,255,255,.20);
  border-top:5px solid var(--cta);
  border-radius:6px;max-width:430px;
  box-shadow:0 20px 55px rgba(0,0,0,.35)}
.ef-glass .ef-head,.ef-frost .ef-head{background:transparent;padding:14px 26px 8px;border-bottom:1px solid hsla(0,0%,100%,.18)}
.ef-glass .ef-head .eyebrow,.ef-frost .ef-head .eyebrow{color:#7DB7FF}
.ef-glass .ef-head h3,.ef-frost .ef-head h3{color:#fff}
.ef-glass .ef-head p,.ef-frost .ef-head p{color:#CBD5E1}
.ef-glass .ef-body,.ef-frost .ef-body{padding:10px 26px 12px}
.ef-glass .ef-field label,.ef-frost .ef-field label{color:#fff}
.ef-glass .ef-opt,.ef-frost .ef-opt{color:hsla(0,0%,100%,.58)}
.ef-glass .ef-field input,.ef-glass .ef-field textarea,
.ef-frost .ef-field input,.ef-frost .ef-field textarea{background:#fff;border-color:transparent}
.ef-glass .ef-alt,.ef-frost .ef-alt{color:hsla(0,0%,100%,.85)}
.ef-glass .ef-alt a,.ef-frost .ef-alt a{color:#fff;text-decoration:underline}
.ef-glass .ef-priv,.ef-frost .ef-priv{color:hsla(0,0%,100%,.62)}
@media(max-width:1080px){
  .hero-grid{grid-template-columns:minmax(300px,44%) 1fr}
}
@media(max-width:760px){
  .hero-grid{grid-template-columns:1fr;min-height:auto}
  .hero-panel{background:transparent;padding:64px 24px 48px}
  .hero-copy{max-width:none}
  .hero-media{position:absolute;inset:0;z-index:0}
  .hero-media::before{display:none}
  .hero-media::after{content:"";position:absolute;inset:0;z-index:1;
    background:linear-gradient(180deg,hsla(214,70%,10%,.5) 0%,hsla(214,66%,12%,.72) 55%,hsla(214,58%,15%,.88) 100%)}
  /* Background video now plays on phones too, per Kris — previously mobile
     always kept the still fallback photo (.yt-bg{display:none}) to save
     mobile data/battery, since autoplaying video isn't free on a cell
     connection. Kris asked for it anyway, so this rule is gone and the JS
     min-width:761px gate below is dropped too; the dark gradient above still
     keeps the form/text readable over the moving footage same as the photo. */
}
/* Persistent "tap to call" button, phones only — the header's phone number
   and BBB/trust badges all disappear below 560-760px to save space, so
   without this there's no one-tap call option while scrolling past the
   first screen. Desktop/tablet already has the header phone number and the
   Free Estimate button, so this stays hidden there. */
.mobile-call-fab{display:none}
@media(max-width:760px){
  .mobile-call-fab{
    display:flex;align-items:center;justify-content:center;
    position:fixed;right:18px;bottom:18px;z-index:60;
    width:56px;height:56px;border-radius:50%;
    background:var(--cta);color:#fff;
    box-shadow:0 10px 24px rgba(7,94,209,.45),0 2px 8px hsla(0,0%,0%,.2);
    transition:transform .15s;
  }
  .mobile-call-fab svg{width:24px;height:24px}
  .mobile-call-fab:hover,.mobile-call-fab:active{transform:scale(1.06);text-decoration:none;color:#fff;background:var(--cta-dark)}
}
@media(max-width:480px){.estimate-card.ef-glass{padding-bottom:16px}}
.estimate-band .split{align-items:center}
.estimate-band .estimate-card{margin:0 auto;max-width:460px}

/* video card — reused inside .local-video-band further down (was also its
   own standalone .video-band section; that section got merged in, per
   Kris, but this card's own styling is unchanged) */
.video-card{position:relative;border-radius:18px;overflow:hidden;box-shadow:0 22px 55px hsla(216,60%,15%,.2);aspect-ratio:16/9;background:#000}
.video-play{all:unset;position:absolute;inset:0;cursor:pointer;display:block;width:100%;height:100%}
.video-play img{width:100%;height:100%;object-fit:cover;display:block}
.video-play .play-btn{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.video-play .play-btn svg{width:72px;height:72px;transition:transform .15s}
.video-play:hover .play-btn svg{transform:scale(1.08)}
.video-card iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* estimate form card — solid Deep Structural Navy card + blue top accent,
   one consistent design shared by every .estimate-card variant sitewide
   (see the ef-glass/ef-frost consolidation above); this rule sets its
   internal spacing/overflow, color/border/shadow come from the shared
   selector above. */
.estimate-card{position:relative;padding:0 0 14px;
  width:100%;overflow:hidden}
.ef-head{background:transparent;padding:18px 26px 14px;border-bottom:1px solid hsla(0,0%,100%,.18)}
.ef-head .eyebrow{color:#7DB7FF;margin-bottom:6px;border-left:none;padding-left:0;font-size:14px}
.ef-head h3{color:#fff;font-family:var(--font-head);font-size:30px;font-weight:700;margin:0 0 6px;line-height:1}
.ef-head p{font-family:var(--font-body);font-size:14px;color:#CBD5E1;margin:0;line-height:1.5}
.ef-body{padding:10px 26px 0}
.ef-field{margin-bottom:10px}
.ef-row{display:flex;gap:12px}
.ef-row .ef-field{flex:1;min-width:0}
@media(max-width:480px){.ef-row{flex-direction:column;gap:0}}
.ef-field label{display:block;font-family:var(--font-body);font-size:13px;font-weight:600;color:#fff;margin-bottom:5px}
.ef-opt{font-weight:600;color:hsla(0,0%,100%,.58);text-transform:none}
.ef-field input,.ef-field textarea{width:100%;min-height:50px;padding:13px 15px;border:2px solid transparent;border-radius:3px;font-family:var(--font-body);font-size:15px;color:#242B33;background:#fff}
.ef-field textarea{resize:vertical;min-height:50px}
.ef-field input:focus,.ef-field textarea:focus{outline:none;border-color:#4D9AFF;box-shadow:0 0 0 3px rgba(7,94,209,.20)}
.ef-submit{width:100%;justify-content:center;margin-top:2px;font-size:16px;padding:13px}
.ef-alt{text-align:center;font-family:var(--font-body);font-size:13.5px;color:hsla(0,0%,100%,.75);margin:8px 0 0}
.ef-alt a{font-weight:700;color:#fff;text-decoration:underline}
.ef-priv{text-align:center;font-family:var(--font-body);font-size:11.5px;color:hsla(0,0%,100%,.55);margin:4px 0 0}
.ef-error{text-align:center;font-size:13.5px;font-weight:700;color:#ffb4b4;margin:8px 0 0}
.hp{position:absolute!important;left:-9999px!important;height:0;overflow:hidden}

/* trust strip below hero */
.trust-strip{background:#fff;border-bottom:1px solid var(--border)}
.trust-strip .ts-inner{display:flex;align-items:center;justify-content:center;gap:14px 30px;flex-wrap:wrap;padding:18px 24px}
.ts-item{display:flex;align-items:center;gap:9px;color:var(--slate);font-size:14.5px;font-weight:700}
.ts-item svg{width:20px;height:20px;color:var(--cta);flex:none}
.ts-dot{color:var(--border);font-weight:400}

/* sections */
section.band{padding:74px 0}
/* Trustindex review widget band, directly under every page's hero — tighter
   padding than a normal .band since the widget brings its own internal
   spacing, and a plain white background so it doesn't compete with the hero
   right above it. NOTE: this needs the section.band.reviews-widget-band
   compound selector, not .reviews-widget-band alone — a single class
   selector (0,1,0) loses the cascade to the plain section.band{padding:74px
   0} rule above (0,1,1), so the padding here was silently never applying
   (same bug pattern as the truck-drive-band fix). */
section.band.reviews-widget-band{padding:36px 0 250px;background:#fff;position:relative}
/* Force the Trustindex Google-review widget to show 2 cards per view
   instead of its default 3 (Kris: the flanking "Have Expectations"/"See
   What Others Are Saying" photos were covering the 3rd card at a lot of
   ordinary desktop widths, and he preferred keeping those photos their
   original size over shrinking them — so this makes room the other way).
   The widget's own markup ships a fixed ".ti-col-4" class (inspected the
   real CSS Trustindex serves: cdn.trustindex.io/assets/widget-presetted-
   css/v2/13-drop-shadow.css) that sets each card to flex:0 0 33.333% —
   always 3 up, at every screen width, it's not something their own CSS
   varies responsively. That "4" in the class name is their internal
   layout-preset id, not a live "show 4 columns" setting we can dial down
   from here — so instead this overrides just the one rule that actually
   controls how many cards fit per row. Card count is otherwise a setting
   in Trustindex's own dashboard (widget editor > layout), which we don't
   have login access to; this CSS override gets the same visual result
   without needing that. Higher specificity than Trustindex's own
   ".ti-col-4 .ti-review-item" rule (3 classes vs. their 2) wins this
   regardless of which stylesheet loads first, but !important is added
   anyway since their loader.js injects this CSS dynamically after page
   load and third-party widgets occasionally re-apply inline styles on
   their own resize/update cycles. There are 10 real reviews loaded into
   the widget's pool (confirmed in its own content.html), so the existing
   next/prev arrows still have plenty to cycle through 2 at a time. */
.reviews-widget-mount .ti-col-4 .ti-review-item{flex:0 0 50%!important;max-width:50%!important}
/* The Trustindex widget used to share this row with the "See What Others
   Are Saying" image via flexbox, each getting part of the width. That
   quietly broke the widget's own responsive layout: Trustindex sizes its
   card carousel off its mount div's actual width, and shrinking that div
   to make room for the image pushed it below the width Trustindex needs to
   show 3 cards side by side, so it fell back to a 1-card mobile view. Fix:
   the mount always keeps its full row width by default (.reviews-widget-
   mount is never resized for the image below 1560px) so the widget always
   gets to lay itself out the way it wants to — the image is an absolutely-
   positioned overlay that never affects the mount's own box, so it can
   share visual space with the widget without ever touching its measured
   width.
   At 1560px+ the mount IS deliberately narrowed (see the width rule further
   down) — that's safe now that we've confirmed (by reading Trustindex's own
   loader.js) their card-count fallback is a plain viewport media query
   (max-width:767px, portrait), not something that reacts to the mount's own
   width. Shrinking a desktop-width container down to ~800-1100px never gets
   near that phone-width trigger, so it can't cause the 1-card fallback the
   old note above was written to avoid — it works exactly like narrowing any
   normal container.
   Below 1560px there generally isn't a clean strip of empty space to the
   right of the review cards for the image to sit beside them (the widget's
   own card row can run close to the mount's full width at those sizes), so
   the image stacks below it instead — reserved into this section's own
   bottom padding, sized to fit there regardless of how tall the widget
   actually renders. From 1560px up there's reliably empty room on both
   sides of the cards (confirmed by the math below), so the image moves up
   beside them, sized close to the review row's own height so it reads as
   a matched pair instead of stacked. Hidden below the widget's own
   tablet/mobile breakpoint so it doesn't fight the cards for space on
   narrow screens. */
.reviews-widget-row{position:relative}
.reviews-widget-mount{width:100%}
.reviews-widget-side{position:absolute;top:100%;right:0;margin-top:22px;width:400px;max-width:40vw;pointer-events:none;z-index:2}
.reviews-widget-side img{display:block;width:100%;height:auto}
@media (max-width:1100px){.reviews-widget-side{width:340px}}
@media (max-width:900px){
  section.band.reviews-widget-band{padding-bottom:36px}
  .reviews-widget-side{display:none}
}
/* Reverted (Sept 2026) — Kris preferred the original look/size of these two
   photos over the shrink-to-fit fix tried earlier (kept here in git
   history, not in this file). Back to the original flat 400px/380px sizing.
   The breakpoint where they move beside the cards was 1400px originally;
   it's 1560px now (see the comment above and the mount-narrowing rule
   below) — moved up so the widget has real, measured, overlap-free room at
   the sizes most desktop visitors actually use, instead of just "sooner but
   still overlapping". Below 1560px both photos stay in their stacked-below
   position, same as they always have below 1400px. */
@media (min-width:1560px){
  section.band.reviews-widget-band{padding-bottom:48px}
  .reviews-widget-side{top:0;right:calc(32px - (100vw - 1180px) / 2);margin-top:0;width:400px;max-width:32vw}
}
/* "Have Expectations? We Meet 'em" — mirrors .reviews-widget-side but sits
   in the LEFT page margin instead, using the same outward-into-the-gutter
   math (see the comment above) flipped to "left". Ships as its own PNG/
   WebP with real transparent edges already baked in (soft fade to alpha 0,
   confirmed by sampling corner pixels — not just a dark background), so it
   blends onto the white band without needing a crop or color-key pass like
   the drill/truck accent videos did. Hidden below 1560px for the same
   reason as .reviews-widget-side: there's no real gutter to sit in yet. */
.reviews-widget-side-left{position:absolute;display:none;top:0;left:0;width:380px;max-width:30vw;pointer-events:none;z-index:2}
.reviews-widget-side-left img{display:block;width:100%;height:auto}
@media (min-width:1560px){
  .reviews-widget-side-left{display:block;left:calc(32px - (100vw - 1180px) / 2)}
}
/* Narrows the Trustindex mount itself once the photos move beside the cards
   (1560px+), so the 2 cards (see .ti-col-4 override above) get real,
   reserved room instead of running their full row width and sliding under
   the right-hand photo. Worked out from the exact position math of both
   flanking photos so the widget clears BOTH of them with a real gap, not
   just the one that got screenshotted:
   - the right photo's left edge always sits at (100vw - 432px) — a
     constant 32px in from the viewport's right edge, since its own
     right:calc(32px - (100vw-1180px)/2) offset cancels the container's
     recentering math down to a flat vw-based constant;
   - this formula (110px + 50vw) puts the mount's right edge a steady 24px
     inside that line at every width from 1560px up, and — because the
     Trustindex footer/profile block reliably eats the mount's own first
     25%, confirmed on the live render — still leaves the first review card
     clear of the LEFT photo too, with growing (never shrinking) breathing
     room as the screen gets wider;
   - capped at 100% because above ~2044px wide there's already enough
     natural gutter that no narrowing is needed at all, and 110px+50vw would
     otherwise overshoot past the container's own width. */
@media (min-width:1560px){
  .reviews-widget-mount{width:min(100%, calc(110px + 50vw))}
}
section.band.cream{background:var(--cream)}
section.band.navy{background:linear-gradient(120deg,var(--navy),var(--navy-2));color:#fff}
section.band.navy h2,section.band.navy h3{color:#fff}
.section-head{max-width:760px;margin-bottom:38px}
.section-head.center{margin-left:auto;margin-right:auto;text-align:center}
/* Section heading scale bumped up toward the rebrand's 48-56px desktop
   spec (was clamp(27px,3.6vw,38px)) — capped at 48px rather than the full
   56px so two-column layouts sharing this class (.split, .lv-top, etc.)
   don't start wrapping/overflowing at their existing widths; 48px still
   reads as a clear, confident size increase from the old 38px max. */
.section-head h2{font-family:var(--font-head);font-size:clamp(28px,4.2vw,48px);font-weight:700;line-height:1.02;color:var(--navy);margin:.12em 0 20px;max-width:760px}
/* Structural divider under every section heading — 64x5px solid blue bar,
   square ends, no pill shape. */
.section-head h2::after{content:"";display:block;width:64px;height:5px;background:var(--cta);margin-top:16px}
.section-head.center h2::after{margin-left:auto;margin-right:auto}
.lead{color:var(--slate);font-size:18px}
section.band.navy .lead{color:hsla(0,0%,100%,.82)}

/* service cards */
.grid{display:grid;gap:20px}
.grid.cols-3{grid-template-columns:repeat(3,1fr)}
.grid.cols-2{grid-template-columns:repeat(2,1fr)}
.grid.cols-4{grid-template-columns:repeat(4,1fr)}
@media(max-width:900px){.grid.cols-3,.grid.cols-4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.grid.cols-2,.grid.cols-3,.grid.cols-4{grid-template-columns:1fr}}
.card{background:#fff;border:1px solid #D9E0E7;border-top:4px solid var(--cta);border-radius:4px;padding:28px;box-shadow:0 10px 30px rgba(16,45,84,.08);transition:transform .18s,box-shadow .18s,border-color .18s}
.card:hover{transform:translateY(-5px);box-shadow:0 16px 38px rgba(16,45,84,.14);border-top-color:var(--cta-dark);text-decoration:none}
.card .ic{width:40px;height:40px;border-radius:4px;background:transparent;display:flex;align-items:center;justify-content:center;margin-bottom:16px}
.card .ic svg{width:26px;height:26px;color:var(--cta);stroke-width:2}
.card h3{font-family:var(--font-head);font-size:20px;font-weight:700;margin:0 0 8px;color:var(--navy)}
.card p{color:var(--slate);font-size:15px;margin:0 0 14px}
.card .more{font-weight:700;font-size:14px;color:var(--cta-dark)}
/* /blog/ index cards reuse the plain .card above (no thumbnail — the posts
   don't have dedicated photography and a stock/generic image would read as
   filler) with one small addition: a category tag above the title so the
   grid reads as organized topics rather than a flat list. */
.card .blog-cat{display:block;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-strong);margin-bottom:8px}
/* photo-led service cards: real jobsite photo (or, for the 3 services without
   one yet, an original topic diagram) fills the top of the card, full-bleed
   to the rounded corners, with the existing text block below unchanged */
.card-photo{padding:0;overflow:hidden;display:block}
.card-img{aspect-ratio:16/10;overflow:hidden;background:#e7edf2}
.card-img img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .25s}
.card-photo:hover .card-img img{transform:scale(1.04)}
/* diagrams (void filling, helical piles, soil stabilization) are flat vector
   art on a light tint, not a photo — object-fit:contain keeps them from
   being cropped like a photo would be */
.card-img-diagram{background:#eaf1f9}
.card-img-diagram img{object-fit:contain}
.card-body{padding:20px 24px 26px}
.card-body h3{font-family:var(--font-head);font-size:20px;font-weight:700;margin:0 0 8px;color:var(--navy)}
.card-body p{color:var(--slate);font-size:15px;margin:0 0 14px}
.card-body .more{font-weight:700;font-size:14px;color:var(--cta-dark)}
/* "Meet the Team" roster on the About page (teamGrid() in build.js) — a
   circular-avatar tile per person instead of the boxed .card treatment
   above, since a wall of 29 photo cards with full card padding/borders/
   shadows would feel heavy; a plain avatar+name+title reads more like a
   team directory and keeps that many repeats visually light. Grouped by
   department, each its own heading + grid, matching the source site's
   own department grouping. */
.team-dept{margin-bottom:40px}
.team-dept:last-child{margin-bottom:0}
.team-dept-name{font-size:13px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent-strong);margin:0 0 20px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.team-grid{row-gap:28px}
.team-card{text-align:center}
.team-photo{display:block;width:120px;height:120px;margin:0 auto 14px;border-radius:50%;overflow:hidden;
  box-shadow:0 6px 18px hsla(216,60%,15%,.14)}
.team-photo img{width:100%;height:100%;object-fit:cover;display:block}
.team-name{font-weight:700;color:var(--navy);margin:0;font-size:15px}
.team-title{color:var(--muted);margin:2px 0 0;font-size:13.5px}
@media(max-width:600px){.team-photo{width:96px;height:96px}}
/* homepage "What We Repair" section: pinned to a clean white band (not the
   page's off-white --bg), each service card carries a soft brand-blue glow
   so the clickable cards read as distinct, inviting targets */
/* z-index:1 (not just position:relative) is what lets the drill accent
   below visually break through into the next section (.video-testi-band):
   without it, that later sibling section — itself position:relative but
   z-index:auto — would simply paint over it in DOM order, hiding the drill
   behind the yellow band instead of on top of it. */
.home-services{background:#fff;position:relative;z-index:1}
/* Helical-pier drill graphic that bores down the margins beside the service
   grid — see drillDescendAccent() in build.js for why this is a native
   transparent (VP9-alpha WebM) <video> rather than a plain mp4, and for the
   scroll-linked vertical position. v4 (Kris's "drill correct" clip — same
   weathered auger, cleaner/crisper blue rim-light outline than v3) is
   cropped tight to the drill itself the same way v3 was (source had ~100px
   of dead transparent space above and below it that would otherwise leave
   a gap between the section's top and where the drill actually starts).
   This clip's own crop is a slightly different aspect ratio than v3's, so
   the height below is re-tuned to match it (150px wide * its 260:1122
   native ratio) rather than reusing v3's number. Positioned a fixed
   distance from the actual browser-window edge (not the 1180px container)
   per Kris — gated to wide screens (>=1680px) so that fixed edge position
   can never collide with the container itself; hidden below that,
   including tablets and most laptops.
   No drop-shadow filter and no dark halo behind it any more — Kris asked
   for the shadow gone; earlier there was a blurred dark radial-gradient
   ::before behind the video (meant to help the clip's neon-blue rim light
   read against this section's pale background, since that glow was shot to
   be seen against black) but it was reading as an unwanted shadow around
   the drill, so it's removed too. If the blue rim light looks washed out
   on this light background without it, that's the tradeoff — worth a look
   on the live site before deciding whether it needs some other fix.
   Left/right lane distance from the browser edge moved in from 40px to
   90px (Kris: "move them a bit towards the service cards") — still pinned
   to the true window edge rather than the 1180px container (see above),
   just a smaller margin. drill-breakthrough below (in the .video-testi-band
   rules) reuses these same two numbers so the torn-edge graphic lines up
   under whichever lane distance is set here. */
.drill-accent{position:absolute;top:0;width:90px;height:388px;pointer-events:none;z-index:5;display:none}
.drill-accent-video{display:block;width:100%;height:100%;object-fit:contain}
.drill-accent-left{left:24px}
.drill-accent-right{right:24px}
/* Kris: "the helical piers driving down the home page are gone completely."
   Root cause — the >=1680px-only gate above meant almost no real visitor
   ever saw this (most laptop/desktop browser windows, including Kris's own
   while checking the site, run well under 1680px wide; a plain 15" laptop
   is commonly 1440-1536px). Fix: show a smaller version starting at a much
   more common 1440px, sized/inset to still clear the 1180px container with
   margin at that width, then step back up to the original full-size
   150x647 @ 90px lane once there's enough true window-edge margin to do
   that safely (>=1680px, same math as before — untouched). */
@media (min-width:1440px){.drill-accent{display:block}}
@media (min-width:1680px){
  .drill-accent{width:150px;height:647px}
  .drill-accent-left{left:90px}
  .drill-accent-right{right:90px}
}

/* border-color shorthand here used to also dim the card's own border-top
   accent to a barely-visible 22%-opacity tint (border-color sets all 4
   sides) — split out so the side/bottom border keeps its soft glow tint
   but border-top stays the full-strength brand-blue accent the rebrand
   spec calls for on every service card. */
.home-services .card{border-color:hsla(214,80%,60%,.22);border-top-color:var(--cta);
  box-shadow:0 0 0 1px hsla(214,90%,55%,.06),0 12px 30px hsla(214,88%,52%,.16),0 6px 18px hsla(216,60%,15%,.05)}
.home-services .card:hover{transform:translateY(-4px);
  box-shadow:0 0 0 1px hsla(214,90%,55%,.14),0 20px 46px hsla(214,88%,52%,.28),0 10px 24px hsla(216,60%,15%,.08)}
/* Shared homepage-only background: a light, mottled marble/concrete texture
   (public/brand/light-concrete-texture.svg — hairline veining + fine grain,
   same visual language as the site's existing cracked-concrete.svg but in a
   light tone) with a soft light-blue tint multiplied on top, behind the
   reviews widget + "What We Repair" grid — per Kris, matching the card-
   section background on dbsrepair.com, instead of the flat white/off-white
   each band uses on its own elsewhere (interior pages keep the plain white
   reviews band). Applied as an extra class directly on each of the two
   sibling <section>s, NOT a wrapping div (a wrapping div was tried and
   removed while chasing an unrelated Trustindex widget issue — see the note
   on reviewsWidgetBand(); it turned out not to be the cause, but this stayed
   un-wrapped since two independent sections work fine here). The texture
   tile repeats identically on both sections at the same fixed pixel size,
   so the seam between them isn't obvious even though it isn't pixel-perfect
   continuous. */
.texture-showcase{background-color:#f1f3f5;
  background-image:
    linear-gradient(160deg,hsla(212,70%,60%,.16),hsla(206,60%,70%,.08)),
    url(/brand/light-concrete-texture.svg);
  background-size:auto,700px 700px;
  background-repeat:repeat,repeat;
  background-blend-mode:multiply,normal}

/* "What We Repair" grid only: Kris's blueprint-style banner (navy edges,
   helical-pier sketches left/right, soft light vignette in the middle)
   replacing the shared light-concrete texture for just this section — the
   reviews-widget-band instance above keeps the original light texture
   unchanged. Same width-driven sizing rationale as the testimonials banner
   (see .video-testi-band): scale by width only, not `cover`, so the pier
   sketches at the left/right edges are never cropped off; only top/bottom
   ever gets trimmed. background-color fallback is sampled from the image's
   own dark navy edge, since on a very tall (mobile stacked) section any
   leftover space below the image is at the BOTTOM, which the image's own
   bottom edge is already that same dark navy — not a bright color a
   safety net would need to guard against here (contrast handled below).
   Selector is a specific compound (section + 3 classes) so it beats both
   the shared .texture-showcase rule above and the plain section.band.navy
   background further down without relying on source order. */
/* At this section's normal rendered width (~1450-1920px) the single
   width-scaled banner above squeezes the pier-sketch corner motifs (drawn
   for a 2400px-wide canvas) down to a nearly-invisible sliver — Kris
   pointed this out directly with a reference banner (the /about/ hero
   below) showing how large/legible these motifs should read. Fix: two
   extra fixed-size layers stacked on top of the same width-scaled wash,
   each a tighter crop of just the auger (left) or the pier (right) held at
   a constant 260px display width regardless of viewport, so the linework
   stays crisp and readable instead of shrinking with the page. */
section.home-services.texture-showcase{
  background-color:#00143a;
  background-image:
    url(/media/motif-auger.jpg),
    url(/media/motif-pier.jpg),
    url(/media/blueprint-bg.jpg);
  background-image:
    image-set(url(/media/motif-auger.webp) type("image/webp"), url(/media/motif-auger.jpg) type("image/jpeg")),
    image-set(url(/media/motif-pier.webp) type("image/webp"), url(/media/motif-pier.jpg) type("image/jpeg")),
    image-set(url(/media/blueprint-bg.webp) type("image/webp"), url(/media/blueprint-bg.jpg) type("image/jpeg"));
  background-repeat:no-repeat,no-repeat,no-repeat;
  background-position:left top,right top,center top;
  background-size:260px auto,260px auto,100% auto;
  /* IMPORTANT: explicit "normal" here, not left to inherit. The shared
     .texture-showcase rule further up sets background-blend-mode:multiply,
     normal for its own (unrelated) 2-layer light texture. This selector is
     more specific and overrides every other background-* property, but
     blend-mode was never listed here before, so it fell through to that
     shared value — and with 3 image layers now instead of 2, the 2-value
     list "multiply,normal" cycles to "multiply,normal,multiply", silently
     multiplying BOTH the auger layer and the base wash against the dark
     background-color. That's what was actually making everything read as
     near-black no matter how the image itself was cropped or sized — not
     something an image change alone could ever have fixed. */
  background-blend-mode:normal}
@media(max-width:900px){
  section.home-services.texture-showcase{background-size:170px auto,170px auto,100% auto}
}
@media(max-width:600px){
  /* Both motifs at once get tight against the single-column card stack on
     phones — drop them and keep just the wash, same as before this fix.
     background-blend-mode:normal repeated here for the same reason as
     above — this is back to a single image layer, but without it, the
     1-layer image would still inherit "multiply" from .texture-showcase's
     cycled 2-value list. */
  section.home-services.texture-showcase{background-image:url(/media/blueprint-bg.jpg);
    background-image:image-set(url(/media/blueprint-bg.webp) type("image/webp"), url(/media/blueprint-bg.jpg) type("image/jpeg"));
    background-repeat:no-repeat;background-position:center top;background-size:100% auto;
    background-blend-mode:normal}
}
/* Jagged seam where the light texture band above meets this new image —
   same pixel-continuation trick as .video-testi-band::before: repeats the
   SAME image at the same width-driven scale, offset down by exactly this
   element's own 16px overhang, so the two paints line up and the tear
   reveals a seamless continuation instead of a hard color cut. */
.home-services.texture-showcase::before{
  content:"";position:absolute;left:0;right:0;top:-16px;height:24px;
  background-color:#00143a;
  background-image:url(/media/blueprint-bg.jpg);
  background-image:image-set(
    url(/media/blueprint-bg.webp) type("image/webp"),
    url(/media/blueprint-bg.jpg) type("image/jpeg")
  );
  background-repeat:no-repeat;background-position:center 16px;
  background-size:100% auto;
  clip-path:polygon(
    0% 46%,3% 62%,6% 33%,9% 58%,13% 40%,17% 67%,21% 36%,
    25% 60%,29% 42%,33% 71%,37% 38%,41% 55%,45% 34%,
    49% 64%,53% 44%,57% 69%,61% 37%,65% 58%,69% 41%,
    73% 66%,77% 35%,81% 61%,85% 43%,89% 68%,93% 39%,
    97% 57%,100% 45%,100% 100%,0% 100%
  );
  z-index:2;pointer-events:none}
/* The section-head text (eyebrow/h2/lead) needs to flip to a light color
   scheme here — checked against an actual render, and at the section
   heights this grid runs (2 rows of cards, 74px band padding above it) the
   heading consistently lands on the image's darker upper band, well above
   its lighter center vignette, not on it. So: same light-on-dark treatment
   already established for section.band.navy elsewhere on the site (white
   h2, translucent-white lead), plus the same warm accent already used for
   the hero's eyebrow on its own dark background, rather than the default
   dark-navy/slate colors tuned for a light section. A dark text-shadow adds
   a little edge definition in case any of this ever does land on the
   lighter vignette instead. The "View all services" link below the grid
   is swapped from btn-ghost-light to btn-ghost in build.js for the same
   reason — its dark-navy text was unreadable here too. */
.home-services.texture-showcase .section-head h2,
.home-services.texture-showcase .section-head h3{
  color:#fff;text-shadow:0 2px 10px hsla(214,70%,8%,.45)}
.home-services.texture-showcase .section-head .eyebrow{
  color:#fff;text-shadow:0 1px 6px hsla(214,70%,8%,.4)}
.home-services.texture-showcase .section-head .lead{
  color:hsla(0,0%,100%,.85);text-shadow:0 1px 6px hsla(214,70%,8%,.4)}

/* Where the texture section meets the dark hero above it, replace the
   perfectly straight section seam with a jagged "broken concrete edge"
   instead — a thin overlapping bar carrying the same texture background,
   cut with an irregular polygon so it reads as a crack line rather than a
   ruled edge. Only the reviews-widget-band instance needs this (it's the
   one directly under the hero); the second texture-showcase section further
   down butts against the first one, not the hero, so it stays a plain seam.
   (position:relative for this now lives on the base section.band.reviews-
   widget-band rule above, needed there for the image-below layout too.) */
.reviews-widget-band.texture-showcase::before{
  content:"";position:absolute;left:0;right:0;top:-16px;height:24px;
  background-color:#f1f3f5;
  background-image:
    linear-gradient(160deg,hsla(212,70%,60%,.16),hsla(206,60%,70%,.08)),
    url(/brand/light-concrete-texture.svg);
  background-size:auto,700px 700px;
  background-repeat:repeat,repeat;
  background-blend-mode:multiply,normal;
  clip-path:polygon(
    0% 46%,3% 62%,6% 33%,9% 58%,13% 40%,17% 67%,21% 36%,
    25% 60%,29% 42%,33% 71%,37% 38%,41% 55%,45% 34%,
    49% 64%,53% 44%,57% 69%,61% 37%,65% 58%,69% 41%,
    73% 66%,77% 35%,81% 61%,85% 43%,89% 68%,93% 39%,
    97% 57%,100% 45%,100% 100%,0% 100%
  );
  z-index:2;pointer-events:none}

/* "Foundation Repair Testimonials" band — rebuilt from the same section on
   the live solidfoundations.com homepage (video, quote, 3-photo collage).
   Background is Kris's supplied navy/gold banner graphic (public/media/
   testimonials-banner.jpg + .webp, source: 74636d93-image.png) replacing
   the flat light-yellow fill. Sized by WIDTH only (100% auto), not the
   more usual `cover` — this graphic's whole design is a decorative frame
   (navy corner wedges, hex/dot texture, wavy divider) so the corners must
   never be cropped off the left/right edges the way `cover` could do on a
   tall/narrow viewport; scaling purely by width keeps both corners intact
   at every screen size and only ever trims the top/bottom, which matters
   far less. background-color is a fallback sampled from the image's own
   light cream/gold center for any leftover space below the image on very
   tall (mostly mobile, stacked-grid) sections — deliberately light rather
   than matching the image's actual navy bottom edge, so text further down
   the section never lands on an unreadably dark fallback fill. */
.video-testi-band{
  background-color:#f7dfa0;
  background-image:url(/media/testimonials-banner.jpg);
  background-image:image-set(
    url(/media/testimonials-banner.webp) type("image/webp"),
    url(/media/testimonials-banner.jpg) type("image/jpeg")
  );
  background-repeat:no-repeat;background-position:center top;
  background-size:100% auto;
  position:relative}
/* Same jagged "broken concrete edge" treatment as the hero/texture seam
   above, mirrored here where the texture band ends and this section's
   banner background begins. Unlike the old flat-color version, this now
   has to reveal a genuinely busy image rather than a solid color, so
   instead of a separately-colored overlay it repeats the SAME banner
   image at the SAME width-driven scale as the section itself, offset
   vertically by exactly this element's own 16px top overhang
   (background-position-y:16px shifts the image's own row 0 down 16px
   inside this 24px box, landing it at the same page position where the
   section below starts drawing its row 0) — so the two paints line up
   pixel-for-pixel and the jagged cut reveals a seamless continuation of
   the banner rather than a mismatched color. */
.video-testi-band::before{
  content:"";position:absolute;left:0;right:0;top:-16px;height:24px;
  background-color:#f7dfa0;
  background-image:url(/media/testimonials-banner.jpg);
  background-image:image-set(
    url(/media/testimonials-banner.webp) type("image/webp"),
    url(/media/testimonials-banner.jpg) type("image/jpeg")
  );
  background-repeat:no-repeat;background-position:center 16px;
  background-size:100% auto;
  clip-path:polygon(
    0% 46%,3% 62%,6% 33%,9% 58%,13% 40%,17% 67%,21% 36%,
    25% 60%,29% 42%,33% 71%,37% 38%,41% 55%,45% 34%,
    49% 64%,53% 44%,57% 69%,61% 37%,65% 58%,69% 41%,
    73% 66%,77% 35%,81% 61%,85% 43%,89% 68%,93% 39%,
    97% 57%,100% 45%,100% 100%,0% 100%
  );
  z-index:2;pointer-events:none}
/* Torn-edge "drill broke through here" patch — restored per Kris (see
   drillDescendAccent()'s comment in build.js: the drill accent travels
   down into this band again). A small dark, jagged debris shape sitting
   right at the jagged seam (top:-14px, matching the seam's own -16px
   overhang above), at the same 90px left/right lane offsets as the drill
   itself so the two line up. Same desktop-only breakpoint as the drill —
   no point showing a "broken" seam where the drill it's marking doesn't
   render. */
.drill-breakthrough{
  position:absolute;top:-14px;width:96px;height:46px;
  background:radial-gradient(ellipse at center, hsla(216,60%,8%,.4), hsla(216,60%,8%,0) 70%);
  clip-path:polygon(10% 0%,30% 40%,0% 55%,22% 78%,5% 100%,45% 92%,55% 100%,
    72% 76%,50% 55%,78% 42%,60% 15%,88% 30%,100% 0%);
  z-index:3;pointer-events:none;display:none}
.drill-breakthrough-left{left:90px}
.drill-breakthrough-right{right:90px}
@media (min-width:1680px){.drill-breakthrough{display:block}}
.video-testi-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:56px;align-items:center;padding:64px 0}
/* .vt-play used to be a bare icon (background:none) sitting directly on
   the section's own background. That was fine against the old flat pale
   yellow, but against the new banner image this button can land on its
   dark navy corner wedge — on mobile, where this content starts almost
   at the top of the section, it does — and a navy icon on a navy photo
   is invisible. Giving it a solid white disc makes it read as a normal
   "play" button chip regardless of what's behind it. */
.vt-play{width:60px;height:60px;border-radius:50%;border:0;
  background:#fff;box-shadow:0 4px 14px hsla(220,45%,20%,.22);color:var(--navy);
  padding:0;margin-bottom:24px;cursor:pointer;transition:transform .15s,color .15s,box-shadow .15s}
.vt-play:hover{transform:scale(1.08);color:var(--accent-strong);box-shadow:0 6px 18px hsla(220,45%,20%,.3)}
.vt-play svg{width:100%;height:100%;display:block}
/* Same reasoning as .vt-play above: this copy block's text colors (dark
   blue/navy/slate) were tuned for the old flat pale-yellow fill. Against
   the new banner image's own dark navy corner, on mobile this block can
   start close enough to the top that the heading lands partly on it — a
   light halo (text-shadow, doubled up to fake a soft stroke; -webkit-
   text-stroke as a crisper add-on where supported) keeps every line
   readable against either the image's light gold center or its dark navy
   edge, without changing how it looks over the plain light area, where
   the effect is subtle to invisible. Reset to none on the button, which
   already has full contrast from its own solid fill. */
.vt-copy{text-shadow:0 1px 2px hsla(0,0%,100%,.75),0 0 12px hsla(0,0%,100%,.55)}
.vt-copy .btn{text-shadow:none}
.vt-copy h2{font-size:clamp(26px,3.2vw,38px);text-transform:uppercase;font-weight:800;
  color:var(--accent-strong);margin:0 0 20px;line-height:1.15;
  -webkit-text-stroke:.6px hsla(0,0%,100%,.7)}
.vt-quote-mark{font-family:var(--font-head);font-size:50px;line-height:.6;
  color:hsl(220 9% 68%);margin:0 0 6px;font-weight:700}
.vt-quote{color:var(--slate);font-size:16.5px;line-height:1.65;margin:0 0 20px;max-width:48ch}
.vt-name{font-weight:700;color:var(--navy);margin:0 0 26px;font-family:var(--font-head)}
.vt-collage{position:relative;min-height:440px}
.vt-photo{position:absolute;border-radius:10px;box-shadow:0 18px 34px hsla(220,45%,20%,.24);
  width:auto;height:auto;max-width:none}
.vt-photo-2{left:0;top:10%;width:56%;z-index:2}
.vt-photo-3{right:2%;bottom:0;width:50%;z-index:3}
@media(max-width:900px){
  .video-testi-grid{grid-template-columns:1fr;gap:36px;padding:48px 0}
  .vt-collage{min-height:340px;max-width:420px;margin:0 auto;width:100%}
}
@media(max-width:480px){.vt-collage{min-height:280px}}
/* Play-button video modal for the testimonials section above — a small
   on-page lightbox (not the inline thumbnail-swap pattern videoSection()
   uses) since this button is a small icon, not a full video thumbnail. */
.vt-modal{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;padding:24px}
.vt-modal[hidden]{display:none}
.vt-modal-backdrop{position:absolute;inset:0;background:hsla(220,40%,8%,.78)}
.vt-modal-inner{position:relative;width:100%;max-width:880px;z-index:1}
.vt-modal-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000;border-radius:10px;overflow:hidden}
.vt-modal-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.vt-modal-close{position:absolute;top:-42px;right:0;width:34px;height:34px;border-radius:50%;border:0;
  background:hsla(0,0%,100%,.14);color:#fff;font-size:22px;line-height:1;cursor:pointer}
.vt-modal-close:hover{background:hsla(0,0%,100%,.26)}

/* Decorative gap band leading into local-video-band below: gives the truck
   graphic room to drive across it. background matches --bg (the next
   section's background) so it reads as one continuous light backdrop, not
   a hard-edged box. The image is sized and pulled up (negative top,
   overflow-y left open) so the cab pokes up over the boundary above it per
   Kris. Its horizontal position is set by JS in truckDriveBand() directly
   from scroll progress — CSS here only sets the starting/no-JS position and
   the stage's size. Padding is intentionally tight now that the truck
   itself bridges the gap visually.
   z-index:2 is required, not decorative: the truck overlaps BOTH neighbors
   (up into video-testi-band above, down into local-video-band below). Plain
   DOM order alone only wins one of those two overlaps (a later sibling
   paints over an earlier one) — without an explicit z-index here, the truck
   would sit on top of the section above but get painted OVER by
   local-video-band's own opaque background below, clipping it. Same
   stacking-context fix as .home-services's z-index for the drill accent;
   see that comment for the general rule.
   overflow-x:clip (not overflow-x:hidden) is required here, not a style
   preference. Per the CSS overflow spec, "hidden" is a scroll-container
   value — if it's set on one axis and the other axis is "visible", the
   "visible" axis is silently forced to compute as "auto" instead (this
   happens even if overflow-y:visible is set explicitly; the spec pairing
   rule overrides it either way — tried that first and it did not fix
   anything). Since this section's content genuinely overflows vertically
   on purpose (the truck pokes up/down into its neighbors, see above), that
   forced auto became a real, space-consuming vertical scrollbar on this
   section in real desktop Chrome (confirmed via getComputedStyle:
   overflowY came back "auto" even with overflow-y:visible set; clientWidth
   was ~15-17px narrower than offsetWidth) — invisible in this project's
   usual headless Playwright checks (overlay scrollbars, no layout impact)
   but very much visible to Kris, who reported the truck "cut off on both
   sides." The scrollbar both ate into the JS's travel-width math
   (stage.clientWidth) AND put the vertically-overflowing part of the truck
   inside a clipped, scrollable box instead of letting it bleed via the
   z-index above. "clip" is a newer overflow value that hard-clips an axis
   without turning the element into a scroll container, so it's exempt from
   that visible/auto pairing rule — overflow-y can stay visible for real.
   overflow-y is left unset (defaults to visible) since explicitly setting
   it made no difference and isn't needed once overflow-x is "clip". */
section.band.truck-drive-band{background:var(--bg);position:relative;z-index:2;overflow-x:clip;padding:6px 0 20px}
.truck-drive-stage{position:relative;width:100%;height:210px}
.truck-drive-img{position:absolute;top:-38px;left:0;width:820px;max-width:76vw;height:auto;
  transform:translate(0,0);will-change:transform;
  filter:drop-shadow(0 18px 22px hsla(214,55%,15%,.22))}
@media (max-width:760px){
  section.band.truck-drive-band{padding:2px 0 14px}
  .truck-drive-stage{height:170px}
  .truck-drive-img{width:460px;max-width:86vw;top:-10px}
}
@media (prefers-reduced-motion:reduce){
  .truck-drive-img{transition:none}
}

/* Combined "Call a Location Near You" + video + rotating-word CTA band —
   per Kris, these were 3 separate sections and are now one (see
   localVideoBand() in build.js for the full rationale). Top row: compact
   phone-number grid alongside the testimonial video. Bottom: the
   full-width rotating-word headline, styled after the "Are You Seeing
   Cracks in Floor?" section on ramjack.com, same as before the merge. */
.local-video-band{background:var(--ice)}
/* Per Kris: this section runs wider than the sitewide 1180px container so
   the number cards have room to breathe and the phone numbers stay on one
   line — a plain .container here was too narrow at 4 columns. */
.local-video-band .container{max-width:1440px}
/* align-items:stretch (was center) so the numbers column matches the video's
   full height per Kris: "verbiage should start at the top of the video and
   numbers should stretch to bottom of the video so formating is even."
   Numbers column got a bigger share of the row (1.3fr vs 1fr) since it needs
   the extra width for one-line phone numbers; the video can afford to be
   relatively narrower and still read as the focal point. */
.lv-top{display:grid;grid-template-columns:1.3fr 1fr;gap:44px;align-items:stretch}
.lv-numbers-col{display:flex;flex-direction:column;height:100%}
.lv-numbers-col .section-head{margin-bottom:14px}
.lv-numbers-col .section-head h3{font-size:clamp(19px,2.2vw,24px);font-weight:800;color:var(--navy);margin:4px 0 0}
/* Compact variant of the original .lnc-card grid — this now shares the row
   with the video instead of spanning the full container. flex:1 + explicit
   row template lets the 2 rows of 4 cards stretch to fill the column's full
   height (matching the video), and the cards themselves got bigger per Kris
   ("the numbers need to be bigger"). white-space:nowrap on the city/phone
   text (below) plus the wider container above keep everything on one line. */
.lnc-grid-compact{display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);gap:16px;flex:1}
.lnc-card-compact{background:#fff;border:1px solid var(--border);border-radius:12px;
  padding:16px 10px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:7px;
  transition:transform .15s,box-shadow .15s,border-color .15s}
.lnc-card-compact:hover{transform:translateY(-2px);box-shadow:0 10px 20px hsla(214,50%,20%,.14);
  border-color:hsla(214,55%,55%,.45);text-decoration:none}
.lnc-city-compact{font-size:12.5px;font-weight:700;letter-spacing:.01em;color:var(--accent-strong);white-space:nowrap}
.lnc-phone-compact{font-weight:800;font-size:16px;color:var(--navy);font-family:var(--font-head);white-space:nowrap}
@media(max-width:900px){
  .lv-top{grid-template-columns:1fr;gap:28px}
  .lv-numbers-col{height:auto}
}
@media(max-width:600px){
  .lnc-grid-compact{grid-template-columns:repeat(2,1fr);grid-template-rows:repeat(4,1fr)}
  .lnc-city-compact{font-size:11.5px}
  .lnc-phone-compact{font-size:14.5px}
}
/* Bottom block: same rotating-word CTA as before the merge, just full-width
   and centered now instead of the left half of a two-column grid. Now sits
   ABOVE the numbers+video row per Kris. */
.lv-bottom{text-align:center;max-width:720px;margin:0 auto 48px}
.lv-bottom p{color:var(--slate);font-size:17px;margin:0 0 22px}
/* Eyebrow for this block only — on ramjack.com's version the "ARE YOU
   SEEING" label is the same dark ink as the headline below it (not a
   colored accent), just smaller with very wide letter-spacing, so headline
   and eyebrow read as one monochrome unit. Separate class from the sitewide
   .eyebrow (which IS colored) so this doesn't affect eyebrows elsewhere. */
.issue-eyebrow{display:block;font-size:13px;font-weight:600;letter-spacing:.32em;
  text-transform:uppercase;color:var(--navy);margin-bottom:10px}
/* rotating headline: all words stacked in the same box, each one fading in
   and out in turn on a shared CSS animation cycle — no JS needed. 5 words
   over a 10s loop = 2s each.
   Height/line-height need real headroom, not just 1.25em: at this font
   weight and size, a plain 1.25em box clips descenders on words like
   "Sinking" or "Settling" — overflow:hidden was cutting the tail off the
   "g" so it rendered as "Sinkina". 1.5em plus a touch of line-height fixes
   it. Also sized closer to ramjack's ~60px/900-weight headline (was
   maxing out at 46px, noticeably smaller/lighter than the reference). */
.issue-rotate{position:relative;font-size:clamp(32px,4.6vw,54px);height:1.5em;line-height:1.5;margin:.06em 0 14px;overflow:hidden}
.iw-word{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;
  font-size:1em;font-weight:800;color:var(--navy);
  animation:issueWordCycle 10s infinite}
.iw-word:nth-child(1){animation-delay:0s}
.iw-word:nth-child(2){animation-delay:2s}
.iw-word:nth-child(3){animation-delay:4s}
.iw-word:nth-child(4){animation-delay:6s}
.iw-word:nth-child(5){animation-delay:8s}
@keyframes issueWordCycle{0%{opacity:0}4%{opacity:1}16%{opacity:1}20%{opacity:0}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .issue-rotate{height:auto}
  .iw-word{position:static;opacity:1;animation:none;display:block}
  .iw-word~.iw-word{display:none}
}

/* about / why-us split */
.split{display:grid;grid-template-columns:1fr 1fr;gap:52px;align-items:center}
@media(max-width:820px){.split{grid-template-columns:1fr;gap:28px}}
.split-copy p{color:var(--slate);font-size:16.5px;line-height:1.65;margin:0 0 1em}
/* Alternates which side text/art land on — used by the Orlando page's
   section-by-section layout below so it isn't the same side every time. */
.split.reverse>:first-child{order:2}
.split.reverse>:last-child{order:1}
.badge-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
.badge-pill{display:inline-flex;align-items:center;gap:7px;background:var(--ice);border:1px solid var(--border);border-radius:4px;padding:8px 14px;font-family:var(--font-body);font-size:13px;font-weight:700;color:var(--navy)}
.badge-pill svg{width:15px;height:15px;color:var(--cta)}
.split-art{background:var(--navy);border-radius:18px;aspect-ratio:16/12;position:relative;overflow:hidden;box-shadow:0 22px 55px hsla(216,60%,15%,.2)}
.split-art svg{position:absolute;inset:0;width:100%;height:100%}

/* Orlando geo page article — per Kris, the page read as one long column of
   text running down the middle with a photo grid bolted on; this reworks
   it into alternating text/photo rows (reusing .split above) so images
   appear throughout the copy instead of only in one block. The plain
   .prose max-width (780px, centered) is lifted here so those rows can use
   the full container width; the short intro/closing text blocks and the
   FAQ opt back into a readable column via .orlando-narrow instead.
   NOTE: needs the "article." tag qualifier to out-specificity the later
   .prose{max-width:780px} rule below — a bare .orlando-prose class lost
   that cascade fight, which is why the split rows were silently stuck at
   780px wide (huge dead margins on wide screens) despite this rule. */
article.orlando-prose{max-width:none}
.orlando-narrow{max-width:760px;margin:0 auto}
.orlando-intro{margin-bottom:3.4em}
.orlando-intro .section-head{margin-bottom:22px}
.orlando-intro-photos{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-self:start}
.orlando-intro-photos .orlando-intro-photo{aspect-ratio:3/4}
/* A solo intro video (no photo partner, e.g. Seawall/Helical Piles hubs) —
   let it span the full width at its natural 16:9 instead of being squeezed
   into half of the 2-up grid at a portrait 3:4 crop. */
.orlando-intro-photos.orlando-intro-solo{grid-template-columns:1fr}
.orlando-intro-photos.orlando-intro-solo .video-card{aspect-ratio:16/9}
/* Kris wants the page reading almost edge-to-edge like the full-bleed
   reference he sent, with roughly an inch of breathing room on each side
   instead of the site's normal narrow centered column. 96px = 1in in CSS. */
.orlando-page-wrap.prose-wrap>.container{max-width:1800px;width:calc(100% - 192px);padding:0}
@media(max-width:900px){.orlando-page-wrap.prose-wrap>.container{width:calc(100% - 48px)}}
/* At >=1560px the "See What Others Are Saying" cutout (.reviews-widget-side,
   see its own comment above) switches to top:0 of the reviews row and hangs
   down far enough to land on top of the intro heading now that the intro
   text sits over on the right where that graphic already lives. Measured
   the overlap directly (Playwright, several widths) rather than guessing:
   consistently ~117px into the intro section at every width >=1400px, 0
   below it — so only push the intro down at that breakpoint. Breakpoint
   raised from 1400px to 1560px alongside .reviews-widget-side itself (see
   its comment) so this still lines up with the exact width where the photo
   actually moves to top:0. */
@media(min-width:1560px){.orlando-intro{padding-top:170px}}
.orlando-split{margin:0 0 3.4em}
.orlando-split .split-copy h2{margin-top:0}
.orlando-split-photo{display:block;border-radius:18px;overflow:hidden;aspect-ratio:4/3;
  box-shadow:0 22px 55px hsla(216,60%,15%,.18)}
.orlando-split-photo img{width:100%;height:100%;object-fit:cover;display:block}
/* A .video-card dropped into the same .orlando-split-photo 4/3 box (see
   cfg.sectionMedia in geoArticleHtml) — used when a section's real media is
   a video instead of a photo (the seawall hub's testimonial + grout-
   injection sections). .video-card defaults to a 16/9 box of its own; here
   it needs to fill whatever box it's placed in instead, same idea as the
   existing .video-hero-embed .video-card override elsewhere in this file. */
.orlando-split-video{padding:0}
.orlando-split-video .video-card{width:100%;height:100%;aspect-ratio:auto;border-radius:0;box-shadow:none}
.orlando-gallery{margin:0 0 2.8em}
/* Google Maps embed. Responsive aspect-ratio box (16:9) in place of the
   fixed 600x450 iframe Google's own embed code ships with, so it scales
   to the container instead of overflowing narrow screens or looking tiny
   on the wide desktop layout. */
/* Full-bleed map band, matching the look of the client's existing live site
   (map stretches edge-to-edge across the viewport, not boxed in the page's
   own content column) instead of the earlier rounded/boxed card. The page
   wrapper this sits inside (.orlando-page-wrap) already carries generous
   left/right margin from .prose-wrap>.container, so the "break out to the
   viewport edge" trick (left:50%/margin-left:-50vw) needs a home wide enough
   to escape that column — .orlando-page-wrap gets overflow-x:clip so the
   100vw breakout can't introduce a page-wide horizontal scrollbar. */
.orlando-page-wrap{overflow-x:clip}
.orlando-map-section{margin:3.4em 0 0}
.orlando-map-full{width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}
.orlando-map-embed{position:relative;aspect-ratio:21/9}
.orlando-map-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
@media(max-width:760px){.orlando-map-embed{aspect-ratio:4/5}}

/* testimonial */
.testi-card{background:#fff;border:1px solid var(--border);border-radius:18px;padding:38px 40px;max-width:720px;margin:0 auto;box-shadow:0 14px 40px hsla(216,60%,15%,.08);text-align:center}
.testi-stars{color:var(--cta);font-size:19px;letter-spacing:3px;margin-bottom:16px}
.testi-quote{font-size:20px;color:var(--navy);font-weight:600;line-height:1.5;margin:0 0 16px}
.testi-name{font-size:14px;color:var(--muted);font-weight:700}
.testi-link{display:block;text-align:center;margin-top:22px;font-weight:700}

/* service-area list — a clean, aligned directory list instead of the old
   variable-width "pill" chips (per Kris: the ragged chip edges read as
   unorganized/lazy). A small bullet + thin bottom rule reads as a tidy
   index/directory rather than a button. Shared by the homepage
   service-area band, the standalone /service-areas/ page, and the grouped
   FL/GA list on crawl-space-repair-florida — restyling here fixes all of
   them at once.
   CSS multi-column (not grid) on purpose: a long alphabetical list like
   this needs to fill top-to-bottom then wrap into a new column as soon as
   one fits — that's what keeps a 90-city state list "stacking" into
   several tidy columns instead of one long scroll (per Kris). A
   fixed-track grid can't do that packing; it only adds columns when the
   *whole container* is wide enough for another full track, so the same
   list stays single-column inside a narrower box like the FL/GA halves. */
.areas-list{column-width:130px;column-gap:24px;margin-top:14px}
.area-pill{background:none;border:0;border-radius:0;padding:8px 0;margin:0;font-size:14px;font-weight:600;
  color:var(--slate);text-decoration:none;display:flex;align-items:center;gap:10px;
  border-bottom:1px solid var(--border);break-inside:avoid;-webkit-column-break-inside:avoid}
.area-pill::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--accent-strong);flex:0 0 auto}
/* Explicit :link/:visited pin — without this, a browser's own visited-link
   memory can bleed a purple/blue tint + underline into rows for pages the
   viewer has already clicked (e.g. during QA), making an otherwise uniform
   list look inconsistent from one visitor/session to the next. */
a.area-pill:link,a.area-pill:visited{color:var(--slate);text-decoration:none}
a.area-pill:hover{color:var(--accent-strong);border-color:var(--accent-strong)}
/* Not-yet-built cities render as a plain <span class="area-pill"> (no href)
   — muted text + gray dot instead of the brand-blue dot, so it visually
   reads as "coming soon" rather than a dead link. */
span.area-pill{color:var(--muted,var(--slate));opacity:.68;cursor:default}
span.area-pill::before{background:var(--border)}

/* Grouped-by-state service-area list (currently just the new Crawl Space
   Repair page, per Kris — "order the geo pages... in two categories,
   Georgia and Florida with the cities going under each" — rather than the
   one flat pill list every other page uses). Two columns side by side on
   desktop, stacked on mobile. City names are real, pulled from the client's
   own live crawl-space-repair-florida page footer; only the handful that
   already have a real built geo page render as a link (via the same
   cityPageHref() lookup the homepage/service-areas pill lists use) — every
   other city is a plain, non-clickable pill, matching "we'll eventually
   build all these geo pages as well." */
/* This section intentionally breaks out of the article's narrow reading
   column (.orlando-narrow, 760px) up to the site's normal wide container
   width — per Kris, the directory was wasting the page's open side margins
   and forcing far more vertical scrolling than the content needed. */
.orlando-areas-section{max-width:var(--maxw);margin:0 auto;padding:0 24px}
/* Florida carries ~5x the cities Georgia does, so the two state columns
   aren't split evenly — Georgia gets a fixed track just wide enough for
   its 3 columns to stay readable, and Florida (6 columns) takes whatever
   space is left over, per Kris ("more columns for Florida, push Georgia
   over more"). A fixed px track for Georgia (rather than a fraction) is
   what actually guarantees room for 3 columns — an `fr` split wide enough
   for Florida's 6 columns left Georgia's share too narrow for words like
   "Statesboro"/"Fort Valley" at 3-across, causing real horizontal overflow. */
.areas-grouped{display:grid;grid-template-columns:1fr 440px;gap:48px;margin-top:22px}
/* Without min-width:0, a multi-column grid item reports its min-content
   width as (column-count × a column's min width) to the grid — often wider
   than its actual 1fr share — which pushes the item past its track and
   overflows the page horizontally instead of respecting the fr size. */
.areas-group{min-width:0}
.areas-group:nth-child(1) .areas-list{column-count:6;column-width:auto}
.areas-group:nth-child(2) .areas-list{column-count:3;column-width:auto}
@media(max-width:1080px){
  .areas-grouped{grid-template-columns:1fr 360px}
  .areas-group:nth-child(1) .areas-list{column-count:4}
}
@media(max-width:760px){
  .areas-grouped{grid-template-columns:1fr;gap:28px}
  .areas-group:nth-child(1) .areas-list,.areas-group:nth-child(2) .areas-list{column-count:2}
}
.areas-group h3{font-size:18px;font-weight:800;color:var(--navy);margin:0 0 4px}
.areas-group .areas-list{margin-top:14px}
/* Wide single-group variant (seawall page only, per Kris — "spread this to
   multiple rows so it doesn't scroll down so far... maybe 8 [columns]
   wide") — that page has just one group (Florida, 110 cities) instead of
   the Florida+Georgia pair .areas-grouped's 1fr/440px split was built for,
   so on this page that layout was giving the list only the 1fr share of
   the row while a 440px track sat empty beside it. Switch to a single full-
   width column and let the list run 8-across instead of 6, cutting the
   page's scroll roughly in half. Opt-in via cfg.areaListWide so the
   crawl-space/helical grouped lists keep their existing two-column layout. */
.orlando-areas-wide .areas-grouped{grid-template-columns:1fr}
.orlando-areas-wide .areas-group:nth-child(1) .areas-list{column-count:8}
@media(max-width:1080px){
  .orlando-areas-wide .areas-group:nth-child(1) .areas-list{column-count:5}
}
@media(max-width:760px){
  .orlando-areas-wide .areas-group:nth-child(1) .areas-list{column-count:2}
}
/* Reversed variant for /service-areas/ (Sept 2026) — every other page using
   .areas-grouped has ~5x more Florida cities than Georgia ones, which is
   what the default 1fr/440px + 6-col/3-col split above is tuned for. This
   page is the opposite: FL_CITIES is a short curated list (16) but
   GA_CITIES lists every real Georgia page (33), so without this override
   Georgia's longer list would be squeezed into the narrow fixed track and
   run noticeably taller than Florida's. Same mechanism as
   .orlando-areas-wide above (an opt-in modifier so the default hub-page
   layout stays untouched) but swapping which side is wide instead of
   going full-width. */
.areas-grouped.areas-grouped-swap{grid-template-columns:440px 1fr}
.areas-grouped.areas-grouped-swap .areas-group:nth-child(1) .areas-list{column-count:3}
.areas-grouped.areas-grouped-swap .areas-group:nth-child(2) .areas-list{column-count:6}
@media(max-width:1080px){
  .areas-grouped.areas-grouped-swap{grid-template-columns:360px 1fr}
  .areas-grouped.areas-grouped-swap .areas-group:nth-child(2) .areas-list{column-count:4}
}
@media(max-width:760px){
  .areas-grouped.areas-grouped-swap{grid-template-columns:1fr}
  .areas-grouped.areas-grouped-swap .areas-group:nth-child(1) .areas-list,
  .areas-grouped.areas-grouped-swap .areas-group:nth-child(2) .areas-list{column-count:2}
}

/* interactive US service-area map — real state outlines (see US_STATES in
   build.js), Solid Foundations brand colors, CSS-only hover highlight.
   Two-tier fill matches the real map on the client's own live homepage:
   light (us-state-full) = Residential & Commercial, dark (us-state-
   commercial) = Commercial Only; everything else is a flat, non-
   interactive gray backdrop. Sized to be a real focal point of the band
   (not a small supporting graphic), per Kris ("the map must be bigger"). */
.us-map-wrap{max-width:1080px;margin:2.4em auto 0;text-align:center}
.us-map{width:100%;height:auto;display:block}
.us-map .us-state{fill:hsl(210 16% 88%);stroke:#fff;stroke-width:1.25;transition:fill .15s ease}
.us-map .us-state-full{fill:var(--accent-strong);cursor:pointer}
.us-map .us-state-commercial{fill:var(--navy);cursor:pointer}
svg.us-map .us-state-full:hover,
svg.us-map .us-state-full:focus-visible{fill:var(--cta-dark);outline:none}
svg.us-map .us-state-commercial:hover,
svg.us-map .us-state-commercial:focus-visible{fill:var(--cta-dark);outline:none}
.us-map-legend{margin:16px 0 0;font-size:14px;color:var(--slate);display:flex;align-items:center;justify-content:center;gap:26px;flex-wrap:wrap}
.us-map-legend-item{display:inline-flex;align-items:center;gap:8px}
.us-map-swatch{display:inline-block;width:13px;height:13px;border-radius:3px;flex:none}
.us-map-swatch-full{background:var(--accent-strong)}
.us-map-swatch-commercial{background:var(--navy)}
@media(max-width:640px){.us-map-wrap{padding:0 8px}}

/* cta band */
.ctaband{background:linear-gradient(120deg,var(--navy),var(--navy-2));color:#fff;text-align:center;border-radius:22px;padding:52px 30px;margin:20px 0}
.ctaband h2{color:#fff;font-size:clamp(26px,4vw,36px);font-weight:800;margin:0 0 20px}
.ctaband .cta-row{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;align-items:center}
.ctaband .phone{color:hsla(0,0%,100%,.85);font-weight:700}

/* Homepage "Follow Us" section, v2 (Sept 2026) — replaces the first version
   Kris flagged as wrong on two counts: (1) the supplied graphic is a real
   background-removed cutout ("gave you a transparent photo for a reason")
   so it now sits at full opacity directly on the page background, left-
   aligned in the same .split two-column grid used elsewhere on the site,
   instead of flattened onto a boxed navy card; (2) modeled on the
   Facebook-post-styled video card Kris pointed to (atlcashrentals.com's
   homepage) — a small header bar (avatar + name + Facebook mark), a real
   testimonial video, a real customer quote, a "Follow Us on Facebook"
   button, and Twitter/YouTube kept small and close by rather than as their
   own big pills. Facebook blue (#1877F2) is used only as a brand accent on
   this one card, matching how a real Facebook surface reads. */
/* Wider-than-normal content column just for this band, so the photo and
   card push out closer to the true browser edges on typical/wide screens
   instead of sitting inside the site's standard 1180px column with a lot
   of flat dead space on either side (Kris: "move the images more to the
   edge"). Deliberately scoped to .follow-band's own .container rather than
   raising the shared --maxw variable, which every other section still
   uses. On any viewport narrower than ~1608px this simply fills out to
   100% width minus the container's own 24px side padding — i.e. edge-to-
   edge with a comfortable margin — which covers ordinary laptop/desktop
   widths; only very wide monitors actually hit the 1560px cap. */
/* Kris picked this geometric light-blue faceted graphic over the plain
   light-concrete .texture-showcase background after comparing the two
   (Sept 2026). Overriding just this one section's background-image/color
   rather than swapping the shared .texture-showcase class itself, so
   nothing else that uses that class (the homepage's reviews/services
   bands) is affected. Because this rule targets .follow-band itself (part
   of followBand()'s own markup, not something passed in per page), it
   applies everywhere that section appears — the homepage and every geo/hub
   page alike — with no extra per-page wiring needed. */
section.band.follow-band.texture-showcase{background-color:#eaf3fb;
  background-image:image-set(url(/media/follow-band-bg-geo.webp) type("image/webp"), url(/media/follow-band-bg-geo.jpg) type("image/jpeg"));
  background-size:cover;background-position:center;background-repeat:no-repeat}
.follow-band .container{max-width:1560px}
.follow-split{align-items:stretch;gap:64px}
.follow-photo-col{display:flex;align-items:center;justify-content:center}
.follow-photo-col img{width:100%;height:auto;display:block}
.fb-card{background:var(--card);border:1px solid var(--border);border-radius:18px;
  padding:20px;box-shadow:0 14px 40px hsla(216,60%,15%,.08);display:flex;flex-direction:column;gap:14px}
.fb-card-head{display:flex;align-items:center;gap:12px}
.fb-card-avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;flex:none;
  border:1px solid var(--border)}
.fb-card-id{display:flex;flex-direction:column;flex:1;min-width:0}
.fb-card-name{font-weight:800;color:var(--navy);font-size:15.5px}
.fb-card-meta{font-size:12.5px;color:var(--muted)}
.fb-card-badge{flex:none;width:30px;height:30px;border-radius:50%;background:#1877F2;color:#fff;
  display:flex;align-items:center;justify-content:center}
.fb-card-badge svg{width:17px;height:17px}
.fb-card-video{box-shadow:none}
.fb-card-quote{background:var(--ice);border-radius:12px;padding:16px 18px}
.fbq-mark{margin:0;color:var(--accent);font-size:26px;line-height:1;font-family:var(--font-head)}
.fbq-text{margin:2px 0 8px;color:var(--navy);font-weight:600;font-size:15px;line-height:1.5}
.fbq-name{margin:0;color:var(--muted);font-size:13px;font-weight:700}
.fb-card-cta{background:#1877F2;border-color:#1877F2;box-shadow:0 12px 26px hsla(214,90%,45%,.28);
  display:flex;align-items:center;justify-content:center;gap:9px;width:100%}
.fb-card-cta svg{width:18px;height:18px;flex:none}
.fb-card-cta:hover{background:#1565d8;text-decoration:none}
.fb-card-more{display:flex;align-items:center;gap:12px;font-size:12.5px;color:var(--muted);font-weight:600}
.fb-card-more a{display:inline-flex;color:var(--slate);opacity:.75}
.fb-card-more a:hover{opacity:1;color:var(--accent-strong)}
.fb-card-more svg{width:19px;height:19px}
@media(max-width:820px){.follow-photo-col{order:1}.fb-card{order:2}}

/* footer */
footer.site{background:var(--navy-2);color:hsla(0,0%,100%,.72);padding:56px 0 28px;font-size:15px}
footer.site .cols{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:32px;margin-bottom:36px}
@media(max-width:820px){footer.site .cols{grid-template-columns:1fr 1fr}}
@media(max-width:520px){footer.site .cols{grid-template-columns:1fr}}
footer.site .flogo{display:inline-flex;align-items:center;background:#fff;border-radius:10px;padding:10px 14px;margin-bottom:14px}
footer.site .flogo img{height:48px;width:auto;display:block}
footer.site h4{color:#fff;font-size:13px;letter-spacing:.08em;text-transform:uppercase;margin:0 0 14px}
footer.site a{color:hsla(0,0%,100%,.72);display:block;margin:6px 0}
footer.site a:hover{color:#fff;text-decoration:none}
footer.site .nap{line-height:1.8;margin:0}
footer.site .badges{font-size:12px;color:hsla(0,0%,100%,.5);margin-top:12px}
footer.site .bottom{border-top:1px solid hsla(0,0%,100%,.14);padding-top:20px;font-size:13px;color:hsla(0,0%,100%,.5);display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px}
/* "Powered by" agency credit, per Kris — inline logo, vertically centered
   with the surrounding legal text rather than full 120px-tall render size. */
footer.site .powered-by{display:flex;align-items:center;gap:6px}
footer.site .powered-by img{height:36px;width:auto;display:block;vertical-align:middle}
.disclaimer{font-size:12.5px;color:var(--muted);font-style:italic;margin-top:6px}

/* interior pages: hero + breadcrumbs + prose */
.page-hero{position:relative;overflow:hidden;padding:110px 0 56px;background:linear-gradient(120deg,var(--navy),var(--navy-2))}
.page-hero .inner{position:relative;z-index:2;max-width:820px}
.page-hero .eyebrow{color:#fff}
.page-hero h1{color:#fff;font-size:clamp(30px,4.6vw,46px);font-weight:800;margin:0}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}
/* Kris's pre-designed "Meet the Team" banner (photo + logo + tagline baked
   in) replaces the plain text page-hero on /about/ only. It's a plain
   <img>, not a background-image, so it scales with real width/height and
   never gets cropped the way a CSS background would on odd viewport
   sizes — the whole graphic (including the team on the sides/bottom) stays
   intact edge-to-edge. Padding/gradient from the shared .page-hero rule
   are overridden off since the image already fills the space and carries
   its own background art. */
.team-hero{padding:0;background:none}
.team-hero picture,.team-hero img{display:block;width:100%;height:auto}
/* Previously capped the rendered width at 1200px and centered it on desktop
   (to shrink the banner down uniformly with nothing cropped), which left
   navy bars on either side. Kris asked to stretch it across the full open
   area instead — same "no cap, full-bleed" fix already applied to the
   /reviews/ hero below, for the same reason. */
/* Orlando geo-page hero — same "real <img>, not background-image" pattern
   as .team-hero above (so the percentage-positioned overlays below always
   track the source photo correctly, at every viewport, with zero cropping).
   .orlando-hero-media wraps ONLY the picture/img + copy + mobile scrim, so
   its box height always equals the image's own rendered height, on both
   breakpoints — that's what keeps the copy and the mobile scrim correctly
   aligned to the photo regardless of whether the form (a sibling, not a
   child of -media) is absolutely positioned over the image (desktop) or
   pushed into normal flow below it (mobile, see the media query). */
.orlando-hero{padding:0;background:var(--navy);overflow:hidden}
.orlando-hero-inner{position:relative}
.orlando-hero-media{position:relative;line-height:0}
.orlando-hero-img{display:block;width:100%;height:auto}
/* Desktop copy sits over the photo's left ~55-60%; safe max-width (42%)
   keeps it clear of the glass panel's diagonal left edge, which per Kris's
   supplied image fluctuates between roughly 53-68% width as it bows. */
.orlando-hero-copy{position:absolute;left:6%;top:50%;transform:translateY(-50%);max-width:42%;z-index:2}
.orlando-hero-copy .eyebrow{color:#fff;margin-bottom:12px}
/* Scaled up from the old clamp(26px,3vw,40px) toward the rebrand's "big
   confident condensed headline" spec, but capped well under the homepage
   .hero's literal 64-72px — this component sits in a fixed 42%-wide column
   over a photo (see .orlando-hero-copy above), so a much bigger size would
   overflow that box or crowd the glass form panel beside it. */
.orlando-hero-copy h1{color:#fff;font-family:var(--font-head);font-size:clamp(28px,3.6vw,46px);font-weight:800;line-height:1.02;letter-spacing:-.02em;margin:0 0 14px}
.orlando-hero-tagline{color:#EEF1F4;font-family:var(--font-body);font-size:15px;line-height:1.6;margin:0 0 22px;max-width:44ch}
/* Same dark-halo trick as .hero-copy (see that rule's comment) — keeps the
   white text readable over the photo's sky/water without a dimming overlay
   that would dull the image itself. */
.orlando-hero-copy .eyebrow,.orlando-hero-copy h1,.orlando-hero-tagline{
  text-shadow:0 1px 3px hsla(214,70%,6%,.85),0 0 14px hsla(214,70%,6%,.6)}
/* Desktop-only left-side scrim behind the hero copy. The text-shadow "halo"
   above reads fine over mid-tone photo areas, but on a bright source photo
   (sky, water, a light building facade — e.g. Jacksonville's skyline shot)
   it wasn't enough contrast; Kris flagged the hero verbiage as hard to read
   across the geo pages. This darkens just the left ~55% where the copy
   sits and fades to nothing by the point the glass form panel starts, so
   the photo itself stays untouched on the right. Mobile already has its
   own top-down scrim below (.orlando-hero-media::after in the mobile query)
   so this is scoped to desktop only to avoid double-darkening there. */
@media(min-width:761px){
  .orlando-hero-media::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
    background:linear-gradient(100deg,hsla(214,75%,5%,.62) 0%,hsla(214,75%,5%,.42) 24%,hsla(214,75%,5%,.14) 42%,hsla(214,75%,5%,0) 54%)}
}
.orlando-hero-call svg{width:16px;height:16px;flex:none}
/* Glass-form safe zone inside the photo's own frosted panel, per Kris's
   supplied image (read via a percentage-grid overlay): left 61% / right 4%
   / top 9% of the image's own box. Positioned against .orlando-hero-inner
   (not -media) so its percentages stay correct on desktop even though the
   form is a flow sibling of -media, not a child — -inner's box height
   equals -media's (the image) exactly on desktop, since both -copy and
   -form are position:absolute there and contribute nothing to flow height.
   NOTE (rebrand): this used to be a fixed top:9%/bottom:9% box with the
   card itself set to max-height:100%;overflow-y:auto — with the rebrand's
   larger form padding/type/input sizing, the card's real content no longer
   fits that box at normal desktop widths, which produced a visible internal
   scrollbar (flagged by Kris — "remove the scrolling bars... have it show
   completely"). Fixed by anchoring the form to the top of the safe zone
   only (align-items:flex-start, no bottom/max-height cap) so the card is
   exactly as tall as its own content and simply grows downward from the
   same top-left starting point as before — measured against every hero
   photo's actual content height (up to 667px at >=961px widths, where the
   two-up phone/email row is side-by-side) against the fixed 760px image
   height, leaving a comfortable ~25px margin above the image's own bottom
   edge. Narrower desktop/tablet widths (761-960px, where phone/email stack
   to a single column and the card runs taller — up to 758px, no longer
   enough headroom in the 760px image) get the separate override just below
   that drops the form out of the absolute overlay entirely instead of
   risking it getting clipped by .orlando-hero's own overflow:hidden. */
.orlando-hero-form{position:absolute;left:61%;right:4%;top:9%;z-index:2;
  display:flex;align-items:flex-start;justify-content:center}
.orlando-hero-form .estimate-card{width:100%;max-width:420px}
/* Narrow desktop/tablet gap (761-960px — e.g. iPad portrait at 768px) where
   the absolute-overlay form above can no longer be guaranteed to fit inside
   the 760px-tall hero image (see note above). Rather than let it overflow
   into .orlando-hero's overflow:hidden clip, drop it out of the overlay and
   into normal document flow directly below the hero, same pattern already
   proven at <=760px (see the max-width:760px block further down) — full
   width, no height cap, so every field is always fully visible with no
   scrollbar regardless of how tall the form's content is. */
@media(min-width:761px) and (max-width:960px){
  .orlando-hero-form{position:relative;left:auto;right:auto;top:auto;inset:auto;
    z-index:2;display:block;padding:18px 20px 0;margin-top:-40px}
  .orlando-hero-form .estimate-card{max-width:520px;margin:0 auto}
}
/* Shorter geo-page hero, desktop only (mobile/tablet already has its own
   separate, already-tuned sizing below). First done for Crawl Space Repair
   per Kris ("make the hero a bit smaller"), then liked enough to roll out
   to every geo page built so far (Orlando/Tampa/Jacksonville/Lake City/Cape
   Coral/Gainesville/Fort Myers/Crawl Space) — applied to the shared
   .orlando-hero-img rule rather than one page's class so it's consistent
   across all of them and automatically covers future geo pages too. Every
   geo hero photo is a similar wide landscape composition (photo+copy on the
   left, glass form panel on the right, sky/background top, ground/piers
   bottom), so a plain center crop reads fine on all of them. */
@media(min-width:761px){
  .orlando-hero-img{height:760px;max-height:760px;object-fit:cover;object-position:center}
}
/* Crawl Space Repair's own hero image has its glass panel positioned
   slightly differently than the shared hero-fl-standard asset — per Kris
   ("move form over"), shifted right just on this page rather than
   retuning the shared default other geo pages already have approved. */
@media(min-width:761px){
  .crawl-space-hero .orlando-hero-form{left:65%;right:2%}
}
/* hero-fl-standard (Lake City/Cape Coral/Gainesville/Fort Myers) bakes a
   full Florida state-outline graphic into the photo, and that outline sits
   with almost no margin above it at the very top of the source image. The
   plain center-crop above (see .orlando-hero-img height:760px block) was
   cutting straight into the top of the state per Kris ("the state of
   florida is being cropped too much... move the image down a bit so we see
   more of florida") — object-position:top stops cropping from the top
   entirely and takes the full 760->916 difference off the bottom instead,
   where it's just more of the seawall/piers photo, not the state graphic. */
@media(min-width:761px){
  .hero-fl-standard .orlando-hero-img{object-position:top}
}
@media(max-width:760px){
  /* Mobile crop is a different image (tall, photo on top ~50%, the glass
     panel continuing below it) — copy repositions to sit inside that top
     photo band instead of vertically centered, and the call button drops
     since the full estimate form sits immediately below the image anyway. */
  .orlando-hero-copy{left:7%;right:7%;top:7%;max-width:none;transform:none}
  .orlando-hero-copy h1{font-size:clamp(23px,7vw,30px)}
  .orlando-hero-tagline{font-size:14px;max-width:none}
  .orlando-hero-call{display:none}
  /* The source mobile photo is a very tall portrait crop (862x1824) — at
     full width:auto that rendered ~825px tall on a typical phone, so on a
     real device the tagline text was the only thing visible in the first
     screen and the form (position:static, right after the image in flow)
     sat a full extra screen of blank photo below that — Kris saw this live
     ("move the form into this section on mobile") and asked for the form
     pulled back up into view. Cap the image to a shorter banner instead of
     its natural height so the form starts right after the copy instead of
     after all that empty photo. object-position keeps the copy's photo
     band (top ~40%, where the buildings/text sit) in frame while cropping
     off the excess bottom. */
  /* Taller than the first cut (was min 290/max 360) specifically to leave
     clear photo below the tagline for the form to overlap into — see
     .orlando-hero-form below. Measured the tagline's actual bottom edge at
     several widths; this height keeps ~70-90px of clear image under it. */
  .orlando-hero-img{height:70vw;max-height:410px;min-height:340px;object-fit:cover;object-position:top}
  .orlando-hero-media::after{content:"";position:absolute;inset:0;height:74%;z-index:1;pointer-events:none;
    background:linear-gradient(180deg,hsla(214,72%,7%,.6) 0%,hsla(214,70%,9%,.22) 65%,transparent 100%)}
  /* Form stays in normal flow (not absolutely overlaid like desktop — the
     whole card is too tall to float over this mobile photo without
     covering the copy), but per Kris it should still read as glass sitting
     on the photo rather than a flat card starting where the photo ends.
     Pulling it up 50px overlaps the ef-frost blur/translucency directly
     over real photo pixels along the image's bottom edge, so the image
     visibly shows through behind the card's top — "some of the
     background... come through" — while the input fields further down
     still sit on solid ground for readability. 50px checked against the
     tagline's actual bottom edge at 360-430px widths (the tightest range)
     to confirm it never overlaps the text itself. */
  .orlando-hero-form{position:relative;inset:auto;z-index:2;margin:-50px 0 0;padding:20px 20px 6px;display:block}
  .orlando-hero-form .estimate-card{max-width:520px;margin:0 auto;max-height:none}
}
/* Recent-work photo gallery, reusing the sitewide .grid.cols-4 vocabulary
   (teamGrid/servicesGrid) rather than a bespoke layout. Fixed aspect-ratio
   crop keeps the grid tidy since the source photos are all different
   shapes/orientations. Down to 3 photos now (2 moved up into the intro —
   see orlandoArticleHtml), and the plain 4-col grid left them stranded in
   the first 3 columns with dead space on the right; fix that by fixing a
   comfortable column width and centering the row as a group instead of
   letting it stretch/left-align across the full-width Orlando container. */
.orlando-gallery-grid{grid-template-columns:repeat(3,minmax(0,380px));justify-content:center}
@media(max-width:900px){.orlando-gallery-grid{grid-template-columns:repeat(2,minmax(0,320px))}}
@media(max-width:600px){.orlando-gallery-grid{grid-template-columns:minmax(0,380px)}}
.orlando-gallery-photo{display:block;border-radius:14px;overflow:hidden;aspect-ratio:4/3;
  box-shadow:0 8px 20px hsla(216,50%,20%,.14)}
.orlando-gallery-photo img{width:100%;height:100%;object-fit:cover;display:block}
/* /reviews/ hero — Kris's "Strong Foundations, Real Results" banner pair.
   Same "real <img>, never cropped" + percentage-positioned-overlay pattern
   as .orlando-hero-media/.team-hero above. Full-bleed edge-to-edge on every
   breakpoint per Kris ("stretch this across the entire page") — this used
   to cap/center at max-width:1200px on desktop the same way .team-hero
   does, but Kris wants the graphic filling the full page width instead, so
   that cap is gone. The percentage-positioned overlays below still track
   correctly at any width since they're positioned against -media's own box,
   which always equals the image's real rendered box regardless of how wide
   that ends up being. */
.reviews-hero{padding:0;background:var(--navy);overflow:hidden}
.reviews-hero-inner{position:relative}
.reviews-hero-media{position:relative;line-height:0}
.reviews-hero-img{display:block;width:100%;height:auto}
/* Safe-zone read directly off the supplied images (percentage-grid check
   against the card's real edges and the quote-mark glyph baked into its
   top-left corner): desktop card spans roughly left 53%/top 3%/right 99%/
   bottom 96% of the 1666x944 image, with the glyph occupying its own top-
   left corner up to about 24% down — inset further from all of that so the
   real quote text sits centered in the card's clear lower-right area with
   comfortable margin, never touching the glyph or the card's rounded edge.
   Mobile card on the 864x1821 portrait crop spans roughly top 60%/bottom 98%
   of the image, glyph in its top-left corner down to about 70% — same
   approach, inset below the glyph.
   Quote box is shorter than before (was top:26%/bottom:9%, nearly the whole
   card) to make room below it for the real "glass" estimate form Kris asked
   to add "in the typical spot" — the same estimateForm({variant:'ef-frost'})
   glass-panel form already used on every geo page hero (see geoHero() /
   .orlando-hero-form), just placed in this card's lower portion instead of
   the whole card, so the page keeps a live lead-capture form the way its
   other big-photo heroes do, alongside the testimonial quote rather than
   instead of it. */
.reviews-hero-quote{position:absolute;left:58%;right:5%;top:4%;bottom:74%;z-index:2;
  display:flex;flex-direction:column;justify-content:center}
.rhq-mark{font-family:var(--font-head);font-weight:800;font-size:clamp(38px,4vw,60px);
  line-height:.6;color:hsla(214,45%,38%,.5);margin:0 0 8px}
.rhq-text{font-size:clamp(15px,1.5vw,19px);font-weight:700;color:var(--navy);line-height:1.5;margin:0 0 16px}
.rhq-name{font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin:0}
@media(max-width:760px){
  /* Right inset is a generous 24% (not the desktop-derived 8%) for a
     specific reason, confirmed live: the fixed .mobile-call-fab call
     button sits pinned to the bottom-RIGHT of the VIEWPORT (not the page,
     see .mobile-call-fab — right:18px, width:56px, so it occupies roughly
     the rightmost 19% of a typical phone width), and since this hero is
     tall enough to still be on-screen at initial page load, the card's
     text was rendering right underneath it. Tuning the BOTTOM inset alone
     can't fix this reliably — the fab's position is viewport-height-
     relative while the card's is image-height-relative, so their vertical
     relationship shifts by device. Pushing the text's right edge past the
     fab's left edge (24% clears its 19% footprint with margin) fixes it
     for every device height at once, since the two then simply never share
     the same horizontal band regardless of where either lands vertically.
     The quote-mark glyph is already baked into the image itself (top-left
     of the card) so .rhq-mark is redundant here and dropped too, freeing
     up a bit more room in an already-tight mobile card. */
  .reviews-hero-quote{left:10%;right:24%;top:71%;bottom:6%}
  .rhq-mark{display:none}
  .rhq-text{font-size:14px;line-height:1.4;margin-bottom:8px}
  .rhq-name{font-size:10.5px}
}
/* The real lead-capture form, in the "typical spot" — same glass-panel
   treatment (estimateForm({variant:'ef-frost'})) as every geo page's hero
   form, positioned in the lower portion of this card, below the quote.
   Desktop: absolutely positioned against .reviews-hero-inner the same way
   .orlando-hero-form is (see that rule) — -inner's box height equals
   -media's (the image) exactly, since -form itself is position:absolute and
   contributes nothing to flow height. Capped to a comfortable form width
   and centered rather than stretched edge-to-edge, so the field rows don't
   get uncomfortably wide now that the card itself can be very large on a
   full-bleed wide screen. */
/* NOTE (rebrand): this was previously an absolute overlay (top:28%;
   bottom:4% of .reviews-hero-inner) with the card itself capped to
   max-height:100%;overflow-y:auto, the same pattern .orlando-hero-form used
   — and the same rebrand-driven scrollbar problem applied here too, worse:
   since .reviews-hero-img has no fixed height (it scales with viewport
   width at its own natural aspect ratio, unlike the geo-hero photos), that
   28-96% box was only ever tall enough to fit the new, taller form design
   at very wide screens (~1920px+); at ordinary desktop widths the card
   overflowed and scrolled internally, exactly what Kris flagged ("remove
   the scrolling bars... have it show completely"). Simplest reliable fix:
   drop the absolute overlay at every width and always place the form in
   normal document flow right after the quote card, same treatment already
   proven at mobile widths below — guarantees every field is always fully
   visible with no internal scrollbar, regardless of viewport size. */
.reviews-hero-form{position:relative;z-index:2;margin:-36px 0 0;padding:0 20px;display:block}
.reviews-hero-form .estimate-card{max-width:520px;margin:0 auto}
@media(max-width:760px){
  .reviews-hero-form{margin:-36px 0 0;padding:0 20px}
}
/* Two-video row under the hero (the live source page runs two videos side
   by side there) and the single seawall video further down — both built
   from the same .video-card component already proven in localVideoBand()
   (real YouTube thumbnail, click-to-embed, no modal needed). */
.reviews-video-row{margin:0 auto;max-width:920px}
.reviews-video-single{max-width:640px;margin:0 auto}
/* Written-testimonial grid — same card language as .testi-card (white,
   rounded, soft shadow) but left-aligned and sized to sit 3-up in a grid
   instead of centered solo, and with no star row: the source page shows no
   star ratings for any of these 7, so none are invented here (the live
   Trustindex widget above is the real, current star-rating data). */
.testi-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;margin-top:8px}
@media(max-width:900px){.testi-grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.testi-grid{grid-template-columns:1fr}}
.testi-grid-card{background:#fff;border:1px solid var(--border);border-radius:16px;padding:28px 26px;
  box-shadow:0 8px 26px hsla(216,60%,15%,.06);display:flex;flex-direction:column}
.tgc-mark{font-family:var(--font-head);font-weight:800;font-size:34px;line-height:.6;
  color:hsla(214,55%,45%,.3);margin:0 0 10px}
.tgc-quote{font-size:15px;line-height:1.6;color:var(--fg);margin:0 0 14px;flex:1}
.tgc-name{font-size:13px;font-weight:700;color:var(--navy);margin:0}
/* /video-gallery/ hero — Kris's "Real Projects. Real Solutions. Real
   Results." banner pair. Reuses .reviews-hero-media/.reviews-hero-img as-is
   (same "real <img>, full-bleed on every breakpoint" mechanics, see that
   rule's comment) since this is the same box sizes/pattern, just a
   different pair of source images and a different page. Only what's
   page-specific is broken out below: the navy backdrop and the overlay
   itself, which here is a real, playable .video-card (see videoCard() in
   build.js) instead of text — the source image's blank panel has a
   play-button glyph baked in, not a quote mark, so a functional video
   thumbnail is the natural fit. Safe-zone read the same percentage-grid
   way as reviews-hero-quote: desktop panel spans roughly left 55%/top 3%/
   right 100%/bottom 97% of the 1748x900 image; mobile panel spans roughly
   top 65%/bottom 98% of the 864x1821 image. Insets below sit comfortably
   inside those edges, with extra RIGHT clearance on mobile for the same
   fixed .mobile-call-fab reason documented on .reviews-hero-quote (generous
   right inset beats bottom-inset tuning since the fab's position is
   viewport-height-relative while the panel's is image-height-relative). */
.video-gallery-hero{padding:0;background:var(--navy);overflow:hidden}
/* Insets tightened to hug the glass panel's real edges (per Kris, after the
   full-bleed stretch above made the panel itself noticeably bigger — the
   video was sitting well inside it with visible unused glass margin on the
   right and especially below) rather than sitting inset a comfortable
   margin from it. Backed off slightly per Kris ("make this video a little
   smaller") — a bit more glass margin visible on all sides than the fully
   hugged version above. */
.video-hero-embed{position:absolute;left:61%;right:4%;top:9%;bottom:7%;z-index:2}
.video-hero-embed .video-card{width:100%;height:100%;aspect-ratio:auto}
@media(max-width:760px){
  .video-hero-embed{left:9%;right:22%;top:68%;bottom:5%;display:flex;align-items:center}
  .video-hero-embed .video-card{width:100%;height:auto;aspect-ratio:16/9}
}
/* Category video grids — same real-thumbnail .video-card component used on
   the homepage and /reviews/, just gridded 3-up (or 2-up for the two
   shorter categories) instead of a single row. */
.video-gallery-grid{display:grid;gap:22px;margin-top:8px;max-width:1080px;margin-left:auto;margin-right:auto}
.video-gallery-grid-3{grid-template-columns:repeat(3,1fr)}
.video-gallery-grid-2{grid-template-columns:repeat(2,1fr);max-width:740px}
@media(max-width:900px){.video-gallery-grid-3{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.video-gallery-grid-3,.video-gallery-grid-2{grid-template-columns:1fr;max-width:420px}}
/* FAQ + a second full estimate form side by side, per Kris ("move faqs
   over and add another full form like you have in the hero"). FAQ gets
   the wider column since accordion rows read better long; the form stays
   at its natural card width in the narrower column, pinned to the top
   (align-items:start) rather than the sitewide .split's vertical-center,
   since the FAQ list runs much taller than the form card. */
/* Yellow rule (brand .btn-yellow hue) separating the photo gallery above
   from the FAQ+form below, with extra breathing room on both sides of it —
   Kris asked for both after seeing the two sections sit right on top of
   each other. */
.orlando-faq-split{grid-template-columns:1.5fr 1fr;align-items:center;margin:2.6em 0 3.4em;
  padding-top:2.6em;border-top:4px solid var(--safety)}
/* The card's own max-width leaves its grid column wider than the card
   itself (FAQ list runs long, form doesn't need to match that height) —
   center it in that column both ways instead of letting it default to the
   column's top-left corner with dead space beside and below it. */
.orlando-faq-split .estimate-card{max-width:460px;justify-self:center}
/* This unconditional rule sits later in the file than the sitewide
   .split{grid-template-columns:1fr} mobile breakpoint, so without its own
   override here it would win at every width (same specificity, later
   source order) and keep FAQ+form side by side on phones. */
@media(max-width:820px){.orlando-faq-split{grid-template-columns:1fr}.orlando-faq-split .estimate-card{max-width:520px;margin:0 auto}}
.crumbs{background:#fff;border-bottom:1px solid var(--border)}
.crumbs .container{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 24px;font-size:13px}
.crumbs a{color:var(--slate);font-weight:600}
.crumbs a:hover{color:var(--navy);text-decoration:none}
.crumbs .crumb-cur{color:var(--navy);font-weight:700}
.crumbs .crumb-sep{color:var(--muted);opacity:.55}
.prose-wrap{padding:56px 0 20px}
.prose{max-width:780px;margin:0 auto}
.prose h2{font-size:clamp(23px,3.2vw,30px);font-weight:800;margin:1.5em 0 .5em}
.prose h3{font-size:21px;margin:1.3em 0 .4em}
.prose p{margin:0 0 1.1em;color:#242B33}
.prose ul{margin:0 0 1.3em;padding-left:1.2em}
.prose li{margin:.3em 0;color:#242B33}
.prose strong{color:var(--navy)}
.prose a{color:var(--accent-strong);font-weight:600;text-decoration:underline;text-decoration-color:hsla(214,55%,38%,.35);text-underline-offset:2px}
.prose blockquote{margin:0 0 1.2em;padding:16px 20px;background:#fff;border-left:4px solid var(--cta);border-radius:8px;color:var(--slate)}
.callout{background:hsla(214,38%,46%,.08);border:1px solid hsla(214,38%,46%,.22);border-radius:14px;padding:20px 24px;margin:0 0 1.6em}
.callout p{margin:0}
.faq details{border-bottom:1px solid var(--border);padding:6px 0}
.faq summary{cursor:pointer;font-family:var(--font-head);font-weight:700;color:var(--navy);font-size:17px;padding:14px 0;list-style:none}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+";float:right;color:var(--cta);font-size:22px;line-height:1}
.faq details[open] summary::after{content:"–"}
.faq details p{padding:0 0 16px;color:#242B33;margin:0}
.local-links{background:var(--cream);border:1px solid var(--border);border-radius:14px;padding:22px 26px;margin:1.8em 0}
.local-links h3{margin:0 0 10px;font-size:18px}
.local-links ul{margin:0;padding-left:1.1em;columns:2}
@media(max-width:600px){.local-links ul{columns:1}}
