/* generated from direction.json */
:root{
  --bg: #F6F1E4;
  --bg-alt: #EEE5CF;
  --surface: #FFFFFF;
  --primary: #15110D;
  --primary-dark: #000000;
  --accent: #C6952E;
  --accent-dark: #96701E;
  --ink: #1C1712;
  --ink-muted: #5B5245;
  --on-dark: #F7F0DC;
  --on-dark-muted: #C7BB9C;
  --soft: #F0E3BF;
  --line: rgba(21,17,13,.14);
  --display: 'Fraunces', system-ui, sans-serif;
  --body: 'Work Sans', system-ui, -apple-system, sans-serif;
  --wrap: 1200px;
  --pad: clamp(20px, 5vw, 48px);
  --r: 12px;
}

/* ==========================================================================
   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; }

html{ scroll-behavior:smooth; }
@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;
}

/* 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; }

.brand{ display:flex; align-items:center; gap:11px; text-decoration:none; flex:none; }

/* 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; }
.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; }

.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;
}
.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-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:column; gap:12px; align-items:flex-start; margin-top:4px; }
/* 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; }
.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;
}
.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; }
}


/* ==========================================================================
   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; }

/* --------------------------------------------------------------- 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. */
.rule-head{ display:flex; align-items:center; gap:18px; }
.rule-head::after{ content:""; flex:1 1 auto; height:1px; 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{
  background:linear-gradient(100deg,
    var(--primary) 0%, var(--accent) 36%, var(--on-dark,#fff) 50%,
    var(--primary-dark,var(--primary)) 64%, var(--primary) 100%);
  background-size:220% auto;
  -webkit-background-clip:text; background-clip:text; 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; background-clip:text; 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));
}
/* 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 horizontal scroll-snap row, not the drag before/after slider (that is
   a different component, [data-slider], reused for a different purpose) -
   deliberately plain CSS scroll-snap so it needs no extra JS wiring and
   works with touch, trackpad and keyboard scrolling identically. */
.review-slider{
  display:flex; gap:16px; overflow-x:auto; scroll-snap-type:x mandatory;
  padding:4px 4px 14px; margin:0 -4px;
  scrollbar-width:thin;
}
.review-card{
  scroll-snap-align:start; flex:0 0 min(320px,82vw);
  background:var(--surface); border-radius:14px;
  padding:20px 22px; position:relative;
  /* Radiant animated border: a flowing conic gradient sits behind the
     card via a pseudo-element one pixel larger than the card itself, so
     it reads as a glowing outline rather than a flat single-color
     border. */
  background-clip:padding-box; border:1px solid transparent;
}
.review-card::before{
  content:""; position:absolute; inset:-1px; border-radius:15px; z-index:-1;
  background:conic-gradient(from var(--angle,0deg),
    var(--primary), var(--accent), var(--primary-dark,var(--primary)), var(--accent), var(--primary));
  animation:review-glow-spin 5s linear infinite;
  opacity:.55;
}
@property --angle{ syntax:'<angle>'; initial-value:0deg; inherits:false; }
@keyframes review-glow-spin{ to{ --angle:360deg; } }
.review-stars{ color:var(--accent); font-size:15px; letter-spacing:2px; margin-bottom:8px; }
.review-text{ font-size:14.5px; line-height:1.6; color:var(--ink-muted); margin:0 0 12px; }
.review-author{ font-size:13px; font-weight:700; color:var(--ink); margin:0; }
.reviews-widget{ text-align:center; }
.review-see-all{ margin-top:6px; }
@media (prefers-reduced-motion:reduce){
  .review-card::before{ animation:none; --angle:0deg; }
}

/* --------------------------------------------------- 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%; }
}


/* ---- signature, this partner only ---- */
/* ==========================================================================
   Signature: The Invisible Patch
   Four independent toggle buttons sit on a plastered wall panel. Default
   state (and the no-JS state) shows every spot already patched: a good
   drywall or popcorn-ceiling repair is invisible, so that is the honest
   resting state of the page. Clicking or activating a spot with the
   keyboard peeks at the real flaw that was there (a nail pop, a torn
   popcorn seam, a dented corner bead, a cracked seam) and names the repair.
   Click it again to put the patch back. Once all four have been peeked at
   least once, a small confirmation line appears. Each spot is a plain,
   independently focusable <button aria-pressed>, no roving tabindex, no
   drag, nothing that depends on JavaScript to look finished.
   ========================================================================== */
.patchwall{
  display:grid; grid-template-columns:1.2fr .8fr; gap:clamp(24px,4vw,44px); align-items:start;
  margin-top:clamp(28px,4vw,44px);
}
.patchwall-panel{
  position:relative; height:clamp(220px,28vw,290px); border-radius:var(--r);
  border:1px solid var(--line); overflow:hidden;
  background:
    radial-gradient(circle at 20% 30%, rgba(0,0,0,.035) 0 2px, transparent 3px),
    radial-gradient(circle at 60% 65%, rgba(0,0,0,.03) 0 2px, transparent 3px),
    radial-gradient(circle at 80% 20%, rgba(0,0,0,.03) 0 2px, transparent 3px),
    linear-gradient(160deg, var(--bg), var(--bg-alt));
  background-size: 34px 34px, 41px 41px, 29px 29px, 100% 100%;
}
.patchwall-spot{
  position:absolute; width:60px; height:60px; margin:-30px 0 0 -30px;
  border:none; background:transparent; padding:0; cursor:pointer;
  display:flex; align-items:center; justify-content:center; border-radius:50%;
}
.patchwall-spot::before{
  content:""; position:absolute; inset:6px; border-radius:50%;
  border:1.5px dashed var(--line); transition:border-color .18s ease;
}
.patchwall-spot:hover::before,
.patchwall-spot:focus-visible::before{ border-color:var(--accent); }
.patchwall-spot:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }

.patchwall-flaw{
  position:absolute; width:22px; height:22px; border-radius:46% 54% 60% 40%/54% 44% 56% 46%;
  background:radial-gradient(circle at 35% 35%, rgba(0,0,0,.32), rgba(0,0,0,.12) 70%, transparent 72%);
  opacity:0; transform:scale(.5); transition:opacity .28s ease, transform .28s ease;
}
.patchwall-spot[aria-pressed="false"] .patchwall-flaw{ opacity:1; transform:scale(1); }

.patchwall-check{
  position:absolute; width:26px; height:26px; border-radius:50%; background:var(--accent);
  display:flex; align-items:center; justify-content:center;
  opacity:1; transform:scale(1); transition:opacity .28s ease, transform .28s ease;
  box-shadow:0 3px 10px rgba(0,0,0,.18);
}
.patchwall-check .ico{ stroke:#fff; width:14px; height:14px; }
.patchwall-spot[aria-pressed="false"] .patchwall-check{ opacity:0; transform:scale(.4); }

.patchwall-readout{
  min-height:150px; background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r); padding:22px 24px; display:flex; flex-direction:column; gap:10px; justify-content:center;
}
.patchwall-hint{ margin:0; color:var(--ink-muted); font-size:14.5px; }
.patchwall-readout h3{ margin:0; font-size:17px; }
.patchwall-readout .patchwall-before{ margin:0; font-size:14px; color:var(--ink-muted); }
.patchwall-readout .patchwall-after{ margin:0; font-size:14px; color:var(--ink); }
.patchwall-readout .patchwall-after b{ color:var(--accent-dark); }
.patchwall-done{
  margin-top:8px; padding-top:10px; border-top:1px solid var(--line);
  font-family:var(--display); font-weight:700; font-size:14.5px; color:var(--accent-dark);
}

@media (max-width:860px){
  .patchwall{ grid-template-columns:1fr; }
}
@media (prefers-reduced-motion:reduce){
  .patchwall-flaw, .patchwall-check{ transition:none; }
}

/* ==========================================================================
   Header brand mark: the supplied logo already carries its own solid black
   background baked into the image (not a transparent cutout), so it reads
   cleanly on the site's light header with no plate/contrast wrapper needed.
   ========================================================================== */
.brand-mark{ display:block; max-height:36px; width:auto; border-radius:4px; }
