
/* ============================================================
   Sentinel Homes — Homepage
   Design tokens extracted from Figma (Sentinel Homes CQ)
   Build slice: Foundation + Navigation + Hero
   ============================================================ */
/* Brand signature font (from The Colab) */
@font-face{
  font-family:"Signerica Fat";
  src:url("home/fonts/Signerica_Fat.ttf") format("truetype");
  font-weight:400;font-style:normal;font-display:swap;
}

:root{
  /* Colour */
  --white:#ffffff;
  --cream:#f7f3ea;
  --off-white:#fdfbf9;
  --charcoal:#424040;
  --mid-grey:rgba(66,64,64,.8);
  --amber:#e8b639;
  --slate-green:#637468;
  --sage:#7a917f;
  --sage-dark:#698070;
  --warm-sand:#efe8d6;
  --terracotta:#7b4a39;

  /* Spacing scale (4px base) */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px;
  --space-5:20px; --space-6:24px; --space-8:32px; --space-12:48px;
  --space-16:64px; --space-24:96px;
  --global-padding:48px;      /* desktop */
  --section-spacing:96px;
  --maxw:1440px; /* Figma 1440 grid (incl. 48px gutters); widens to 1600 on very large screens */

  /* Shadows */
  --shadow-sm:0 1px 4px rgba(0,0,0,.15);
  --shadow-lg:0 12px 48px rgba(0,0,0,.2);
  /* Build Options card hover (client round 26 item 6). The old --shadow-lg
     was one hard layer of 20% black reaching 60px below the card — too heavy
     to read as "soft and feathered", and far too tall for the clipped card
     window. Three low-alpha charcoal layers give a properly feathered falloff
     in the design system's own colour, and the negative spreads keep the
     horizontal reach to ~8px so the leftmost card loses nothing meaningful to
     the window's left clip (which must stay exactly on the content edge). */
  --shadow-card-hover:
    0 2px 4px rgba(66,64,64,.06),
    0 8px 16px -6px rgba(66,64,64,.10),
    0 18px 40px -12px rgba(66,64,64,.16);

  /* Motion (client button-studies reference — shared sitewide) */
  --ease-fluid:cubic-bezier(.16,1,.3,1);
  --ease-soft:cubic-bezier(.4,0,.2,1);
  --ease-spring:cubic-bezier(.34,1.56,.64,1);
  /* Build Options card hover (client round 25). The client asked for
     "Normal -> Hover: smooth ease-out" and "Hover -> Normal: smooth ease-in",
     so the two directions get their own curves rather than one shared one.
     Neither overshoots — no bounce, no spring, per the brief. */
  --ease-card-open:cubic-bezier(.22,.61,.36,1);
  --ease-card-close:cubic-bezier(.45,.05,.55,.95);
  /* FAQ open/close — slow, refined, no overshoot (client round 25). */
  --ease-faq:cubic-bezier(.33,1,.68,1);
  /* Local Office card switch — unhurried, no overshoot (client round 25). */
  --ease-office:cubic-bezier(.32,.72,.28,1);
  /* ONE curve for every entrance fade on the page (client round 26 items 4
     and 14: "Smooth, Subtle, Premium, Controlled" — one visual language, no
     abrupt fades, no sudden position changes). easeOutCubic: quick to become
     legible, unhurried as it settles, and no overshoot anywhere. */
  --ease-fade:cubic-bezier(.33,1,.68,1);
  /* Button lift shadows (reference: light grounds = charcoal-tinted,
     dark grounds / imagery = black-tinted) */
  --lift-shadow:0 1px 2px rgba(66,64,64,.10),0 3px 8px rgba(66,64,64,.08),0 8px 18px rgba(66,64,64,.06);
  --lift-shadow-dark:0 1px 2px rgba(0,0,0,.18),0 3px 8px rgba(0,0,0,.13),0 10px 22px rgba(0,0,0,.15);
  --lift-shadow-active:0 1px 2px rgba(66,64,64,.08);
  --underline-base:rgba(226,217,195,.5);
  --underline-base-dark:rgba(253,251,249,.22);

  /* Type families */
  --serif:"Playfair Display",Georgia,serif;
  --sans:"Montserrat",-apple-system,"Segoe UI",sans-serif;
}

/* Very large screens: content grid widens (per AJ 2026-08-13) */
@media (min-width:1700px){
  :root{--maxw:1600px}
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  font-family:var(--sans);
  color:var(--charcoal);
  background:var(--off-white);
  font-size:16px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{display:block;max-width:100%}
/* Scroll reveal — client round 26 items 4 and 14. This used to fade AND rise
   28px. With the section-level fade also running, that read as two separate
   effects layered on one another, and the rise is exactly the "sudden
   position change" the client asked to remove. It is now a pure opacity fade
   on the shared --ease-fade curve, so every entrance on the page — sections,
   Plan, Build Options, headings, copy, buttons — speaks one language.
   Dropping the transform also retires a long-standing conflict: the reveal's
   `transform:none` used to outrank hover transforms until it was shed. */
.js .reveal{opacity:0;transition:opacity .75s var(--ease-fade)}
/* FAQ content answers faster (client round 13 item 15) — same fade, quicker
   duration; the per-item stagger is also tightened in home.js.
   Feedback 31 Aug item 2: still too slow, so the duration drops again from
   .55s to .38s. Same fade, same --ease-fade curve, same styling - only the
   duration moves, and only for .faq. */
.js .faq .reveal{transition-duration:.38s}
.js .reveal.is-visible{opacity:1}
/* Once revealed, drop any compositor hint so text rasterizes crisply */
.js .reveal.is-visible{will-change:auto}

/* ============================================================
   SECTION ENTRANCE CHOREOGRAPHY (client round 18, items 4+5).
   `.sh-seq` = armed (pre-states below), `.is-in` = play. home.js
   adds the classes only with JS + motion allowed, and sheds them
   once the sequence has played, so the resting DOM is untouched.

   Floor Plans & Style Ideas:  background wipes up → both images
   reveal bottom-to-top (short stagger) → heading → copy → button.
   Build Options:              background wipes up → heading →
   copy → button → cards rise in.
   ============================================================ */
/* --- armed states --- */
/* Sitewide section fade (client round 25) — see home.js. A soft opacity lift
   on first entry, never a movement and never a scroll behaviour. It starts at
   0.28 rather than 0 so the section is readable the instant it appears and the
   fade cannot make the user wait; the classes are shed once played so no
   stacking context survives to affect the sticky pins or hover transforms. */
.js .sh-fade{opacity:.28}
.js .sh-fade.is-shown{opacity:1;transition:opacity .7s var(--ease-fade)}
@media (prefers-reduced-motion: reduce){.js .sh-fade,.js .sh-fade.is-shown{opacity:1;transition:none}}

/* Plan section entrance — REBUILT in client round 25. The round-18 sequence
   (background clip-path wipe, then images wiping up 24px, then text rising
   26px) has been removed entirely: the client asked for a luxurious, elegant
   fade with no movement at all. What is left is pure opacity, staggered in
   the order the client specified — section ground, plan image, heading,
   description, then the link — with no transform, clip-path, scale or slide
   anywhere in the sequence, so nothing can shift the layout or fight a
   scroll. Delays are deliberately short (the last element starts at 0.44s)
   so the reader never waits for the copy to become legible. */
.js .ideas.sh-seq::before,
.js .ideas.sh-seq .ideas__col--plans::before{opacity:0}
.js .ideas.sh-seq .ideas__media,
.js .ideas.sh-seq .ideas__plan-m{opacity:0}
.js .ideas.sh-seq .ideas__text h2,
.js .ideas.sh-seq .ideas__text p,
.js .ideas.sh-seq .ideas__text .link-btn{opacity:0}
/* Build Options entrance — REBUILT in client round 26 item 5. The round-18
   sequence wiped the background up with clip-path, then rose the copy 26px,
   then the cards: the client says that staged background → content → cards
   choreography is not wanted. What is left is the same subtle fade the rest
   of the page uses, with only enough stagger to read as one considered
   arrival rather than a list of separate effects. No wipe, no rise, no
   background swap — so nothing can shift the layout or fight the horizontal
   card scrub that lives in this section. */
.js .build.sh-seq .build__intro .h1,
.js .build.sh-seq .build__intro p,
.js .build.sh-seq .build__head .btn,
.js .build.sh-seq .build__trackrow,
.js .build.sh-seq .build__card{opacity:0}
/* --- play (transitions live on the target state) --- */
.js .ideas.sh-seq.is-in::before{opacity:1;transition:opacity .8s var(--ease-fade)}
.js .ideas.sh-seq.is-in .ideas__col--plans::before{opacity:1;transition:opacity .8s var(--ease-fade) .04s}
.js .ideas.sh-seq.is-in .ideas__media,
.js .ideas.sh-seq.is-in .ideas__plan-m{opacity:1;transition:opacity .8s var(--ease-fade) .08s}
.js .ideas.sh-seq.is-in .ideas__col--style .ideas__media{transition-delay:.14s}
.js .ideas.sh-seq.is-in .ideas__text h2{opacity:1;transition:opacity .8s var(--ease-fade) .18s}
.js .ideas.sh-seq.is-in .ideas__text p{opacity:1;transition:opacity .8s var(--ease-fade) .26s}
.js .ideas.sh-seq.is-in .ideas__text .link-btn{opacity:1;transition:opacity .8s var(--ease-fade) .34s}
.js .build.sh-seq.is-in .build__intro .h1{opacity:1;transition:opacity .8s var(--ease-fade)}
.js .build.sh-seq.is-in .build__intro p{opacity:1;transition:opacity .8s var(--ease-fade) .08s}
.js .build.sh-seq.is-in .build__head .btn{opacity:1;transition:opacity .8s var(--ease-fade) .16s}
.js .build.sh-seq.is-in .build__card{opacity:1;transition:opacity .8s var(--ease-fade) .20s}
.js .build.sh-seq.is-in .build__card:nth-child(2){transition-delay:.25s}
.js .build.sh-seq.is-in .build__card:nth-child(3){transition-delay:.30s}
.js .build.sh-seq.is-in .build__card:nth-child(n+4){transition-delay:.34s}
.js .build.sh-seq.is-in .build__trackrow{opacity:1;transition:opacity .8s var(--ease-fade) .34s}
@media (prefers-reduced-motion: reduce){
  .js .reveal{opacity:1!important;transition:none!important}
}
a{color:inherit;text-decoration:none}
ul{list-style:none}

/* ---------- Typography helpers ---------- */
.h1{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-.59px} /* Desktop/H1 token: -1% of 59px */
.h2{font-family:var(--serif);font-weight:400;font-size:48px;line-height:1.17}
.h3{font-family:var(--sans);font-weight:500;font-size:27px;line-height:1.27}
.h4{font-family:var(--sans);font-weight:500;font-size:18px;line-height:1.4}
.body-lg{font-size:20px;line-height:1.5}
.body{font-size:16px;line-height:1.5}
.label{font-weight:600;font-size:12px;line-height:1.2;letter-spacing:1.44px;text-transform:uppercase}
.label-lg{font-weight:600;font-size:14px;line-height:1.2;letter-spacing:1.68px;text-transform:uppercase}

/* ---------- Layout ---------- */
.wrap{max-width:var(--maxw);margin:0 auto;position:relative}

/* ---------- Buttons ----------
   Interaction ported 1:1 from the client button-studies reference
   (sentinel-demo-423.netlify.app/button-studies.html): lift on hover,
   circular fill sweep growing from the left, arrow advance, quick press
   scale on tap — shared by every pill; colours stay per-variant. */
.btn{
  position:relative;isolation:isolate;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);
  /* 11px + 1px border = Figma's 12px inset with inside stroke → 40px total height */
  padding:11px 20px;border-radius:999px;
  font-family:var(--sans);font-weight:600;font-size:12px;line-height:16px;
  letter-spacing:1.6px;text-transform:uppercase;cursor:pointer;border:1px solid transparent;
  -webkit-tap-highlight-color:transparent;
  transition:transform .45s var(--ease-spring),box-shadow .55s var(--ease-fluid),
    background-color .45s var(--ease-soft),color .45s var(--ease-soft),border-color .45s var(--ease-soft);
  white-space:nowrap;
}
/* Fill sweep — reference .btn--sweep::before (circle scales from the left).
   Negative z-index inside the isolated context paints ABOVE the button's own
   background but below its text/arrow, so the label stays crisp and stable. */
.btn::before{
  content:"";position:absolute;top:50%;left:-10%;width:250%;aspect-ratio:1;border-radius:50%;
  background:var(--sweep,transparent);transform:translate(-50%,-50%) scale(0);
  transition:transform .55s var(--ease-fluid);z-index:-1;
}
.btn:hover::before,.btn:focus-visible::before{transform:translate(-50%,-50%) scale(1)}
.btn:hover,.btn:focus-visible{transform:translateY(-2px)}
.btn:active{transform:translateY(0) scale(.95);transition-duration:.11s}
.btn:focus-visible{outline:2px solid var(--amber);outline-offset:3px}
.btn .arrow{width:16px;height:16px;flex:0 0 16px;transition:transform .45s var(--ease-fluid)}
.btn:hover .arrow,.btn:focus-visible .arrow{transform:translateX(4px)}
@media (prefers-reduced-motion: reduce){
  /* Fill/colour feedback stays (appears instantly via the global duration
     collapse); all movement — lift, press, arrow advance — is removed. */
  .btn:hover,.btn:focus-visible,.btn:active{transform:none}
  .btn:hover .arrow,.btn:focus-visible .arrow{transform:none}
}

.btn--primary{background-color:var(--cream);--sweep:var(--warm-sand);color:var(--charcoal);box-shadow:0 1px 2px rgba(0,0,0,.15)}
.btn--primary:hover,.btn--primary:focus-visible{box-shadow:var(--lift-shadow-dark)}
.btn--primary:active{box-shadow:var(--lift-shadow-active)}

.btn--outline-light{border-color:var(--white);color:var(--white);background-color:transparent;--sweep:rgba(255,255,255,.14)}
.btn--outline-light:hover,.btn--outline-light:focus-visible{box-shadow:0 3px 10px rgba(0,0,0,.12)}

.btn--outline-dark{border-color:var(--charcoal);color:var(--charcoal);background-color:transparent;--sweep:var(--charcoal)}
.btn--outline-dark:hover{color:var(--cream)}
.btn--outline-dark:hover,.btn--outline-dark:focus-visible{box-shadow:var(--lift-shadow)}
.btn--white{background-color:#fff;--sweep:var(--cream);color:var(--charcoal);box-shadow:0 1px 2px rgba(0,0,0,.15)}
.btn--white:hover,.btn--white:focus-visible{box-shadow:var(--lift-shadow)}
.btn--white:active{box-shadow:var(--lift-shadow-active)}

/* Text link with amber underline (Figma "Sentinel / Button" link variant).
   Hover per reference "slide underline": the faint base rule sits underneath
   (the border), gold fills over it from the left at a constant weight. */
.link-btn{
  position:relative;
  display:inline-flex;align-items:center;gap:var(--space-2);
  border-bottom:1px solid var(--amber);padding:8px 0;
  font-weight:600;font-size:12px;line-height:16px;letter-spacing:1.6px;
  text-transform:uppercase;color:var(--charcoal);
  align-self:flex-start;white-space:nowrap;
  transition:color .2s var(--ease-soft);
}
.link-btn{border-bottom-color:var(--underline-base)}
.link-btn::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;background:var(--amber);
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease-fluid);
}
.link-btn:hover::after,.link-btn:focus-visible::after{transform:scaleX(1)}
.link-btn:active{transform:scale(.97)}
.link-btn:focus-visible{outline:2px solid var(--amber);outline-offset:3px}
.link-btn .arrow{width:16px;height:16px;transition:transform .45s var(--ease-fluid)}
.link-btn:hover .arrow,.link-btn:focus-visible .arrow{transform:translateX(4px)}
.link-btn--light{color:var(--off-white);border-bottom-color:var(--underline-base-dark)}
@media (prefers-reduced-motion: reduce){
  .link-btn:active{transform:none}
  .link-btn:hover .arrow,.link-btn:focus-visible .arrow{transform:none}
}
/* Touch devices (client round 13 item 11): a tap must never leave a button
   stuck in its hover state — hover motion/fill is neutralised where hover
   isn't a real capability; the quick :active press remains as tap feedback. */
@media (hover:none){
  .btn:hover{transform:none}
  .btn:hover::before{transform:translate(-50%,-50%) scale(0)}
  .btn:hover .arrow{transform:none}
  .link-btn:hover::after{transform:scaleX(0)}
  .link-btn:hover .arrow{transform:none}
  .step__cta:hover{background:transparent;border-color:transparent;border-bottom-color:var(--amber);border-radius:0;padding:8px 2px;margin-bottom:0;box-shadow:none;transform:none}
  .step__cta:hover .arrow{transform:none}
}

/* ============================================================
   NAVIGATION  (Figma: Desktop Nav 330:1140)
   Transparent, overlaid on hero. 48px x / 32px y padding.
   ============================================================ */
.nav{position:fixed;top:0;left:0;right:0;z-index:50}
/* WordPress admin bar (logged-in users): offset every fixed chrome element by
   the bar's own height via WP's core class + CSS variable. No effect when the
   admin bar is absent (body.admin-bar missing -> rules never apply). */
body.admin-bar .nav,
body.admin-bar .nav__sheet,
body.admin-bar .mnav{top:var(--wp-admin--admin-bar--height,32px)}
@media screen and (max-width:782px){
  body.admin-bar .nav,
  body.admin-bar .nav__sheet,
  body.admin-bar .mnav{top:var(--wp-admin--admin-bar--height,46px)}
}
/* Bar background surface — its own layer so the scrim never darkens the bar
   (demo stacking: scrim < bar background < sheet < bar content). */
.nav::before{content:"";position:absolute;inset:0;z-index:1;background:transparent;
  transition:background-color .5s cubic-bezier(.4,0,.2,1),box-shadow .5s cubic-bezier(.4,0,.2,1)}
.nav__inner{
  max-width:var(--maxw);margin:0 auto;
  display:flex;align-items:center;justify-content:space-between;
  padding:32px var(--global-padding); /* Figma: 32 / 48 → 119.76px bar */
}
.nav__logo{display:inline-flex;align-items:center;line-height:0;transition:opacity .3s ease}
.nav__logo img{height:55.76px;width:auto;display:block} /* Figma logo 242.06 x 55.76 */
.nav__links{display:flex;align-items:center;gap:40px;transition:opacity .3s ease}
.nav__links a{color:var(--off-white);font-family:var(--sans);font-weight:600;font-size:12px;line-height:1.2;letter-spacing:1.44px;text-transform:uppercase;position:relative;padding-bottom:2px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition:color .2s ease}
/* Shared sliding underline (demo .nav-underline): one 1px bar that glides
   between items on hover/open. */
.nav__underline{position:absolute;bottom:-6px;left:0;height:1px;width:0;background:currentColor;opacity:0;
  transition:transform .45s cubic-bezier(.16,1,.3,1),width .45s cubic-bezier(.16,1,.3,1),opacity .25s cubic-bezier(.4,0,.2,1);pointer-events:none}
.nav__underline.is-on{opacity:1}
.nav__links{position:relative;color:var(--off-white)}
.nav.scrolled .nav__links,.nav.panel-open .nav__links{color:var(--charcoal)}
/* Demo hover/open state: links dim to 72% (plus the sliding underline). */
.nav__links a{transition:color .2s ease,opacity .25s cubic-bezier(.4,0,.2,1)}
.nav__links a:hover,.nav__links a:focus-visible,.nav__links a.is-open{opacity:.72}
.nav__links a:focus-visible{outline:2px solid var(--amber);outline-offset:3px;border-radius:2px}

/* Right cluster: saved-plans button + contact (Figma: gap 16) */
.nav__actions{display:flex;align-items:center;gap:var(--space-4)}
.nav__save{
  position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;border-radius:999px;
  background:transparent;border:1px solid var(--off-white);color:var(--off-white);
  font-weight:600;font-size:12px;letter-spacing:1.44px;text-transform:uppercase;line-height:1.2;
  transition:background-color .3s ease,color .3s ease,border-color .3s ease;
}
.nav__save-count{
  position:absolute;top:-5px;left:23px;
  display:inline-flex;align-items:center;justify-content:center;
  min-width:16px;padding:3px;border-radius:999px;
  background:var(--amber);color:var(--off-white);
  font-weight:600;font-size:10px;line-height:1.2;letter-spacing:0;
}
.nav__contact{color:var(--off-white);letter-spacing:1.44px;line-height:1.2;transition:background-color .3s ease,color .3s ease,border-color .3s ease}
.nav__toggle{display:none;color:var(--off-white)}

/* Scrolled state (per client demo): solid cream bar, charcoal recolour, and —
   like the demo (--nav-h 120px → --nav-h-scrolled 92px) — the bar COMPACTS so
   it covers less of the section underneath. The desktop compact rules used to
   sit inside the mobile media query by mistake, which left a full 120px bar
   parked over every snapped section top (the "section under the header"
   overlap AJ flagged 19-Aug). */
.nav__logo img{transition:filter .3s ease,height .35s cubic-bezier(.4,0,.2,1)}
.nav__inner{transition:padding .35s cubic-bezier(.4,0,.2,1)}
.nav.scrolled::before{background:var(--cream);box-shadow:0 4px 24px rgba(66,64,64,.08)}
.nav.scrolled .nav__logo img{filter:brightness(0) invert(.26)}
@media (min-width:1024px){
  /* 26 + 40 (actions row) + 26 = 92px — the demo's scrolled bar height. */
  .nav.scrolled .nav__inner{padding-top:26px;padding-bottom:26px}
  .nav.scrolled .nav__logo img{height:34px}
}
.nav.scrolled .nav__links a{color:var(--charcoal)}
.nav.scrolled .nav__save{background:var(--white);border-color:transparent;color:var(--charcoal);box-shadow:var(--shadow-sm)}
.nav.scrolled .nav__contact{background-color:var(--sage);--sweep:rgba(45,55,47,.28);border-color:transparent;color:var(--off-white)}
.nav.scrolled .nav__toggle{color:var(--charcoal)}

/* Menu-open state: bar goes solid cream, seamless with the sheet */
.nav.panel-open::before{background:var(--cream);box-shadow:none}
.nav.panel-open .nav__logo img{filter:brightness(0) invert(.26)}
.nav.panel-open .nav__links a{color:var(--charcoal)}
.nav.panel-open .nav__save{background:var(--white);border-color:transparent;color:var(--charcoal);box-shadow:var(--shadow-sm)}
.nav.panel-open .nav__contact{background-color:var(--sage);--sweep:rgba(45,55,47,.28);border-color:transparent;color:var(--off-white)}

/* ============================================================
   HERO  (Figma: Hero Full Bleed 271:785) — 1440 x 881
   ============================================================ */
.hero{position:relative;height:881px;overflow:hidden}
.hero__bg{position:absolute;inset:0;z-index:0}
.hero__bg img{width:100%;height:100%;object-fit:cover;object-position:center 30%}
/* graceful fallback before real photo is added */
.hero__bg{background:linear-gradient(120deg,#c9c3b4,#e7e0d2)}
.hero__overlay{
  position:absolute;inset:0;z-index:1;mix-blend-mode:multiply;
  background:linear-gradient(180deg,rgba(66,64,64,.4) 0%,rgba(66,64,64,0) 60%,rgba(53,53,53,.8) 100%);
}
/* Logo/header top shade (client round 25). The approved Figma frame carries a
   dark tinge across the very top of the hero photo that the image asset does
   not contain, so the logo and nav always sit on enough contrast. Measured off
   the Figma frame against this build at 1440x881: the darkening is neutral
   (equal R/G/B), uniform across the full width, ~31% at the top edge and gone
   by ~90px — precisely the band the logo occupies.

   It is a fixed-height band in px, not a percentage, because it belongs to the
   nav rather than to the hero's height: the hero is 100vh on desktop, so a
   percentage would grow with the viewport and drift off the logo.

   Deliberately a sibling layer of the photo rather than anything inside
   .hero__bg: the hero's slow zoom transforms the IMAGE, and a shade painted
   over the top of the section can never move, scale or jump with it. It sits
   under the nav (z-index 50) and under the hero copy (z-index 2), takes no
   pointer events, and is inset:0-anchored inside .hero{overflow:hidden}, so it
   adds no width and cannot change the section's dimensions. */
.hero::before{
  content:"";position:absolute;left:0;right:0;top:0;height:96px;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(0,0,0,.31) 0,
    rgba(0,0,0,.25) 24px,
    rgba(0,0,0,.215) 46px,
    rgba(0,0,0,.145) 60px,
    rgba(0,0,0,.105) 72px,
    rgba(0,0,0,.025) 84px,
    rgba(0,0,0,0) 96px);
}
/* Hero background VIDEO (client round 25) — same box, same framing rules as
   the photo, no controls, no zoom. */
.hero__video{width:100%;height:100%;object-fit:cover;object-position:center 30%;display:block;pointer-events:none}
.hero__inner{position:relative;z-index:2;max-width:var(--maxw);margin:0 auto;height:100%}
/* Recognition + live chat overlay layer — same max-width geometry as
   .hero__inner; kept separate so mobile can reflow them below the photo. */
.hero__meta{position:absolute;inset:0;z-index:2;max-width:var(--maxw);margin:0 auto;pointer-events:none}
.hero__meta>*{pointer-events:auto}

.hero__copy{position:absolute;left:48px;top:531px;width:620px;display:flex;flex-direction:column;gap:32px}
.hero__headline{display:flex;flex-direction:column;gap:20px}
.hero__title{
  font-family:var(--serif);font-weight:400;color:var(--white);
  font-size:80px;line-height:86px;letter-spacing:-1px;
  text-shadow:0 2px 12px rgba(0,0,0,.25);
}
.hero__title .em{font-style:italic;display:block}
.hero__sub{
  font-size:20px;line-height:32px;color:var(--cream);
  text-shadow:0 1px 6px rgba(0,0,0,.2);
}
.hero__actions{display:flex;gap:16px;align-items:center}

.hero__recognition{position:absolute;right:48px;top:781px;width:256px;display:flex;flex-direction:column;align-items:flex-end;gap:6px;text-align:right}
.hero__recognition .rec-title{font-weight:600;font-size:10px;letter-spacing:1.6px;text-transform:uppercase;color:var(--amber)}
.hero__recognition .rec-rule{width:91px;height:2px;background:var(--amber)}
.hero__recognition .rec-desc{font-size:13px;line-height:20px;color:var(--cream)}

/* Round 20 item 1: the chat bubble is FIXED to the viewport (it used to be
   absolute inside the hero, so it scrolled away with the banner). Same
   Figma offsets (48 right / 117 up = its designed resting spot over the
   hero), visible through the whole page scroll on every device; below the
   nav layers (50/80) so open menus still cover it, and it still hides when
   the real respond.io widget is configured (rule in functions.php). */
/* Round 22 item 1: the bubble is anchored to the MAIN CONTENT CONTAINER's
   right edge — the same edge the Recognition line uses — not to the viewport.
   `100%` on a fixed element resolves against the viewport's content box (the
   scrollbar excluded), which is exactly what the centred container is centred
   in, so the two edges line up to the pixel at every width. Vertically it
   sits 40px above the Recognition text (Recognition rests 51px up and is 26px
   tall → 51+26+40 = 117... measured live: 140px). Because it is fixed in BOTH
   states there is no transition to jump: it starts in its Hero position and
   simply stays there as the page scrolls. */
.hero__livechat{
  position:fixed;right:max(48px,calc((100% - var(--maxw))/2 + 48px));bottom:140px;width:61px;height:61px;border-radius:50%;z-index:40;
  background:var(--slate-green);display:flex;align-items:center;justify-content:center;
  border:none;box-shadow:none;cursor:pointer;transition:transform .25s ease;
}
.hero__livechat:hover{transform:scale(1.06)}
.hero__livechat svg{width:35.24px;height:27.79px;color:var(--cream)}

/* ---- Full-viewport hero (client feedback #8/#9) — desktop only.
   The hero fills the viewport (Stanley St reference) and its overlay content
   anchors to the BOTTOM edge (offsets = Figma 1440×881 geometry: copy bottom
   54, recognition bottom 51, chat bottom 117), so nothing crops at 1280×800
   and the composition scales naturally to 1920+. The hero scrolls naturally
   with the page (AJ 19-Aug: the sticky "cover" pinning was reverted). */
@media (min-width:1024px){
  .hero{height:100vh;height:100svh;min-height:660px}
  .hero__copy{top:auto;bottom:54px}
  .hero__recognition{top:auto;bottom:51px}
  /* (chat bubble is viewport-fixed since round 20 — no per-hero override) */
  /* Client feedback #4: award line reads as white; long names clip cleanly
     inside the fixed frame instead of spilling out of it. */
  .hero__recognition .rec-desc{color:var(--white)}
  .rec-desc.is-multi .rec-award{max-width:100%;overflow:hidden;text-overflow:ellipsis}
}

/* ---- Hero entrance (on load — above the fold; Figma-consistent choreography:
        copy rises in sequence, recognition fades, chat pops, image fades in
        at its resting framing - opacity only, the zoom is hover-driven, see
        the hover zoom-out block below).
        fill-mode:backwards → end state = natural styles, so hovers still work. */
@keyframes hero-rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@keyframes hero-fade{from{opacity:0}to{opacity:1}}
@keyframes hero-pop{from{opacity:0;transform:scale(.8)}to{opacity:1;transform:none}}
.hero__bg img{animation:hero-fade 1.2s cubic-bezier(.16,1,.3,1) backwards}
.hero__title{animation:hero-rise .7s cubic-bezier(.16,1,.3,1) .1s backwards}
.hero__sub{animation:hero-rise .7s cubic-bezier(.16,1,.3,1) .24s backwards}
.hero__actions{animation:hero-rise .7s cubic-bezier(.16,1,.3,1) .38s backwards}
.hero__recognition{animation:hero-fade .8s cubic-bezier(.16,1,.3,1) .6s backwards}
.hero__livechat{animation:hero-pop .5s cubic-bezier(.16,1,.3,1) .85s backwards}
@media (prefers-reduced-motion: reduce){
  .hero__bg img,.hero__title,.hero__sub,.hero__actions,.hero__recognition,.hero__livechat{animation:none}
}

/* ---- Hero hover zoom-OUT (client request 20-Aug, direction swapped on a
   follow-up request the same day). Interaction-driven, never ambient: at
   rest the image is STATIC at its default framing (scale 1.12 - slightly
   over-filled so a zoom-out never exposes the frame edges; this IS the
   normal resting scale, nothing animates until the pointer arrives). On
   hover it eases DOWN toward 1 on a long cinematic curve (slow zoom-out);
   when the pointer leaves, the transition retargets from whatever scale it
   is mid-flight back to the resting 1.12 just as gradually - CSS
   transitions reverse from the current value, so there is no reset jump,
   snap or animation break. Scrolling away clears :hover, so returning to
   the hero always starts from the resting framing. The effect applies ONLY
   to the background image (copy, nav, buttons untouched) and stays inside
   .hero{overflow:hidden} with the same object-position, so no layout shift
   or horizontal scroll. Gated to hover-capable fine pointers (touch
   devices keep the static image) and disabled under prefers-reduced-motion.
   Tunables: resting 1.12 / hover-out 14s / return 7s. No libraries. */
/* Client round 17: the hero behaviour above is now ONE reusable utility —
   `sh-zoom` on the section + `sh-zoom-media` on its full-width background
   wrapper. Applied to the hero (image backgrounds only), Build With
   Confidence, FAQ and the Take the First Step banner. Beautifully Built and
   Free Site Check were removed from the list in client round 25 — the client
   asked for those two images to stay completely static. It targets only
   images inside a .sh-zoom-media wrapper, so card/split-layout/content
   images can never inherit it. The wrapper clips its own image, layout and
   object-position are untouched, and the whole thing stays hover-capable
   fine-pointer + reduced-motion-off only (touch keeps static images). */
.sh-zoom-media{overflow:hidden}
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .sh-zoom .sh-zoom-media img{transform:scale(1.12);transition:transform 7s cubic-bezier(.25,.46,.45,.94);will-change:transform}
  .sh-zoom:hover .sh-zoom-media img{transform:scale(1);transition-duration:14s;transition-timing-function:cubic-bezier(.22,.61,.24,1)}
}

/* ============================================================
   IDEAS & PLANS  (Figma 261:957) — 1440 x 853, two columns
   Left: Floor Plans (off-white) · Right: Style Ideas (cream)
   ============================================================ */
/* ---------- Ideas & Plans — Figma 261:957 (1440×853) ----------
   Left: Floor Plans on an off-white panel that bleeds to the viewport's left
   edge and ends at content x694. Right: Style Ideas on cream, image bleeding
   to the right edge. CTAs of both columns bottom-align at y≈770 by design. */
.ideas{position:relative;display:grid;grid-template-columns:694fr 746fr;align-items:stretch;max-width:var(--maxw);margin:0 auto;min-height:853px}
/* Cream ground for the whole band (body is off-white), then the off-white
   panel over the plans column — both bleed to the viewport edges. */
.ideas::before{content:"";position:absolute;top:0;bottom:0;left:calc(50% - 50vw);width:100vw;background:var(--cream);z-index:0}
.ideas__col{position:relative;display:flex;flex-direction:column;align-items:flex-start}
.ideas__col--plans::before{content:"";position:absolute;top:0;bottom:0;right:0;width:100vw;background:var(--off-white);z-index:0}
.ideas__media,.ideas__text{position:relative;z-index:1}
/* Plan drawing: Figma ink 499×425 at (101,76) = the bundled asset at 0.4995
   scale, whitespace included → 500px-wide box at (101,75). */
/* multiply = Figma's own technique on this node: the drawing's white canvas
   disappears into the off-white panel, only the ink stays. The blend lives on
   the wrapper — its z-index makes it a stacking context, which would isolate
   a blend set on the img inside. */
.ideas__col--plans .ideas__media{margin:75px 48px 0 min(101px,7.014vw);mix-blend-mode:multiply}
.ideas__col--plans .ideas__media img{display:block;width:min(500px,34.723vw);height:auto}
/* Materials photo: 693×570 at content (747,54), bleeds right. The exposed
   top/bottom/left edges fade into the background so no hard boundary shows;
   the right edge runs off the viewport and stays solid. */
.ideas__col--style .ideas__media{align-self:stretch;margin:54px calc(-1 * max(0px,(100vw - var(--maxw)) / 2)) 0 min(53px,3.681vw)}
.ideas__col--style .ideas__media img{display:block;width:100%;height:min(570px,39.584vw);object-fit:cover;border-radius:4px;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 64px,#000 calc(100% - 64px),transparent 100%),linear-gradient(to right,transparent 0,#000 64px);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(to bottom,transparent 0,#000 64px,#000 calc(100% - 64px),transparent 100%),linear-gradient(to right,transparent 0,#000 64px);
  mask-composite:intersect}
.ideas__text{display:flex;flex-direction:column;gap:24px}
/* Floor Plans copy at x50: title top 568 (media box ends 535 + 33). */
.ideas__col--plans .ideas__text{padding:33px 48px 0 50px}
/* Style Ideas copy at x758: title top 597 — 27px over the photo's quiet
   lower edge, exactly as composed in Figma. */
.ideas__col--style .ideas__text{margin-top:-27px;padding:0 64px 0}
.ideas__text .h1{color:var(--charcoal)}
.ideas__text p{max-width:365px;color:var(--charcoal)}
.ideas__text .link-btn{align-self:flex-start}
/* Mobile-only plan graphic (hidden on desktop). */
.ideas__plan-m{display:none}
.ideas__d{display:inline}
.ideas__m{display:none}

/* ============================================================
   BUILD OPTIONS  (Figma 261:997) — 1440 x 1122, warm-sand
   Heading + intro + ALL BUILD OPTIONS pill, then card carousel
   ============================================================ */
.build{position:relative;background:var(--warm-sand);overflow:hidden}
/* the ::before carries the warm-sand ground as well as the pattern so the
   round-18 entrance can wipe the whole background up in one layer */
.build::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;background:var(--warm-sand) url("home/graphics.png") no-repeat center top;background-size:cover}
.build__inner{position:relative;z-index:1;max-width:var(--maxw);margin:0 auto;padding:96px 0 129.6px}
.build__head{display:flex;justify-content:space-between;align-items:center;gap:24px;padding:0 48px}
.build__intro{max-width:460px}
.build__intro .h1{color:var(--charcoal)}
.build__intro p{margin-top:16px;font-size:16px;line-height:1.5;color:var(--mid-grey)}
.build__cards{display:flex;gap:32px;padding:56px 0 0;margin:0 48px;overflow-x:auto;scrollbar-width:none;scroll-snap-type:x mandatory}
.build__cards::-webkit-scrollbar{display:none}
.build__card{flex:0 0 358px;height:493px;background:#fff;border-radius:14.4px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 1px 4px rgba(0,0,0,.06);scroll-snap-align:start}
.build__card img{width:100%;flex:1;min-height:0;object-fit:cover}
.build__card-body{padding:24px 24px 30.6px;display:flex;flex-direction:column;gap:0}
.build__card-title{font-family:var(--sans);font-weight:500;font-size:27px;line-height:1.27;color:var(--charcoal)}
.build__card-desc{font-size:16px;line-height:1.5;color:var(--mid-grey)}
/* Build Options card hover — REBUILT in client round 25.

   The old expand animated `max-height: 0 -> 198px`. Two things were wrong
   with that. It is a guess: 198px is smaller than the taller descriptions,
   so on those cards the bottom of the link was permanently cut off — and
   because the sweep underline (.link-btn::after) is drawn 1px BELOW the
   link's own box, that edge is the very first thing an overflow:hidden
   ancestor eats. That is the broken arrow/underline the client reported.
   It also never looked smooth: easing toward a max-height larger than the
   content means the visible motion finishes early and then stalls.

   Now the panel is a grid row easing 0fr -> 1fr, so it animates to the
   description's real height on every card, whatever its length — no magic
   number, no stall, nothing clipped. The clipped child carries 2px of
   bottom padding so the underline's 1px overhang always has room, and the
   clip is released the moment the card is open, so the arrow stays whole
   for the entire transition and after it.

   Easing follows the client's brief exactly: opening is ease-OUT (the base
   rule below carries the collapse, the :hover rule carries the open), and
   closing is a gentle ease-IN. Card lift, shadow, panel and typography all
   run on the same clock so the whole card moves as one piece. */
.build__card-reveal{
  display:grid;grid-template-rows:0fr;margin-top:0;opacity:0;
  transition:grid-template-rows .8s var(--ease-card-close),opacity .55s var(--ease-card-close),margin-top .8s var(--ease-card-close);
}
.build__card-reveal-in{overflow:hidden;min-height:0;display:flex;flex-direction:column;gap:12px;padding-bottom:2px}
.build__card:hover .build__card-reveal,.build__card:focus-within .build__card-reveal{
  grid-template-rows:1fr;opacity:1;margin-top:12.6px;
  transition:grid-template-rows .9s var(--ease-card-open),opacity .68s var(--ease-card-open) .06s,margin-top .9s var(--ease-card-open);
}
/* Once open, stop clipping entirely — belt and braces for the underline. */
.build__card:hover .build__card-reveal-in,.build__card:focus-within .build__card-reveal-in{overflow:visible}
.build__card .link-btn{align-self:flex-start;margin-top:2px}
.build__card:hover,.build__card:focus-within{box-shadow:var(--shadow-card-hover)}
/* Card hover motion (client round 13 item 1): a subtle lift on a smooth curve
   with a small intent delay, hover-capable fine pointers only, reduced-motion
   off. transform-only, so surrounding cards never move; the Figma "Large
   Shadow" above completes the lift. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .build__card{transition:transform .8s var(--ease-card-close),box-shadow .8s var(--ease-card-close)}
  .build__card:hover,.build__card:focus-within{transform:translateY(-6px);transition:transform .9s var(--ease-card-open),box-shadow .9s var(--ease-card-open)}
}
/* Progress row (Figma "scrollbar" 648:4825): track + the white prev/next
   arrows on its right (client round 18 item 5). */
.build__trackrow{display:flex;align-items:center;gap:16px;margin:68.4px 48px 0;max-width:calc(var(--maxw) - 96px)}
/* round 22 item 2.2: exact client colours — track #FDFBF9, active #424040 */
.build__track{flex:1;min-width:0;height:3px;background:#fdfbf9;position:relative;border-radius:2px;cursor:pointer}
.build__track-fill{position:absolute;left:0;top:0;height:100%;width:25%;background:#424040;border-radius:2px}
.build__nav{display:flex;align-items:center;gap:10px}
/* Round 20 item 2.1: CLEAN WHITE ARROWS per the original Figma — no circle,
   no fill, no border, no shadow. The 40px hit area stays for usability;
   only the visible glyph is the 24px white chevron. */
.build__arrow{width:40px;height:40px;flex:0 0 40px;border:0;border-radius:50%;background:transparent;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:none;transition:opacity .25s ease,transform .25s var(--ease-spring)}
.build__arrow svg{width:24px;height:24px}
.build__arrow:hover,.build__arrow:focus-visible{opacity:.7}
.build__arrow:active{transform:scale(.9);transition-duration:.11s}
.build__arrow:focus-visible{outline:2px solid var(--amber);outline-offset:3px;opacity:1}
.build__arrow[disabled]{opacity:.5;pointer-events:none}
@media (prefers-reduced-motion: reduce){.build__arrow:active{transform:none}}
@media (max-width:1023px){
  .build__nav{display:none}
  /* Below 1024 the section is not pinned, so .build__cards is itself the
     horizontal scroller and clips the hover shadow at its own bottom edge.
     Same trick as the desktop window: grow the clip box, cancel it with an
     equal negative margin so the layout does not move. (Kept out of the base
     rule because the pinned-mode override resets the cards' margin, which
     would leave the padding uncompensated and add 48px to the section.) */
  .build__cards{padding-bottom:48px;margin-bottom:-48px}
}
/* Round 22: the chat launcher is fixed at the content container's right edge
   (item 1), which is exactly where these arrows sat — it covered the "next"
   arrow for the whole Build Options section. The arrows step inboard by the
   launcher's footprint so both stay usable; they remain on the right of the
   progress bar as designed, and the track simply extends to meet them. This
   also keeps them clear of the real respond.io widget, which docks in the
   same corner once its ID is configured. */
@media (min-width:1024px){.build__nav{margin-right:77px}}

/* ============================================================
   FOUNDER  (Figma 261:1038) — 1440 x 800, charcoal band
   Photo left (897) fading to charcoal · story text right
   ============================================================ */
.founder{background:var(--charcoal)}
.founder__inner{display:flex;align-items:stretch;min-height:800px}
.founder__media{position:relative;flex:0 0 52%;overflow:hidden}
.founder__media img{width:100%;height:100%;object-fit:cover}
/* Round 26 item 7: the text now starts over the photo's faded edge, and the
   client's reference point is a WOODEN PANEL — so the fade must not have
   swallowed the timber by then. It reaches full charcoal at the photo's own
   right edge, as before, only easing in a little sooner so the copy has a
   settled ground without hiding the panels behind it. */
.founder__media::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(66,64,64,0) 62%,rgba(66,64,64,.72) 84%,var(--charcoal) 100%)}
/* Round 26 item 7: the client asked for the right-hand content to move
   ~130px left so it begins around the second vertical wooden panel, just
   past the man's elbow — the block now overlaps the photo's faded edge
   rather than starting where the image ends. Width, vertical centring,
   typography and the rest of the composition are unchanged; only the
   starting position moves. Desktop only — below 1024 the section stacks. */
.founder__text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:24px;padding:0 64.8px;max-width:594px}
@media (min-width:1024px){
  .founder__text{margin-left:-130px;position:relative;z-index:1}
}
.founder__label{font-weight:600;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--amber)}
.founder__title{font-family:var(--serif);font-weight:400;font-size:52px;line-height:58px;color:var(--white)}
.founder__body p{font-size:16px;line-height:1.5;color:var(--cream);margin-bottom:10px}
.founder__body .founder__sign{font-family:"Signerica Fat","Dancing Script",cursive;font-weight:400;font-size:32.4px;color:var(--amber);line-height:1;margin:36px 0 5.4px;white-space:nowrap}
.founder__name{font-size:16px;line-height:1.5;color:var(--cream);margin:0}
.founder__name strong{font-weight:700}

/* ============================================================
   REVIEWS  (Figma 261:1055) — 1440 x 913
   Entry photo right, charcoal gradient left, 3 Google Review cards
   ============================================================ */
.reviews{position:relative;min-height:820.8px;background:var(--cream);overflow:hidden}
.reviews__bg{position:absolute;inset:0;z-index:0;overflow:hidden}
.reviews__bg img{width:100%;height:100%;object-fit:cover;object-position:72% center}
/* Client round 18 item 6: on wide desktops the band gets much wider than the
   photo's aspect, so a centred crop pushed the homeowner's head to the top
   edge. Anchoring the visible band higher up the photo keeps the intended
   composition (doorway, homeowner, dog, planting) at every large width. */
@media (min-width:1560px){.reviews__bg img{object-position:72% 35%}}
@media (min-width:1800px){.reviews__bg img{object-position:72% 30%}}
/* Round 22 item 4.1: this section's band is much wider than the photo's own
   3:2 shape, so `cover` already has to crop it vertically — and the shared
   zoom utility's RESTING scale(1.12) was cropping a further 12% on top of
   that, which is what still read as "cropped incorrectly" on desktop. The
   zoom-out interaction is kept exactly as designed, just started from a much
   gentler 1.04, so ~8% more of the composition is on screen at rest at every
   desktop size. Aspect ratio and focal point are untouched. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  /* Client round 25: Beautifully Built no longer zooms — the section dropped
     its sh-zoom hooks, so this override is retired. The photo now renders at
     its natural cover framing and stays completely still while the
     testimonial column scrolls. */
}
.reviews__overlay{position:absolute;inset:0;z-index:1;background:linear-gradient(to left,rgba(66,64,64,0) 48%,#424040 67%)}
.reviews__tagline{position:absolute;z-index:1;bottom:60px;left:max(0px,calc(50vw - 192px));right:0;text-align:center;margin:0;font-family:var(--serif);font-style:italic;font-weight:400;font-size:101.68px;line-height:1;color:var(--amber);white-space:nowrap;pointer-events:none}
.reviews__inner{position:relative;z-index:2;max-width:var(--maxw);margin:0 auto;padding:60px 48px;display:flex}
.reviews__col{display:flex;flex-direction:column;gap:32px;width:480px}
.review-card{width:480px;background:var(--off-white);border-radius:4px;overflow:hidden}
.review-card__media{position:relative;padding:12px}
.review-card__media img{width:100%;aspect-ratio:332/172;object-fit:cover;border-radius:2px;display:block}
.review-card__tag{position:absolute;top:20px;right:17px;background:var(--warm-sand);color:var(--charcoal);font-weight:600;font-size:12px;letter-spacing:0.72px;text-transform:uppercase;line-height:1.2;padding:4px 8px;border-radius:999px}
.review-card__body{padding:12px 16px;display:flex;flex-direction:column;gap:10px}
.review-card__meta{display:flex;align-items:center;justify-content:space-between}
.review-card__stars{display:flex;gap:2px;color:var(--amber)}
.review-card__stars svg{width:15px;height:15px}
.review-card__date{font-size:14px;color:var(--mid-grey);letter-spacing:0.28px}
.review-card__quote{font-size:18px;line-height:1.4;color:var(--charcoal);letter-spacing:0.36px}
.review-card__name{font-weight:700;font-size:14px;color:var(--charcoal);letter-spacing:0.28px}
/* (client round 13 item: the "--sm" small-type card variant was removed —
   every testimonial uses the same Figma "Google Review" base typography:
   18px/1.4 quote, 14px bold name.) */

/* ============================================================
   FEATURE / CHECKLIST  (Figma 307:1529) — 1440 x 929, cream
   Living-room image left, guide-book download text + check list right
   ============================================================ */
.feature{background:var(--cream);position:relative;z-index:0;display:flex;align-items:stretch;max-width:var(--maxw);margin:0 auto;min-height:929px}
.feature::before{content:"";position:absolute;top:0;bottom:0;left:calc(50% - 50vw);width:100vw;background:#F7F3EA;z-index:-1;pointer-events:none}
.feature__media{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:96px 96px 96px 48px}
.feature__media img{width:526px;height:679px;object-fit:cover;display:block}
.feature__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:24px;padding:128px 64px}
.feature__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--charcoal)}
.feature__title{font-family:var(--serif);font-weight:400;font-size:48px;line-height:1.17;color:var(--charcoal)}
.feature__list{list-style:none;display:flex;flex-direction:column;gap:6px}
.feature__list li{display:flex;align-items:center;gap:12px;font-size:20px;line-height:1.5;color:#000}
.feature__list svg{flex:0 0 24px;width:24px;height:24px}
.feature__text .btn{align-self:flex-start;margin-top:8px}

/* ============================================================
   IMAGE SECTION  (Figma 309:1556) — 1440 x 929, terracotta
   "Build vs buy" text left, tall image right
   ============================================================ */
.image-section{background:var(--terracotta);display:flex;align-items:stretch;min-height:929px}
.image-section__text{flex:0 0 50%;box-sizing:border-box;min-width:0;display:flex;flex-direction:column;justify-content:center;padding:128px 48px 128px max(48px,calc(50vw - var(--maxw)/2 + 48px))}
.image-section__inner{max-width:640px;display:flex;flex-direction:column;gap:24px}
.image-section__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--white)}
.image-section__title{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--off-white)}
.image-section__body{font-size:20px;line-height:1.5;color:var(--off-white)}
.image-section__actions{display:flex;gap:24px;flex-wrap:wrap}
.image-section__media{flex:0 0 50%;box-sizing:border-box;min-width:0;overflow:hidden}
.image-section__media img{width:100%;height:100%;object-fit:cover;display:block}

/* ============================================================
   SECTION IMAGE  (Figma 309:1585) — 1440 x 929, full-bleed
   "Build with confidence" centered over builder photo + MB badge
   ============================================================ */
.section-image{position:relative;min-height:929px;background:var(--terracotta);overflow:hidden;display:flex;align-items:flex-start;justify-content:center}
/* Round 21 item 1: the section keeps the Figma frame's own proportions
   (1440×929) as the viewport widens, so the bottom-anchored photo shows the
   IDENTICAL composition at every width — the sky band, and the text's place
   inside it, scale together instead of the sky collapsing under a fixed
   height. Below 1440 the 929px minimum simply shows MORE sky (safer). */
@media (min-width:1024px){
  /* width:100% keeps the box stretch-sized — otherwise, when min-height
     (the 100svh floor) beats the aspect height, the aspect-ratio would
     transfer the forced height back into a WIDER box (horizontal overflow) */
  /* Client feedback, 25 Aug item 3: past 1440 the aspect ratio kept growing
     the section — 1239px tall at 1920 — so it read as far too tall. The frame
     height is now capped at the Figma frame's own 929px, so the section stops
     growing once it reaches the design's height and the bottom-anchored photo
     simply shows less sky above the house. Composition, text position and the
     colour treatment are untouched. */
  /* Client feedback, 25 Aug item 3 — the section was too tall. The cause was
     the FLOOR below, not this rule: min-height was max(929px,100svh), so on a
     tall window the section was stretched to the whole viewport — 1080px on a
     1280x1080 screen, where the design wants 929 — and simply showed a band of
     extra sky. That floor is now a flat 929px, so the section is exactly the
     Figma proportion and never more.
       A hard 929px cap was tried and rejected on sight: the photograph is
     square, so a 1920-wide box only 929 tall crops nearly all the sky away and
     the heading ends up sitting on the mountains. Capping to the viewport has
     the same effect on a wide, short window. Past 1440 the section therefore
     stays proportional — that is the design's own composition, and shortening
     it further needs a wider crop of the photo, not CSS. */
  .section-image{width:100%;height:auto;aspect-ratio:1440/929}
}
.section-image__bg{position:absolute;inset:0;z-index:0}
/* round 19: the new approved photo (timber frame + lake) anchors to the
   bottom like the Figma frame — the sky takes the crop, never the house */
/* Round 26 item 9 — image treatment. The photo was pulled from Figma at
   native resolution in round 19, but Figma applies its adjustments to the
   image FILL, not to the exported file, so the export came across without
   them: on the page it reads far more saturated than the frame does.
   Measured off the client's own side-by-side in the feedback document
   (same screenshot, same rendering, matched bands): their reference is
   0.75x the saturation and about 1.13x the luminance of ours. Saturation
   and brightness only — no hue or temperature shift, so the colour balance
   and composition are untouched, exactly as the brief asks. */
.section-image__bg img{width:100%;height:100%;object-fit:cover;object-position:center bottom;filter:saturate(.75) brightness(1.09)}
.section-image__text{position:relative;z-index:2;width:838px;max-width:100%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:24px;padding:128px 96px 128px 48px}
.section-image__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--white)}
.section-image__title{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--off-white)}
.section-image__body{font-size:20px;line-height:1.5;color:var(--off-white)}
/* round 22 item 5.1: the Master Builders logo tracks the MAIN CONTENT
   CONTAINER's right edge at every width (the old `50% - 672px` assumed a
   fixed 1344 container, so it fell outside at 1920 and inside at 1280). */
.section-image__mb{position:absolute;top:0;right:max(48px,calc((100% - var(--maxw))/2 + 48px));width:75px;height:103px;z-index:3}
.section-image__mb img{width:100%;height:100%;object-fit:contain;display:block}

/* ============================================================
   LOCAL OFFICE + MAP  (Figma 309:1668) — 1440 x 929
   NZ map (left) + Find your local office panel (right)
   ============================================================ */
/* round 19: ONE background across the whole section — #AECCD5, the exact
   colour baked into the map artwork, so map and content halves read as one */
.local{position:relative;background:#aeccd5;display:flex;min-height:929px}
.local__map{flex:0 0 50%;position:relative;overflow:hidden;background:#aeccd5}
/* The artwork paints the stretched map pane absolutely, so its intrinsic
   height can never inflate the section (round 21) — the map is always exactly
   the height of the right-side content, top to bottom edge.
   Mobile keeps the designed 300px cropped band. */
.local__mapfit{position:absolute;inset:0}
.local__mapbox{position:absolute;inset:0}
.local__map img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* Client feedback, 25 Aug item 6: the gap under the office list was too deep.
   Bottom padding is exactly 50px now (was 96px); the top stays at 96px so the
   heading keeps its position. */
.local__panel{flex:1;min-width:0;padding:96px max(48px,calc(50vw - var(--maxw)/2 + 48px)) 50px 48px;display:flex;flex-direction:column;gap:24px}
.local__title{font-family:var(--serif);font-weight:400;font-size:48px;line-height:1.17;color:var(--charcoal)}
/* round 19: the search is a compact 352px control (Figma "map search"
   309:1779 — narrower than the address card), borderless with the small
   shadow; the functionality is untouched. */
/* round 21 item 2.5: a little more room than the Figma 352 — still well
   narrower than the address card */
.local__search{display:flex;height:48px;width:420px;max-width:100%;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.15)}
.local__search input{flex:1;min-width:0;border:none;border-radius:4px 0 0 4px;padding:0 16px;font-family:var(--sans);font-size:16px;color:var(--charcoal);background:#fff}
.local__search input::placeholder{color:var(--mid-grey)}
.local__search button{width:53px;flex:0 0 53px;background:var(--sage);border:none;border-radius:0 4px 4px 0;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center}
/* Postcode search "no match" note (client round 13 item 8) — quiet, in the
   section's existing type style; hidden until a search finds nothing. */
.local__search-msg{margin-top:8px;font-size:14px;line-height:1.4;color:var(--mid-grey)}
.local__search-msg[hidden]{display:none}
.local__search button svg{width:20px;height:20px}
.local__markers{position:absolute;inset:0;z-index:2;pointer-events:none}
/* round 21 item 2.4: solid marker dots — no white stroke/outline */
.map-marker{position:absolute;width:30px;height:30px;border-radius:50%;transform:translate(-50%,-50%);background:var(--sage);border:none;box-shadow:0 2px 6px rgba(0,0,0,.28);cursor:pointer;pointer-events:auto;padding:0;transition:background .25s ease,transform .2s ease}
.map-marker:hover{transform:translate(-50%,-50%) scale(1.14)}
.map-marker:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
.map-marker.is-active{background:var(--amber);z-index:3}
.local__list{display:flex;flex-direction:column;gap:8px}
/* Round 21 item 2: the round-19 fixed-height/inner-scroll experiment is
   gone — the client wants NO nested scrollbar and natural free-flow
   scrolling. The section grows to its content (929px minimum per Figma),
   every office is simply on the page, and the map — a stretched flex
   sibling — always runs the full height of the right-side content down to
   the same bottom edge. */
@media (min-width:1024px){
  .local__panel{padding-bottom:0} /* the last card IS the bottom edge, map matches */
  /* Round 22 item 6: on desktop the map is FITTED, never cropped.
     · The fit layer starts at the panel's own 96px top padding, so the map's
       top edge and the right-hand content's top edge begin on exactly the
       same line (item 6.1).
     · The box is sized to min(pane width, available height × the artwork's
       aspect), i.e. true "contain" — so the right-hand side of the map stops
       being cut at 1440/1366/1280 and the "New Zealand" label stays on screen
       at every desktop width (items 6.2 / 6.3). Aspect ratio is preserved
       exactly; nothing is stretched.
     · The markers live inside this same box, so they stay locked to the map
       instead of drifting as the crop changes. The pane keeps the section's
       #AECCD5, so the fitted artwork reads as sitting on the section's field. */
  .local__mapfit{top:96px;bottom:0;container-type:size;display:flex;justify-content:center;align-items:flex-start}
  .local__mapbox{position:relative;inset:auto;width:100%;width:min(100%,calc(100cqh * 1061 / 1483));aspect-ratio:1061/1483;flex:0 0 auto}
  .local__mapbox img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;object-position:center top}
}
/* Office list scrolling (client round 25). The client asked for a vertical
   scroll "if there are more locations than can be displayed at once", so the
   list is capped and scrolls only when its content actually exceeds that cap —
   with few offices it still simply flows with the page, exactly as before.

   Deliberately NOT a scroll trap: overscroll-behavior is left at auto, so
   reaching either end of the list hands the gesture straight back to the page
   and the user is never held inside the panel. That was the specific problem
   with the round-19 version the client rejected. The scrollbar is a slim,
   quiet one in the section's own palette rather than the browser default. */
@media (min-width:1024px){
  .local__list{
    /* Feedback 31 Aug item 4: the panel already carried the 50px bottom
       padding, but the gap under the last card measured 131px. The cap was
       derived from the VIEWPORT (100svh - 320px = 550px at an 870px window)
       while the panel's height comes from the section's own 929px Figma
       frame - so the list under-filled its column by 81px and that slack fell
       below it, on top of the 50px padding. The cap now comes from the panel
       itself: the list flexes to fill the column, which leaves exactly the
       50px padding beneath it at any window size, and it still scrolls only
       when the cards genuinely exceed the space (the round-26 intent), still
       with no scroll trap and no visible scrollbar.
       Flex basis is 0, not auto: with `auto` the list asks for its full
       content height (751px for seven offices) and the panel is pushed to
       1049px, growing the section past its own 929px Figma frame. At basis 0
       the list only ever takes the space the panel has left, so the section
       keeps its designed height, the gap is the 50px padding, and the list
       goes back to scrolling internally exactly as round 26 intended. */
    flex:1 1 0;min-height:0;max-height:none;
    overflow-y:auto;overscroll-behavior:auto;scroll-behavior:smooth;
    /* Round 26 item 10: the scrollbar itself is hidden — the client does not
       want it visible — while the list still scrolls with wheel, trackpad,
       keyboard and touch exactly as before, and still never traps the page. */
    scrollbar-width:none;-ms-overflow-style:none;
  }
  /* Flex items shrink by default. In a height-capped column that means the
     OPEN office card gets squashed to fit and then clipped by .office's own
     overflow:hidden — the address and the Email/Phone/Find-out-more row simply
     disappear. Every row must keep its natural height and let the list scroll
     instead. (Same trap as the round-13 scroll lists.) */
  .local__list>.office{flex:0 0 auto}
  .local__list::-webkit-scrollbar{width:0;height:0;display:none}
  /* the list now owns the bottom edge, so the panel keeps its own padding.
     Client feedback, 25 Aug item 6: 96px left too deep a gap under the last
     office card — exactly 50px now. */
  .local__panel{padding-bottom:50px}
}
/* Office card — client round 26 item 10.
   · The white stroke round 25 added is gone: the client explicitly does not
     want a visible border around the card.
   · The stark #fff is replaced by the design system's off-white, so the card
     no longer reads as a "plain/stock white" block against the section.
   · The content area is inset by 15px (the client's 14-16px), so the photo
     and the address sit inside the card rather than bleeding to its edges. */
.office{background:var(--off-white);border-radius:6px;overflow:hidden;transition:background-color .45s var(--ease-office),box-shadow .45s var(--ease-office)}
.office[open]{box-shadow:0 2px 10px rgba(66,64,64,.10)}
.office__card{padding:15px; background: var(--cream); }
.office__card img{border-radius:4px}
.office__body{border-radius:4px}
/* The expand/collapse height is animated from home.js — a <details> cannot be
   transitioned from CSS in every browser, and the native exclusive group was
   snapping the outgoing card shut. The panel is the clipped, height-animated
   wrapper; the card inside it fades so the two read as one movement. */
.office__panel{overflow:hidden;height:0;opacity:0}
.js .office__panel{transition:height .5s var(--ease-office),opacity .38s var(--ease-office)}
.office.is-open>.office__panel{opacity:1}
html:not(.js) .office[open]>.office__panel{height:auto;opacity:1}
@media (prefers-reduced-motion: reduce){
  .office{transition:none}
  .js .office__panel{transition:none}
}
.office__summary{list-style:none;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:15px 20px;cursor:pointer}
.office__summary::-webkit-details-marker{display:none}
.office__summary span{font-size:16px;color:var(--charcoal);font-weight:500}
.office__chev{width:16px;height:16px;color:var(--charcoal);flex:0 0 auto;transition:transform .25s ease}
.office__summary:hover .office__chev{transform:translateY(2px)}
.office[open]>.office__summary{display:none}
.office__card img{width:100%;height:172px;object-fit:cover;display:block}
.office__body{background:var(--cream);padding:18px 2px 20px;display:flex;flex-direction:column;gap:12px}
.office__name{font-weight:600;font-size:20px;line-height:1.4;color:var(--charcoal)}
.office__meta{font-size:14px;line-height:1.6;color:var(--mid-grey)}
.office__actions{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-top:4px}
.office__contact{display:flex;gap:12px}
/* round 19: solid WHITE pills per Figma (309:1795/1796) — never transparent */
/* Client feedback, 25 Aug item 6: these two set a white background but never
   set --sweep, so .btn::before painted "transparent" and the circular fill —
   the signature of the button-studies reference — did nothing on exactly these
   buttons. The markup now carries btn--white, which supplies both the white
   background and its cream sweep, so all that is left here is dropping the
   border. Same root cause as the footer's "Get in Touch" button. */
.office__contact .btn{border:none}
.office__contact .btn svg{width:14px;height:14px}
/* Feedback 31 Aug item 3: this override forced the rest rule back to amber, so
   the standard .link-btn amber sweep filled gold over gold and the Find out
   more buttons looked static. Dropping the colour restores the reference
   interaction (faint var(--underline-base) rule, amber sweeping from the left
   over .5s var(--ease-fluid), arrow advancing over .45s) with no other change:
   the charcoal text colour this rule also sets is kept. */
.office__body .link-btn{color:var(--charcoal)}

/* ============================================================
   SITE CHECK  (Figma 435:3976) — 1440 x 929, full-bleed photo
   Centered "Site Check & Ballpark Pricing"
   ============================================================ */
.site-check{position:relative;min-height:929px;overflow:hidden;display:flex;justify-content:center}
.site-check__bg{position:absolute;inset:0;z-index:0;overflow:hidden}
.site-check__bg img{width:100%;height:100%;object-fit:cover;object-position:center}
.site-check__text{position:relative;z-index:2;text-align:center;display:flex;flex-direction:column;align-items:center;gap:24px;padding-top:112px;width:660px;max-width:90%}
.site-check__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--charcoal)}
.site-check__title{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--charcoal)}
.site-check__body{font-size:20px;line-height:1.5;color:var(--charcoal)}

/* ============================================================
   PROCESS STEPS  (Figma 310:2037) — "Our Process", 3 steps
   ============================================================ */
.process{background:#F7F3EA;padding:96px 0}
.process__header{display:flex;flex-direction:column;align-items:center;gap:24px}
.process__header h2{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--charcoal)}
.process__rule{width:251px;height:2px;background:var(--amber)}
.process__steps{display:flex;padding:96px 48px 0;max-width:var(--maxw);margin:0 auto}
.step{flex:1;min-width:0;display:flex;flex-direction:column;position:relative}
.step__media{height:340px;display:flex;align-items:flex-end;justify-content:center;padding:0 24px}
.step__media img{max-width:100%;max-height:100%;object-fit:contain}
.step__body{display:flex;align-items:flex-start;gap:34px;padding:0 24px 0 0;position:relative}
.step__num{font-family:var(--serif);font-weight:400;font-size:200px;line-height:.7;color:var(--sage);flex:0 0 auto;position:relative;z-index:0;margin-top:-45px}
.step__text{display:flex;flex-direction:column;gap:12px;padding-top:30px;min-width:0;position:relative;z-index:1}
.step__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--charcoal)}
.step__title{font-family:var(--serif);font-weight:400;font-size:36px;line-height:1.17;color:var(--charcoal)}
.step__desc{font-size:14px;line-height:1.4;letter-spacing:0.28px;color:#000}
.step__text .link-btn,.step__text .btn{align-self:flex-start;margin-top:4px}
.step__cta{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;margin-top:8px;font-family:inherit;font-weight:600;font-size:12px;letter-spacing:1.44px;text-transform:uppercase;color:var(--charcoal);text-decoration:none;background:transparent;border:1px solid transparent;border-bottom-color:var(--amber);border-radius:0;padding:8px 2px;transition:background .3s ease,border-color .3s ease,border-radius .3s ease,padding .3s ease,margin .3s ease,box-shadow .3s ease,color .3s ease,transform .45s var(--ease-spring)}
.step__cta .arrow{width:14px;height:14px;flex:0 0 auto;transition:transform .45s var(--ease-fluid)}
/* Client round 13 items 12+14: the morph fires ONLY when the button itself is
   hovered/focused (it previously reacted to hovering anywhere on the step,
   which read as the button animating by itself) — with the button-studies
   lift, arrow advance and press. */
.step__cta:hover,.step__cta:focus-visible{background:#ffffff;border-color:rgba(0,0,0,.08);border-radius:999px;padding:13px 24px;margin-bottom:-10px;box-shadow:0 4px 14px rgba(0,0,0,.10);transform:translateY(-2px)}
.step__cta:hover .arrow,.step__cta:focus-visible .arrow{transform:translateX(4px)}
.step__cta:active{transform:translateY(0) scale(.96);transition-duration:.11s}
.step__cta:focus-visible{outline:2px solid var(--amber);outline-offset:3px}
@media (prefers-reduced-motion: reduce){
  .step__cta:hover,.step__cta:focus-visible,.step__cta:active{transform:none}
  .step__cta:hover .arrow,.step__cta:focus-visible .arrow{transform:none}
}
.step__num::before{content:"";position:absolute;width:279px;height:279px;border-radius:50%;background:#ffffff;left:50%;top:50%;z-index:-1;opacity:0;transform:translate(-50%,-38%) scale(.8);transform-origin:center;transition:opacity .35s ease,transform .35s ease;pointer-events:none}
.step:hover .step__num::before{opacity:1;transform:translate(-50%,-38%) scale(1)}

/* ============================================================
   BLOG CARDS  (Figma 310:2097) — slate-green band, featured + 3 cards
   ============================================================ */
.blog{position:relative}
.blog__band{position:absolute;top:0;left:0;right:0;height:929px;background:var(--slate-green);z-index:0}
.blog__inner{position:relative;z-index:1;display:flex;align-items:flex-start}
.blog__media{flex:1;min-width:0;height:929px;overflow:hidden}
.blog__media img{width:100%;height:100%;object-fit:cover;display:block}
.blog__right{flex:1;min-width:0;display:flex;flex-direction:column;padding-bottom:64px}
.blog__feature{padding:128px max(48px,calc(50vw - var(--maxw)/2 + 48px)) 40px 64px;display:flex;flex-direction:column;gap:24px}
.blog__label{font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;color:var(--off-white)}
.blog__title{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--off-white)}
.blog__desc{font-size:20px;line-height:1.5;color:var(--off-white)}
.blog__cards{padding:0 max(48px,calc(50vw - var(--maxw)/2 + 48px)) 0 64px;display:flex;flex-direction:column;gap:24px}
.blog__slider{overflow:hidden;width:100%}
.blog__track{display:flex;transition:transform .55s cubic-bezier(.4,0,.2,1)}
.blog__track .blog-card{flex:0 0 100%;min-width:0}
/* Wrap-around duplicates exist only for the desktop scroll-driven loop —
   the tablet/mobile slider and reduced-motion layouts never show them. */
.blog__track .blog-card--wrap{display:none}
.blog__dots{display:flex;gap:10px;align-items:center;justify-content:flex-start}
.blog__dots button{width:10px;height:10px;border-radius:50%;border:none;padding:0;background:rgba(255,255,255,.4);cursor:pointer;transition:background .3s ease,width .3s ease}
.blog__dots button[aria-selected="true"]{background:var(--off-white);width:28px;border-radius:5px}
.blog-card{background:#fff;border-radius:4px;overflow:hidden;display:flex;align-items:stretch}
.blog-card__img{flex:1;min-width:0;padding:12px}
.blog-card__img .frame{height:226px;border-radius:2px;overflow:hidden}
.blog-card__img img{width:100%;height:100%;object-fit:cover;display:block}
.blog-card__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:8px;padding:12px 16px 16px}
.blog-card__date{font-size:14px;color:var(--mid-grey);letter-spacing:0.28px;line-height:1.4}
.blog-card__ct{font-family:var(--sans);font-weight:500;font-size:18px;line-height:1.4;color:var(--charcoal)}
.blog-card__cd{font-size:16px;line-height:1.5;color:var(--mid-grey)}

/* ============================================================
   FAQ  (Figma 426:4548) — 1440 x 857, cream + faded photo right
   ============================================================ */
.faq{position:relative;background:var(--cream);overflow:hidden;min-height:857px}
.faq__bg{position:absolute;inset:0;z-index:0}
.faq__bg img{width:100%;height:100%;object-fit:cover;object-position:bottom;mix-blend-mode:multiply}
.faq__overlay{position:absolute;inset:0;z-index:1;background:linear-gradient(269.5deg,rgba(247,243,234,0) 40.7%,var(--cream) 74.2%)}
.faq__inner{position:relative;z-index:2;width:min(100%,calc(859px + max(48px,calc(50vw - var(--maxw)/2 + 48px))));padding:96px 48px 96px max(48px,calc(50vw - var(--maxw)/2 + 48px));display:flex;flex-direction:column;gap:24px}
.faq__title{font-family:var(--serif);font-weight:400;font-size:59px;line-height:1.08;letter-spacing:-0.59px;color:var(--charcoal)}
.faq__sub{font-size:20px;line-height:1.5;color:var(--charcoal);margin-top:20px}
.faq__list{width:674px;max-width:100%;display:flex;flex-direction:column;gap:12px}
.faq-item{background:#fff;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.15);padding:16px}
.faq-item>summary{list-style:none;display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer}
.faq-item>summary::-webkit-details-marker{display:none}
.faq-item>summary p{font-family:var(--sans);font-weight:500;font-size:18px;line-height:1.4;color:var(--charcoal);margin:0}
/* FAQ expand/collapse — client round 25.

   The chevron and the answer now run on ONE curve and ONE duration, so the
   arrow finishes turning exactly as the answer finishes arriving. The panel's
   height is driven from home.js (a <details> element cannot be transitioned
   from CSS in every browser we support), and the arrow keys off `.is-open`
   rather than `[open]` — on the way closed the `open` attribute survives
   until the animation ends, so keying off it would snap the arrow back early
   and break the synchronisation the client asked for.

   Nothing here changes the content, type, colours, padding or spacing: the
   answer keeps its 12px offset, now carried inside the panel so the collapsed
   state measures exactly zero. */
.faq-item>summary svg{width:24px;height:24px;flex:0 0 24px;color:var(--charcoal);transition:transform .55s var(--ease-faq)}
.faq-item.is-open>summary svg{transform:rotate(180deg)}
.faq-item__panel{overflow:hidden;height:0;opacity:0;transform:translateY(-6px);will-change:height}
.js .faq-item__panel{transition:height .55s var(--ease-faq),opacity .42s var(--ease-faq),transform .55s var(--ease-faq)}
.faq-item.is-open>.faq-item__panel{opacity:1;transform:none}
/* No JS, or the browser rendering before home.js runs: a plain open accordion. */
html:not(.js) .faq-item[open]>.faq-item__panel{height:auto;opacity:1;transform:none}
.faq-item__a{font-size:14px;line-height:1.4;letter-spacing:0.28px;color:var(--mid-grey);margin-top:12px}
@media (prefers-reduced-motion: reduce){
  .faq-item>summary svg,.js .faq-item__panel{transition:none}
  .faq-item__panel{transform:none}
}

/* ============================================================
   FOOTER CTA  (Figma 426:4449) — 1440 x 845, full-bleed photo
   ============================================================ */
.footer-cta{position:relative;min-height:845px;overflow:hidden;display:flex}
.footer-cta__bg{position:absolute;inset:0;z-index:0}
.footer-cta__bg img{width:100%;height:100%;object-fit:cover;display:block}
.footer-cta__overlay{position:absolute;inset:0;z-index:1;mix-blend-mode:multiply;background:linear-gradient(-85.9deg,rgba(118,118,118,0) 31%,rgb(128,144,160) 98%)}
.footer-cta__text{position:relative;z-index:2;width:min(100%,calc(548px + max(48px,calc(50vw - var(--maxw)/2 + 48px))));display:flex;flex-direction:column;justify-content:center;gap:24px;padding:180px 80px 180px max(48px,calc(50vw - var(--maxw)/2 + 48px))}
.footer-cta__label{font-weight:600;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--off-white);opacity:.7;margin-bottom:4px}
.footer-cta__title{font-family:var(--serif);font-weight:400;font-size:60px;line-height:66px;color:var(--off-white)}
.footer-cta__body{font-family:var(--sans);font-weight:500;font-size:18px;line-height:28px;color:var(--off-white)}
.footer-cta__actions{display:flex;flex-direction:column;gap:16px;align-items:flex-start}

/* ============================================================
   FOOTER  (Figma 322:2201) — charcoal
   ============================================================ */
.footer{background:var(--charcoal)}
.footer__inner{max-width:var(--maxw);margin:0 auto;padding:80px 48px;display:flex;flex-direction:column;gap:56px;container-type:inline-size}
/* Round 22 item 9: the heading is sized from the CONTAINER, not the viewport,
   so its proportion holds at every width instead of being capped by a
   viewport clamp. Round 25b: the client asked for it bigger still — filling
   the full width of the content container rather than the 94% the Figma
   frame measures (1265px of line in a 1344px box, which is where 8.185cqi
   came from). Measured across 1920/1600/1440/1280, the line sat at exactly
   94.0% at every width, and 8.705cqi is the factor that would make it 100%.
   8.64cqi lands it at ~99.3% — visually flush with the container edges, with
   just under 10px of slack at 1440 so sub-pixel font-metric variance can
   never tip the line into a wrap or a clip. Still one line, still the Figma
   spacing between "Your life.", "Beautifully" and "built.", and still a
   fraction OF the container, so it cannot outgrow it. clamp() keeps mobile at
   44px; the upper bound is inert on supported widths (the container tops out
   at 1504, i.e. ~130px) and exists only as a backstop. */
.footer__brand-title{font-family:var(--serif);font-weight:400;font-size:clamp(44px,8.64cqi,138px);line-height:1.1;color:var(--warm-sand)}
.footer__brand-title em{font-style:italic}
.footer__brand-text{font-size:16px;line-height:1.4;letter-spacing:0.32px;color:var(--cream);margin-top:24px;max-width:960px}
.footer__contact{display:flex;gap:48px;align-items:center;flex-wrap:wrap}
/* Round 25b: the Get in Touch pill now carries .btn--outline-light in the
   markup instead of this bespoke rule. The bespoke version set the border,
   colour and transparent background but never set --sweep, so .btn::before
   painted with `transparent` and the circular fill — the "grow" half of the
   reference interaction — did nothing on this one button. The lift, the arrow
   advance and the easing were already reference-exact site-wide; adopting the
   shared variant restores the fill and keeps this button identical to every
   other CTA. Nothing bespoke is needed here now. */
.footer__contact-item{display:flex;gap:12px;align-items:center;color:var(--cream)}
.footer__contact-item .lbl{font-weight:600;font-size:14px;letter-spacing:1.68px;text-transform:uppercase}
.footer__contact-item .val{font-size:16px;letter-spacing:0.32px}
.footer__nav{display:flex;gap:32px}
.footer__col{flex:1;min-width:0;display:flex;flex-direction:column;gap:12px}
.footer__col h4{font-weight:600;font-size:14px;letter-spacing:1.68px;text-transform:uppercase;color:var(--cream)}
.footer__col ul{display:flex;flex-direction:column;gap:8px}
.footer__col a{font-size:16px;line-height:1.4;letter-spacing:0.32px;color:var(--cream);transition:opacity .2s}
.footer__col a:hover{opacity:.7}
.footer__midrow{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;flex-wrap:wrap}
.footer__news h4{font-weight:600;font-size:14px;letter-spacing:1.68px;text-transform:uppercase;color:var(--cream);margin-bottom:12px}
.footer__news-row{display:flex}
.footer__news-row input{width:245px;max-width:100%;border:none;background:var(--cream);border-radius:4px 0 0 4px;padding:12px 16px;font-family:var(--sans);font-size:16px;color:var(--charcoal)}
.footer__news-row input::placeholder{color:var(--mid-grey)}
.footer__news-row button{display:inline-flex;align-items:center;gap:8px;background:#fff;color:var(--charcoal);border:none;border-radius:0 4px 4px 0;padding:12px 16px 12px 20px;font-family:var(--sans);font-weight:600;font-size:12px;letter-spacing:1.6px;text-transform:uppercase;cursor:pointer}
.footer__news-row button svg{width:16px;height:16px}
/* round 19: icons use the exact Figma glyphs (Social Links 329:1706) — all
   three are a true 20px tall on one baseline, widths follow each glyph. */
.footer__social{display:flex;gap:16px;align-items:center}
.footer__social a{color:var(--cream);display:flex;align-items:center}
.footer__social svg{height:20px;width:auto;display:block}
.footer__crumbs{border-top:1px solid rgba(255,255,255,.2);padding-top:12px;display:flex;gap:10px;align-items:center}
.footer__crumbs span{font-weight:600;font-size:14px;letter-spacing:1.68px;text-transform:uppercase;color:var(--cream)}
.footer__crumbs svg{width:16px;height:16px;color:var(--cream)}
.footer__bottom{display:flex;flex-direction:column;gap:12px;font-size:14px;letter-spacing:0.28px;color:var(--cream)}
.footer__bottom .links{display:flex;align-items:center;justify-content:space-between}
.footer__bottom .links .left{display:flex;gap:16px}
.footer__bottom a{color:var(--cream)}

/* ============================================================
   TAGLINE  (Figma 305:1505) — decorative amber statement
   ============================================================ */
.tagline{background:var(--off-white)}
.tagline__inner{max-width:var(--maxw);margin:0 auto;position:relative;height:230px}
.tagline__text{position:absolute;left:630px;top:58px;margin:0;font-family:var(--serif);font-style:italic;font-weight:400;font-size:101.68px;line-height:114.39px;color:var(--amber);white-space:nowrap}

/* ============================================================
   RESPONSIVE — scale desktop down; mobile layout added in next slice
   (Home • Mobile Figma frame)
   ============================================================ */
/* ---------- Tablet & down: relax fixed/absolute desktop layouts ---------- */
@media (max-width:1439px){
  .nav__links{gap:28px}
  .founder__text{padding:0 64px}
  .tagline__text{left:auto;right:48px}
}
@media (max-width:1200px){
  /* Desktop nav persists to 1024 (agreed tablet rule): compact the bar so
     logo + 6 links + actions fit without collision. */
  .nav__inner{padding-left:32px;padding-right:32px}
  .nav__logo img{height:40px}
  .nav__links{gap:16px}
  .nav__actions{gap:12px}
}

/* ---------- Hero, stacked layout (Figma Home • Mobile) ----------
   Applies below the desktop-nav breakpoint: cream page, charcoal copy,
   buttons overlapping the photo edge, charcoal recognition band below,
   floating chat bubble. Tablet (769–1023) refines the same structure. */
@media (max-width:1023px){
  .hero{height:auto;min-height:0;display:flex;flex-direction:column;background:var(--cream)}
  .hero__overlay{display:none}
  .hero::before{display:none}
  .hero__inner,.hero__meta{display:contents}
  /* Top padding = fixed mobile bar (~63px) + 65px gap to the title (Figma). */
  .hero__copy{position:static;order:1;width:auto;padding:128px 24px 0;gap:0}
  .hero__headline{gap:12px}
  .hero__title{font-size:36px;line-height:1.08;letter-spacing:-.36px;color:var(--charcoal);text-shadow:none}
  .hero__sub{font-family:var(--sans);font-weight:500;font-size:18px;line-height:1.4;color:var(--charcoal);text-shadow:none}
  /* Buttons straddle the photo's top edge (Figma: 21px above, 71px over). */
  .hero__actions{position:relative;z-index:2;flex-direction:column;align-items:flex-start;gap:12px;margin:23px 0 -71px;padding:0 24px}
  .hero__actions .btn--outline-light{background-color:var(--cream);border-color:transparent;color:var(--charcoal);--sweep:var(--warm-sand);box-shadow:0 1px 2px rgba(0,0,0,.15)}
  .hero__bg{position:static;order:2;width:100%;height:auto}
  /* Ratio lives on the replaced element — a ratio on the wrapper gets beaten
     by the img's content-based minimum height once the real photo loads. */
  .hero__bg img,.hero__video{width:100%;height:auto;aspect-ratio:402/393;object-fit:cover;object-position:center 30%;display:block}
  /* Recognition — full-width charcoal band (Figma 1026:26794). */
  .hero__recognition{position:static;order:3;width:100%;background:var(--charcoal);padding:24px;align-items:flex-start;text-align:left;gap:6px;animation:none}
  .hero__recognition .rec-title{font-size:12px;letter-spacing:1.44px;color:var(--off-white)}
  .hero__recognition .rec-rule{width:75px}
  .hero__recognition .rec-desc{font-size:14px;line-height:1.4;letter-spacing:.28px;font-weight:700;min-height:0;max-width:22ch}
  /* Rotating awards in the band: keep a one-line clip frame (absolute children
     collapse height otherwise) and anchor left to match the band's alignment. */
  .hero__recognition .rec-desc.is-multi{min-height:20px;max-width:none;width:100%}
  .rec-desc.is-multi .rec-award{left:0;right:auto;max-width:100%;overflow:hidden;text-overflow:ellipsis}
  /* Chat bubble floats like the real widget will (demo behaviour). */
  .hero__livechat{position:fixed;top:auto;right:24px;bottom:24px;z-index:40}

  /* ---------- Ideas & Plans, stacked (Figma Home•Mobile 1026:26813/26789) ----------
     Order flips: Ideas (style) first on cream with the materials square bleeding
     off the right edge, then Plans on an off-white band with the small plan
     drawing floating right. Mobile uses the shorter Figma copy (.ideas__m). */
  .ideas{display:flex;flex-direction:column;align-items:stretch;max-width:100%;min-height:0;overflow:hidden}
  .ideas__d{display:none}
  .ideas__m{display:inline}
  .ideas__col--plans::before{display:none}
  .ideas__col--style{order:1;background:var(--cream);min-height:348px}
  .ideas__col--plans{order:2;background:var(--off-white)}
  /* Ideas: narrow text column beside the 296px materials square (Figma x140,
     bleeding 34px past the right edge, top-aligned with the block). */
  .ideas__col--style .ideas__media{position:absolute;top:0;right:-34px;bottom:auto;left:auto;align-self:auto;margin:0;width:296px;height:296px}
  /* Mobile shows the square as a crisp photo card — no edge fade. */
  .ideas__col--style .ideas__media img{width:100%;height:100%;object-fit:cover;border-radius:4px;-webkit-mask-image:none;mask-image:none}
  .ideas__col--style .ideas__text{margin:0;padding:64px 24px;width:204px;box-sizing:border-box}
  /* Plans: off-white band, small plan drawing at (193,49), 190×224. */
  .ideas__col--plans .ideas__media{display:none}
  .ideas__plan-m{display:block;position:absolute;top:49px;right:19px;width:190px;height:224px;z-index:1;mix-blend-mode:multiply}
  .ideas__col--plans .ideas__text{padding:96px 24px;width:auto}
  .ideas__text{gap:15px}
  .ideas__text .h1{font-size:36px;line-height:1.17;letter-spacing:0}
  .ideas__text p{font-size:16px;line-height:1.5;max-width:135px}
}
/* Tablet refinements (769–1023): same structure, more generous scale. */
@media (min-width:769px) and (max-width:1023px){
  .hero__copy{padding:160px 48px 0}
  .hero__title{font-size:56px}
  .hero__sub{font-size:20px}
  .hero__actions{flex-direction:row;align-items:center;margin:32px 0 -20px;padding:0 48px}
  .hero__bg img,.hero__video{aspect-ratio:16/9}
  .hero__recognition{padding:24px 48px}
  /* Ideas & Plans: same stacked structure, 48px gutters. */
  .ideas__col--style .ideas__text{padding:64px 48px;width:280px}
  .ideas__col--plans .ideas__text{padding:96px 48px}
  .ideas__plan-m{right:48px}
  /* Our Story at tablet (client round 25, found while checking item 7 across
     breakpoints). Between 769 and 1023 the section still uses the two-column
     desktop layout, but the right-hand column could not shrink into the space
     left by the 52% image: a flex item's default min-width:auto holds it at
     its content's minimum, and the signature line is white-space:nowrap, so
     the column stayed wide and pushed the whole PAGE 130px sideways at 820.
     Letting the column shrink, trimming its gutters to the 48px used
     elsewhere at this width, and allowing the signature to wrap keeps the
     composition intact and the overflow gone. Desktop and mobile are
     untouched — this block only ever applies in the tablet band. */
  .founder__text{min-width:0;padding:0 48px}
  .founder__sign{white-space:normal}
  /* Guide Book at tablet (same round-25 breakpoint sweep). The media column is
     flex:0 0 auto around a hard 526px image plus 144px of padding, so it took
     670 of the 820 available and left the text column 150px — narrower than
     its own 128px of padding, which pushed the CTA (white-space:nowrap) clean
     off the page. Letting the image scale within the column and trimming both
     gutters to the 48px used elsewhere at this width fixes it without
     touching the desktop composition. */
  .feature__media{flex:0 1 auto;min-width:0;padding:64px 32px 64px 48px}
  .feature__media img{width:100%;max-width:360px;height:auto;aspect-ratio:526/679}
  .feature__text{padding:64px 48px 64px 0}
}

/* ---------- Mobile (Home • Mobile) ---------- */
@media (max-width:768px){
  :root{--global-padding:24px}

  /* BUILD OPTIONS */
  .build{min-height:0}
  .build__inner{padding:64px 0 0}
  .build__head{flex-direction:column;align-items:flex-start;padding:0 24px}
  .build__intro .h1{font-size:36px}
  .build__cards{padding:40px 0 0;margin:0 24px;gap:16px}
  .build__card{flex:0 0 100%;height:auto}
  .build__card img{flex:none;height:340px;max-height:400px}
  .build__card-reveal{grid-template-rows:1fr;opacity:1;margin-top:14px;transition:none}
  .build__card-reveal-in{overflow:visible}
  .build__trackrow{margin:40px 24px 0}

  /* FOUNDER */
  .founder__inner{flex-direction:column;height:auto}
  .founder__media{width:100%;flex:none;height:320px}
  .founder__media::after{background:none}
  .founder__text{padding:48px 24px}
  .founder__title{font-size:36px;line-height:1.17}

  /* REVIEWS */
  .reviews{min-height:0}
  .reviews__overlay{background:linear-gradient(180deg,rgba(66,64,64,.72),rgba(66,64,64,.85))}
  .reviews__inner{padding:48px 24px}
  .reviews__col,.review-card{width:100%}

  /* TAGLINE */
  .tagline__inner{height:auto;padding:48px 24px}
  .tagline__text{position:static;font-size:44px;line-height:50px;white-space:normal}

  /* FEATURE / CHECKLIST */
  .feature{flex-direction:column;min-height:0}
  .feature__media{padding:32px 24px 0}
  .feature__media img{width:100%;height:auto;max-height:420px}
  .feature__text{padding:32px 24px 48px}
  .feature__title{font-size:36px}
  .feature__list li{font-size:16px}

  /* IMAGE SECTION */
  .image-section{flex-direction:column;min-height:0}
  .image-section__text{padding:48px 24px}
  .image-section__title{font-size:36px}
  .image-section__media{width:100%;height:360px}
  .image-section__actions{flex-direction:column;align-items:stretch}

  /* SECTION IMAGE */
  .section-image{min-height:0}
  .section-image__text{width:auto;padding:64px 24px;gap:16px}
  .section-image__title{font-size:36px}
  .section-image__mb{left:auto;right:16px;width:56px;height:77px}

  /* LOCAL OFFICE */
  .local{flex-direction:column;min-height:0}
  .local__map{flex:none;width:100%;height:300px}
  .local__panel{padding:48px 24px}
  .local__title{font-size:36px}

  /* SITE CHECK */
  .site-check{min-height:0}
  .site-check__text{width:auto;padding:64px 24px;gap:16px}
  .site-check__title{font-size:36px}

  /* PROCESS */
  .process{padding:64px 0}
  .process__header h2{font-size:36px}
  .process__steps{flex-direction:column;padding:48px 24px 0;gap:48px}
  .step__num{font-size:120px;margin-top:0}
  .step__num::before{display:none}
  .step__body{gap:20px}
  .step__cta{margin-bottom:0}
  .step__title{font-size:28px}

  /* BLOG */
  .blog{background:var(--slate-green)}
  .blog__band{display:none}
  .blog__inner{flex-direction:column;align-items:stretch}
  .blog__media{height:300px;width:100%}
  .blog__feature{padding:48px 24px}
  .blog__title{font-size:36px}
  .blog__cards{padding:0 24px 48px;min-width:0}
  .blog__right{min-width:0}
  .blog__slider{max-width:100%}
  .blog__track .blog-card{flex:0 0 100%;width:100%;min-width:0}
  .blog-card{flex-direction:column}
  .blog-card__text{min-width:0}
  .blog-card__ct,.blog-card__cd{overflow-wrap:break-word}
  .blog-card__img .frame{height:200px}

  /* FAQ */
  .faq{min-height:0}
  .faq__bg img{opacity:.2}
  .faq__inner{width:auto;padding:48px 24px}
  .faq__title{font-size:36px}
  .faq__list{width:100%}

  /* FOOTER CTA */
  .footer-cta{min-height:0}
  .footer-cta__text{width:auto;padding:64px 24px}
  .footer-cta__title{font-size:40px;line-height:44px}
  .footer-cta__actions{align-items:stretch}

  /* FOOTER */
  .footer__inner{padding:48px 24px;gap:40px}
  .footer__brand-title{font-size:44px;line-height:1.1}
  .footer__contact{flex-direction:column;align-items:flex-start;gap:20px}
  .footer__nav{flex-wrap:wrap;gap:32px 24px}
  .footer__col{flex:0 0 42%}
  .footer__midrow{flex-direction:column;align-items:stretch;gap:24px}
  .footer__news{width:100%}
  .footer__news-row{width:100%}
  .footer__news-row input{width:auto;flex:1;min-width:0}
  .footer__news-row button{flex:0 0 auto;white-space:nowrap}
  .footer__bottom .links{flex-direction:column;gap:12px;align-items:flex-start}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
  html{scroll-behavior:auto}
}


/* ============================================================
   MEGA MENUS — ported from the client reference demo
   (sentinel-demo-423.netlify.app/desktop-nav.html):
   soft scrim (z below), one full-height cream sheet docked to the
   right edge, left edge anchored to the open item; sliding/resizing
   between menus; header content floats above the sheet.
   ============================================================ */
.nav__scrim{position:fixed;inset:0;background:rgba(45,44,44,.55);opacity:0;pointer-events:none;
  transition:opacity .6s cubic-bezier(.4,0,.2,1);z-index:0}
.nav.panel-open .nav__scrim{opacity:1;pointer-events:auto}
.nav__inner{position:relative;z-index:3}

.nav__sheet{
  position:fixed;top:0;bottom:0;right:0;left:auto;z-index:2;background:var(--cream);
  overflow:hidden; /* panels scroll individually (demo .menu-content) */
  transform:translateX(105%);visibility:hidden;
  transition:transform .85s cubic-bezier(.16,1,.3,1),left .7s cubic-bezier(.16,1,.3,1),visibility 0s .85s;
  will-change:transform,left;
}
.nav__sheet.is-open{transform:none;visibility:visible;transition:transform .85s cubic-bezier(.16,1,.3,1),left .7s cubic-bezier(.16,1,.3,1),visibility 0s 0s}

/* Panels: absolutely stacked inside the sheet (demo .menu-content). Menus
   cross-fade with a 6px rise while the sheet slides between anchors — the
   outgoing menu is never cut off abruptly. */
.nav__panel{position:absolute;inset:0;overflow-y:auto;overscroll-behavior:contain;
  padding:140px 40px 48px;
  opacity:0;transform:translateY(6px);visibility:hidden;pointer-events:none;
  transition:opacity .5s cubic-bezier(.4,0,.2,1),transform .55s cubic-bezier(.16,1,.3,1),visibility 0s .55s}
.nav__panel.is-current{opacity:1;transform:none;visibility:visible;pointer-events:auto;
  transition:opacity .5s cubic-bezier(.4,0,.2,1),transform .55s cubic-bezier(.16,1,.3,1),visibility 0s 0s}
.nav__panel--plans{display:flex;align-items:flex-start;gap:56px}
.nav__panel--plans .nav__panel-body{flex:0 0 auto;min-width:240px}

/* Row entrance stagger (demo itemIn: rows slide 28px in from the right,
   55ms curve, 35ms/row + 60ms head start). JS restarts the animation each
   time a panel becomes current, so every open/switch re-plays it. */
.nav__panel-list li,
.np-office-block>*,
.np-office-list__title,
.np-regions li{
  opacity:0;
  animation:sh-item-in .75s cubic-bezier(.16,1,.3,1) both;
  animation-delay:calc(var(--i,0)*45ms + 120ms);
}
@keyframes sh-item-in{from{opacity:0;transform:translateX(28px)}to{opacity:1;transform:none}}

.nav__panel-list{display:flex;flex-direction:column;gap:24px;align-items:flex-start}
.nav__panel-list>li{display:flex} /* row height = content height (demo line boxes) */
/* 18px rows (demo .heading-link): mid-grey -> charcoal, hidden arrow slides in */
.np-item a{display:inline-flex;align-items:center;gap:8px;font-weight:500;font-size:18px;line-height:1.4;color:var(--mid-grey);transition:color .25s cubic-bezier(.4,0,.2,1)}
.np-item a:hover,.np-item a:focus-visible{color:var(--charcoal)}
.np-item .np-arrow{width:20px;height:20px;flex:0 0 20px;opacity:0;transform:translateX(-8px);
  transition:opacity .25s cubic-bezier(.4,0,.2,1),transform .35s cubic-bezier(.16,1,.3,1)}
.np-item a:hover .np-arrow,.np-item a:focus-visible .np-arrow{opacity:1;transform:none}
/* 14px rows (demo .menu-sublist): charcoal -> sage, 4px nudge */
.np-sub{margin-top:-12px}
.np-sub + .np-sub{margin-top:-16px}
.np-sub a,.np-sub button{font-size:14px;line-height:1.5;font-weight:400;color:var(--charcoal);background:none;border:0;padding:0;cursor:pointer;font-family:var(--sans);
  transition:color .2s cubic-bezier(.4,0,.2,1),padding-left .25s cubic-bezier(.16,1,.3,1)}
.np-sub a:hover,.np-sub button:hover,.np-sub a:focus-visible,.np-sub button:focus-visible{color:var(--sage-dark);padding-left:4px}
/* Icon rows (demo .plan-list): 14px/400, 21px icons, same hover nudge */
.np-icon a{display:inline-flex;align-items:center;gap:10px;font-size:14px;line-height:1.5;font-weight:400;color:var(--charcoal);
  transition:color .2s cubic-bezier(.4,0,.2,1),padding-left .25s cubic-bezier(.16,1,.3,1)}
.np-icon a:hover,.np-icon a:focus-visible{color:var(--sage-dark);padding-left:4px}
.np-icon svg{width:21px;height:21px;flex:0 0 21px}
.np-icon{margin-top:-4px}
.np-icon + .np-icon{margin-top:-14px}
.np-divider{width:100%;max-width:288px;height:2px;background:var(--warm-sand)}
/* Section label (demo .menu-label: plain small-caps text, not a chip) */
.np-tag span{font-weight:600;font-size:12px;line-height:1.2;letter-spacing:1.6px;text-transform:uppercase;color:var(--charcoal)}

/* Good-to-know circle — pops in 1s after the sheet (demo noteIn: scale .8,
   .62s fluid curve; Figma comment #94). Restarted with the row stagger. */
.np-circle{
  flex:0 0 auto;width:385px;max-width:40vw;aspect-ratio:1/1;border-radius:50%;margin-top:60px;
  background:var(--off-white);display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 54px;text-align:center;
  opacity:0;transform-origin:center;
  animation:sh-note-in 1.05s cubic-bezier(.16,1,.3,1) 1.15s both;
}
@keyframes sh-note-in{from{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}
.np-circle__tag{display:inline-block;background:var(--warm-sand);padding:6px 12px;margin-bottom:18px;font-weight:700;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--charcoal)}
.np-circle__head{font-size:19px;line-height:1.35;font-weight:400;color:var(--charcoal);margin:0 0 18px}
.np-circle__body{font-size:13px;line-height:1.5;color:var(--mid-grey);margin:0}

/* ---- Contact panel (demo .contact-menu + client-specified CHANGE toggle) --
   Initial state: full white popup, current office only, right-aligned.
   CHANGE reveals the cream "Find your local office" section below. */
.nav__panel--contact{padding:0;background:var(--off-white);display:flex;flex-direction:column;text-align:right;overflow-x:hidden}
.np-office-block{background:var(--off-white);padding:144px 48px 30px}
.np-office-eyebrow{display:flex;justify-content:flex-end;align-items:center;gap:6px;font-weight:600;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--charcoal);margin:0 0 10px}
.np-office-change{display:inline-flex;align-items:center;gap:6px;background:none;border:0;padding:0;font-family:var(--sans);font-weight:600;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--charcoal);cursor:pointer;transition:color .2s cubic-bezier(.4,0,.2,1)}
.np-office-change:hover,.np-office-change:focus-visible{color:var(--sage-dark)}
.np-office-change .np-chev{width:9px;height:6px;flex:0 0 auto;transition:transform .3s cubic-bezier(.16,1,.3,1)}
.nav__panel--contact.is-picking .np-office-change .np-chev{transform:rotate(180deg)}
.np-office-name{font-size:18px;font-weight:500;line-height:1.15;color:var(--charcoal);margin:0 0 4px}
.np-office-addr{font-size:14px;line-height:1.4;color:var(--mid-grey);margin:0}
.np-office-hours{font-size:14px;line-height:1.4;color:var(--mid-grey);margin:0 0 16px}
.np-office-meet-row{margin:0}
/* Client feedback, 25 Aug item 1: "Meet the team" now uses the button-studies
   hover — the underline fills from the left and the arrow advances, on the
   reference's own durations and curves. The old effect widened the gap, which
   moved the arrow's resting position; the brief asks for the animation only,
   so size, type, colour and spacing are all untouched. The amber rule stays
   exactly as it was at rest — the sweep paints over it. */
/* Feedback 31 Aug item 1: the hover was already the reference sweep but it was
   INVISIBLE - the rest border was var(--amber) and the ::after sweep is amber
   too, so gold filled over gold and nothing appeared to happen. The reference
   (button-studies .btn--underline) puts a faint var(--underline-base) rule
   underneath and sweeps var(--amber) over it; that base colour is the one
   change the brief authorises ("only update the hover animation/effect and
   button bottom border color"). The sweep is also brought to the same 1px
   weight as the rest rule and as every other .link-btn on the page, so the
   underline fills rather than thickening from 1px to 2px mid-hover. Size,
   typography, padding and position are untouched. */
.np-office-meet{position:relative;display:inline-flex;align-items:center;gap:8px;font-weight:600;font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--charcoal);padding-bottom:6px;border-bottom:1px solid var(--underline-base);transition:color .45s var(--ease-soft)}
.np-office-meet::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;background:var(--amber);
  transform:scaleX(0);transform-origin:left center;transition:transform .5s var(--ease-fluid)}
.np-office-meet:hover::after,.np-office-meet:focus-visible::after{transform:scaleX(1)}
.np-office-meet .np-arrow{width:16px;height:16px;flex:0 0 16px;transition:transform .45s var(--ease-fluid)}
.np-office-meet:hover .np-arrow,.np-office-meet:focus-visible .np-arrow{transform:translateX(4px)}
.np-office-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-top:22px}
/* Client feedback, 25 Aug item 1: the Email and Phone pills now carry the
   button-studies interaction 1:1, same as every .btn on the page — a 2px lift
   on the spring curve, the circular fill sweeping from the left, the layered
   lift shadow, and the quick press. Cream pill, so the sweep is warm sand,
   matching .btn--primary. Geometry, type and colour at rest are unchanged. */
.np-pill{position:relative;isolation:isolate;overflow:hidden;
  display:inline-flex;align-items:center;gap:6px;white-space:nowrap;background:var(--cream);--sweep:var(--warm-sand);box-shadow:0 1px 2px rgba(0,0,0,.15);border-radius:999px;padding:10px 13px;font-weight:600;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--charcoal);
  transition:transform .45s var(--ease-spring),box-shadow .55s var(--ease-fluid),color .45s var(--ease-soft)}
.np-pill::before{content:"";position:absolute;top:50%;left:-10%;width:250%;aspect-ratio:1;border-radius:50%;
  background:var(--sweep,transparent);transform:translate(-50%,-50%) scale(0);
  transition:transform .55s var(--ease-fluid);z-index:-1}
.np-pill:hover::before,.np-pill:focus-visible::before{transform:translate(-50%,-50%) scale(1)}
.np-pill:hover,.np-pill:focus-visible{transform:translateY(-2px);box-shadow:var(--lift-shadow)}
.np-pill:active{transform:translateY(0) scale(.95);transition-duration:.11s}
.np-pill svg{width:14px;height:12px;flex:0 0 auto}
/* Collapsible office list: 0fr -> 1fr grid rows, transparent -> cream */
.np-office-list{flex:1 1 auto;display:grid;grid-template-rows:0fr;background:transparent;
  transition:grid-template-rows .5s cubic-bezier(.16,1,.3,1),background-color .4s cubic-bezier(.4,0,.2,1)}
.nav__panel--contact.is-picking .np-office-list{grid-template-rows:1fr;background:var(--cream)}
.np-office-list__inner{overflow:hidden;min-height:0;visibility:hidden;padding:0 48px;
  transition:visibility 0s .5s,padding .5s cubic-bezier(.16,1,.3,1)}
.nav__panel--contact.is-picking .np-office-list__inner{visibility:visible;padding:28px 48px 48px;transition:visibility 0s 0s,padding .5s cubic-bezier(.16,1,.3,1)}
.np-office-list__title{font-size:18px;font-weight:500;line-height:1.15;color:var(--mid-grey);margin:0 0 16px}
.np-regions{display:flex;flex-direction:column;align-items:flex-end;gap:8px;margin:0;padding:0;list-style:none}
.np-regions li{display:flex;justify-content:flex-end}
.np-regions button{background:none;border:0;padding:0;font-family:var(--sans);font-size:14px;line-height:1.5;font-weight:400;color:var(--charcoal);cursor:pointer;
  transition:color .2s cubic-bezier(.4,0,.2,1),padding-right .25s cubic-bezier(.16,1,.3,1)}
.np-regions button:hover,.np-regions button:focus-visible{color:var(--sage-dark);padding-right:4px}

/* Reduced motion: keep the menus usable, drop the theatrics */
@media (prefers-reduced-motion:reduce){
  .nav__sheet,.nav__sheet.is-open{transition:none}
  .nav__panel,.nav__panel.is-current{transition:none}
  .nav__scrim,.np-office-list{transition:none}
  .nav__panel-list li,.np-office-block>*,.np-office-list__title,.np-regions li,.np-circle{animation-duration:.01ms;animation-delay:0ms}
  /* the fill still appears (durations collapse globally); movement does not */
  .np-pill:hover,.np-pill:focus-visible,.np-pill:active{transform:none}
  .np-office-meet:hover .np-arrow,.np-office-meet:focus-visible .np-arrow{transform:none}
}

/* ============================================================
   MOBILE NAVIGATION  (<1024px; Figma Mobile Nav 1101:71207)
   ============================================================ */
@media (max-width:1023px){
  /* Solid mobile chrome bar (Figma Mobile Page Chrome 126:789) */
  .nav::before{background:var(--cream)}
  .nav__inner{padding:16px 24px}
  .nav__logo img{height:31px;filter:brightness(0) invert(.26)}
  .nav__links,.nav__contact,.nav__save{display:none}
  .nav__sheet,.nav__scrim,.nav__underline{display:none!important}
  .nav__toggle{display:flex;align-items:center;justify-content:center;width:40px;height:40px;color:var(--sage);background:none;border:none;cursor:pointer}
  .nav__toggle svg{width:26px;height:26px}
  .nav.scrolled .nav__inner{padding-top:12px;padding-bottom:12px}
  .nav.scrolled .nav__logo img{height:28px}
}
@media (min-width:1024px){
  .mnav{display:none!important}
}

.mnav{
  position:fixed;inset:0;z-index:80;color:var(--off-white);
  /* Figma Mobile Nav (1101:70985): sage ground + leafy texture (multiply,
     32%) + a sage→transparent wash under the header. */
  background:linear-gradient(180deg,var(--sage) 0%,rgba(122,145,127,0) 163px),var(--sage);
  display:flex;flex-direction:column;
  opacity:0;transition:opacity .3s ease;
}
.mnav::before{
  content:"";position:absolute;inset:0;background:url("home/leafy-overlay.png") center/cover no-repeat;
  mix-blend-mode:multiply;opacity:.32;pointer-events:none;
}
.mnav>*{position:relative;z-index:1}
.mnav[hidden]{display:none}
.mnav.is-open{opacity:1}
html.mnav-lock,body.mnav-lock{overflow:hidden}

.mnav__chrome{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;flex:0 0 auto}
.mnav__logo img{height:31px;width:auto;display:block}
.mnav__close{background:none;border:0;color:var(--off-white);width:40px;height:40px;display:flex;align-items:center;justify-content:center;cursor:pointer}
.mnav__close svg{width:22px;height:22px}

.mnav__screens{position:relative;flex:1;overflow:hidden}
.mnav__screen{
  position:absolute;inset:0;padding:16px 24px 24px;overflow-y:auto;display:flex;flex-direction:column;
  transform:translateX(100%);visibility:hidden;
  transition:transform .35s cubic-bezier(.16,1,.3,1),visibility 0s .35s;
}
.mnav__screen.is-active{transform:none;visibility:visible;transition:transform .35s cubic-bezier(.16,1,.3,1),visibility 0s 0s}
.mnav__screen.is-left{transform:translateX(-24%);visibility:hidden;transition:transform .35s cubic-bezier(.16,1,.3,1),visibility 0s .35s}

.mnav__list{display:flex;flex-direction:column;gap:24px;align-items:stretch}
.mnav__row a,.mnav__row button{
  display:flex;align-items:center;justify-content:space-between;width:100%;text-align:left;
  background:none;border:0;padding:0;cursor:pointer;color:var(--off-white);
  font-family:var(--sans);font-weight:500;font-size:18px;line-height:1.3;
}
.mnav__row svg{width:8px;height:12px;flex:0 0 8px}
.mnav__row--sub a{font-size:15px;font-weight:400;color:rgba(253,251,249,.9)}
.mnav__row--icon a{justify-content:flex-start;gap:10px;font-size:15px}
.mnav__row--icon svg{width:20px;height:20px;flex:0 0 20px}
.mnav__divider{height:2px;background:rgba(253,251,249,.28);width:100%}
.mnav__tag span{display:inline-flex;background:var(--off-white);border-radius:4px;padding:4px 8px;font-weight:600;font-size:11px;letter-spacing:1.32px;text-transform:uppercase;color:var(--charcoal)}
.mnav__tag--crumb{margin:12px 0 20px}
.mnav__back{
  display:inline-flex;align-items:center;gap:8px;background:none;border:0;padding:0;cursor:pointer;
  color:var(--off-white);font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:1.32px;text-transform:uppercase;
}
.mnav__back svg{width:8px;height:12px}

/* Drawer footer row (Figma Mobile Nav / Overlay Footer): SAVED ITEMS pill on
   the left, round live-chat launcher on the right, bottom-aligned. */
.mnav__foot{margin-top:auto;padding:32px 0 25px;display:flex;align-items:flex-end;justify-content:space-between;gap:16px}
.mnav__saved{
  display:inline-flex;align-items:center;gap:8px;border:1px solid var(--white);border-radius:999px;
  padding:12px 20px;font-weight:600;font-size:12px;line-height:16px;letter-spacing:1.6px;text-transform:uppercase;color:var(--white);
  transition:background-color .3s var(--ease-soft);
}
.mnav__saved svg{width:16px;height:14px;flex:0 0 16px}
.mnav__saved-count{background:var(--amber);color:var(--off-white);border-radius:999px;min-width:16px;padding:2px 5px;font-size:10px;line-height:1.2;text-align:center}
/* Live chat launcher inside the drawer (Figma: 40px slate-green circle,
   20×16 chat glyph — the mobile menu's missing chat icon). */
.mnav__chat{
  width:40px;height:40px;border-radius:50%;background:var(--slate-green);border:0;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;color:var(--off-white);flex:0 0 40px;
  transition:transform .25s var(--ease-fluid);
}
.mnav__chat svg{width:20px;height:16px}
.mnav__chat:active{transform:scale(.92)}

/* Office contact block (Figma Mobile Nav / Office Contact — the mobile
   menu's "Contact Us" section) on a full-width slate-green band. */
.mnav__office{background:var(--slate-green);margin:0 -24px -24px;padding:24px 24px 24px;display:flex;flex-direction:column;gap:0}
.mnav__office-label{display:flex;align-items:center;gap:8px;font-weight:600;font-size:12px;line-height:1.2;letter-spacing:1.44px;text-transform:uppercase;color:var(--white);margin-bottom:15px}
.mnav__office-label button{display:inline-flex;align-items:center;gap:4px;background:var(--sage);border:0;border-radius:4px;padding:4px 4px 4px 8px;color:var(--off-white);font-family:var(--sans);font-weight:600;font-size:12px;line-height:1.2;letter-spacing:1.44px;text-transform:uppercase;cursor:pointer}
.mnav__office-label button svg{width:7px;height:10px}
.mnav__office-name{font-weight:500;font-size:18px;line-height:1.3;margin-bottom:12px}
.mnav__office-addr{font-size:14px;line-height:1.4;letter-spacing:.28px;color:var(--off-white);margin-bottom:12px}
.mnav__office-hours{display:none} /* Figma Office Contact shows name + address only */
.mnav__office-cta{display:flex;gap:14px;margin-top:0}
.mnav__office-cta .btn{gap:8px}
.mnav__office-cta .btn svg{width:14px;height:12px;flex:0 0 14px} /* same glyph scale as the desktop contact pills */
.mnav__office-cta .btn[data-office-phone]{flex:1 1 auto}

/* ============================================================
   FULL-PAGE SECTION SCROLLING (client comment #62 — gentle
   proximity snapping on desktop; disabled for reduced motion)
   ============================================================ */
/* Client round 18 (items 1+3): CSS scroll-snap REMOVED as the root cause of
   the jerking/stuck feel — proximity snap tugged at the page while wheel
   inertia was still running and while the Section Flow module was driving,
   so the browser and the assists fought over the scroll position. Native
   scrolling is now untouched; the Section Flow module in home.js handles
   section transitions AND a gentle after-idle settle (it only ever acts once
   scrolling has fully stopped, so nothing can fight or trap the user).
   scroll-padding stays: it keeps anchor jumps landing flush under the bar.
   home.js sets --sh-snap-pad = nav offset + the 92px compact bar height. */
html{scroll-padding-top:var(--sh-snap-pad,92px)}

/* Award-line rotation (client comment #41) */
/* Award rotation (client comment #41): a vertical PUSH — the incoming award
   slides in from above while the current one is pushed down and fully out of
   the clipped line frame. Both move together (same duration + easing), no fade,
   so it reads as one connected motion. Site-standard easing. */
.hero__recognition .rec-desc{position:relative;display:block;min-height:20px;overflow:hidden}
/* With absolute children the clip frame would shrink to zero width — hold it open. */
.hero__recognition .rec-desc.is-multi{width:100%}
.rec-award{display:block}
.rec-desc.is-multi .rec-award{position:absolute;right:0;top:0;white-space:nowrap;transform:translateY(-110%);transition:transform .6s cubic-bezier(.16,1,.3,1)}
.rec-desc.is-multi .rec-award.is-current{transform:none}
.rec-desc.is-multi .rec-award.is-leaving{transform:translateY(110%)}
@media (prefers-reduced-motion: reduce){
  .rec-desc.is-multi .rec-award{transition:none}
}

/* ============================================================
   FULL-VIEWPORT BANNERS (client feedback #9 — full-height image
   sections). Desktop only; tablet/mobile keep their existing
   layouts so nothing gets cropped by a forced 100vh. NOTE: the
   sticky "cover" pinning + scroll-linked scrim that briefly
   shipped here was REVERTED (AJ 19-Aug) — every section scrolls
   naturally and its background moves with it.
   ============================================================ */
@media (min-width:1024px){
  .image-section,.site-check,.footer-cta{min-height:100vh;min-height:100svh}
  /* round 21: Build With Confidence keeps the 929px Figma floor even on
     short viewports — below it the sky band gets too shallow for the text.
     Client feedback, 25 Aug item 3: the 100svh half of this floor is gone.
     On a tall window it forced the section past the Figma frame — 1080px on a
     1080px screen — and a min-height always beats a max-height, so the
     ceiling above could never take effect. The 929px Figma floor stays. */
  .section-image{min-height:929px}
  .blog{background:var(--off-white)}
}

/* ============================================================
   SCROLL-DRIVEN SECTIONS (client backend round, items 3/4/6).
   Desktop + motion-safe only: Build Options cards travel
   horizontally, the Beautifully Built testimonial column and the
   Featured Articles step vertically, while each section pins
   inside its own scroll runner (.sh-pin-wrap) and releases when
   its content is exhausted. Tablet/mobile keep their existing
   interactions untouched.
   ============================================================ */
@media (min-width:1024px) and (prefers-reduced-motion: no-preference){
  .sh-pin-wrap{position:relative}
  .sh-pin-wrap>.sh-pinned{position:sticky}

  /* Build Options: the strip is scrubbed by page scroll — no native
     scrollbar, no snap, cards keep their exact design and size.
     Round 20 items 2.3–2.5: the cards move inside a CLIPPED WINDOW whose
     left edge is the content container's own left padding (responsive,
     centred with the 1440 grid) — so the left alignment is identical at
     the beginning, middle and end of the scroll and cards can never touch
     the viewport's left edge. The window's right side extends to the
     viewport edge so the strip keeps its designed right-hand bleed. */
  /* Round 26 item 6: the window clipped the hover shadow 54px short at the
     bottom. Padding grows the clip box 48px above and below while the equal
     negative margins keep the layout byte-identical, so the shadow is fully
     visible and the LEFT clip — which round 20 requires to sit exactly on the
     content edge — is untouched. */
  .build.sh-hscroll .build__window{overflow:hidden;margin-left:48px;margin-right:min(0px,calc((var(--maxw) - 100vw)/2));padding-top:48px;padding-bottom:48px;margin-top:-48px;margin-bottom:-48px}
  .build.sh-hscroll .build__cards{overflow:visible;scroll-snap-type:none;scrollbar-width:auto;margin:0}
  .build.sh-hscroll .build__cards{will-change:transform}

  /* Beautifully Built: fixed visible window, the column travels through it. */
  .reviews.sh-vscroll .reviews__viewport{display:block;overflow:hidden}
  .reviews.sh-vscroll .reviews__col{will-change:transform}
  /* round 18 item 6: the card window runs to the section's bottom edge */
  /* Round 25 item 8.1: no top band either — the testimonial column starts
     flush with the section's top edge so the cards reach it as they rise. */
  .reviews.sh-vscroll .reviews__inner{padding-top:0;padding-bottom:0}

  /* Featured Articles: single-card window, column steps vertically; the
     active article's text + image cross-fade slowly (premium, no flicker). */
  /* Client round 13: the card column GLIDES 1:1 with the scroll (no stepped
     transition — that stepping read as jerking), showing one full card plus a
     partial next card (Figma 310:2136: 250px card, 16px gap, ~105px peek).
     The left hero/text crossfade is quick so nothing feels like a jump. */
  /* round 19: the card window sits flush on the section's bottom edge
     (Figma 310:2097 — the cards region ends exactly at the 929px band) */
  .blog.sh-steps .blog__right{height:929px;padding-bottom:0}
  .blog.sh-steps .blog__cards{margin-top:auto}
  .blog.sh-steps .blog__slider{overflow:hidden}
  .blog.sh-steps .blog__track{flex-direction:column;width:auto;gap:16px;transition:none;will-change:transform}
  .blog.sh-steps .blog__track .blog-card{flex:0 0 auto;width:100%}
  .blog.sh-steps .blog-card--wrap{display:flex}
  /* Round 26 item 11: the left image's opacity is now written every frame
     from the scroll position (see home.js), so it must NOT also be
     transitioned here — a transition would lag behind the scrub and undo the
     whole point of tying the fade to the scroll. */
  .blog.sh-steps [data-article-media] img,
  .blog.sh-steps [data-article-title],
  .blog.sh-steps [data-article-desc],
  .blog.sh-steps [data-article-link]{transition:none;will-change:opacity}
  /* The column cards' opacity is written per frame by home.js from each
     card's position in the window, so it must not also be transitioned here —
     that would lag the scroll. */
  .blog.sh-steps .blog__track .blog-card{will-change:opacity}
}

/* The testimonial viewport is invisible to layout everywhere except the
   pinned desktop mode (JS sizes it there). */
.reviews__viewport{display:contents}

/* ============================================================
   FULL-PAGE SECTION SNAPPING (client comment #62, round 28).

   Third attempt at this feature, and deliberately unlike the two
   that were rejected. Rounds 12 and 23 both used a JS "Section
   Flow" module that listened for wheel events, called
   preventDefault() and animated the page with scrollTo() - so the
   module and the browser were both driving the scroll position at
   once, which is what produced every "jerking / stopping /
   jumping / I have to scroll several times" report. That module is
   gone and is not coming back: nothing here touches the wheel, the
   trackpad, the keyboard or touch, and no JS moves the page.

   This is native CSS scroll-snap and nothing else. The browser
   keeps sole ownership of the scroll; it simply has a set of
   positions it prefers to come to rest on. Round 18 removed CSS
   snap as well, but for a cause that no longer exists - back then
   the proximity snap was fighting the Section Flow module for the
   scroll position. With one party driving instead of two, that
   specific conflict cannot recur.

   Why a marker rail rather than scroll-snap-align on the sections:
   the three scroll-LINKED sections (Build Options, Beautifully
   Built, Featured Articles) are position:sticky inside a tall
   runner, and the page's scroll offset INSIDE that runner is their
   animation timeline. Snap points at section edges only would let
   the browser rest at progress 0 or 1 and nowhere between, which
   would make the Build strip jump between its first and last card
   and the article stepper unusable. So home.js instead lays down a
   rail of 1x1 markers at every position worth resting on: each
   plain section's top, each CARD of the Build strip, each ARTICLE
   of the Featured strip, each testimonial step. The pinned
   sections GAIN card-to-card snapping instead of losing their
   scrub.

   STRENGTH - the one decision left, and it is a real trade-off.
   Measured on this page at 1440x900, where consecutive sections
   sit 808-1056px apart and one mouse-wheel notch travels ~98px:

     mandatory  A gesture must clear ~404px (5+ notches) to reach
                the next marker. Anything shorter is NEAREST to the
                marker it started from, so the browser returns the
                page there - the gesture is undone. A trackpad
                swipe carries momentum and clears it easily; a
                discrete mouse wheel often does not. Undoing a
                small wheel gesture is exactly the "I have to
                scroll several times" report from rounds 18-22.

     proximity  Small gestures are left alone (a 3-notch scroll
                holds where it lands); once a gesture ends NEAR a
                marker the page settles onto it. Never undoes
                anything, so it cannot reproduce that report, but
                it is not an absolute one-pager - a slow reader can
                come to rest between two sections.

   There is no CSS-only third option: making a short wheel notch
   advance a whole section requires intercepting the wheel, which
   is the Section Flow module that rounds 12 and 23 built and the
   client rejected both times. Adding closer markers would only
   trade the problem for resting mid-section.

   Set below, and switchable in one line - `mandatory` for the
   strict one-pager, `proximity` for never fighting the user:
       html{--sh-snap-strength:proximity}

   scroll-snap-stop stays `normal` either way, so a fast flick can
   still cross several sections in one gesture - the brief asks for
   elegant, not restrictive.
   ============================================================ */
@media (min-width:1024px) and (prefers-reduced-motion: no-preference){
  html.sh-snap{scroll-snap-type:y var(--sh-snap-strength,mandatory)}
  /* 1px wide, transparent, non-interactive. It must stay VISIBLE -
     visibility:hidden or display:none would disqualify the markers as snap
     areas - and height:0 keeps it out of layout entirely. */
  .sh-snap-rail{position:absolute;top:0;left:0;width:1px;height:0;pointer-events:none;z-index:-1}
  .sh-snap-rail>i{position:absolute;left:0;width:1px;height:1px;display:block;background:transparent;
    scroll-snap-align:start;scroll-snap-stop:normal}
}

/* ============================================================
   ONE FULL-SCREEN SNAP PER SECTION (client feedback, 1 Sep).

   The client asked for exactly ONE snap per section and explicitly does not
   want "two separate snapping points" inside one section. The previous round
   solved unreachable content the other way round - by ADDING interior snap
   stops so the overflow could be reached - and that is what produced the
   second stop. This block removes the cause instead of adding stops: each
   named section is now exactly one screen tall, so there is nothing below the
   fold left to reach.

   One screen = 100svh MINUS the fixed bar, because a section that is a full
   100svh tall cannot fit under a 92px (or 124px when logged in) bar. That
   mismatch is precisely why these sections used to overflow by roughly the
   bar height. --sh-snap-pad is published by home.js and already drives
   scroll-padding-top, so the two always agree.

   How the space is reclaimed, per the brief: padding is reduced, and images
   flex to absorb whatever is left rather than being given fixed heights.
   Nothing here changes a font size, and every image only ever scales DOWN,
   which costs no clarity. Below 1024px none of this applies, so small screens
   keep their existing image behaviour untouched.
   ============================================================ */
@media (min-width:1024px){
  :root{--sh-screen:calc(100svh - var(--sh-snap-pad,92px))}

  .ideas,.feature,.image-section,.site-check,.process,.faq,.footer-cta,.local{
    min-height:var(--sh-screen);max-height:var(--sh-screen)}
  /* .founder has no min-height of its own - its inner row drives the height */
  .founder__inner{min-height:var(--sh-screen);max-height:var(--sh-screen)}

  /* 1. Floor Plan. The implicit grid row was auto-sized to max content
     (803px at an 870px window), so the column never had a height to fit
     into and the "Explore plans" link fell outside the section. Pinning the
     row lets the text keep its natural size while the photo takes the rest. */
  .ideas{grid-template-rows:minmax(0,1fr)}
  .ideas__col{display:flex;flex-direction:column;min-height:0}
  .ideas__media{flex:1 1 auto;min-height:0;overflow:hidden}
  .ideas__media img{width:100%;height:100%;object-fit:cover}
  .ideas__text{flex:0 0 auto}

  /* 3. Download the Guide Book - reduced top and bottom padding, and the
     book image contained so it scales down instead of being cropped. */
  .feature__media{padding:48px 96px 48px 48px}
  .feature__text{padding:56px 64px}
  .feature__media img{width:auto;height:auto;max-height:100%;max-width:526px;object-fit:contain}

  /* 4. Build vs Buy - reduced top and bottom padding (was 128px each). */
  .image-section__text{padding-top:56px;padding-bottom:56px}

  /* 7. Our Process. 96px of the overflow was a padding-top on the step grid
     that is invisible in the section's own rule. Each step's photo now
     flexes, so the caption and CTA can never be pushed off the screen. */
  .process{padding:48px 0}
  .process__steps{padding-top:32px}
  .step{display:flex;flex-direction:column;min-height:0}
  .step__media{flex:1 1 auto;min-height:0;overflow:hidden}
  .step__media img{width:100%;height:100%;object-fit:cover}
  .step__body{flex:0 0 auto}

  /* 8. FAQ - headroom above the answer block home.js reserves. */
  .faq__inner{padding-top:44px;padding-bottom:44px}
}
