/* generated from direction.json */
:root{
  --bg: #FFFFFF;
  --bg-alt: #EEF3F8;
  --surface: #FFFFFF;
  --primary: #12222E;
  --primary-dark: #060D13;
  --accent: #3E86C0;
  --accent-dark: #235A83;
  --ink: #14222C;
  --ink-muted: #57646F;
  --on-dark: #FFFFFF;
  --on-dark-muted: rgba(255,255,255,.72);
  --soft: #BFDAF0;
  --line: rgba(18,34,46,.14);
  --display: 'Barlow Semi Condensed', system-ui, sans-serif;
  --body: 'Inter', system-ui, sans-serif;
  --wrap: 1200px;
  --pad: clamp(20px, 5vw, 48px);
  --r: 8px;
}

/* ==========================================================================
   partner-site kit :: base.css
   STRUCTURE ONLY. Carries no visual identity.

   build.py prepends a :root block from direction.json defining every token
   below. Nothing here hardcodes a colour or a typeface, which is why reusing
   this file across partners does not make their sites look alike.

   TOKEN CONTRACT (all required):
     --bg --bg-alt --surface --primary --primary-dark --accent --accent-dark
     --ink --ink-muted --on-dark --on-dark-muted --soft --line
     --display --body
     --wrap --pad --r

   FIXES ALREADY PAID FOR, DO NOT REGRESS:
     - img{height:auto}  or width/height attributes pin height and every
       aspect-ratio rule below is silently ignored
     - reveal/annotation elements drop out below 760px where pills clip
     - prefers-reduced-motion honoured throughout
   ========================================================================== */

*,*::before,*::after{ box-sizing:border-box; }

/* Grid and flex children default to min-width:auto, so one wide child (a row of
   tabs meant to scroll inside its own box, a long unbroken word) forces its whole
   column wider than the screen and the page scrolls sideways on phones. This is
   what made pahomesupply.com 722px wide on a 390px phone. Everything inside the
   page container may shrink; :where() keeps the specificity at zero, so any
   component that sets its own min-width still wins.
   Layout boxes only: text elements (links, buttons, labels, spans) keep their
   natural minimum, or a crowded row squeezes them narrower than their words.
   Page content only: the header manages its own row (nav items shrinking into
   each other is worse than the header switching to the menu button). */
:where(main .wrap, main .wrap :is(div, section, article, aside, figure, picture, ul, ol, li, form, fieldset, nav, table)){ min-width:0; }

html{ scroll-behavior:smooth; overflow-x:hidden; }
@media (prefers-reduced-motion:reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0;
  background:var(--bg);
  color:var(--ink);
  font-family:var(--body);
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

/* height:auto is required, otherwise the width/height HTML attributes pin the
   rendered height and every aspect-ratio rule below is silently ignored. */
img{ max-width:100%; display:block; height:auto; }
a{ color:inherit; }

:focus-visible{ outline:3px solid var(--accent); outline-offset:3px; }

.skip{
  position:absolute; left:-9999px; top:0; z-index:999;
  background:var(--primary); color:var(--on-dark);
  padding:12px 20px; border-radius:0 0 var(--r) 0; text-decoration:none;
}
.skip:focus{ left:0; }

.wrap{ max-width:var(--wrap); margin:0 auto; padding-left:var(--pad); padding-right:var(--pad); }

/* ---------- Type ---------- */
.eyebrow{
  font-size:11px; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  color:var(--accent); margin:0 0 12px; display:flex; align-items:center; gap:10px;
}
.eyebrow-light{ color:var(--soft); }

h1,h2,h3,h4{ font-family:var(--display); font-weight:800; letter-spacing:-.02em; line-height:1.1; margin:0; }
h1{ font-size:clamp(30px,4.6vw,52px); }
h2{ font-size:clamp(25px,3.4vw,38px); }
h3{ font-size:clamp(18px,2vw,21px); font-weight:700; }
h4{ font-size:16px; font-weight:700; letter-spacing:-.01em; }

.lede{ font-size:clamp(15px,1.6vw,18px); color:var(--ink-muted); max-width:62ch; margin:14px 0 0; }

/* ---------- Buttons ---------- */
.btn{
  display:inline-block; font-family:var(--display); font-weight:700; font-size:15px;
  padding:16px 26px; border-radius:var(--r); text-decoration:none; border:2px solid transparent;
  transition:background .18s ease, transform .18s ease, color .18s ease, border-color .18s ease;
  cursor:pointer; text-align:center;
}
.btn-primary{ background:var(--accent); color:#fff; }
.btn-primary:hover{ background:var(--accent-dark); transform:translateY(-2px); }
.btn-secondary{ background:transparent; border-color:var(--line); color:var(--ink); }
.btn-secondary:hover{ background:var(--bg-alt); }
.cta-band .btn-secondary{ background:#fff; border-color:#fff; color:var(--ink); }
.cta-band .btn-secondary:hover{ background:var(--bg-alt); }
.cta-band .btn-primary{ background:transparent; border-color:#fff; color:#fff; }
.cta-band .btn-primary:hover{ background:rgba(255,255,255,.14); }
.btn-ghost{ background:transparent; color:var(--on-dark); border-color:rgba(243,239,228,.45); }
.btn-ghost:hover{ background:rgba(243,239,228,.12); transform:translateY(-2px); }
.btn-light{ background:var(--on-dark); color:var(--primary); }
.btn-light:hover{ background:#fff; transform:translateY(-2px); }
.btn-dark{ background:var(--primary); color:var(--on-dark); }
.btn-dark:hover{ background:var(--primary-dark); transform:translateY(-2px); }

/* ---------- Header ---------- */
.site-header{
  position:sticky; top:0; z-index:100; background:rgba(231,225,211,.94);
  backdrop-filter:blur(10px); border-bottom:1px solid var(--line);
}
.header-inner{ display:flex; align-items:center; justify-content:space-between; gap:20px; padding-top:14px; padding-bottom:14px; }

/* flex:1 1 auto + min-width:0 (not flex:none) is required so a long
   business name + tagline can shrink/truncate instead of forcing
   .header-inner wider than the viewport on mobile, which pushes
   .nav-toggle off-screen and makes the whole page scroll horizontally.
   min-width:0 overrides flexbox's default min-width:auto, which is what
   actually allows text content to shrink below its intrinsic width. */
.brand{ display:flex; align-items:center; gap:11px; text-decoration:none; flex:1 1 auto; min-width:0; }

/* Every partner's logo, whatever size or shape the supplied file is, must
   never break the header layout. This is unconditional for every site. */
.brand img{ max-height:40px; width:auto; display:block; }
.brand svg{ height:40px; width:auto; display:block; }

/* Reusable contrast plate. Use this when the supplied logo is light-colored
   or on a transparent background and would be hard to see against the
   header's own background (see references/site-config.md and SKILL.md's
   logo-authoring rule). Wrap the logo markup with
   <span class="mark-plate">...</span> in the site's "mark" field; this class
   is defined once here so no partner needs its own custom CSS for it. */
.mark-plate{ display:inline-flex; align-items:center; justify-content:center;
  background:var(--primary); border-radius:8px; padding:6px 10px; flex:none; }
.mark-plate img{ max-height:30px; }
.mark-plate svg{ height:30px; }

.brand-name{ font-family:var(--display); font-weight:800; font-size:16px; line-height:1.15; letter-spacing:-.02em;
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.brand-name span{ display:block; font-family:var(--body); font-weight:500; font-size:10.5px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--accent); margin-top:2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Below ~420px even a moderately long name + tagline can outgrow the
   remaining space next to the nav toggle. Cap it explicitly rather than
   relying on flex math alone, so it degrades to a clean ellipsis instead
   of a layout fight. */
@media (max-width:420px){
  .brand-name, .brand-name span{ max-width:52vw; }
}

.nav{ display:flex; align-items:center; gap:26px; }
.nav a{ font-size:14.5px; font-weight:500; text-decoration:none; color:var(--ink); padding:6px 0; position:relative; }
.nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:2px; background:var(--accent);
  transform:scaleX(0); transform-origin:left; transition:transform .2s ease;
}
.nav a:hover::after, .nav a[aria-current="page"]::after{ transform:scaleX(1); }

.header-cta{ display:flex; align-items:center; gap:16px; flex:none; }
.header-phone{ font-family:var(--display); font-weight:700; font-size:15px; text-decoration:none; white-space:nowrap; }
.header-phone small{ display:block; font-family:var(--body); font-weight:400; font-size:10.5px; color:var(--ink-muted); letter-spacing:.04em; }
.header-cta .btn{ padding:12px 20px; font-size:14px; }

.nav-toggle{
  display:none; background:none; border:1px solid var(--line); border-radius:8px;
  width:44px; height:44px; cursor:pointer; padding:0; place-items:center;
  flex:none; /* a long business name must never squeeze the menu button under 44px */
}
.nav-toggle span{ display:block; width:20px; height:2px; background:var(--ink); position:relative; }
.nav-toggle span::before,.nav-toggle span::after{ content:""; position:absolute; left:0; width:20px; height:2px; background:var(--ink); }
.nav-toggle span::before{ top:-6px; } .nav-toggle span::after{ top:6px; }

@media (max-width:1000px){
  .nav-toggle{ display:grid; }
  .nav{
    position:absolute; top:100%; left:0; right:0; background:var(--surface);
    flex-direction:column; align-items:stretch; gap:0; padding:8px var(--pad) 20px;
    border-bottom:1px solid var(--line); display:none;
  }
  .nav.open{ display:flex; }
  .nav a{ padding:14px 0; border-bottom:1px solid var(--line); }
  .header-cta .btn{ display:none; }
}
@media (max-width:560px){ .header-phone{ display:none; } }

/* Nav dropdown (service areas, and reusable for any future grouped nav
   items). Desktop: a positioned menu under the toggle. Mobile: an inline
   expanding section inside the same slide-down nav panel, matching how the
   rest of the mobile nav already behaves (display toggling, not opacity). */
.nav-dropdown{ position:relative; }
.nav-dropdown-toggle{
  font:inherit; font-size:14.5px; font-weight:500; color:var(--ink); background:none; border:none;
  padding:6px 0; cursor:pointer; display:inline-flex; align-items:center; gap:5px;
}
.nav-dropdown-caret{ width:11px; height:11px; transition:transform .15s ease; }
.nav-dropdown.is-open .nav-dropdown-caret{ transform:rotate(180deg); }
.nav-dropdown-menu{
  position:absolute; top:100%; left:0; margin-top:2px; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r); padding:8px; min-width:210px;
  max-height:340px; overflow-y:auto; display:grid; gap:2px; box-shadow:0 16px 40px rgba(0,0,0,.14);
  opacity:0; visibility:hidden; transform:translateY(-6px); transition:opacity .15s ease, transform .15s ease;
  z-index:60;
}
.nav-dropdown.is-open .nav-dropdown-menu{ opacity:1; visibility:visible; transform:translateY(0); }
.nav-dropdown-menu a{ padding:9px 10px; border-radius:7px; font-size:14px; text-decoration:none; color:var(--ink); }
.nav-dropdown-menu a:hover, .nav-dropdown-menu a:focus-visible{ background:var(--bg-alt); }

/* Real mouse users additionally get hover-to-open, on top of the existing
   click toggle (which still works for keyboard, and remains the only
   interaction on touch devices, since touch never matches this media
   query). The margin-top above is kept small specifically so there is no
   dead zone between the button and the menu that would close it while
   moving the mouse down into it. */
@media (hover:hover) and (pointer:fine){
  .nav-dropdown:hover .nav-dropdown-menu{ opacity:1; visibility:visible; transform:translateY(0); }
  .nav-dropdown:hover .nav-dropdown-caret{ transform:rotate(180deg); }
}

@media (max-width:1000px){
  .nav-dropdown-menu{
    position:static; margin-top:6px; box-shadow:none; border:none; padding:0 0 6px 14px;
    max-height:none; opacity:1; visibility:visible; transform:none; display:none;
  }
  .nav-dropdown.is-open .nav-dropdown-menu{ display:grid; }
  .nav-dropdown-toggle{ width:100%; justify-content:space-between; padding:14px 0; border-bottom:1px solid var(--line); }
}

/* ---------- Hero ---------- */
.hero{ display:flex; align-items:stretch; min-height:min(84vh,660px); background:var(--primary); }
/* Hero spacing contract. A hero built as .hero with its content in a plain .wrap
   (not the split .hero-panel layout) had no padding and no vertical centering:
   on a phone the content touched the header and the bottom edge, on desktop it
   sat at the top with a 185px dead gap under the buttons. Every such hero now
   gets the same room as the standard page hero, content centered. */
.hero:has(> .wrap){ align-items:center; }
.hero > .wrap{
  /* centred from the hero side: the wrap keeps whatever layout it has (a
     side-by-side hero stayed side by side), it only gets the vertical room */
  width:100%;
  padding-top:clamp(40px,6vw,88px); padding-bottom:clamp(40px,6vw,88px);
}
.hero-panel{
  flex:0 0 42%; background:var(--primary); color:var(--on-dark);
  padding:clamp(36px,4.5vw,64px) clamp(24px,3.4vw,56px);
  display:flex; flex-direction:column; justify-content:center; gap:18px;
}
.hero-panel h1{ color:var(--on-dark); }
.hero-panel p{ font-size:16px; line-height:1.62; color:var(--on-dark-muted); margin:0; max-width:44ch; }
.hero-actions{ display:flex; flex-direction:row; flex-wrap:wrap; gap:12px; align-items:flex-start; margin-top:4px; }
/* Button rows on phones: two buttons share one row as equal halves instead of
   stacking full width (which made the hero taller for no reason). A long label
   wraps inside its own button rather than pushing the button onto a new line.
   !important because most content files set the row inline
   (style="flex-direction:row;flex-wrap:wrap"), and wrap is what stacked them.
   Three or more buttons may still wrap. Every button stays 48px or taller. */
@media (max-width:640px){
  .hero-actions, .cta-actions{ flex-direction:row !important; flex-wrap:nowrap !important; align-items:stretch; gap:10px; }
  :is(.hero-actions, .cta-actions):has(> .btn:nth-of-type(3)){ flex-wrap:wrap !important; }
  /* a single button keeps its natural width */
  :is(.hero-actions, .cta-actions) > .btn:only-of-type{ flex:0 1 auto; padding-left:22px; padding-right:22px; }
  :is(.hero-actions, .cta-actions) > .btn{
    flex:1 1 0; min-width:0; min-height:48px; padding:13px 10px; font-size:14px; line-height:1.2;
    display:flex; align-items:center; justify-content:center; text-align:center; white-space:normal;
  }
}
/* Alignment is driven by the single modifier class on the hero section
   itself (e.g. .photo-hero-center), never by a second class that has to be
   remembered separately on .hero-actions/.hero-tel. Before this rule,
   .hero-actions always defaulted to flex-start regardless of whether the
   heading/subhead above it were centered by .photo-hero-center - the exact
   "heading centered, buttons still left" bug. Fixing it here means it is
   structurally impossible to regress: there is only one place to set
   alignment, not two places that must agree. */
:where(.photo-hero-center, .hero-center) .hero-actions{ align-items:center; justify-content:center; }
:where(.photo-hero-center, .hero-center) .hero-tel{ align-self:center; }
/* the eyebrow is a flex row, so text-align cannot centre it: centre its content too */
:where(.photo-hero-center, .hero-center) .eyebrow{ justify-content:center; }
.hero-tel{ font-size:14px; color:var(--soft); text-decoration:none; }
.hero-tel b{ color:var(--on-dark); font-size:15px; }

/* reveal stage */
.stage{ flex:1 1 auto; position:relative; overflow:hidden; background:var(--primary-dark); }
.reveal{ position:absolute; inset:0; cursor:ew-resize; touch-action:none; --pos:0; }
.reveal-layer{ position:absolute; inset:0; overflow:hidden; }
.reveal-layer img{ width:100%; height:100%; object-fit:cover; }
.reveal-after{ clip-path:inset(0 calc(100% - var(--pos) * 1%) 0 0); }
/* Placeholder treatment: the "before" is the same photo aged with filters so the
   geometry matches. Replace both with real matched project photos when available. */
.reveal-before img{ filter:sepia(.30) saturate(.66) contrast(.90) brightness(.76) hue-rotate(-6deg); }
.reveal-before::after{
  content:""; position:absolute; inset:0; background:linear-gradient(180deg,rgba(124,98,44,.20),rgba(58,46,22,.30));
}
.reveal-vignette{
  position:absolute; inset:0; pointer-events:none; z-index:4;
  background:radial-gradient(ellipse at 50% 45%,transparent 56%,rgba(42,38,34,.24) 100%);
}

.reveal-handle{
  position:absolute; top:0; bottom:0; left:calc(var(--pos) * 1%); width:2px; background:var(--on-dark);
  box-shadow:0 0 0 1px rgba(42,38,34,.18); opacity:0; transition:opacity .45s ease; z-index:6;
}
.reveal-handle.on{ opacity:1; }
.reveal-grip{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:54px; height:54px; border-radius:50%; background:var(--on-dark); color:var(--primary);
  display:grid; place-items:center; box-shadow:0 6px 22px rgba(42,38,34,.4); border:none; cursor:ew-resize;
}

.reveal-tag{
  position:absolute; top:20px; z-index:5; font-family:var(--display); font-size:11px; font-weight:700;
  letter-spacing:.14em; padding:6px 13px; border-radius:999px; transition:opacity .4s ease;
}
.tag-before{ right:20px; background:rgba(42,38,34,.6); color:var(--on-dark); }
.tag-after{ left:20px; background:var(--on-dark); color:var(--primary); opacity:0; }
.tag-before.off{ opacity:0; }
.tag-after.on{ opacity:1; }

.reveal-pin{
  position:absolute; z-index:5; transform:translate(-50%,-50%); opacity:0; transition:opacity .5s ease;
  display:flex; align-items:center; gap:8px; pointer-events:none;
}
.reveal-pin.on{ opacity:1; }
.reveal-pin i{ width:11px; height:11px; border-radius:50%; background:var(--accent);
  box-shadow:0 0 0 4px rgba(243,239,228,.9); flex:none; }
.reveal-pin b{ font-size:11.5px; font-weight:600; background:rgba(243,239,228,.95);
  padding:5px 10px; border-radius:6px; white-space:nowrap; }
.reveal-pin.flip{ transform:translate(-100%,-50%); flex-direction:row-reverse; }

@media (max-width:980px){
  .hero{ flex-direction:column; min-height:0; }
  .hero-panel{ flex:none; width:100%; }
  .stage{ flex:none; height:min(58vw,360px); }
  /* Three pins crowd each other once the stage narrows. */
  .reveal-pin[data-x="50"]{ display:none; }
}
@media (max-width:760px){
  /* Pills are wider than the space left beside them and clip at the frame edge,
     so the annotations drop entirely. The sweep and drag still carry the section. */
  .reveal-pin{ display:none; }
}

/* ---------- Service area strip ---------- */
.area-strip{ background:var(--bg-alt); border-bottom:1px solid var(--line); }
.area-inner{ display:flex; align-items:center; gap:14px; flex-wrap:wrap; padding-top:18px; padding-bottom:18px; }
.area-label{ font-size:12px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-muted); }
.chip{
  font-size:12.5px; background:var(--surface); border:1px solid var(--line);
  padding:6px 13px; border-radius:999px; text-decoration:none; transition:background .16s ease, border-color .16s ease;
}
a.chip:hover{ background:#fff; border-color:var(--accent); }
.chip-more{ background:transparent; border-style:dashed; color:var(--accent); font-weight:600; }

/* ---------- Sections ---------- */
.section{ padding-top:clamp(52px,7vw,92px); padding-bottom:clamp(52px,7vw,92px); }
.section-surface{ background:var(--surface); }
.section-alt{ background:var(--bg-alt); }
.section-forest{ background:var(--primary); color:var(--on-dark); }
.section-forest h2{ color:var(--on-dark); }
.section-forest .lede{ color:var(--on-dark-muted); }

.section-head{ max-width:66ch; margin-bottom:clamp(28px,3.6vw,46px); }

/* ---------- Services ---------- */
.services{ display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:22px; }
.service-card{
  background:var(--surface); border-radius:var(--r); overflow:hidden;
  border:1px solid var(--line); display:flex; flex-direction:column;
  transition:transform .2s ease, box-shadow .2s ease;
}
.section-surface .service-card{ background:var(--bg); }
.service-card:hover{ transform:translateY(-4px); box-shadow:0 14px 34px rgba(42,38,34,.13); }
.service-card img{ width:100%; aspect-ratio:4/3; object-fit:cover; }
.service-body{ padding:24px; display:flex; flex-direction:column; gap:12px; flex:1; }
.service-body h3{ color:var(--ink); }
.service-body p{ margin:0; font-size:14.5px; color:var(--ink-muted); }
.service-list{ margin:0; padding-left:18px; font-size:14px; color:var(--ink-muted); display:grid; gap:5px; }
.service-link{
  margin-top:auto; font-family:var(--display); font-weight:700; font-size:14.5px;
  color:var(--accent); text-decoration:none; display:inline-flex; align-items:center; gap:7px;
  min-height:44px;
}
.service-link:hover{ gap:12px; }

/* ---------- Blog post body ---------- */
/* Long-form reading typography for blog post connective prose, sized
   larger than the browser default 16px, matching the visual weight the
   rest of the site's type system already carries (.lede, service-card
   copy). */
.blog-post-body{ max-width:68ch; }
.blog-post-body p{
  font-size:clamp(15px,1.3vw,17px); line-height:1.75; color:var(--ink);
  margin:0 0 18px;
}
.blog-post-body p:last-child{ margin-bottom:0; }
.blog-post-body h2,.blog-post-body h3{ margin-top:28px; margin-bottom:10px; }
.blog-post-body ul,.blog-post-body ol{ font-size:15.5px; line-height:1.7; color:var(--ink); }

.blog-byline{
  font-size:13.5px; color:var(--soft); margin:14px 0 0; letter-spacing:.02em;
}

/* ---------- Process ---------- */
.process{ display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:20px; counter-reset:step; }
.step{ background:var(--surface); border-radius:var(--r); padding:26px 22px; border-top:4px solid var(--accent); }
.section-surface .step{ background:var(--bg); }
.step-num{ font-family:var(--display); font-weight:800; font-size:30px; color:var(--accent); line-height:1; }
.step h3{ margin:10px 0 7px; }
.step p{ margin:0; font-size:14px; color:var(--ink-muted); }

/* ---------- Why local ---------- */
.why{ display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:26px; }
.why-item{ padding-left:20px; border-left:3px solid var(--soft); }
.section-forest .why-item{ border-left-color:var(--accent); }
.why-item h3{ margin-bottom:8px; }
.why-item p{ margin:0; font-size:14.5px; color:var(--ink-muted); }
.section-forest .why-item p{ color:var(--on-dark-muted); }

/* ---------- Gallery ---------- */
.gallery{ display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:22px; }
.ba{ border-radius:var(--r); overflow:hidden; border:1px solid var(--line); background:var(--surface); }
.ba-stage{ position:relative; aspect-ratio:4/3; overflow:hidden; cursor:ew-resize; touch-action:none; --pos:50; }
.ba-stage img{ width:100%; height:100%; object-fit:cover; }
.ba-layer{ position:absolute; inset:0; }
.ba-after{ clip-path:inset(0 calc(100% - var(--pos) * 1%) 0 0); }
.ba-before img{ filter:sepia(.30) saturate(.66) contrast(.90) brightness(.76) hue-rotate(-6deg); }
.ba-handle{ position:absolute; top:0; bottom:0; left:calc(var(--pos) * 1%); width:2px; background:var(--on-dark); z-index:3;
  box-shadow:0 0 0 1px rgba(42,38,34,.2); }
.ba-grip{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:42px; height:42px;
  border-radius:50%; background:var(--on-dark); color:var(--primary); display:grid; place-items:center;
  box-shadow:0 4px 16px rgba(42,38,34,.36); border:none; cursor:ew-resize;
}
.ba-caption{ padding:16px 20px; display:flex; justify-content:space-between; align-items:center; gap:12px; }
.ba-caption h3{ font-size:16px; }
.ba-caption span{ font-size:12.5px; color:var(--ink-muted); }
.ba-tag{
  position:absolute; top:14px; left:14px; z-index:3; font-family:var(--display); font-size:10px;
  font-weight:700; letter-spacing:.12em; padding:5px 11px; border-radius:999px;
  background:rgba(42,38,34,.62); color:var(--on-dark);
}

.filters{ display:flex; gap:10px; flex-wrap:wrap; margin-bottom:26px; }
.filter{
  font-family:var(--display); font-weight:700; font-size:13.5px; padding:10px 20px; border-radius:999px;
  border:1px solid var(--line); background:transparent; color:var(--ink); cursor:pointer;
  transition:background .16s ease, color .16s ease, border-color .16s ease;
}
.filter:hover{ border-color:var(--accent); }
.filter[aria-pressed="true"]{ background:var(--primary); color:var(--on-dark); border-color:var(--primary); }

/* ---------- Reviews slot ---------- */
.reviews-slot{
  background:var(--surface); border:2px dashed var(--accent); border-radius:var(--r);
  padding:clamp(28px,4vw,44px); text-align:center;
}
.reviews-slot h3{ margin-bottom:10px; }
.reviews-slot p{ margin:0 auto; max-width:52ch; font-size:14.5px; color:var(--ink-muted); }

/* ---------- FAQ ---------- */
.faq{ max-width:820px; }
.faq-item{ border-bottom:1px solid var(--line); }
.faq-q{
  width:100%; background:none; border:none; text-align:left; cursor:pointer;
  font-family:var(--display); font-weight:700; font-size:clamp(15px,1.7vw,17.5px); color:var(--ink);
  padding:22px 44px 22px 0; position:relative; line-height:1.35;
}
.faq-q::after{
  content:""; position:absolute; right:8px; top:50%; width:12px; height:12px; margin-top:-6px;
  border-right:2px solid var(--accent); border-bottom:2px solid var(--accent);
  transform:rotate(45deg); transition:transform .22s ease;
}
.faq-q[aria-expanded="true"]::after{ transform:rotate(-135deg); }
.faq-a{ display:none; padding:0 44px 24px 0; }
.faq-a.open{ display:block; }
.faq-a p{ margin:0; font-size:15px; color:var(--ink-muted); }

/* ---------- CTA band ---------- */
.cta-band{ background:var(--accent); color:#fff; text-align:center; padding:clamp(48px,6vw,80px) 0; }
.cta-band h2{ color:#fff; max-width:20ch; margin:0 auto; }
.cta-band p{ max-width:52ch; margin:16px auto 26px; color:rgba(255,255,255,.9); font-size:16px; }
.cta-actions{ display:flex; gap:14px; justify-content:center; flex-wrap:wrap; }

/* ---------- Reviews embed (Trustindex injects here) ---------- */
.reviews-embed{ min-height:220px; }

/* ---------- Call card (replaces the booking form) ---------- */
.call-card{
  background:var(--primary); color:var(--on-dark); border-radius:var(--r);
  padding:clamp(30px,4vw,46px); display:flex; flex-direction:column; align-items:flex-start; gap:14px;
}
.call-card .eyebrow{ color:var(--soft); }
.call-card h2{ color:var(--on-dark); font-size:clamp(23px,2.7vw,32px); }
.call-card p{ margin:0; color:var(--on-dark-muted); font-size:15.5px; line-height:1.6; max-width:44ch; }
.btn-big{ font-size:17px; padding:18px 30px; margin-top:4px; }
.call-hours{ font-size:13.5px !important; color:var(--soft) !important; }
.call-alt{ font-size:13.5px !important; }
.call-alt a{ color:var(--on-dark); font-weight:600; }

/* ---------- Page hero (inner pages) ---------- */
.page-hero{ background:var(--primary); color:var(--on-dark); padding:clamp(48px,6vw,86px) 0; position:relative; }
.page-hero h1{ color:var(--on-dark); max-width:19ch; }
.page-hero .lede{ color:var(--on-dark-muted); }
.page-hero-grid{ display:grid; grid-template-columns:1.1fr .9fr; gap:clamp(28px,4vw,56px); align-items:center; }
.page-hero-grid img{ width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:var(--r); }
@media (max-width:860px){ .page-hero-grid{ grid-template-columns:1fr; } }

/* Page hero with a real photo as its full-bleed background (a partner's
   own photo behind the H1/lede, not a plain color). This is a shared,
   reusable pattern: <section class="page-hero photo-hero"><img ...>
   <div class="wrap">...</div></section>. A dark tint sits over the photo
   so the on-dark heading/lede text stays readable against any image. */
.photo-hero{ overflow:hidden; }
.photo-hero > img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0;
}
.photo-hero::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(180deg, rgba(0,0,0,.38), rgba(0,0,0,.58));
}
.photo-hero .wrap{ position:relative; z-index:2; }
.photo-hero-center{ text-align:center; }
.photo-hero-center .wrap{ max-width:760px; margin-left:auto; margin-right:auto; }
.photo-hero-center .lede{ margin-left:auto; margin-right:auto; }

/* ---------- Towns grid ---------- */
.towns{ display:grid; grid-template-columns:repeat(auto-fill,minmax(190px,1fr)); gap:14px; }
.town{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
  padding:18px 20px; display:flex; align-items:center; gap:11px;
}
.section-surface .town{ background:var(--bg); }
.town i{ width:9px; height:9px; border-radius:50%; background:var(--accent); flex:none; }
.town b{ font-family:var(--display); font-weight:700; font-size:15px; }

/* ---------- Split content ---------- */
.split{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,4vw,56px); align-items:center; }
.split img{ width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:var(--r); }
@media (max-width:860px){ .split{ grid-template-columns:1fr; } }

/* ---------- Contact ---------- */
.contact-grid{ display:grid; grid-template-columns:.85fr 1.15fr; gap:clamp(28px,4vw,52px); align-items:start; }
@media (max-width:900px){ .contact-grid{ grid-template-columns:1fr; } }
.contact-block{ margin-bottom:28px; }
.contact-block h3{ margin-bottom:8px; }
.contact-block p, .contact-block a{ margin:0; font-size:15.5px; color:var(--ink-muted); text-decoration:none; }
.contact-block a:hover{ color:var(--accent); }
.contact-big{ font-family:var(--display); font-weight:800; font-size:clamp(22px,2.6vw,29px); color:var(--accent); text-decoration:none; }

/* ---------- Footer ---------- */
.site-footer{ background:var(--ink); color:var(--on-dark-muted); padding:clamp(44px,5vw,68px) 0 0; }
.footer-grid{ display:grid; grid-template-columns:1.5fr 1fr 1fr 1.2fr; gap:34px; padding-bottom:38px; }
@media (max-width:900px){ .footer-grid{ grid-template-columns:1fr 1fr; } }
@media (max-width:520px){ .footer-grid{ grid-template-columns:1fr; } }
.site-footer h4{ color:var(--on-dark); margin-bottom:14px; font-size:13px; letter-spacing:.1em; text-transform:uppercase; }
.footer-links{ list-style:none; margin:0; padding:0; display:grid; gap:9px; }
.footer-links a{ font-size:14px; text-decoration:none; color:var(--on-dark-muted); }
.footer-links a:hover{ color:var(--on-dark); }
.footer-brand .brand-name{ color:var(--on-dark); font-size:17px; }
.social-links{ list-style:none; margin:16px 0 0; padding:0; display:flex; gap:10px; flex-wrap:wrap; }
.social-links:empty{ display:none; margin:0; }
.social-link{ display:inline-flex; align-items:center; justify-content:center;
  width:36px; height:36px; border-radius:999px; border:1px solid var(--on-dark-muted);
  color:var(--on-dark-muted); text-decoration:none; transition:border-color .15s, color .15s; }
.social-link:hover{ border-color:var(--on-dark); color:var(--on-dark); }
.social-link .ico{ width:17px; height:17px; stroke:currentColor; }

/* Map widget. build.py always fills this via {{map_embed}}, either a real
   Google Maps iframe (no API key needed, it is the public embed share link)
   or a keyless "Open in Google Maps" fallback derived from the NAP address.
   Never ships empty; see audit.py check_map_embed. */
.map-embed{ width:100%; border-radius:var(--r); overflow:hidden; }
.map-embed iframe{ width:100%; aspect-ratio:16/9; border:0; display:block; }
.map-embed-fallback{ aspect-ratio:auto; display:flex; align-items:center; justify-content:center;
  min-height:160px; background:var(--bg-alt); padding:24px; text-align:center; }

/* Trust badges + NAP block, used on service-area "mini funnel" pages. Only
   ever rendered from real, existing partner data (see trust_badges_html and
   nap_block_html in build.py), never a fabricated claim. */
/* Trust stats row (reuses the site's own .stat cell styling from
   visuals.css) and NAP block, used on service-area "mini funnel" pages.
   Only ever rendered from real, existing partner data (see
   trust_stats_html and nap_block_html in build.py), never a fabricated
   claim. */
.trust-stats{ display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:0; }
.nap-block{ list-style:none; margin:10px 0 0; padding:0; display:grid; gap:10px; }
.nap-block li{ display:flex; align-items:center; gap:9px; font-size:14.5px; color:var(--ink); }
.nap-block a{ color:inherit; text-decoration:none; }
.nap-block a:hover{ color:var(--accent); }
.nap-block .ico{ stroke:var(--accent); flex:none; }

/* A plain photo with a visible caption underneath, used for the
   service-area "recent work" gallery. Distinct from .ba, which is the
   before/after drag-slider component and needs a different inner
   structure: this is just a real, properly cropped/framed photo. */
.photo-card{ border-radius:var(--r); overflow:hidden; border:1px solid var(--line); background:var(--surface); }
.photo-card img{ width:100%; aspect-ratio:4/3; object-fit:cover; display:block; }
.photo-card figcaption{ padding:14px 16px; font-size:13.5px; color:var(--ink-muted); }

/* Blog listing page. A plain, readable list of post links, matching this
   site's other simple list patterns, not the footer's dark-background
   link style. */
.blog-listing{ list-style:none; margin:0; padding:0; display:grid; gap:16px; max-width:640px; }
.blog-listing li{ border-bottom:1px solid var(--line); padding-bottom:16px; }
.blog-listing a{
  font-family:var(--display); font-weight:700; font-size:16.5px;
  text-decoration:none; color:var(--ink);
}
.blog-listing a:hover{ color:var(--accent); }
.lead-form-copy img{ width:100%; aspect-ratio:16/10; object-fit:cover; border-radius:var(--r); margin-bottom:18px; }
.footer-about{ font-size:14px; line-height:1.6; margin:14px 0 0; max-width:36ch; }
.footer-bottom{
  border-top:1px solid rgba(243,239,228,.14); padding:20px 0 28px;
  display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; font-size:12.5px;
}

/* ---------- Scroll reveal ---------- */
.reveal-up{ opacity:0; transform:translateY(20px); transition:opacity .6s ease, transform .6s ease; }
.reveal-up.in{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  .reveal-up{ opacity:1; transform:none; transition:none; }
  .btn:hover, .service-card:hover{ transform:none; }
}

/* ---------- Lead form ---------- */
.lead-form-section{ border-top:1px solid var(--line); }
.lead-form-shell{ display:grid; grid-template-columns:.8fr 1.2fr; gap:clamp(28px,5vw,64px); align-items:start; }
.lead-form-copy{ position:sticky; top:110px; }
.lead-form-copy h2{ max-width:12ch; }
.lead-form{ background:var(--surface); border:1px solid var(--line); border-radius:var(--r); padding:clamp(24px,4vw,40px); box-shadow:0 16px 40px rgba(42,38,34,.08); }
.lead-form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:18px; }
.lead-form label{ display:grid; gap:7px; font-family:var(--display); font-weight:700; font-size:13px; color:var(--ink); }
.lead-form input,.lead-form select,.lead-form textarea{
  width:100%; border:1px solid var(--line); border-radius:8px; background:var(--bg); color:var(--ink);
  font:500 15px/1.45 var(--body); padding:12px 13px; min-height:48px;
}
.lead-form textarea{ resize:vertical; min-height:132px; }
.lead-form input:focus,.lead-form select:focus,.lead-form textarea:focus{ border-color:var(--accent); outline:3px solid var(--accent); outline-offset:2px; }
.lead-form-wide{ grid-column:1/-1; }
.lead-form-note{ color:var(--ink-muted); font-size:12.5px; line-height:1.5; margin:18px 0; }
.lead-form-status{ min-height:1.4em; color:var(--ink-muted); font-size:13px; margin:10px 0 0; }
.honeypot{ position:absolute !important; left:-10000px !important; top:auto !important; width:1px !important; height:1px !important; overflow:hidden !important; }
.footer-form-cta{ margin:18px 0 0; }
.footer-form-cta .btn{ padding:10px 15px; font-size:13px; }

/* ---------- Legal and utility pages ---------- */
.legal-page{ min-height:60vh; }
.prose{ max-width:820px; }
.prose h1{ max-width:18ch; margin-bottom:18px; }
.prose h2{ margin-top:34px; margin-bottom:10px; }
.prose p,.prose li{ color:var(--ink-muted); }
.prose ul{ color:var(--ink-muted); }

@media (max-width:900px){
  .lead-form-shell{ grid-template-columns:1fr; }
  .lead-form-copy{ position:static; }
}
@media (max-width:620px){
  .lead-form-grid{ grid-template-columns:1fr; }
  .lead-form-wide{ grid-column:auto; }
  .lead-form{ padding:22px; }
}

/* one or two real trust facts, shown as one card of fact rows rather than a
   half-empty card grid; side by side when there is room */
.trust-strip{
  list-style:none; margin:0; padding:0; display:grid; gap:1px;
  grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));
  background:var(--line); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;
}
.trust-strip li{
  display:grid; grid-template-columns:auto 1fr; column-gap:14px; row-gap:2px; align-items:start;
  padding:18px 20px; background:var(--surface); font-size:14.5px; line-height:1.5; color:var(--ink-muted);
}
.trust-strip .ico{ grid-row:span 2; width:22px; height:22px; margin-top:2px; color:var(--accent); }
.trust-strip li b{ color:var(--ink); font-family:var(--display); font-size:17px; }
.trust-strip li span{ grid-column:2; }

/* Phones: nothing readable under 12px and every contact link a full 44px tap.
   Desktop keeps its own sizes; these only lift what is too small on a phone. */
@media (max-width:640px){
  .eyebrow, .brand-name span{ font-size:12px; }
  .site-header .brand{ min-height:44px; }
  .contact-big, .nap-block a{ display:inline-flex; align-items:center; min-height:44px; }
  .nap-block{ gap:0; }
}


/* ==========================================================================
   Visual devices
   --------------------------------------------------------------------------
   Structure only. Every device below draws itself from the partner tokens, so
   the same device looks like a different studio made it once the palette and
   type change.

   This library exists because of one specific failure. A partner arrived with
   no photography, the build had no vocabulary for "make something to look at",
   and it shipped seven pages of stacked text with zero images, zero graphics
   and zero texture. It passed every audit, because nothing measured whether
   there was anything to see.

   These are NOT signatures. The signature is one bespoke thing per partner and
   still gets designed from scratch. These are the supporting texture around it.
   ========================================================================== */

/* ---------------------------------------------------------------- figures ---
   A framed image. The offset accent panel behind it is what separates a
   designed figure from a bare img dropped on a page. */
.figure{ position:relative; margin:0; }
.figure img{ width:100%; border-radius:var(--radius); position:relative; z-index:1; }
.figure figcaption{
  margin-top:10px; font-size:12.5px; color:var(--ink-muted); line-height:1.5;
}
.figure-offset::before{
  content:""; position:absolute; z-index:0; inset:auto auto -14px -14px;
  width:58%; height:62%; background:var(--accent); border-radius:var(--radius);
  opacity:.16;
}
.figure-rule img{ border:1px solid var(--line); }
.figure-tall img{ aspect-ratio:3/4; object-fit:cover; }
.figure-wide img{ aspect-ratio:16/9; object-fit:cover; }
.figure-square img{ aspect-ratio:1/1; object-fit:cover; }

/* A full width atmospheric band. Never a portfolio claim, so it carries no
   caption naming a place or a project. */
.band{ position:relative; overflow:hidden; background:var(--primary-dark); }
.band img{ width:100%; height:100%; object-fit:cover; position:absolute; inset:0; }
.band-inner{ position:relative; z-index:2; padding-top:clamp(64px,9vw,132px); padding-bottom:clamp(64px,9vw,132px); }
.band::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg,var(--primary) 8%,rgba(0,0,0,.42) 62%,rgba(0,0,0,.16));
}
.band h2,.band h3,.band p{ color:var(--on-dark); }
.band p{ color:var(--on-dark-muted); }
.band-tint::after{ background:linear-gradient(180deg,rgba(0,0,0,.30),rgba(0,0,0,.62)); }

/* -------------------------------------------------------------- stat band ---
   Numbers set large enough to work as graphics. Only ever fed by fields the
   brief actually supports, never invented to fill the row. */
.statband{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:1px; background:var(--line); }
.stat{ background:var(--bg); padding:clamp(22px,3vw,34px) clamp(16px,2vw,24px); }
.section-forest .stat,.band .stat{ background:transparent; }
.stat b{
  display:block; font-family:var(--display); font-weight:800;
  font-size:clamp(34px,5.2vw,60px); line-height:1; letter-spacing:-.03em;
  color:var(--accent); font-variant-numeric:tabular-nums;
}
/* Direct-child only (not a descendant selector) - this must never catch a
   grad-text span nested inside .stat b (the stat NUMBER), only the
   label span that is a direct sibling of b. A descendant selector here
   was a real bug: it force-shrunk a stat number down to label size the
   moment grad-text was used on it, since .grad-text itself doesn't set
   font-size and just inherits whatever matches - and this rule matched
   too broadly. */
.stat > span{ display:block; margin-top:9px; font-size:13px; line-height:1.45; color:var(--ink-muted); }
.section-forest .stat > span,.band .stat > span{ color:var(--on-dark-muted); }
/* A stat whose value is a word rather than a figure. Numerals stay huge,
   but a word at 60px wraps inside the cell and reads as clipped. */
.stat b.stat-word{ font-size:clamp(21px,2.6vw,32px); letter-spacing:-.01em; }

.statband-bare{ background:transparent; gap:clamp(18px,3vw,40px); }
.statband-bare .stat{ padding:0; }

/* Phones: two stat cells a row. An odd last stat takes the whole row instead of
   leaving an empty grey cell, and word stats ("Homeowners") are sized for a
   half-width cell so they never touch or cross its edge. Desktop unchanged. */
@media (max-width:640px){
  .statband:not(.rb-bento){ grid-template-columns:1fr 1fr; }
  .statband:not(.rb-bento) > .stat:last-child:nth-child(odd){ grid-column:1 / -1; }
  /* No overflow-wrap:anywhere here. It breaks a word in half to hide an
     overflow ("Whole/sale"), which reads as broken text and, worse, hides the
     overflow from the loader's fit pass, which only shrinks an element that
     actually overflows. Left to wrap between words, a label too long for the
     cell is measured and sized down instead. */
  .statband:not(.rb-bento) .stat b.stat-word{ font-size:clamp(17px, 5.2vw, 22px); }
}

/* --------------------------------------------------------------- marquee ---
   A moving strip of the things this business actually does or the towns it
   covers. The duplicated half is aria-hidden so it is announced once. */
.marquee{ overflow:hidden; position:relative; padding:15px 0; border-block:1px solid var(--line); }
.marquee-track{ display:flex; width:max-content; gap:0; animation:marquee 42s linear infinite; }
.marquee:hover .marquee-track,.marquee:focus-within .marquee-track{ animation-play-state:paused; }
.marquee-track span{
  display:flex; align-items:center; gap:clamp(20px,3vw,42px); padding-right:clamp(20px,3vw,42px);
  font-family:var(--display); font-weight:700; letter-spacing:-.01em;
  font-size:clamp(15px,2.1vw,22px); white-space:nowrap; color:var(--ink);
}
.marquee-track span i{ font-style:normal; color:var(--accent); font-size:.8em; }
.section-forest .marquee-track span,.band .marquee-track span{ color:var(--on-dark); }
@keyframes marquee{ to{ transform:translateX(-50%); } }
@media (prefers-reduced-motion:reduce){
  .marquee-track{ animation:none; flex-wrap:wrap; width:auto; }
}

/* ---------------------------------------------------------- section edges ---
   Sections that merely stack read as a document. An edge between them reads as
   a designed page, and it costs nothing to render. */
.edge{ position:relative; }
.edge-top-angle{ clip-path:polygon(0 3.2vw,100% 0,100% 100%,0 100%); margin-top:-3.2vw; padding-top:calc(3.2vw + clamp(52px,7vw,92px)); }
.edge-bot-angle{ clip-path:polygon(0 0,100% 0,100% calc(100% - 3.2vw),0 100%); padding-bottom:calc(3.2vw + clamp(52px,7vw,92px)); }
.edge-top-notch{ clip-path:polygon(0 0,42% 0,47% 2.4vw,100% 2.4vw,100% 100%,0 100%); margin-top:-2.4vw; padding-top:calc(2.4vw + clamp(52px,7vw,92px)); }
.edge-top-round{ border-radius:clamp(28px,4vw,64px) clamp(28px,4vw,64px) 0 0; margin-top:clamp(-64px,-4vw,-28px); position:relative; z-index:2; padding-top:calc(clamp(52px,7vw,92px) + 18px); }
.edge-top-step{ clip-path:polygon(0 2.6vw,33% 2.6vw,33% 1.3vw,66% 1.3vw,66% 0,100% 0,100% 100%,0 100%); margin-top:-2.6vw; padding-top:calc(2.6vw + clamp(52px,7vw,92px)); }

/* ------------------------------------------------------------ photo hero ---
   A hero standing on a photograph rather than a flat panel. Asked for
   directly in review: "theres an image in the background, which also makes it
   look a lot better".

   The scrim is not decoration. Type over an unmodified photograph fails
   contrast somewhere in the frame no matter which colour it is, because the
   photograph varies and the type does not. The gradient guarantees a floor
   under the copy side while leaving the far side of the image open. */
.photo-hero{ position:relative; overflow:hidden; background:var(--primary-dark); }
.photo-hero > img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  z-index:0;
}
.photo-hero::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(96deg,
    var(--primary) 0%,
    color-mix(in srgb,var(--primary) 82%,transparent) 38%,
    color-mix(in srgb,var(--primary) 34%,transparent) 66%,
    rgba(0,0,0,.22) 100%);
}
.photo-hero > .wrap{ position:relative; z-index:2; }
.photo-hero h1,.photo-hero h2{ color:var(--on-dark); }
.photo-hero .lede,.photo-hero p{ color:var(--on-dark-muted); }
.photo-hero .eyebrow,.photo-hero .eyebrow-light{ color:var(--soft); }
/* Centre weighted variant, for a page hero with no side by side content. */
.photo-hero-center::after{
  background:linear-gradient(180deg,rgba(0,0,0,.30),
             color-mix(in srgb,var(--primary) 78%,transparent));
}

/* Light variant. Puts a photograph behind a hero that already has dark type
   and its own signature element, without inverting either. The wash stays
   near opaque over the copy and thins towards the far edge, so the picture
   reads as depth rather than as a subject competing with the headline.
   Use when the hero has content that cannot flip to light type. */
.photo-hero-soft{ background:var(--bg); }
.photo-hero-soft::after{
  background:linear-gradient(100deg,
    var(--bg) 0%,
    color-mix(in srgb,var(--bg) 94%,transparent) 34%,
    color-mix(in srgb,var(--bg) 80%,transparent) 62%,
    color-mix(in srgb,var(--bg) 62%,transparent) 100%);
}
.photo-hero-soft h1,.photo-hero-soft h2{ color:var(--ink); }
.photo-hero-soft .lede,.photo-hero-soft p{ color:var(--ink-muted); }
.photo-hero-soft .eyebrow,.photo-hero-soft .eyebrow-light{ color:var(--ink-muted); }

/* A photograph carrying a section rather than sitting beside it. Lighter than
   .band: the image stays legible and the copy sits on a panel over it. */
.photo-panel{ position:relative; overflow:hidden; border-radius:var(--radius); }
.photo-panel > img{ width:100%; height:100%; object-fit:cover; display:block; }
.photo-panel-copy{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  padding:clamp(20px,3vw,34px);
  background:linear-gradient(0deg,rgba(0,0,0,.78),rgba(0,0,0,.34) 62%,transparent);
  color:var(--on-dark);
}
.photo-panel-copy h3{ color:var(--on-dark); margin:0 0 6px; }
.photo-panel-copy p{ color:var(--on-dark-muted); margin:0; font-size:14.5px; }

/* ---------------------------------------------------- coloured sections ---
   Review note was that the palette read as bland. It was not the palette, it
   was that nine of eleven sections were white or near white and the accent
   only ever appeared as a thin icon stroke. These give a section real colour
   without inventing a new one. */
.section-accent{ background:var(--accent); color:#fff; }
.section-accent h2,.section-accent h3{ color:#fff; }
.section-accent p,.section-accent .lede{ color:rgba(255,255,255,.88); }
.section-accent .eyebrow,.section-accent .eyebrow-light{ color:rgba(255,255,255,.72); }

.section-tint{ background:color-mix(in srgb,var(--accent) 11%,var(--bg)); }
.section-deep{ background:var(--primary-dark); color:var(--on-dark); }
.section-deep h2,.section-deep h3{ color:var(--on-dark); }
.section-deep p,.section-deep .lede{ color:var(--on-dark-muted); }
.section-deep .eyebrow,.section-deep .eyebrow-light{ color:var(--soft); }

/* -------------------------------------------------------------- patterns ---
   Pure gradient texture. Trade flavoured, so pick the one that belongs to the
   work rather than the one that looks nicest in isolation. Applied to a
   section, they sit under the content at low opacity. */
.pat{ position:relative; }
.pat > *{ position:relative; z-index:1; }
.pat::before{ content:""; position:absolute; inset:0; z-index:0; opacity:var(--pat-a,.055); pointer-events:none; }

/* blueprint, for anyone who works from drawings */
.pat-blueprint::before{
  background-image:linear-gradient(var(--pat-c,currentColor) 1px,transparent 1px),
                   linear-gradient(90deg,var(--pat-c,currentColor) 1px,transparent 1px);
  background-size:34px 34px;
}
/* subway tile, for tile setters, bath remodels, backsplash work */
.pat-tile::before{
  background-image:linear-gradient(var(--pat-c,currentColor) 1.5px,transparent 1.5px),
                   linear-gradient(90deg,var(--pat-c,currentColor) 1.5px,transparent 1.5px);
  background-size:76px 38px; background-position:0 0,38px 0;
}
/* brushed, for painters */
.pat-brush::before{
  background-image:repeating-linear-gradient(96deg,var(--pat-c,currentColor) 0 1px,transparent 1px 7px);
}
/* diagonal hatch, general trades */
.pat-diag::before{
  background-image:repeating-linear-gradient(45deg,var(--pat-c,currentColor) 0 2px,transparent 2px 13px);
}
/* board and batten, for siding, decks, fencing, carpentry */
.pat-board::before{
  background-image:repeating-linear-gradient(90deg,var(--pat-c,currentColor) 0 2px,transparent 2px 58px);
}
/* dots, quietest option when the trade suggests nothing in particular */
.pat-dots::before{
  background-image:radial-gradient(var(--pat-c,currentColor) 1.4px,transparent 1.4px);
  background-size:24px 24px;
}
/* shingle scallop, for roofers */
.pat-shingle::before{
  background-image:radial-gradient(circle at 50% 100%,transparent 13px,var(--pat-c,currentColor) 13px 14px,transparent 14px);
  background-size:32px 20px;
}
.pat-strong{ --pat-a:.10; }
.pat-faint{ --pat-a:.03; }

/* ----------------------------------------------------------- sticky split ---
   One panel pins while the detail beside it scrolls. Reads as considered and
   costs no JavaScript. Collapses to plain stacking on narrow screens, where
   sticky behaviour is mostly unwanted. */
.split-sticky{ display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr); gap:clamp(28px,5vw,72px); align-items:start; }
.split-pin{ position:sticky; top:calc(var(--header-h,72px) + 26px); }
.split-flow > * + *{ margin-top:clamp(28px,4vw,52px); }
.split-flow article{ padding-bottom:clamp(28px,4vw,52px); border-bottom:1px solid var(--line); }
.split-flow article:last-child{ border-bottom:0; padding-bottom:0; }
@media (max-width:900px){
  .split-sticky{ grid-template-columns:1fr; }
  .split-pin{ position:static; }
}

/* -------------------------------------------------------------- step flow ---
   Process as a path rather than a numbered list. The connector line is what
   makes it read as a sequence. */
.stepflow{ display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:clamp(20px,3vw,34px); position:relative; }
.stepflow .step{ position:relative; padding-top:52px; }
.stepflow .step::before{
  content:""; position:absolute; top:19px; left:0; right:0; height:2px;
  background:repeating-linear-gradient(90deg,var(--line) 0 7px,transparent 7px 13px);
}
.stepflow .step:last-child::before{ right:50%; }
.stepflow .step:first-child::before{ left:50%; }
.stepflow .step-num{
  position:absolute; top:0; left:0; width:40px; height:40px; border-radius:50%;
  display:grid; place-items:center; background:var(--accent); color:#fff;
  font-family:var(--display); font-weight:800; font-size:15px; z-index:1;
}
.stepflow .step h3{ font-size:17px; margin:0 0 7px; }
.stepflow .step p{ font-size:14.5px; color:var(--ink-muted); margin:0; line-height:1.6; }
@media (max-width:640px){
  .stepflow .step::before{ display:none; }
}

/* -------------------------------------------------------------- big quote ---
   One sentence at a size that makes it an image. Use once per site at most. */
/* The measure lives on the p, never on the wrapper. ch resolves against the
   font of the element it is declared on, and the wrapper inherits body text
   at 16px while the p runs up to 54px display. Declaring 22ch on the wrapper
   measured it in body type and produced a 219px column, which broke a 54px
   sentence across nine lines on four pages of a live partner site. */
.bigquote{ max-width:100%; }
.bigquote p{
  font-family:var(--display); font-weight:700; letter-spacing:-.028em;
  font-size:clamp(27px,4.6vw,54px); line-height:1.12; margin:0; color:var(--ink);
  max-width:22ch; text-wrap:balance;
}
.section-forest .bigquote p,.band .bigquote p,
.section-deep .bigquote p{ color:var(--on-dark); }
.bigquote .bq-mark{ color:var(--accent); }
.bigquote cite{
  display:block; margin-top:20px; font-family:var(--body); font-style:normal;
  font-size:13px; letter-spacing:.09em; text-transform:uppercase; color:var(--ink-muted);
}

/* ------------------------------------------------------------ swatch wall ---
   Material or colour fields. For a painter these are paint colours, for a
   remodeler the finishes actually installed. Labels come from the brief. */
.swatchwall{ display:grid; grid-template-columns:repeat(auto-fit,minmax(128px,1fr)); gap:12px; }
.swatch{
  border-radius:var(--radius); overflow:hidden; background:var(--surface);
  border:1px solid var(--line); transition:transform .3s ease, box-shadow .3s ease;
}
.swatch:hover{ transform:translateY(-4px); box-shadow:0 12px 26px rgba(0,0,0,.13); }
.swatch i{ display:block; height:96px; background:var(--sw,var(--accent)); }
.swatch b{ display:block; padding:11px 12px 3px; font-family:var(--display); font-weight:700; font-size:13.5px; }
.swatch span{ display:block; padding:0 12px 12px; font-size:11.5px; color:var(--ink-muted); letter-spacing:.05em; }
/* material fills, so a swatch can read as oak or stone rather than flat paint */
.sw-wood{ background:
  repeating-linear-gradient(93deg,rgba(0,0,0,.07) 0 1px,transparent 1px 6px),
  linear-gradient(160deg,#b8895a,#95653c); }
.sw-stone{ background:
  radial-gradient(circle at 30% 25%,rgba(255,255,255,.5),transparent 42%),
  radial-gradient(circle at 72% 68%,rgba(0,0,0,.10),transparent 38%),
  linear-gradient(150deg,#d9d6cf,#b3aea4); }
.sw-tile{ background:
  linear-gradient(rgba(255,255,255,.42) 2px,transparent 2px) 0 0/44px 22px,
  linear-gradient(90deg,rgba(255,255,255,.42) 2px,transparent 2px) 22px 0/44px 22px,
  linear-gradient(160deg,#9fb2b4,#7d9396); }
.sw-metal{ background:linear-gradient(103deg,#8f979c,#c9ced1 42%,#7d858a); }

/* --------------------------------------------------------- annotated spec ---
   A drawing with numbered call-outs. Carries real information, which is what
   separates it from decoration. */
.spec{ position:relative; }
.spec svg{ width:100%; height:auto; display:block; }
.spec-pin{
  position:absolute; transform:translate(-50%,-50%); display:flex; align-items:center; gap:8px;
}
.spec-pin i{
  width:25px; height:25px; border-radius:50%; background:var(--accent); color:#fff;
  display:grid; place-items:center; font-style:normal; font-family:var(--display);
  font-weight:800; font-size:12px; flex:none;
}
.spec-pin b{
  font-size:12px; font-weight:600; background:var(--surface); color:var(--ink);
  padding:5px 10px; border-radius:100px; border:1px solid var(--line); white-space:nowrap;
}
@media (max-width:720px){ .spec-pin b{ display:none; } }

/* ------------------------------------------------------------------ icons ---
   Referenced from the sprite the build injects:
   <svg class="ico" aria-hidden="true"><use href="#ico-roller"></use></svg> */
.ico{ width:26px; height:26px; stroke:var(--accent); fill:none; stroke-width:1.6;
      stroke-linecap:round; stroke-linejoin:round; display:block; }
.ico-lg{ width:38px; height:38px; }
.ico-sm{ width:19px; height:19px; }
.icobox{
  width:52px; height:52px; border-radius:var(--radius); background:var(--bg-alt);
  display:grid; place-items:center; margin-bottom:16px;
}
.section-forest .icobox,.band .icobox{ background:rgba(255,255,255,.09); }
.section-forest .ico,.band .ico{ stroke:var(--on-dark); }

/* ------------------------------------------------------------ card polish ---
   Applied on top of the existing service card. An index numeral and an accent
   rule give a card a top and a bottom instead of it being a floating box. */
.card-num{
  font-family:var(--display); font-weight:800; font-size:12px; letter-spacing:.14em;
  color:var(--accent); display:block; margin-bottom:12px;
}
.card-accent{ border-top:3px solid var(--accent); }
.card-hover{ transition:transform .32s cubic-bezier(.2,.8,.2,1), box-shadow .32s ease; }
.card-hover:hover{ transform:translateY(-6px); box-shadow:0 18px 40px rgba(0,0,0,.13); }

/* Heading with a rule running out to the margin.
   Not flex: in a flex heading every run of text and every inline span becomes
   its own flex item, so an accent word written straight into the heading
   (<h2 class="rule-head">Four services, one <span class="grad-text">steam
   machine</span>.</h2>) sat a full 18px gap away from its sentence, or dropped
   onto a line of its own. The heading stays normal text; the rule is an
   inline-block after the last word that reaches the margin and is clipped
   there (its negative right margin means it only needs 18px to fit). */
.rule-head{ display:block; overflow:hidden; }
.rule-head::after{
  content:""; display:inline-block; vertical-align:middle;
  width:100vw; height:1px; margin:0 -100vw 0 18px; background:var(--line);
}

/* ----------------------------------------------------------- coverage map ---
   Towns positioned on a plotted field. Every partner has a service area, so
   this device is available even with no photography at all. */
.covermap{
  position:relative; aspect-ratio:4/3; border-radius:var(--radius);
  background:var(--bg-alt); border:1px solid var(--line); overflow:hidden;
}
.covermap::before{
  content:""; position:absolute; inset:0; opacity:.5;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),
                   linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:46px 46px;
}
.covermap-pin{ position:absolute; transform:translate(-50%,-50%); display:flex; align-items:center; gap:7px; }
.covermap-pin i{
  width:10px; height:10px; border-radius:50%; background:var(--accent); flex:none;
  box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 55%,transparent);
  animation:pin-pulse 2.6s ease-out infinite;
}
.covermap-pin b{ font-size:11.5px; font-weight:600; color:var(--ink); white-space:nowrap; }
.covermap-pin.hq i{ width:15px; height:15px; }
.covermap-pin:nth-child(2) i{ animation-delay:.4s; }
.covermap-pin:nth-child(3) i{ animation-delay:.8s; }
.covermap-pin:nth-child(4) i{ animation-delay:1.2s; }
.covermap-pin:nth-child(5) i{ animation-delay:1.6s; }
@keyframes pin-pulse{
  0%{ box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 55%,transparent); }
  70%{ box-shadow:0 0 0 15px transparent; }
  100%{ box-shadow:0 0 0 0 transparent; }
}
@media (prefers-reduced-motion:reduce){ .covermap-pin i{ animation:none; } }

/* ------------------------------------------------------------- reveal set ---
   The kit already reveals on scroll. This staggers children of one container
   so a grid arrives as a group rather than all at once. */
.stagger > *{ transition-delay:calc(var(--i,0) * 70ms); }


/* ------------------------------------------------------------------ legal ---
   Privacy policy, terms and anything else that is a document rather than a
   sales page. One readable column, generous spacing, no devices. */
.legal{ max-width:68ch; }
.legal h2{ font-size:clamp(19px,2.2vw,24px); margin:clamp(30px,4vw,44px) 0 10px; }
.legal h2:first-of-type{ margin-top:0; }
.legal p{ margin:0 0 14px; line-height:1.72; color:var(--ink-muted); }
.legal a{ color:var(--accent); font-weight:600; }
.legal-updated{
  font-size:12.5px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--ink-muted); margin:0 0 clamp(24px,3vw,34px);
}
.legal-list{ margin:0 0 16px; padding-left:20px; }
.legal-list li{ margin-bottom:10px; line-height:1.7; color:var(--ink-muted); }
.legal-list b{ color:var(--ink); }

/* ----------------------------------------------------------- grad-text ---
   A 2-3 word gradient moment inside a heading, using the partner's own
   direction.json tokens (--primary/--accent), so it is always on-brand
   automatically and never a hardcoded color. Applied by wrapping the
   specific words in <span class="grad-text">...</span> inside an
   otherwise normal <h1>/<h2> - this is a Claude Code authoring
   instruction (see references/signature.md), applied to every partner's
   homepage hero by default, not something flagged per partner. */
.grad-text{
  /* The shine is a light tint of the accent, not white: on a light section a
     white band made whichever letter it crossed vanish, so a short word such as
     "do" read as "d  o". The clip is !important because a later rule that sets
     the gradient with the background shorthand resets background-clip to the
     whole box, which turned accent words into solid bars (pahomesupply.com). */
  background:linear-gradient(100deg,
    var(--primary) 0%, var(--accent) 36%, color-mix(in srgb, var(--accent) 55%, var(--on-dark,#fff)) 50%,
    var(--primary-dark,var(--primary)) 64%, var(--primary) 100%);
  background-size:220% auto;
  -webkit-background-clip:text !important; background-clip:text !important; color:transparent;
  -webkit-text-fill-color:transparent;
  animation:grad-shine 3.5s linear infinite;
}
/* One continuous sweep, not a ping-pong bounce back and forth - reads as
   a clean light pass over the text rather than a shimmer with visible
   ends. */
@keyframes grad-shine{ to{ background-position:-220% center; } }
.photo-hero .grad-text,.hero-panel .grad-text{
  /* On a dark photo/video hero the same primary/accent pairing can go too
     dark to read against the overlay - lighten the stops for on-dark use. */
  background:linear-gradient(100deg,
    var(--on-dark) 0%, var(--accent) 36%, var(--primary) 50%,
    var(--accent) 64%, var(--on-dark) 100%);
  background-size:220% auto;
  -webkit-background-clip:text !important; background-clip:text !important; color:transparent;
  -webkit-text-fill-color:transparent;
  animation:grad-shine 3.5s linear infinite;
}
@media (prefers-reduced-motion:reduce){
  .grad-text,.photo-hero .grad-text,.hero-panel .grad-text{ animation:none; background-position:0% 50%; }
}
@media (prefers-contrast:more){
  .grad-text,.photo-hero .grad-text,.hero-panel .grad-text{
    background:none; -webkit-text-fill-color:currentColor; color:inherit; animation:none;
  }
}

/* --------------------------------------------------------- hero video ---
   Background video behind the homepage hero, same dark-overlay-plus-white-
   text treatment as the existing photo-hero, so no other hero rule needs
   to change to support it. Rendered by build.py's hero_video_html() only
   when site.config.json sets "hero_video": true; falls back to the
   existing photo-hero <img> otherwise, so this never has to be "on" for a
   page to be correct. */
.hero-video-bg{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; z-index:0;
}
.hero-video-overlay{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(180deg,rgba(0,0,0,.42),rgba(0,0,0,.62));
}
/* .photo-hero-soft sets DARK text (var(--ink)), meant to sit on a light
   wash, not the default hero's white/on-dark text. A hero video paired
   with that variant must use the same light overlay treatment, not the
   default dark one above - otherwise dark text sits over a darkened
   video and becomes illegible. This was caught before shipping, not
   after: pairing an update request that switches a partner to
   photo-hero-soft with hero video already enabled would otherwise have
   produced exactly this broken combination. */
.photo-hero-soft .hero-video-overlay{
  background:linear-gradient(100deg,
    var(--bg) 0%,
    color-mix(in srgb,var(--bg) 90%,transparent) 34%,
    color-mix(in srgb,var(--bg) 74%,transparent) 62%,
    color-mix(in srgb,var(--bg) 55%,transparent) 100%);
}
/* The homepage hero is freeform Claude-authored HTML, not a build.py
   template, so the exact nesting around the existing <img> can't be
   assumed (a wrapping div is common). A descendant selector (not a
   direct-child >) hides the photo regardless of nesting depth, and the
   ancestor gets position:relative + overflow:hidden here explicitly
   rather than assumed, so the absolutely-positioned video always has a
   correctly bounded positioning context to fill. */
:is(.photo-hero, .hero, [class*="hero"]):has(.hero-video-bg){
  position:relative; overflow:hidden;
}
:is(.photo-hero, .hero, [class*="hero"]):has(.hero-video-bg) img{ display:none; }
@media (prefers-reduced-motion:reduce){
  .hero-video-bg{ display:none; }
  :is(.photo-hero, .hero, [class*="hero"]):has(.hero-video-bg) img{ display:block; }
}

/* --------------------------------------------------- masonry gallery ---
   A denser, mixed-aspect-ratio grid for "recent work" photo sets, plus a
   "Show all N photos" expand toggle. Purely a presentation layer over the
   same real, partner-uploaded photos the existing .gallery grid already
   uses - falls back to nothing extra if a partner has too few photos; see
   references/visuals.md for the minimum-count rule Claude Code follows
   when deciding whether to use this device at all for a given partner. */
.gallery-masonry{
  columns:3; column-gap:14px;
}
.gallery-masonry img{
  width:100%; display:block; margin:0 0 14px; border-radius:10px;
  break-inside:avoid;
}
.gallery-masonry.is-collapsed{ max-height:900px; overflow:hidden; position:relative; }
.gallery-masonry.is-collapsed::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:120px;
  background:linear-gradient(180deg,transparent,var(--bg));
}
.gallery-show-all{ display:block; margin:18px auto 0; }
@media (max-width:900px){ .gallery-masonry{ columns:2; } }
@media (max-width:560px){ .gallery-masonry{ columns:1; } }

/* --------------------------------------------------------- reviews --- ---
   Real Google reviews only (see build.py's reviews_widget_html - the data
   comes from scripts/fetch_reviews.py, refreshed weekly, never invented).
   A three-visible endless carousel (small neighbor, large centered
   review, small neighbor), continuously auto-advancing with manual
   arrow/dot controls that never fight the auto-advance rhythm - the
   carousel logic itself (padding/cloning for the seamless loop,
   centering math, nav wiring) lives in kit/base.js.

   Star color, review text/author color, card background, the Google
   logo and the "leave a review" button are deliberately fixed to
   Google's own real brand colors rather than the partner's tokens - the
   whole point of this component is that it reads as genuine third-party
   Google content, not as one more brand-colored section of the page.
   The heading and the cloudy ambient background ARE built from the
   partner's own tokens, since those are this site's own framing around
   the reviews, not the reviews themselves. */
.reviews-widget{
  position:relative; overflow:hidden; text-align:center;
  padding:clamp(48px,6vw,88px) clamp(16px,3vw,32px);
}
.review-cloud-layer{ position:absolute; inset:-20% -10%; z-index:0; pointer-events:none; }
.review-cloud{
  position:absolute; border-radius:50%;
  background:radial-gradient(circle, color-mix(in srgb,var(--accent) 16%,transparent), transparent 70%);
  filter:blur(2px);
  animation:review-cloud-drift 28s ease-in-out infinite;
}
.review-cloud.c1{ width:420px; height:420px; top:-120px; left:-80px; }
.review-cloud.c2{
  width:340px; height:340px; top:35%; right:-110px; animation-duration:22s; animation-delay:-6s;
  background:radial-gradient(circle, color-mix(in srgb,var(--primary) 14%,transparent), transparent 70%);
}
.review-cloud.c3{ width:300px; height:300px; bottom:-130px; left:15%; animation-duration:32s; animation-delay:-12s; }
@keyframes review-cloud-drift{
  0%   { transform:translate(0,0) scale(1); }
  33%  { transform:translate(32px,-24px) scale(1.05); }
  66%  { transform:translate(-24px,16px) scale(.97); }
  100% { transform:translate(0,0) scale(1); }
}
.review-heading{
  position:relative; z-index:1; font-size:clamp(21px,2.6vw,26px); font-weight:800;
  margin:0 0 28px; letter-spacing:-.01em; color:var(--ink);
}
.review-badge{
  position:relative; z-index:1;
  display:inline-flex; align-items:center; gap:9px; margin:0 auto 18px;
  padding:9px 18px; border-radius:100px; background:#fff;
  border:1px solid rgba(0,0,0,.10); box-shadow:0 6px 20px rgba(0,0,0,.10);
  font-size:13.5px; font-weight:600; color:#202124;
}
.review-badge svg{ width:18px; height:18px; }
.review-badge-star{ color:#FBBC04; font-size:16px; }
.review-stage{
  position:relative; z-index:1; overflow:hidden; padding:20px 0 56px;
}
.review-track{
  display:flex; align-items:center;
  transition:transform .6s cubic-bezier(.2,.8,.2,1);
}
.review-slide{
  flex:0 0 auto; width:min(340px,74vw); margin:0 clamp(8px,2vw,14px);
  display:flex; justify-content:center;
}
.review-card{
  width:100%; height:400px;
  display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start;
  text-align:left; background:#fff; border-radius:20px; padding:24px 24px 26px;
  box-shadow:0 14px 34px rgba(0,0,0,.10), 0 3px 10px rgba(0,0,0,.05);
  border:1px solid rgba(0,0,0,.08);
  transform:scale(.85); opacity:.5;
  transition:transform .6s cubic-bezier(.2,.8,.2,1), opacity .6s ease, box-shadow .6s ease;
  /* Hard safety net, not the primary fix (that's overflow-wrap on
     .review-text below) - confirmed on a real review whose text spilled
     visibly past this card's rounded corners on one card out of twenty,
     while every other card clipped fine. Whatever the exact cause on any
     given review's text, nothing should ever be able to escape this
     card's own box. */
  overflow:hidden;
}
.review-slide.is-center .review-card{
  /* The centered review is the only one allowed to grow beyond the base
     height - min-height keeps it visually consistent with its neighbors
     when clamped, height:auto lets it genuinely expand once "Read more"
     removes the clamp, instead of ever being a fixed box pretending to
     hold more text than it can show. */
  height:auto; min-height:400px;
  transform:scale(1.08); opacity:1;
  box-shadow:0 24px 56px rgba(0,0,0,.16), 0 6px 16px rgba(0,0,0,.08);
}
.review-head{
  /* Who wrote it, the way Google shows it: profile photo, name with the
     verified tick when the review is verified, and the Google "G" marking
     it as a Google review. */
  display:flex; align-items:center; gap:12px; margin-bottom:16px;
}
.review-avatar{
  position:relative; flex:none; width:44px; height:44px; border-radius:50%;
  overflow:hidden; display:flex; align-items:center; justify-content:center;
  color:#fff; font-size:19px; font-weight:700; line-height:1;
}
.review-avatar img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover; border-radius:50%;
}
.review-author{
  flex:1; min-width:0; margin:0;
  display:flex; align-items:center; gap:6px;
  font-size:15px; font-weight:700; line-height:1.3; color:#202124;
}
.review-author-name{ min-width:0; overflow-wrap:anywhere; }
.review-verified{ flex:none; width:16px; height:16px; }
.review-google{ flex:none; width:22px; height:22px; }
.review-stars{ color:#FBBC04; font-size:18px; letter-spacing:4px; margin-bottom:12px; }
.review-text{
  font-size:16px; line-height:1.55; color:#3c4043; margin:0 0 14px;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:6;
  overflow:hidden;
  /* The actual fix: a single long unbroken token (a long word, a run-
     together phrase, a URL a reviewer pasted in) has no spaces for the
     browser to wrap at, so it can push straight through the line-clamp
     box instead of wrapping onto the next of its 6 lines - confirmed as
     the real cause on the one review card out of twenty whose text
     visibly spilled outside its card. Normal reviews with normal
     word-spacing were never affected, which is why only one card showed
     it. */
  overflow-wrap:break-word; word-break:break-word;
}
.review-slide.is-expanded .review-text{
  -webkit-line-clamp:unset; overflow:visible;
}
.review-read-more{
  display:inline-flex; align-self:flex-start; align-items:center; min-height:44px; margin:-18px 0 2px; background:none; border:none;
  padding:0 12px 0 0; font-size:13.5px; font-weight:700; color:var(--accent,#1A73E8);
  cursor:pointer; text-decoration:underline;
}
.review-nav{
  position:relative; z-index:1;
  display:flex; align-items:center; justify-content:center; gap:18px; margin-top:0;
}
.review-arrow{
  flex:none; width:44px; height:44px; border-radius:50%; border:1px solid rgba(0,0,0,.10);
  background:#fff; color:var(--ink); cursor:pointer; display:flex;
  align-items:center; justify-content:center; padding:0;
  transition:background .2s ease, border-color .2s ease;
}
.review-arrow:hover{ background:#F1F3F4; border-color:rgba(0,0,0,.18); }
.review-arrow svg{ width:18px; height:18px; }
/* dots stay 7px to look at but each is a 26x44 tap target */
.review-dots{ display:flex; gap:0; }
.review-dot{
  position:relative; width:26px; height:44px; background:none;
  border:none; padding:0; cursor:pointer;
}
.review-dot::before{
  content:""; position:absolute; left:50%; top:50%; width:7px; height:7px; margin:-3.5px 0 0 -3.5px;
  border-radius:50%; background:color-mix(in srgb,var(--ink) 22%,transparent);
  transition:background .25s ease, transform .25s ease;
}
.review-dot.is-active::before{ background:var(--accent); transform:scale(1.3); }
@media (max-width:400px){ .review-nav{ gap:8px; } .review-dot{ width:24px; } }
.review-actions{
  position:relative; z-index:1;
  display:flex; flex-wrap:wrap; justify-content:center; gap:16px; margin-top:34px;
}
.review-leave,.review-see-all{
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  padding:13px 26px; border-radius:10px; font-size:15px; font-weight:600;
  text-decoration:none; border:1px solid transparent; cursor:pointer;
}
.review-leave{
  /* Google's own blue - same reasoning as the star color: this should
     read as "go do something on Google," not as another brand button. */
  background:#1A73E8; border-color:#1A73E8; color:#fff;
}
.review-leave:hover{ background:#1558B0; border-color:#1558B0; }
.review-see-all{ background:#fff; color:#202124; border-color:rgba(0,0,0,.12); }
.review-see-all:hover{ background:#F1F3F4; }
@media (max-width:640px){
  .review-heading{ font-size:21px; }
  .review-actions{ flex-direction:column; align-items:center; }
  .review-leave,.review-see-all{ width:100%; max-width:340px; }
}
@media (prefers-reduced-motion:reduce){
  .review-cloud{ animation:none; }
  .review-card,.review-track{ transition:none; }
}

/* --------------------------------------------------- radiant divider ---
   A thin, flowing gradient line between major sections. Uses the
   partner's own tokens so it's always on-brand. Place
   <div class="divider-radiant"></div> between sections, not inside them.
   One-directional sweep (matching .grad-text's shine mechanic), not a
   ping-pong bounce - reads as a clean traveling light, not a shimmer.

   A real SVG+SMIL gradient-stroke divider (like a genuine reference
   site's wavebeam technique, with actual wave-shaped section boundaries)
   would look better than this flat CSS bar, but needs build.py to
   auto-generate unique gradient IDs and inject the markup deterministically
   (Claude Code hand-writing SVG with unique IDs per instance is exactly
   the kind of thing that goes wrong silently) - that's a real follow-up,
   not shipped here to avoid shipping something untested. */
.divider-radiant{
  height:4px; width:100%;
  background:linear-gradient(90deg,
    var(--primary) 0%, var(--accent) 33%, var(--primary-dark,var(--primary)) 66%, var(--accent) 100%);
  background-size:220% auto;
  animation:grad-shine 5s linear infinite;
}
@media (prefers-reduced-motion:reduce){
  .divider-radiant{ animation:none; background-position:0% 50%; }
}

@media (max-width:640px){ .covermap-pin b, .swatch span{ font-size:12px; } }


/* partner-site kit :: visuals-rb.css
   React Bits effect styles. Every rule is scoped under .rb-visuals, which
   build.py puts on <body> only when site.config.json opts in, so a site that
   does not use the effects renders exactly as it did before. Variables and
   keyframes stay global; they are inert on their own.
   See references/react-bits.md for the device catalogue. */

/* ============================================================
   React Bits library: shared tokens and gate fallback contract.
   ============================================================ */
:root{
  --rb-display:'Bebas Neue', Impact, 'Arial Narrow Bold', sans-serif;
  --rb-glow:var(--accent);
  --rb-ink-dark:var(--primary-dark);
}
.rb-visuals .rb-live > .rb-fallback{ display:none; }
.rb-visuals .rb-word{ display:inline-block; white-space:nowrap; }
.rb-visuals .rb-char{ display:inline-block; will-change:transform; }
.rb-visuals .rb-unit{ display:inline-block; will-change:transform; }
/* rb: click spark, one fixed viewport canvas above the page */
.rb-visuals .rb-spark{ position:fixed; inset:0; width:100%; height:100%; z-index:90; pointer-events:none; }
/* ============================================================
   rb: dark header. GooeyNav's blend effect is designed for light
   text on a dark bar, and it matches the Galaxy reference.
   ============================================================ */
:root{
  /* goo particles pass through contrast(100): pick colours that stay pleasant there */
  --color-1:#ffffff; --color-2:#ffe9a8; --color-3:#fff6dc; --color-4:#ffd966;
}
.rb-visuals .site-header{
  background:color-mix(in srgb, var(--primary-dark) 90%, transparent);
  border-bottom-color:color-mix(in srgb, var(--accent) 22%, transparent);
  color:var(--on-dark);
}
.rb-visuals .site-header .brand-name{ color:var(--on-dark); }
.rb-visuals .site-header .nav > a, .rb-visuals .site-header .nav-dropdown-toggle{ color:var(--on-dark); }
.rb-visuals .site-header .header-phone{ color:var(--on-dark); }
.rb-visuals .site-header .header-phone small{ color:var(--on-dark-muted); }
.rb-visuals .site-header .nav-toggle{ border-color:color-mix(in srgb, var(--on-dark) 30%, transparent); }
.rb-visuals .site-header .nav-toggle span, .rb-visuals .site-header .nav-toggle span::before, .rb-visuals .site-header .nav-toggle span::after{ background:var(--on-dark); }
.rb-visuals .site-header .nav-dropdown-menu a{ color:var(--ink); }
/* rb: gooey nav (desktop only) */
@media (min-width:1001px){
  .rb-visuals .nav.rb-gooey{ position:relative; }
  .rb-visuals .nav.rb-gooey > a{ position:relative; z-index:3; padding:10px 0; }
  .rb-visuals .nav.rb-gooey > a::after{ display:none; }
  .rb-visuals .rb-gooey-effect{
    position:absolute; left:0; top:0; width:0; height:0; pointer-events:none;
    display:grid; place-items:center; z-index:1;
  }
  .rb-visuals .rb-gooey-text{
    z-index:4; font-size:14.5px; font-weight:600; line-height:1; color:transparent;
    font-family:var(--body); transition:color .3s ease;
  }
  .rb-visuals .rb-gooey-text.active{ color:var(--primary-dark); }
  /* The registry hides a solid black ::before with mix-blend-mode: lighten. The kit
     header is a backdrop root (backdrop-filter) and a stacking context, so the blend is
     isolated and the black paints raw, spilling past the header. The backing is made
     transparent: particles keep their goo look, the pill lives outside the filter. */
  .rb-visuals .rb-gooey-filter{ filter:blur(5px) contrast(40) blur(0); z-index:2; }
  .rb-visuals .rb-gooey-filter::before{ content:""; position:absolute; inset:-75px; z-index:-2; background:transparent; }
  .rb-visuals .rb-gooey-pill{ z-index:1; }
  .rb-visuals .rb-gooey-pill::after{
    content:""; position:absolute; inset:0; background:var(--accent); border-radius:100vw;
    transform:scale(0); opacity:0; box-shadow:0 6px 22px -6px color-mix(in srgb, var(--accent) 70%, transparent);
  }
  .rb-visuals .rb-gooey-pill.active::after{ animation:rb-pill .45s cubic-bezier(.34,1.56,.64,1) both; }
  .rb-visuals .rb-gooey-filter .rb-particle, .rb-visuals .rb-gooey-filter .rb-point{
    display:block; opacity:0; width:20px; height:20px; border-radius:100%; transform-origin:center;
  }
  .rb-visuals .rb-gooey-filter .rb-particle{
    --time:5s; position:absolute; top:calc(50% - 8px); left:calc(50% - 8px);
    animation:rb-particle calc(var(--time)) ease 1 -350ms;
  }
  .rb-visuals .rb-gooey-filter .rb-point{ background:var(--color); opacity:1; animation:rb-point calc(var(--time)) ease 1 -350ms; }
}
@keyframes rb-pill{ to{ transform:scale(1); opacity:1; } }
@keyframes rb-particle{
  0%{ transform:rotate(0deg) translate(var(--start-x), var(--start-y)); opacity:1; animation-timing-function:cubic-bezier(.55,0,1,.45); }
  70%{ transform:rotate(calc(var(--rotate) * .5)) translate(calc(var(--end-x) * 1.2), calc(var(--end-y) * 1.2)); opacity:1; animation-timing-function:ease; }
  85%{ transform:rotate(calc(var(--rotate) * .66)) translate(var(--end-x), var(--end-y)); opacity:1; }
  100%{ transform:rotate(calc(var(--rotate) * 1.2)) translate(calc(var(--end-x) * .5), calc(var(--end-y) * .5)); opacity:1; }
}
@keyframes rb-point{
  0%{ transform:scale(0); opacity:0; animation-timing-function:cubic-bezier(.55,0,1,.45); }
  25%{ transform:scale(calc(var(--scale) * .25)); }
  38%{ opacity:1; }
  65%{ transform:scale(var(--scale)); opacity:1; animation-timing-function:ease; }
  85%{ transform:scale(var(--scale)); opacity:1; }
  100%{ transform:scale(0); opacity:0; }
}
/* ============================================================
   rb: full-screen staggered menu (Galaxy Stars style)
   ============================================================ */
.rb-visuals .rb-menu-lock, .rb-visuals .rb-menu-lock body{ overflow:hidden; }
.rb-visuals .rb-menu{ position:fixed; inset:0; z-index:200; }
.rb-visuals .rb-menu[hidden]{ display:none; }
.rb-visuals .rb-menu-layers{ position:absolute; inset:0; pointer-events:none; }
.rb-visuals .rb-menu-layer{ position:absolute; inset:0; }
.rb-visuals .rb-menu-layer:nth-child(1){ background:var(--accent); }
.rb-visuals .rb-menu-layer:nth-child(2){ background:var(--primary); }
.rb-visuals .rb-menu-panel{
  position:absolute; inset:0; background:var(--primary-dark); color:var(--on-dark);
  display:flex; flex-direction:column; overflow-y:auto; overscroll-behavior:contain;
  padding:0 clamp(18px,5vw,40px) calc(28px + env(safe-area-inset-bottom));
}
.rb-visuals .rb-menu-top{
  display:flex; align-items:center; justify-content:space-between;
  min-height:76px; border-bottom:1px solid color-mix(in srgb, var(--on-dark) 14%, transparent);
  position:sticky; top:0; background:var(--primary-dark); z-index:2;
}
.rb-visuals .rb-menu-brand img{ max-height:38px; width:auto; display:block; }
.rb-visuals .rb-menu-close{
  width:48px; height:48px; border-radius:50%; border:0; cursor:pointer;
  background:color-mix(in srgb, var(--on-dark) 14%, transparent); position:relative;
}
.rb-visuals .rb-menu-close span::before, .rb-visuals .rb-menu-close span::after{
  content:""; position:absolute; left:50%; top:50%; width:20px; height:2px; background:var(--on-dark); border-radius:2px;
}
.rb-visuals .rb-menu-close span::before{ transform:translate(-50%,-50%) rotate(45deg); }
.rb-visuals .rb-menu-close span::after{ transform:translate(-50%,-50%) rotate(-45deg); }
.rb-visuals .rb-menu-close:focus-visible{ outline:3px solid var(--accent); outline-offset:3px; }
.rb-visuals .rb-menu-list{ list-style:none; margin:14px 0 0; padding:0; }
.rb-visuals .rb-menu-row{
  --rb-rule:1; position:relative; overflow:hidden;
}
.rb-visuals .rb-menu-row::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:color-mix(in srgb, var(--on-dark) 16%, transparent);
  transform:scaleX(var(--rb-rule)); transform-origin:left;
}
.rb-visuals .rb-menu-link{
  display:flex; align-items:center; justify-content:space-between; gap:16px; width:100%;
  padding:10px 0 4px; min-height:64px; background:none; border:0; cursor:pointer; text-align:left;
  font-family:var(--rb-display); font-size:clamp(46px, 13.5vw, 92px); line-height:.95; letter-spacing:.005em;
  text-transform:uppercase; color:var(--on-dark); text-decoration:none; overflow:hidden;
}
.rb-visuals .rb-menu-link .rb-menu-label{ display:inline-block; transform-origin:0 100%; }
.rb-visuals .rb-menu-link.is-current, .rb-visuals .rb-menu-cta{ color:var(--accent); }
.rb-visuals .rb-menu-link:hover .rb-menu-label, .rb-visuals .rb-menu-link:focus-visible .rb-menu-label{ color:var(--accent); }
.rb-visuals .rb-menu-link:focus-visible{ outline:none; }
.rb-visuals .rb-menu-link:focus-visible .rb-menu-label{ text-decoration:underline; text-decoration-thickness:3px; text-underline-offset:6px; }
.rb-visuals .rb-menu-arrow{ width:28px; height:28px; flex:none; opacity:.45; display:inline-block; }
.rb-visuals .rb-menu-link:hover .rb-menu-arrow{ opacity:1; color:var(--accent); }
.rb-visuals .rb-menu-plus{ position:relative; width:26px; height:26px; flex:none; display:inline-block; opacity:.6; }
.rb-visuals .rb-menu-plus::before, .rb-visuals .rb-menu-plus::after{
  content:""; position:absolute; left:50%; top:50%; width:22px; height:2.5px; background:currentColor;
  transform:translate(-50%,-50%); transition:transform .3s ease;
}
.rb-visuals .rb-menu-plus::after{ transform:translate(-50%,-50%) rotate(90deg); }
.rb-visuals .rb-menu-expand[aria-expanded="true"] .rb-menu-plus::after{ transform:translate(-50%,-50%) rotate(0deg); }
.rb-visuals .rb-menu-tag{
  font-family:var(--body); font-size:11px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
  color:var(--on-dark-muted); align-self:flex-end; margin-bottom:12px; display:inline-block;
}
.rb-visuals .rb-menu-sub[hidden]{ display:none; }
.rb-visuals .rb-menu-sub{
  list-style:none; margin:0 0 16px; padding:0; display:grid; grid-template-columns:1fr 1fr; gap:2px 16px;
}
.rb-visuals .rb-menu-sub a{
  display:block; padding:10px 0; min-height:44px; box-sizing:border-box;
  color:var(--on-dark-muted); text-decoration:none; font-size:16px; font-weight:500;
}
.rb-visuals .rb-menu-sub a:hover, .rb-visuals .rb-menu-sub a:focus-visible{ color:var(--accent); }
.rb-visuals .rb-menu-phone{
  margin-top:auto; padding-top:28px; font-family:var(--rb-display); font-size:34px; letter-spacing:.02em;
  color:var(--on-dark); text-decoration:none;
}
@media (min-width:1001px){
  .rb-visuals:not(.rb-nav-collapsed) .rb-menu{ display:none !important; }
}
/* ============================================================
   rb: hero. Video stays the opening; light rays sit above the
   video scrim and below the copy, screened so they only add light.
   ============================================================ */
/* tall enough to feel cinematic, short enough not to leave dead space */
.rb-visuals .hero.photo-hero{ min-height:min(80vh, 720px); display:flex; }
.rb-visuals .hero.photo-hero > .wrap{ width:100%; }
.rb-visuals .rb-rays{
  position:absolute; inset:0; z-index:1; pointer-events:none; mix-blend-mode:screen; overflow:hidden;
}
.rb-visuals .rb-rays-canvas{ position:absolute; inset:0; width:100% !important; height:100% !important; display:block; }
.rb-visuals .rb-rays-glow{
  position:absolute; inset:-10% 0 auto; height:80%;
  background:radial-gradient(60% 70% at 50% 0%, color-mix(in srgb, var(--accent) 38%, transparent), transparent 70%);
  opacity:.55;
}
.rb-visuals .photo-hero h1{ font-size:clamp(40px, 6.2vw, 84px); line-height:1.02; letter-spacing:-.025em; max-width:15ch; perspective:600px; }
.rb-visuals .rb-split .rb-char, .rb-visuals .rb-split .rb-unit{ transform-origin:50% 100%; }
.rb-visuals .rb-rotator{
  display:flex; align-items:center; flex-wrap:wrap; gap:12px; margin:18px 0 6px;
  font-family:var(--rb-display); font-size:clamp(28px, 3.4vw, 44px); line-height:1; letter-spacing:.02em;
  text-transform:uppercase; color:var(--on-dark) !important;
}
/* in a centred hero the rotator line centres too (it is a flex row, so text-align cannot) */
.rb-visuals :is(.photo-hero-center, .hero-center) .rb-rotator{ justify-content:center; }
.rb-visuals .rb-rotating{
  display:inline-flex; position:relative; overflow:hidden; white-space:nowrap;
  background:var(--accent); color:var(--primary-dark); border-radius:10px;
  padding:6px 0 2px; box-sizing:content-box; justify-content:center;
}
.rb-visuals .rb-rotating-word{ display:inline-flex; padding:0 14px; }
.rb-visuals .rb-rotating-word.is-entering{ position:absolute; left:0; top:6px; }
.rb-visuals .rb-rotating-char{ display:inline-block; }
/* rb: star border on a kit button */
.rb-visuals .btn.rb-star{
  --rb-star-color:#fff7d6; --rb-star-speed:5s;
  position:relative; overflow:hidden; isolation:isolate;
}
.rb-visuals .rb-star-label{ position:relative; z-index:2; }
.rb-visuals .rb-star-orb{
  position:absolute; width:300%; height:60%; border-radius:50%; opacity:.9; z-index:1; pointer-events:none;
  background:radial-gradient(circle, var(--rb-star-color), transparent 12%);
  animation:rb-star-bottom var(--rb-star-speed) linear infinite alternate;
}
.rb-visuals .rb-star-orb-bottom{ bottom:-18px; right:-250%; }
.rb-visuals .rb-star-orb-top{ top:-18px; left:-250%; animation-name:rb-star-top; }
.rb-visuals .btn.rb-star::after{
  content:""; position:absolute; inset:2px; border-radius:inherit; z-index:1; pointer-events:none;
  background:inherit; background-color:var(--accent);
}
@keyframes rb-star-bottom{ 0%{ transform:translate(0,0); opacity:1; } 100%{ transform:translate(-100%,0); opacity:0; } }
@keyframes rb-star-top{ 0%{ transform:translate(0,0); opacity:1; } 100%{ transform:translate(100%,0); opacity:0; } }
@media (prefers-reduced-motion: reduce){
  .rb-visuals .rb-star-orb{ animation:none; opacity:0; }
}
@media (max-width:640px){
  .rb-visuals .hero.photo-hero{ min-height:0; }
  .rb-visuals .rb-rotator{ margin-top:14px; }
}
/* On narrower screens the label and the word pill always sit on two separate
   rows. Left inline, a long word ("fence and deck staining") no longer fits
   beside the label and wraps, so the line changed height with every word and
   pushed the whole page up and down. Two fixed rows keep the height constant. */
@media (max-width:900px){
  .rb-visuals .rb-rotator{ display:grid; justify-items:start; gap:10px; }
  .rb-visuals .photo-hero-center .rb-rotator, .rb-visuals .hero-center .rb-rotator{ justify-items:center; }
  .rb-visuals .rb-rotating{ max-width:100%; }
}
/* ============================================================
   rb: marquee under the hero, restyled as a bold ticker band.
   ============================================================ */
.rb-visuals .photo-hero + .marquee{
  background:var(--accent); border:0; padding:18px 0;
}
.rb-visuals .photo-hero + .marquee .marquee-track span{
  font-family:var(--rb-display); font-size:clamp(26px, 3vw, 38px); letter-spacing:.03em;
  text-transform:uppercase; color:var(--primary-dark);
}
.rb-visuals .photo-hero + .marquee .marquee-track span i{ color:var(--primary-dark); opacity:.55; }
/* ============================================================
   rb: magic bento stats. Dark cards, border lit by the pointer.
   ============================================================ */
.rb-visuals .rb-bento-section{ background:var(--primary-dark); position:relative; overflow:hidden; }
.rb-visuals .rb-bento-section::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(60% 80% at 15% 0%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%);
}
.rb-visuals .statband.rb-bento{
  --rb-glow-rgb:217, 166, 30;
  position:relative; background:transparent; gap:14px;
  grid-template-columns:repeat(6, 1fr); grid-auto-rows:minmax(190px, auto);
}
.rb-visuals .rb-bento .rb-bento-card{
  --glow-x:50%; --glow-y:50%; --glow-intensity:0; --glow-radius:320px;
  grid-column:span 2; position:relative; overflow:hidden; isolation:isolate;
  display:flex; flex-direction:column; justify-content:flex-end;
  background:color-mix(in srgb, var(--primary) 70%, #000);
  border:1px solid color-mix(in srgb, var(--on-dark) 12%, transparent);
  border-radius:18px; padding:clamp(20px, 2.4vw, 30px);
  transition:transform .35s cubic-bezier(.22,1,.36,1), border-color .3s ease;
}
.rb-visuals .rb-bento .rb-bento-wide{ grid-column:span 4; }
.rb-visuals .rb-bento .rb-bento-card::after{
  content:""; position:absolute; inset:0; border-radius:inherit; padding:1.5px; pointer-events:none; z-index:2;
  background:radial-gradient(var(--glow-radius) circle at var(--glow-x) var(--glow-y),
    rgba(var(--rb-glow-rgb), calc(var(--glow-intensity) * .95)) 0%,
    rgba(var(--rb-glow-rgb), calc(var(--glow-intensity) * .4)) 30%, transparent 60%);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask:linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
.rb-visuals .rb-bento .rb-bento-card::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(calc(var(--glow-radius) * .8) circle at var(--glow-x) var(--glow-y),
    rgba(var(--rb-glow-rgb), calc(var(--glow-intensity) * .12)), transparent 70%);
}
.rb-visuals .rb-bento .rb-bento-card:hover{ transform:translateY(-4px); }
.rb-visuals .rb-bento-tag{
  position:absolute; top:clamp(18px,2.2vw,26px); left:clamp(20px,2.4vw,30px);
  font-size:11px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; color:var(--accent);
}
.rb-visuals .rb-bento .stat b{
  font-family:var(--rb-display); font-weight:400; color:var(--on-dark);
  font-size:clamp(72px, 9vw, 132px); line-height:.85; letter-spacing:.01em;
}
.rb-visuals .rb-bento .stat b.stat-word{ font-size:clamp(46px, 5.4vw, 80px); line-height:.9; }
.rb-visuals .rb-bento .stat > span:not(.rb-bento-tag){ color:var(--on-dark-muted); font-size:14px; margin-top:12px; }
.rb-visuals .rb-bento-particle{
  position:absolute; width:4px; height:4px; border-radius:50%; pointer-events:none; z-index:3;
  background:rgba(var(--rb-glow-rgb), 1); box-shadow:0 0 8px rgba(var(--rb-glow-rgb), .7);
}
.rb-visuals .rb-bento-ripple{
  position:absolute; border-radius:50%; pointer-events:none; z-index:3;
  background:radial-gradient(circle, rgba(var(--rb-glow-rgb), .4) 0%, rgba(var(--rb-glow-rgb), .2) 30%, transparent 70%);
}
@media (max-width:900px){
  .rb-visuals .statband.rb-bento{ grid-template-columns:1fr 1fr; grid-auto-rows:minmax(150px, auto); }
  .rb-visuals .rb-bento .rb-bento-card, .rb-visuals .rb-bento .rb-bento-wide{ grid-column:span 1; }
  .rb-visuals .rb-bento .rb-bento-card:first-child, .rb-visuals .rb-bento .rb-bento-card:last-child{ grid-column:span 2; }
}
/* Phones: the rule above spans only the first and last card across the row, so
   every card between them is half width. rb-bento-wide does not change that, so
   a word stat there is still sized for a full-width card and does not fit: size
   it for the narrow cell instead, keyed off position, which is what actually
   decides the width. The card element is itself the .stat, so this must not ask
   for a descendant .stat. Wrapping is left at the default (between words only):
   a single word must never be split across lines, and an overflow left visible
   is what lets the loader's fit pass measure it and step the size down. */
@media (max-width:520px){
  .rb-visuals .rb-bento .rb-bento-card:not(:first-child):not(:last-child) b.stat-word{
    font-size:clamp(21px, 8vw, 40px);
  }
}
/* bento: tag sits in the flow at the top so it never collides with the figure */
.rb-visuals .rb-bento .rb-bento-card{ justify-content:flex-start; gap:0; min-height:210px; }
.rb-visuals .rb-bento-tag{ position:static; margin-bottom:auto; padding-bottom:18px; }
.rb-visuals .rb-bento .stat b{ margin-top:auto; }
/* the kit's on-dark gradient has a --primary stop that vanishes on these cards */
.rb-visuals .rb-bento .grad-text{
  background:linear-gradient(100deg, var(--on-dark) 0%, var(--accent) 40%, #fff3c9 55%, var(--accent) 70%, var(--on-dark) 100%);
  background-size:220% auto; -webkit-background-clip:text; background-clip:text;
}
@media (max-width:900px){
  .rb-visuals .rb-bento .rb-bento-card{ min-height:170px; }
}
.rb-visuals .rb-bento .stat > .rb-bento-tag{ color:var(--accent); margin-top:0; font-size:11px; }
/* ============================================================
   rb: drift wall section (Galaxy Stars reference). The host
   measures its own height, so the section sets it explicitly.
   ============================================================ */
.rb-visuals .rb-drift-section{
  position:relative; overflow:hidden; background:var(--primary-dark);
  height:min(96vh, 900px); min-height:640px; display:grid; place-items:center; isolation:isolate;
}
.rb-visuals .rb-drift-host{ position:absolute; inset:0; z-index:0; }
.rb-visuals .rb-drift{
  perspective:1200px; perspective-origin:50% 50%; overflow:hidden;
  -webkit-mask-image:radial-gradient(ellipse 80% 84% at 50% 46%, #000 40%, transparent 100%);
  mask-image:radial-gradient(ellipse 80% 84% at 50% 46%, #000 40%, transparent 100%);
}
.rb-visuals .rb-drift-plane{
  position:absolute; left:50%; top:50%; display:flex; flex-direction:row;
  transform-style:preserve-3d; transform-origin:50% 50%; will-change:transform;
}
.rb-visuals .rb-drift-col{ position:relative; width:calc(var(--dw-tile-w) + var(--dw-gap)); transform-style:preserve-3d; }
.rb-visuals .rb-drift-track{ display:flex; flex-direction:column; transform-style:preserve-3d; will-change:transform; }
.rb-visuals .rb-drift-tile{
  position:relative; display:block; flex:none; width:100%; height:calc(var(--dw-tile-h) + var(--dw-gap));
  transform-style:preserve-3d; outline:none; cursor:pointer;
}
.rb-visuals .rb-drift-inner{
  position:absolute; inset:calc(var(--dw-gap) / 2); display:block; overflow:hidden; border-radius:var(--dw-radius);
  background:#0b1417; opacity:.62; transform:translateZ(0);
  transition:transform .42s cubic-bezier(.22,1,.36,1), opacity .42s cubic-bezier(.22,1,.36,1), box-shadow .42s ease;
}
.rb-visuals .rb-drift-inner img{
  display:block; width:100%; height:100%; object-fit:cover; user-select:none;
  filter:saturate(.85); transition:filter .42s cubic-bezier(.22,1,.36,1);
}
.rb-visuals .rb-drift-shade{
  position:absolute; inset:0; pointer-events:none; background:var(--primary-dark); opacity:.42;
  transition:opacity .42s cubic-bezier(.22,1,.36,1);
}
.rb-visuals .rb-drift-tile.is-active .rb-drift-inner, .rb-visuals .rb-drift-tile:focus-visible .rb-drift-inner{
  opacity:1; transform:translateZ(var(--dw-lift)); box-shadow:0 24px 60px -18px rgba(0,0,0,.75);
}
.rb-visuals .rb-drift-tile:focus-visible .rb-drift-inner{ box-shadow:0 24px 60px -18px rgba(0,0,0,.75), 0 0 0 3px var(--accent); }
.rb-visuals .rb-drift-tile.is-active img, .rb-visuals .rb-drift-tile:focus-visible img{ filter:saturate(1.05); }
.rb-visuals .rb-drift-tile.is-active .rb-drift-shade, .rb-visuals .rb-drift-tile:focus-visible .rb-drift-shade{ opacity:0; }
.rb-visuals .rb-drift-section::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    radial-gradient(ellipse 55% 45% at 50% 50%, color-mix(in srgb, var(--primary-dark) 78%, transparent), transparent 75%),
    linear-gradient(180deg, var(--primary-dark) 0%, transparent 16%, transparent 84%, var(--primary-dark) 100%);
}
.rb-visuals .rb-drift-copy{
  position:relative; z-index:2; text-align:center; padding:0 var(--pad, 20px);
  display:flex; flex-direction:column; align-items:center; pointer-events:none;
}
.rb-visuals .rb-drift-copy > *{ pointer-events:auto; }
.rb-visuals .rb-tag{
  display:inline-block; margin:0 0 18px; padding:6px 12px; background:var(--accent); color:var(--primary-dark) !important;
  font-size:12px; font-weight:800; letter-spacing:.2em; text-transform:uppercase; border-radius:3px;
}
.rb-visuals .rb-mega{
  margin:0; font-family:var(--rb-display); font-weight:400; text-transform:uppercase;
  font-size:clamp(64px, 12.5vw, 188px); line-height:.84; letter-spacing:.005em; color:var(--on-dark);
  text-shadow:0 6px 40px rgba(0,0,0,.45);
}
.rb-visuals .rb-mega-line{ display:block; }
.rb-visuals .rb-mega-accent{ color:var(--accent); }
.rb-visuals .rb-drift-cta{ margin-top:30px; }
@media (max-width:767px){
  .rb-visuals .rb-drift-section{ height:600px; min-height:0; }
  .rb-visuals .rb-mega{ font-size:clamp(58px, 17vw, 92px); }
}
@media (prefers-reduced-motion: reduce){
  .rb-visuals .rb-drift-inner, .rb-visuals .rb-drift-inner img, .rb-visuals .rb-drift-shade{ transition:none; }
}
/* The copy block must never be wider than the section. It is a centred grid
   item, so without an explicit width it sizes to its widest child; a one-line
   tag then stretched it past the screen and the headline and button were cut
   off on both sides. The tag wraps like normal text. */
.rb-visuals .rb-drift-copy{ width:100%; max-width:1200px; box-sizing:border-box; }
.rb-visuals .rb-tag{ max-width:100%; line-height:1.45; }
.rb-visuals .rb-mega{ max-width:100%; overflow-wrap:break-word; }
@media (max-width:767px){
  .rb-visuals .rb-tag{ font-size:10.5px; letter-spacing:.1em; }
  /* the headline may wrap onto more lines here, so the section grows to fit it */
  .rb-visuals .rb-drift-section{ height:auto; min-height:600px; padding:88px 0; }
  .rb-visuals .rb-mega{ font-size:clamp(46px, 14vw, 92px); }
}

/* Never cut the business name or tagline with an ellipsis. The shared kit
   truncates them so a long name cannot push the menu button off screen; here
   they wrap instead, in the header and the footer, at every width. The brand
   block still shrinks (the kit's min-width:0), it just gets taller. */
.rb-visuals .brand-name, .rb-visuals .brand-name span{
  white-space:normal; text-overflow:clip; overflow:visible;
}
/* Up to 1280px the inline nav leaves the header brand too little room, and a
   wrapped name plus a wrapped tagline stacks six lines high. The header drops
   the tagline there; the footer still shows it. */
@media (max-width:1280px){
  .rb-visuals .site-header .brand-name span{ display:none; }
}
/* Header name on phones: at most two lines, and the tagline is dropped; it is
   the first thing to go when the header is this tight. */
@media (max-width:560px){
  /* Wrap onto as many lines as the name needs, a touch smaller; never clamp it
     with an ellipsis. The logo is capped so the name keeps enough room. */
  .rb-visuals .site-header .brand-name{
    white-space:normal; text-overflow:clip; overflow:visible; display:block;
    font-size:clamp(12.5px, 3.7vw, 15px); line-height:1.15;
  }
  .rb-visuals .site-header .brand-name span{ display:none; }
  .rb-visuals .site-header .brand img, .rb-visuals .site-header .brand svg{ max-width:min(34vw, 132px); height:auto; }
  .rb-visuals .site-header .brand .mark-plate{ max-width:min(38vw, 148px); }
}
/* ============================================================
   rb: shiny text sweep (CSS port of React Bits ShinyText)
   ============================================================ */
.rb-visuals .rb-shiny{
  --rb-shiny-speed:2.6s; --rb-shiny-color:var(--ink); --rb-shiny-shine:var(--accent);
  background-image:linear-gradient(120deg, var(--rb-shiny-color) 0%, var(--rb-shiny-color) 35%, var(--rb-shiny-shine) 50%, var(--rb-shiny-color) 65%, var(--rb-shiny-color) 100%);
  background-size:200% auto; -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  animation:rb-shiny var(--rb-shiny-speed) linear infinite;
}
@keyframes rb-shiny{ from{ background-position:150% center; } to{ background-position:-50% center; } }
@media (prefers-reduced-motion: reduce){
  .rb-visuals .rb-shiny{ animation:none; background-position:0 center; }
}
/* ============================================================
   rb: electric border map frame
   ============================================================ */
.rb-visuals .rb-map-frame{ position:relative; border-radius:22px; margin-top:8px; }
.rb-visuals .rb-map-frame .map-embed{ position:relative; z-index:1; border-radius:inherit; overflow:hidden; }
.rb-visuals .rb-map-frame .map-embed iframe{ display:block; }
.rb-visuals .rb-eb{ position:relative; isolation:isolate; }
.rb-visuals .rb-eb-layers{ position:absolute; inset:0; border-radius:inherit; pointer-events:none; z-index:0; }
.rb-visuals .rb-eb-layers > span{ position:absolute; inset:0; border-radius:inherit; box-sizing:border-box; }
.rb-visuals .rb-eb-glow1{ border:2px solid rgba(var(--rb-eb-rgb), .6); filter:blur(1px); }
.rb-visuals .rb-eb-glow2{ border:2px solid rgba(var(--rb-eb-rgb), 1); filter:blur(4px); }
.rb-visuals .rb-eb-bg{
  z-index:-1; transform:scale(1.06); filter:blur(32px); opacity:.28;
  background:linear-gradient(-30deg, rgba(var(--rb-eb-rgb), 1), transparent, rgba(var(--rb-eb-rgb), .8));
}
.rb-visuals .rb-eb-holder{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); pointer-events:none; z-index:2; display:block;
}
.rb-visuals .rb-eb-canvas{ display:block; }
.rb-visuals .section:has(.rb-eb){ overflow-x:clip; }
@media (max-width:767px){
  .rb-visuals .rb-eb-bg{ transform:none; filter:blur(18px); }
}
/* ============================================================
   rb: services as a ChromaGrid. Dark section, gradient cards,
   four across on wide screens so no card is ever orphaned.
   ============================================================ */
.rb-visuals .rb-services-section{ background:var(--primary-dark); }
.rb-visuals .rb-services-section.pat::before{ opacity:.06; }
.rb-visuals .rb-services-section .eyebrow{ color:var(--accent); }
.rb-visuals .rb-services-section .rule-head{ color:var(--on-dark); }
.rb-visuals .rb-services-section .rule-head::after{ background:color-mix(in srgb, var(--on-dark) 18%, transparent); }
.rb-visuals .services.rb-chroma{
  --x:50%; --y:50%; --r:300px;
  position:relative; gap:16px; grid-template-columns:repeat(4, minmax(0, 1fr));
}
@media (max-width:1199px){
  .rb-visuals .services.rb-chroma{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}
@media (max-width:639px){
  .rb-visuals .services.rb-chroma{ grid-template-columns:1fr; }
}
.rb-visuals .rb-chroma .service-card{
  --mouse-x:50%; --mouse-y:50%;
  position:relative; overflow:hidden; border-radius:20px; display:flex; flex-direction:column;
  border:1px solid color-mix(in srgb, var(--on-dark) 14%, transparent);
  background:linear-gradient(160deg, color-mix(in srgb, var(--accent) 34%, var(--primary)) 0%, #050b0d 78%);
  box-shadow:none; transition:border-color .3s ease;
  will-change:transform;
}
.rb-visuals .rb-chroma .service-card:nth-child(2){ background:linear-gradient(200deg, color-mix(in srgb, var(--accent) 22%, #3b2a12) 0%, #050b0d 78%); }
.rb-visuals .rb-chroma .service-card:nth-child(3){ background:linear-gradient(150deg, color-mix(in srgb, #7fb5c0 30%, var(--primary)) 0%, #050b0d 78%); }
.rb-visuals .rb-chroma .service-card:nth-child(4){ background:linear-gradient(210deg, color-mix(in srgb, var(--accent) 40%, #2a1a0c) 0%, #050b0d 78%); }
.rb-visuals .rb-chroma .service-card:hover{ border-color:var(--accent); box-shadow:0 28px 60px -24px rgba(0,0,0,.7); }
.rb-visuals .rb-chroma .service-card::before{
  content:""; position:absolute; inset:0; z-index:2; pointer-events:none; opacity:0; transition:opacity .5s ease;
  background:radial-gradient(circle at var(--mouse-x) var(--mouse-y), rgba(255,255,255,.18), transparent 70%);
}
.rb-visuals .rb-chroma .service-card:hover::before{ opacity:1; }
.rb-visuals .rb-chroma .service-card img{ margin:10px 10px 0; width:calc(100% - 20px); border-radius:12px; aspect-ratio:4/3; position:relative; z-index:1; }
.rb-visuals .rb-chroma .service-body{ position:relative; z-index:1; padding:18px 20px 22px; }
.rb-visuals .rb-chroma .service-body h3{ color:var(--on-dark); font-size:19px; }
.rb-visuals .rb-chroma .service-body p{ color:var(--on-dark-muted); }
.rb-visuals .rb-chroma .icobox{ background:color-mix(in srgb, var(--accent) 22%, transparent); color:var(--accent); }
.rb-visuals .rb-chroma .service-link{ color:var(--accent); margin-top:auto; }
.rb-visuals .rb-chroma-overlay, .rb-visuals .rb-chroma-fade{
  position:absolute; inset:-8px; pointer-events:none; border-radius:24px;
  -webkit-backdrop-filter:grayscale(1) brightness(.72); backdrop-filter:grayscale(1) brightness(.72);
  background:rgba(0,0,0,.001);
}
.rb-visuals .rb-chroma-overlay{
  z-index:3;
  -webkit-mask-image:radial-gradient(circle var(--r) at var(--x) var(--y), transparent 0%, transparent 15%, rgba(0,0,0,.1) 30%, rgba(0,0,0,.22) 45%, rgba(0,0,0,.35) 60%, rgba(0,0,0,.5) 75%, rgba(0,0,0,.68) 88%, #fff 100%);
  mask-image:radial-gradient(circle var(--r) at var(--x) var(--y), transparent 0%, transparent 15%, rgba(0,0,0,.1) 30%, rgba(0,0,0,.22) 45%, rgba(0,0,0,.35) 60%, rgba(0,0,0,.5) 75%, rgba(0,0,0,.68) 88%, #fff 100%);
}
.rb-visuals .rb-chroma-fade{
  z-index:4; opacity:1;
  -webkit-mask-image:radial-gradient(circle var(--r) at var(--x) var(--y), #fff 0%, #fff 15%, rgba(255,255,255,.9) 30%, rgba(255,255,255,.78) 45%, rgba(255,255,255,.65) 60%, rgba(255,255,255,.5) 75%, rgba(255,255,255,.32) 88%, transparent 100%);
  mask-image:radial-gradient(circle var(--r) at var(--x) var(--y), #fff 0%, #fff 15%, rgba(255,255,255,.9) 30%, rgba(255,255,255,.78) 45%, rgba(255,255,255,.65) 60%, rgba(255,255,255,.5) 75%, rgba(255,255,255,.32) 88%, transparent 100%);
}
/* the overlays also dim the gaps between cards: keep them inside the grid and mild */
.rb-visuals .rb-chroma-overlay, .rb-visuals .rb-chroma-fade{
  inset:0; border-radius:0;
  -webkit-backdrop-filter:grayscale(1) brightness(.9); backdrop-filter:grayscale(1) brightness(.9);
}
/* ============================================================
   rb: "built to last" band and the family owned statement
   ============================================================ */
.rb-visuals .band .rb-band-title{
  font-family:var(--rb-display); font-weight:400; text-transform:uppercase;
  font-size:clamp(46px, 7vw, 104px); line-height:.9; letter-spacing:.005em; max-width:16ch;
}
.rb-visuals .band .rb-hl{ color:var(--accent); }
.rb-visuals .band .rb-band-title .rb-word{ margin-right:.02em; }
.rb-visuals .section-surface .bigquote p{
  font-family:var(--rb-display) !important; font-weight:400; text-transform:uppercase;
  font-size:clamp(40px, 6.4vw, 96px) !important; line-height:.95 !important; letter-spacing:.005em; max-width:18ch;
}
.rb-visuals .bigquote .rb-word{ will-change:opacity, filter; }
.rb-visuals .bigquote cite{ margin-top:22px; }
/* ============================================================
   rb: how it works, drawn connector and spotlight steps
   ============================================================ */
.rb-visuals .stepflow.rb-steps{ position:relative; }
.rb-visuals .stepflow.rb-steps .step::before{ display:none; }
.rb-visuals .rb-step-line{
  position:absolute; left:0; right:0; top:26px; height:4px; width:100%; overflow:visible; pointer-events:none; z-index:0;
}
.rb-visuals .rb-step-line line{ stroke-linecap:round; }
.rb-visuals .rb-step-track{ stroke:color-mix(in srgb, var(--ink) 12%, transparent); stroke-width:3; }
.rb-visuals .rb-step-draw{ stroke:var(--accent); stroke-width:4; stroke-dasharray:1; stroke-dashoffset:1; }
.rb-visuals .rb-step-v{ display:none; }
.rb-visuals .stepflow.rb-steps .step{
  --mx:50%; --my:0%;
  position:relative; z-index:1; overflow:hidden; border-top:0; border-radius:16px;
  box-shadow:0 1px 0 var(--line), 0 18px 40px -28px rgba(12,32,36,.35);
  background:
    radial-gradient(260px circle at var(--mx) var(--my), color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%),
    var(--surface);
}
.rb-visuals .stepflow.rb-steps .step-num{
  font-family:var(--rb-display); font-weight:400; font-size:30px; letter-spacing:.02em;
  background:var(--accent); color:var(--primary-dark);
}
@media (max-width:900px){
  .rb-visuals .rb-step-line{ left:20px; right:auto; top:0; bottom:0; width:4px; height:100%; }
  .rb-visuals .rb-step-h{ display:none; }
  .rb-visuals .rb-step-v{ display:inline; }
}
/* ============================================================
   rb: FAQ polish (behaviour unchanged, kit toggles .open)
   ============================================================ */
.rb-visuals #faq .faq{ counter-reset:rbfaq; }
.rb-visuals #faq .faq-item{ counter-increment:rbfaq; position:relative; transition:background .3s ease; border-radius:12px; }
.rb-visuals #faq .faq-q{ padding-left:64px; position:relative; }
.rb-visuals #faq .faq-q::before{
  content:counter(rbfaq, decimal-leading-zero); position:absolute; left:14px; top:50%; transform:translateY(-50%);
  font-family:var(--rb-display); font-size:30px; color:color-mix(in srgb, var(--ink) 25%, transparent); transition:color .3s ease;
}
.rb-visuals #faq .faq-q[aria-expanded="true"]::before, .rb-visuals #faq .faq-q:hover::before{ color:var(--accent); }
.rb-visuals #faq .faq-item:has(.faq-q[aria-expanded="true"]){ background:color-mix(in srgb, var(--accent) 9%, transparent); }
.rb-visuals #faq .faq-a{ padding-left:64px; }
.rb-visuals #faq .faq-a.open{ animation:rb-faq-in .35s cubic-bezier(.22,1,.36,1); }
@keyframes rb-faq-in{ from{ opacity:0; transform:translateY(-6px); } to{ opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce){
  .rb-visuals #faq .faq-a.open{ animation:none; }
}
/* ============================================================
   rb: CTA band, grain and a dark-on-gold treatment
   ============================================================ */
.rb-visuals .rb-cta{ position:relative; overflow:hidden; isolation:isolate; }
.rb-visuals .rb-cta > .wrap{ position:relative; z-index:1; }
.rb-visuals .rb-noise{
  position:absolute; inset:0; width:100%; height:100%; z-index:0; pointer-events:none;
  image-rendering:pixelated; mix-blend-mode:multiply; opacity:.9;
}
.rb-visuals .rb-cta .rb-cta-title{
  font-family:var(--rb-display); font-weight:400; text-transform:uppercase; color:var(--primary-dark);
  font-size:clamp(56px, 9vw, 140px); line-height:.86; letter-spacing:.005em; max-width:none;
}
.rb-visuals .rb-cta p{ color:color-mix(in srgb, var(--primary-dark) 82%, transparent); }
.rb-visuals .rb-cta .btn-light{ background:var(--primary-dark); color:var(--on-dark); }
.rb-visuals .rb-cta .btn-light:hover{ background:var(--primary); }
.rb-visuals .rb-cta .btn-ghost{ color:var(--primary-dark); border-color:var(--primary-dark); }
.rb-visuals .rb-cta .btn-ghost:hover{ background:color-mix(in srgb, var(--primary-dark) 10%, transparent); }
.rb-visuals .rb-cta .btn.rb-eb{ border-color:transparent; }
.rb-visuals .rb-cta .cta-actions{ overflow:visible; }
/* small buttons: the glow rings read as a smudge on gold, keep only the crawling line */
.rb-visuals .rb-cta .btn.rb-eb .rb-eb-glow2, .rb-visuals .rb-cta .btn.rb-eb .rb-eb-bg{ display:none; }
.rb-visuals .rb-cta .btn.rb-eb .rb-eb-glow1{ border-width:1.5px; filter:none; }
/* ============================================================
   rb: circular gallery on service pages. The WebGL host needs an
   explicit height; the fallback strip is the phone experience.
   ============================================================ */
.rb-visuals .rb-gallery-section{ background:var(--primary-dark); overflow:hidden; }
.rb-visuals .rb-gallery-section .eyebrow{ color:var(--accent); }
.rb-visuals .rb-gallery-section .rule-head{ color:var(--on-dark); }
.rb-visuals .rb-circular{ position:relative; width:100%; height:clamp(420px, 44vw, 560px); outline:none; cursor:grab; touch-action:pan-y; }
.rb-visuals .rb-circular.is-dragging{ cursor:grabbing; }
.rb-visuals .rb-circular:focus-visible{ box-shadow:inset 0 0 0 3px var(--accent); }
.rb-visuals .rb-circular-canvas{ position:absolute; inset:0; width:100% !important; height:100% !important; display:block; }
.rb-visuals .rb-strip{
  display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; height:100%;
  padding:8px var(--pad, 20px) 18px; scrollbar-width:thin; -webkit-overflow-scrolling:touch;
}
.rb-visuals .rb-strip img{
  flex:0 0 auto; height:100%; width:auto; aspect-ratio:4/5; object-fit:cover; border-radius:16px; scroll-snap-align:center;
}
@media (max-width:767px){
  .rb-visuals .rb-circular{ height:320px; cursor:auto; }
  .rb-visuals .rb-strip img{ aspect-ratio:3/4; }
}

/* ============================================================
   Heading scale. One set of ceilings for every heading, so no
   heading can grow past the others or float out of its section.
   Sizes used to follow screen width alone with very high ceilings
   (a 180px drift headline beside 38px section headings, a CTA
   bigger than the hero on phones). This block comes last so it
   wins over the per-effect sizes above.
   Tier 1 hero headline, the same on every page and hero style.
   Tier 2 statement headings (drift wall, CTA, band, big quote):
          at most 1.5x the hero; long statements drop a size
          (rb-long is set by the loader for text over 30 chars).
   Tier 3 section headings.
   Balanced wrapping keeps lines even, so no single word is left
   hanging on the last line.
   ============================================================ */
.rb-visuals main :is(h1, h2, h3){ text-wrap:balance; }
.rb-visuals :is(.photo-hero, .page-hero, .hero) h1{
  font-size:clamp(34px, 5vw, 64px); line-height:1.04; max-width:16ch;
}
/* a measure-limited heading is a narrower box: in a centred block, centre the box
   too, or the text sits left of the line the eyebrow and lede share */
.rb-visuals :is(.photo-hero-center, .hero-center, .band, .cta-band) :is(h1, h2, .rb-band-title){ margin-left:auto; margin-right:auto; }
.rb-visuals .rb-mega,
.rb-visuals .rb-cta .rb-cta-title,
.rb-visuals .band .rb-band-title{
  font-size:clamp(40px, 6.6vw, 96px); max-width:18ch; margin-left:auto; margin-right:auto;
}
/* the drift headline is written as two designed lines; each should stay on one line */
.rb-visuals .rb-mega{ max-width:26ch; }
.rb-visuals .band .rb-band-title{ margin-left:0; }
.rb-visuals .section-surface .bigquote p{ font-size:clamp(36px, 5.6vw, 84px) !important; max-width:18ch; }
.rb-visuals :is(.rb-cta-title, .rb-band-title).rb-long{ font-size:clamp(34px, 5.2vw, 76px); max-width:20ch; }
.rb-visuals .rb-mega.rb-long{ font-size:clamp(34px, 5.2vw, 76px); }
.rb-visuals main h2:not(.rb-mega):not(.rb-cta-title):not(.rb-band-title){ font-size:clamp(26px, 3.2vw, 40px); }
.rb-visuals .reviews-widget h2{ font-size:clamp(24px, 2.8vw, 34px); }
@media (max-width:767px){
  .rb-visuals .rb-mega, .rb-visuals .rb-cta .rb-cta-title, .rb-visuals .band .rb-band-title{ font-size:clamp(38px, 11vw, 56px); }
  .rb-visuals :is(.rb-mega, .rb-cta-title, .rb-band-title).rb-long{ font-size:clamp(34px, 9.5vw, 48px); }
}
/* Header collapsed because its links do not fit (set by the loader at any width):
   hide the inline nav, show the menu button, which opens the full-screen menu. */
@media (min-width:1001px){
  .rb-visuals .site-header.nav-collapsed .nav{ display:none; }
  .rb-visuals .site-header.nav-collapsed .nav-toggle{ display:grid; order:3; }
  .rb-visuals .site-header.nav-collapsed .header-inner{ gap:16px; }
  .rb-visuals .site-header.nav-collapsed .header-cta{ margin-left:auto; }
}
/* Desktop nav labels never wrap: a label on two lines is the first sign the row
   is crowded. Crowded rows are tightened before they collapse into the menu. */
@media (min-width:1001px){
  .rb-visuals .site-header .nav > a, .rb-visuals .site-header .nav-dropdown-toggle{ white-space:nowrap; }
  .rb-visuals .site-header.nav-tight .nav{ gap:14px; }
  .rb-visuals .site-header.nav-tight .nav > a,
  .rb-visuals .site-header.nav-tight .nav-dropdown-toggle{ font-size:13.5px; }
  .rb-visuals .site-header.nav-tight .header-cta{ gap:12px; }
}
/* Phones: small labels in the visual pack lifted to a readable 12px */
@media (max-width:640px){
  .rb-visuals .rb-bento .stat > .rb-bento-tag, .rb-visuals .rb-tag{ font-size:12px; }
}


/* ---- signature, this partner only ---- */
/* Light contrast plate for a dark-on-transparent logo sitting on the dark
   header. The kit's own .mark-plate assumes a light logo needing a dark
   backing (background:var(--primary)); this partner's logo is the opposite
   (black roofline, navy wordmark), so it needs a light plate instead. Same
   layout as .mark-plate, reversed fill. */
.mark-plate-light{
  display:inline-flex; align-items:center; justify-content:center;
  background:#FFFFFF; border-radius:8px; padding:6px 10px; flex:none;
  box-shadow:0 1px 3px rgba(0,0,0,.18);
}
.mark-plate-light img{ max-height:30px; }

/* The Click-Lock: signature flooring device. See direction.json "signature_detail". */

.cl-layout{
  display:grid;
  grid-template-columns:1.05fr .95fr;
  gap:clamp(28px,4vw,64px);
  align-items:center;
}
@media (max-width:860px){
  .cl-layout{ grid-template-columns:1fr; text-align:center; }
}

.cl{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:18px;
}

.cl-floor{
  perspective:700px;
  display:flex;
  gap:4px;
  width:min(320px,70vw);
  height:170px;
}

.cl-plank{
  flex:1;
  border-radius:4px;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.16), 0 1px 2px rgba(0,0,0,.08);
  background:linear-gradient(180deg,#C9A46A,#A9793E);
  transition:background .3s ease;
}

/* Default (no-JS) state: the new board already sits flat and seated, same
   as its neighbours, so the signature degrades to a correct, finished
   floor rather than a plank stuck hanging in mid-air. JS opts a visitor
   into the lifted starting pose only when it can also animate it shut. */
.cl-floor.is-ready .cl-plank-new{
  transform-origin:bottom center;
  transform:rotateX(-55deg) translateY(-6px);
  box-shadow:0 16px 22px rgba(0,0,0,.28), inset 0 0 0 1px rgba(0,0,0,.16);
}

.cl-floor.is-locking .cl-plank-new{
  animation:cl-lock .9s cubic-bezier(.32,.88,.28,1.2) both;
}
@keyframes cl-lock{
  0%{ transform:rotateX(-55deg) translateY(-6px); }
  70%{ transform:rotateX(4deg) translateY(1px); }
  100%{ transform:rotateX(0deg) translateY(0); }
}

/* Material fills, swapped by toggling a class on .cl-floor. CSS textures
   only, never a photograph swapped in, since no per-material job photo
   exists to honestly show. */
.cl-floor.cl-mat-oak .cl-plank{ background:linear-gradient(180deg,#C9A46A,#A9793E); }
.cl-floor.cl-mat-engineered .cl-plank{ background:linear-gradient(180deg,#B98A54,#8B6239); }
.cl-floor.cl-mat-laminate .cl-plank{ background:linear-gradient(180deg,#D8C9A9,#B7A67D); }
.cl-floor.cl-mat-tile .cl-plank{
  background-color:#BEC4CA;
  background-image:linear-gradient(135deg,#CDD1D5,#AAB0B7), repeating-linear-gradient(0deg, transparent 0 34px, rgba(0,0,0,.2) 34px 36px);
  background-blend-mode:normal;
}
.cl-floor.cl-mat-lvp .cl-plank{ background:linear-gradient(180deg,#B7A88F,#8E7C61); }
.cl-floor.cl-mat-carpet .cl-plank{
  background-color:#6E7B74;
  background-image:radial-gradient(circle at 2px 2px, rgba(255,255,255,.22) 1px, transparent 1.6px);
  background-size:6px 6px;
}

.cl-caption{
  font:500 14px/1.5 var(--font-body,inherit);
  color:var(--ink-muted);
  min-height:2.6em;
  text-align:center;
  max-width:340px;
}

.cl-materials{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:10px;
  max-width:400px;
}
@media (max-width:520px){
  .cl-materials{ grid-template-columns:repeat(2,1fr); }
}
.cl-chip{
  border:2px solid var(--line);
  background:var(--surface);
  color:var(--ink);
  border-radius:999px;
  padding:9px 14px;
  font:600 13px/1.15 var(--font-body,inherit);
  cursor:pointer;
  min-height:44px;
}
.cl-chip.is-active{
  border-color:var(--accent);
  background:var(--bg-alt);
  color:var(--accent-dark);
}
.cl-chip:focus-visible{
  outline:3px solid var(--accent);
  outline-offset:2px;
}

@media (prefers-reduced-motion:reduce){
  .cl-floor.is-ready .cl-plank-new{ transform:none; box-shadow:inset 0 0 0 1px rgba(0,0,0,.16), 0 1px 2px rgba(0,0,0,.08); }
  .cl-floor.is-locking .cl-plank-new{ animation:none; }
}

.map-link{ display:inline-flex; align-items:center; min-height:44px; margin-top:12px; font-weight:600; color:var(--primary); text-decoration:none; }
.map-link:hover{ text-decoration:underline; }
