/* ==========================================================================
   SPARTAN HOMES — COMPONENTS
   A SMALL, STRICT, SYMMETRICAL SYSTEM.

   Requires: tokens.css → base.css → THIS → animations.css

   ---------------------------------------------------------------------------
   THE FIVE LAWS. Every rule below exists to enforce one of them.

   1. ONE RAIL.      Every section's content lives in .container (80rem).
                     Same max-width, same gutter, top to bottom.
   2. ONE RHYTHM.    Every section pads with --section-y. No exceptions.
   3. CENTRED HEADS. .section-head is eyebrow → rule → H2 → one lede. Centred.
                     Every time. Same shape. The page runs on rails.
   4. EVEN GRIDS.    repeat(N, 1fr). Never auto-fit — auto-fit is how you get
                     an orphan card stretched across a whole row. Equal widths,
                     equal heights, one aspect-ratio per grid.
   5. NO VOIDS.      A two-column row is 50/50 and BOTH halves are full: the
                     image covers its column edge to edge, the text is
                     vertically centred in its column. There is no third option
                     in this file. Staggered/asymmetric splits do not exist.

   If you find yourself needing a class that isn't here, you are about to break
   one of the five laws. Don't add the class. Use the section you have.
   ========================================================================== */


/* ==========================================================================
   0. BUTTON REFINEMENT — pill shape, kept understated. (Base .btn lives in
   base.css; these are additive value overrides only — same amber brand fill,
   same type, just a cleaner compact rounded-pill silhouette to match the
   Arzuman tier. Do NOT recolour: --btn-bg stays --accent on the primary.)
   ========================================================================== */
.btn { border-radius: var(--radius-pill); }
/* Slightly less tall so the large CTA reads compact/understated, not chunky. */
.btn--lg { min-height: 52px; padding: var(--space-xs) var(--space-xl); }


/* ==========================================================================
   1. SECTION — the band. The only vertical rhythm on the site.
   ==========================================================================
   <section class="section section--cream">
     <div class="container"> … </div>
   </section>

   Variants (pick exactly one surface):
     .section--cream   cream band   (default surface)
     .section--dark    ink band     (flips every token — type, rules, forms)
     .section--photo   photo background + near-opaque scrim. Pair with --dark.

   Modifiers:
     .section--tight   half rhythm. ONLY for a band butting a full-bleed image.
     .section--flush-top / --flush-bottom   kill padding on one side.
   -------------------------------------------------------------------------- */
.section {
  position: relative;
  padding-block: var(--section-y);
  background-color: var(--surface);
}

.section--cream { --surface: var(--c-sand-100); }
.section--white { --surface: var(--c-white); }

/* .section--dark inherits the whole token flip from tokens.css §11 */

.section--tight        { padding-block: var(--section-y-tight); }
.section--flush-top    { padding-block-start: 0; }
.section--flush-bottom { padding-block-end: 0; }

/* A hairline between two same-coloured bands, so they read as two, not one */
.section--ruled { border-block-start: 1px solid var(--line); }

/* --- .section--photo ------------------------------------------------------
   A photograph as a section background. The photo is TEXTURE, not subject —
   the scrim is near-opaque (0.88) because type lands all over it. Anything
   lighter and the lead form stops being readable over a bright kitchen.

   <section class="section section--dark section--photo">
     <img class="section__bg" src="…" alt="" aria-hidden="true">
     <div class="container"> … </div>
   </section>
   -------------------------------------------------------------------------- */
.section--photo { isolation: isolate; }

.section__bg {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  z-index: -2;
  pointer-events: none;
}

.section--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--scrim-section);
  pointer-events: none;
}

.section > .container { position: relative; }


/* ==========================================================================
   2. SECTION-HEAD — ALWAYS CENTRED. eyebrow → rule → title → one lede line.
   ==========================================================================
   <header class="section-head" data-reveal>
     <p class="section-head__eyebrow">Our Work</p>
     <h2 class="section-head__title">Homes built once, and built right</h2>
     <p class="section-head__lede">One sentence. That is the entire budget.</p>
   </header>

   The lede is capped at --measure-lede and centred, so it can never run long
   and pull the head off-axis. There is no left-aligned variant. On purpose.
   -------------------------------------------------------------------------- */
.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-inline-size: 46rem;
  margin-inline: auto;
  margin-block-end: var(--head-gap);
}

.section-head__eyebrow {
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--accent-ink-on-light);
}

/* The one piece of orange ornament in the system. 2px. Centred. */
.section-head__eyebrow::after {
  content: "";
  display: block;
  inline-size: 2.5rem;
  block-size: 2px;
  margin: var(--space-sm) auto 0;
  background-color: var(--accent);
}

.section-head__title {
  margin-block-start: var(--space-md);
  font-size: var(--step-5);
  line-height: var(--lh-snug);
  text-wrap: balance;
  color: var(--ink-strong);
}

.section-head__lede {
  margin-block-start: var(--space-md);
  max-inline-size: var(--measure-lede);
  font-size: var(--step-1);
  font-weight: var(--wt-light);
  line-height: var(--lh-loose);
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* Section-level CTA, centred under the content */
.section-cta {
  display: flex;
  justify-content: center;
  margin-block-start: var(--head-gap);
}

/* Stacked variant: a label sitting above a centred row (e.g. a badge strip),
   so the trust marks read as a captioned credentials strip, not orphans. */
.section-cta--stack {
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

.badges__label { text-align: center; }


/* ==========================================================================
   3. HERO — full-bleed photo, two-layer scrim, centred content, stat bar.
   ==========================================================================
   <section class="hero on-media">
     <div class="hero__media">
       <img class="hero__img" src="…" alt="…" fetchpriority="high"
            data-parallax data-parallax-speed="0.14">
     </div>
     <div class="hero__inner container">
       <div class="hero__content">
         <p class="hero__eyebrow">Spotsylvania · Lake Anna, Virginia</p>
         <h1 class="hero__title reveal-words">Your land. Your home. Built once.</h1>
         <p class="hero__lede">One sentence.</p>
         <div class="hero__actions">
           <a class="btn btn--primary btn--lg" href="#lead">Start your build</a>
           <a class="btn btn--ghost btn--lg" href="tel:…">Call (540) 000-0000</a>
         </div>
       </div>
     </div>
     <div class="hero__bar">
       <div class="container"><ul class="stats stats--4"> … </ul></div>
     </div>
   </section>

   THE SCRIM IS NOT NEGOTIABLE. Two layers — a flat tint AND a vertical ramp
   (see tokens.css §10 for the arithmetic). Floor is 4.7:1 for cream type
   ANYWHERE on the image, measured against a pure-white house. The last hero put
   grey text on a white farmhouse. This one cannot, whatever photo you feed it.
   Do not "lighten it a bit" — you would be re-breaking a solved problem.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-block-size: clamp(34rem, 88svh, 56rem);
  isolation: isolate;
  overflow: hidden;
  background-color: var(--c-ink-900);
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}

.hero__img {
  position: absolute;
  inset-inline: 0;
  /* Parallax pulls UP only (motion.js clamps the offset to <= 0), so the image
     is overscaled downward to keep the top edge covered at rest. */
  inset-block-start: -6%;
  inline-size: 100%;
  block-size: 112%;
  object-fit: cover;
}

/* Hero background VIDEO — a clean, muted, looping cover video. No controls, no
   chrome. It sits ON TOP of .hero__img and fades in once playback starts; the
   .hero__img underneath is the poster / reduced-motion / autoplay-blocked
   fallback, so the hero is never blank. */
.hero__video {
  position: absolute;
  inset-block-start: -6%;
  inset-inline: 0;
  inline-size: 100%;
  block-size: 112%;
  object-fit: cover;
  z-index: 0;                 /* above .hero__img (auto/0 in same -2 stacking ctx) */
  opacity: 0;
  transition: opacity 0.8s ease;
  pointer-events: none;       /* never interactive — no clicks, no controls */
}
.hero__video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }   /* honour reduced motion — show the still */
}

/* Layer 1: flat tint. Layer 2: vertical ramp. Both, always. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--scrim-flat);
  background-image: var(--scrim-hero);
  pointer-events: none;
}

.hero__inner {
  display: flex;
  justify-content: center;
  padding-block: var(--section-y) var(--space-3xl);
}

.hero__content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-inline-size: 52rem;
}

.hero__eyebrow {
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1.4;
}

.hero__eyebrow::after {
  content: "";
  display: block;
  inline-size: 2.5rem;
  block-size: 2px;
  margin: var(--space-sm) auto 0;
  background-color: var(--accent);
}

.hero__title {
  margin-block-start: var(--space-md);
  /* Cinzel caps are wider & taller than the old Vidaloka mixed-case, so the
     hero title runs one step smaller than before to keep long H1s on the
     scrim without clipping. */
  font-size: var(--step-6);
  line-height: 1.06;
  letter-spacing: var(--ls-display);
  color: var(--c-white);
  text-wrap: balance;
  /* A whisper of shadow. Not a crutch — the scrim already carries the contrast —
     but it keeps the caps crisp against busy foliage. */
  text-shadow: 0 2px 24px rgba(15, 11, 3, 0.35);
}

.hero__lede {
  margin-block-start: var(--space-md);
  max-inline-size: var(--measure-lede);
  font-size: var(--step-1);
  font-weight: var(--wt-light);
  line-height: var(--lh-loose);
  color: var(--c-sand-100);
  text-shadow: 0 1px 16px rgba(15, 11, 3, 0.4);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-xl);
}

/* The stat bar. Solid ink, hairline top — NOT translucent. A frosted bar over a
   photo is a contrast lottery; a solid one is a guarantee, and it reads as the
   base of the hero rather than a sticker on top of it. */
.hero__bar {
  position: relative;
  background-color: var(--c-ink-900);
  border-block-start: 1px solid rgba(252, 245, 234, 0.14);
}

@media (max-width: 47.99rem) {
  .hero { min-block-size: auto; }
  .hero__inner { padding-block: var(--section-y); }
  .hero__title { font-size: var(--step-5); }
  .hero__actions { inline-size: 100%; flex-direction: column; }
  .hero__actions .btn { inline-size: 100%; }
}


/* ==========================================================================
   4. STATS — an even, centred, symmetrical row. Dividers, not boxes.
   ==========================================================================
   <ul class="stats stats--4" data-reveal-group>
     <li class="stat">
       <span class="stat__num" data-count data-count-to="20"
             data-count-suffix="+">0</span>
       <span class="stat__label">Years building</span>
     </li>
     …
   </ul>
   -------------------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(var(--stat-cols, 4), 1fr);
  align-items: stretch;
  text-align: center;
}

.stats--2 { --stat-cols: 2; }
.stats--3 { --stat-cols: 3; }
.stats--4 { --stat-cols: 4; }

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding-block: var(--space-xl);
  padding-inline: var(--space-sm);
  min-inline-size: 0;
}

/* Hairline dividers BETWEEN cells only — never a leading or trailing edge.
   (border-inline-start on every cell leaves a stray rule at the left margin.) */
.stat + .stat { border-inline-start: 1px solid var(--line); }

.stat__num {
  font-family: var(--font-display);
  font-size: var(--step-5);
  line-height: 1;
  color: var(--ink-strong);
  font-variant-numeric: tabular-nums lining-nums;
}

.stat__label {
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--ink-subtle);
  line-height: 1.4;
  text-wrap: balance;
}

/* The hero bar is its own dark island — the page tokens have not flipped there */
.hero__bar .stat + .stat { border-inline-start-color: rgba(252, 245, 234, 0.14); }
.hero__bar .stat__num    { color: var(--c-white); }
.hero__bar .stat__label  { color: rgba(252, 245, 234, 0.66); }

/* The plinth background bleeds full-width, but the four cells sit on the 80rem
   rail. Without a rule on the outer edges the leftmost/rightmost numbers read
   ~60px off-centre (the dark simply runs past them to the viewport edge). Frame
   the lanes at the rail so every number sits dead-centre in an equal 1/4 lane;
   the remaining dark margins are the same gutter every other section uses. */
@media (min-width: 62rem) {
  .hero__bar .stat:first-child { border-inline-start: 1px solid rgba(252, 245, 234, 0.14); }
  .hero__bar .stat:last-child  { border-inline-end:  1px solid rgba(252, 245, 234, 0.14); }
}

/* 4-up → 2×2. Still even. Still symmetrical. */
@media (max-width: 61.99rem) {
  .stats--4 { --stat-cols: 2; }
  .stats--4 .stat:nth-child(3),
  .stats--4 .stat:nth-child(4) { border-block-start: 1px solid var(--line); }
  .stats--4 .stat:nth-child(odd) { border-inline-start: 0; }
  .hero__bar .stats--4 .stat:nth-child(3),
  .hero__bar .stats--4 .stat:nth-child(4) {
    border-block-start-color: rgba(252, 245, 234, 0.14);
  }
}

@media (max-width: 39.99rem) {
  .stat { padding-block: var(--space-lg); }
  .stat__num { font-size: var(--step-4); }
  .stat__label { font-size: var(--step--2); letter-spacing: 0.06em; }
}


/* ==========================================================================
   5. CARDS — even 2/3/4-up. Equal widths. Equal heights. One aspect-ratio.
   ==========================================================================
   <ul class="cards cards--3" data-reveal-group>
     <li class="card">
       <div class="card__media">
         <img class="card__img" src="…" alt="…" loading="lazy">
       </div>
       <div class="card__body">
         <h3 class="card__title">Lakefront</h3>
         <p class="card__text">One sentence.</p>
       </div>
     </li>
     …
   </ul>

   repeat(N, 1fr), NOT auto-fit. Every tile is exactly 1/N of the rail and the
   media is aspect-ratio locked, so the grid is a perfect lattice whatever the
   copy does. .card__body flexes to fill, so a long card and a short card end at
   the same baseline.
   -------------------------------------------------------------------------- */
.cards {
  display: grid;
  grid-template-columns: repeat(var(--card-cols, 3), 1fr);
  gap: var(--grid-gap);
  align-items: stretch;
}

.cards--2 { --card-cols: 2; }
.cards--3 { --card-cols: 3; }
.cards--4 { --card-cols: 4; }

.card {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  background-color: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  overflow: hidden;
  transition: transform var(--dur-slow) var(--ease),
              box-shadow var(--dur-slow) var(--ease),
              border-color var(--dur-slow) var(--ease);
}

/* Only cards that actually go somewhere get the lift */
a.card:hover,
.card:has(a:hover) {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: var(--line-strong);
}

.card__media {
  position: relative;
  flex: none;
  aspect-ratio: var(--aspect-card);
  overflow: hidden;
  background-color: var(--c-sand-300);
}

.card__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-slowest) var(--ease);
}

.card:hover .card__img { transform: scale(var(--img-zoom)); }

.card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-lg);
}

.card__title {
  font-size: var(--step-2);
  line-height: var(--lh-heading);
  color: var(--ink-strong);
}

.card__text {
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink-muted);
  text-wrap: pretty;
}

/* Pushes a link/meta line to the bottom edge so every card's footer aligns */
.card__foot { margin-block-start: auto; padding-block-start: var(--space-md); }

@media (max-width: 61.99rem) {
  .cards--4 { --card-cols: 2; }
}

@media (max-width: 47.99rem) {
  .cards--2,
  .cards--3,
  .cards--4 { --card-cols: 1; }
  .card { max-inline-size: 30rem; margin-inline: auto; inline-size: 100%; }
  .card__body { padding: var(--space-md); }
}


/* ==========================================================================
   6. GALLERY — a clean, EVEN photo grid. Uniform aspect ratios. Not masonry.
   ==========================================================================
   <ul class="gallery gallery--3" data-lightbox data-reveal-group>
     <li>
       <a class="gallery__item" href="assets/img/…-full.jpg"
          data-lightbox-item data-caption="Great room · Lake Anna">
         <img class="gallery__img" src="…" alt="…" loading="lazy">
         <span class="gallery__cap">Great room · Lake Anna</span>
       </a>
     </li>
     …
   </ul>

   Feed it a MULTIPLE OF THE COLUMN COUNT (6 or 9 for a 3-up). A 3-up with 7
   photos leaves a hole in the last row, and that hole is exactly the void the
   client was complaining about.
   -------------------------------------------------------------------------- */
.gallery {
  display: grid;
  grid-template-columns: repeat(var(--gal-cols, 3), 1fr);
  gap: var(--grid-gap);
}

.gallery--2 { --gal-cols: 2; }
.gallery--3 { --gal-cols: 3; }
.gallery--4 { --gal-cols: 4; }

.gallery > li { display: flex; min-inline-size: 0; }

.gallery__item {
  position: relative;
  display: block;
  inline-size: 100%;
  aspect-ratio: var(--aspect-card);
  overflow: hidden;
  border-radius: var(--radius-card);
  background-color: var(--c-sand-300);
  cursor: zoom-in;
}

.gallery__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-slowest) var(--ease);
}

.gallery__item:hover .gallery__img { transform: scale(var(--img-zoom)); }

/* Caption rides the bottom on its own scrim (0.92 at the baseline), because
   these land on white farmhouses. Revealed on hover where there IS a hover;
   always visible on touch, where there is no hover to discover it with. */
.gallery__cap {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  padding: var(--space-xl) var(--space-md) var(--space-sm);
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-ui);
  line-height: 1.4;
  color: var(--c-sand-100);
  background: var(--scrim-caption);
  pointer-events: none;
}

@media (hover: hover) {
  .gallery__cap {
    opacity: 0;
    transform: translateY(6px);
    transition: opacity var(--dur-base) var(--ease),
                transform var(--dur-base) var(--ease);
  }
  .gallery__item:hover .gallery__cap,
  .gallery__item:focus-visible .gallery__cap { opacity: 1; transform: none; }
}

/* Two-up, never one-up: a single column of full-width photos turns a 6-photo
   gallery into six full screens of scrolling. */
@media (max-width: 61.99rem) {
  .gallery--2,
  .gallery--3,
  .gallery--4 { --gal-cols: 2; }
}

@media (max-width: 39.99rem) {
  .gallery__cap { display: none; }
}


/* ==========================================================================
   7. FEATURE-ROW — 50/50. BOTH HALVES FULL. NO VOID. EVER.
   ==========================================================================
   <div class="feature-row" data-reveal>
     <div class="feature-row__media">
       <img class="feature-row__img" src="…" alt="…" loading="lazy">
     </div>
     <div class="feature-row__body">
       <p class="feature-row__eyebrow">Build on your lot</p>
       <h3 class="feature-row__title">You already own the view</h3>
       <p class="feature-row__text">One or two sentences. Not five.</p>
       <ul class="feature-row__list">
         <li>Site evaluation before you sign</li>
         <li>Well, septic and grading priced up front</li>
       </ul>
       <a class="btn btn--ghost" href="#lead">See the process</a>
     </div>
   </div>

   Add .feature-row--reverse to flip the image to the right. Alternate them down
   the page — but the geometry NEVER changes: two equal columns,
   align-items:stretch, image object-fit:cover filling its column top to bottom,
   text vertically centred in its own. Whichever half is taller sets the row
   height and the other half COVERS it. That is why there is no void.
   -------------------------------------------------------------------------- */
.feature-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 4vw, 4.5rem);
  align-items: stretch;
  min-block-size: 27rem;
}

.feature-row + .feature-row { margin-block-start: var(--head-gap); }

.feature-row__media {
  position: relative;
  min-inline-size: 0;
  overflow: hidden;
  border-radius: var(--radius-card);
  background-color: var(--c-sand-300);
  /* Fixed aspect on desktop so BOTH rows render identical media dimensions —
     the image never inherits its height from the adjacent text column, so the
     two stacked rows mirror each other instead of reading as mismatched. */
  aspect-ratio: 4 / 5;
  align-self: center;
}

.feature-row__img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-slowest) var(--ease);
}

.feature-row__media:hover .feature-row__img { transform: scale(var(--img-zoom)); }

.feature-row__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;      /* ← the vertical centring. This kills the void. */
  gap: var(--space-md);
  min-inline-size: 0;
  padding-block: var(--space-lg);
  /* The body fills its own grid track (no auto-centring cap) so its left/right
     margins match the image column exactly — otherwise stacked rows stagger by
     ~14px. Vertical centring above still kills the void; individual copy blocks
     keep their own --measure cap. */
  inline-size: 100%;
}

.feature-row--reverse .feature-row__media { order: 2; }

.feature-row__eyebrow {
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent-ink-on-light);
  line-height: 1.4;
}

.feature-row__title {
  font-size: var(--step-4);
  line-height: var(--lh-snug);
  color: var(--ink-strong);
  text-wrap: balance;
}

.feature-row__text {
  font-size: var(--step-0);
  line-height: var(--lh-loose);
  color: var(--ink-muted);
  /* Narrower than a full paragraph measure so the copy reads as a tight island
     against the photo instead of a wide slab — the Arzuman restraint cue. */
  max-inline-size: var(--measure-narrow);
  text-wrap: pretty;
}

/* Ticked list. The tick is an accent rule — not an emoji, not an icon font. */
.feature-row__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  inline-size: 100%;
}

.feature-row__list > li {
  position: relative;
  padding-inline-start: var(--space-md);
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink);
}

.feature-row__list > li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.72em;
  inline-size: 0.5rem;
  block-size: 2px;
  background-color: var(--accent);
}

/* Desktop only: let the photo carry slightly more of the row than the text.
   Viewport-guarded (not a bare class override) so it can never beat the mobile
   single-column rule on specificity. The --reverse variant swaps the track
   sizes too, so the MEDIA always gets the larger column whichever side it's on. */
@media (min-width: 62rem) {
  .feature-row          { grid-template-columns: 1.08fr 0.92fr; }
  .feature-row--reverse { grid-template-columns: 0.92fr 1.08fr; }
}

@media (max-width: 61.99rem) {
  .feature-row {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
    min-block-size: 0;
  }
  /* On one column the image ALWAYS leads, whatever the desktop order was —
     alternating on a phone just looks like a bug. */
  .feature-row--reverse .feature-row__media { order: 0; }
  .feature-row__media { aspect-ratio: var(--aspect-photo); }
  .feature-row__body { padding-block: 0; max-inline-size: none; }
}


/* ==========================================================================
   8. BLEED-IMAGE — full-width edge-to-edge photo break, optional pull-quote.
   ==========================================================================
   <section class="bleed-image on-media" data-reveal="fade">
     <img class="bleed-image__img" src="…" alt="…" loading="lazy">
     <div class="bleed-image__inner container">
       <blockquote class="bleed-image__quote">
         <p>“They built it like it was their own.”</p>
         <cite class="bleed-image__cite">Whitcomb family · Lake Anna</cite>
       </blockquote>
     </div>
   </section>

   Drop __inner entirely for a silent photo break. The scrim applies EITHER WAY —
   an unscrimmed bleed image reads as a raw asset, and the moment someone adds a
   quote to it later it would be unreadable.
   -------------------------------------------------------------------------- */
.bleed-image {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: clamp(26rem, 52vw, 40rem);
  isolation: isolate;
  overflow: hidden;
  background-color: var(--c-ink-900);
}

.bleed-image__img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  z-index: -2;
}

.bleed-image::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--scrim-bleed-flat);
  background-image: var(--scrim-bleed);
  pointer-events: none;
}

/* A SILENT break carries no type — so it needs no readability scrim. Let the
   photograph read at full brightness (the whole point of the break). Use ONLY
   on a bleed-image with no __inner/quote. */
.bleed-image--plain::before { display: none; }

.bleed-image__inner {
  display: flex;
  justify-content: center;
  padding-block: var(--section-y-tight);
}

.bleed-image__quote {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-inline-size: 40rem;
}

.bleed-image__quote > p {
  font-family: var(--font-display);
  font-size: var(--step-4);
  line-height: var(--lh-snug);
  color: var(--c-white);
  text-wrap: balance;
  text-shadow: 0 2px 24px rgba(15, 11, 3, 0.4);
}

.bleed-image__cite {
  margin-block-start: var(--space-lg);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
}

.bleed-image__cite::before {
  content: "";
  display: block;
  inline-size: 2.5rem;
  block-size: 2px;
  margin: 0 auto var(--space-md);
  background-color: var(--accent);
}

/* Editorial photo band — a full-bleed photo carrying a large centred Cinzel
   headline (and optional one-line subhead + button). Photo-heavy, near-text-
   free. Taller than a silent break. */
.bleed-image--tall { min-block-size: clamp(34rem, 78vh, 52rem); }
.bleed-image__title {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-inline-size: 46rem;
  padding-inline: var(--space-lg);
}
.bleed-image__title > .eyebrow { color: var(--c-sand-100); margin-block-end: var(--space-sm); }
.bleed-image__title > h2,
.bleed-image__title > h1 {
  font-size: var(--step-6);
  line-height: 1.05;
  color: var(--c-white);
  text-wrap: balance;
  text-shadow: 0 2px 30px rgba(15, 11, 3, 0.45);
}
.bleed-image__title > p {
  margin-block-start: var(--space-md);
  max-inline-size: 34rem;
  font-size: var(--step-1);
  font-weight: var(--wt-light);
  color: var(--c-sand-100);
  text-shadow: 0 1px 16px rgba(15, 11, 3, 0.5);
}
.bleed-image__title > .btn { margin-block-start: var(--space-xl); }

/* Two-up (or more) photo-tile row — big edge-to-edge images, each with a small
   overlay label. Reuses the gallery lightbox (data-lightbox / data-lightbox-item).
   The Arzuman-style "duo" split. */
.photo-duo {
  display: grid;
  grid-template-columns: repeat(var(--duo-cols, 2), 1fr);
  /* Arzuman stacks photos with a tight, hairline-consistent seam — not a gutter.
     8px reads as "two panes of one image", never as spaced cards. */
  gap: var(--space-2xs);
}
.photo-tile {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 3 / 4;
  min-block-size: clamp(20rem, 44vh, 34rem);
  background: var(--c-ink-900);
}
.photo-tile img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--ease-out, ease);
}
.photo-tile:hover img,
.photo-tile:focus-visible img { transform: scale(1.04); }
.photo-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(15, 11, 3, 0.55), rgba(15, 11, 3, 0) 45%);
  pointer-events: none;
}
.photo-tile__label {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 1;
  padding: var(--space-lg);
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.1;
  color: var(--c-white);
  text-shadow: 0 2px 18px rgba(15, 11, 3, 0.5);
}
@media (max-width: 47.99rem) {
  .photo-duo { grid-template-columns: 1fr; }
  .photo-tile { aspect-ratio: 4 / 3; }
}


/* ==========================================================================
   9. STEPS — a clean numbered process row. 3 or 4 even columns.
   ==========================================================================
   <ol class="steps steps--4" data-reveal-group>
     <li class="step">
       <div class="step__media">
         <img class="step__img" src="…" alt="…" loading="lazy">
       </div>
       <p class="step__num">01</p>
       <h3 class="step__title">Walk your land</h3>
       <p class="step__text">One sentence.</p>
     </li>
     …
   </ol>

   The number sits BELOW the photo, on the axis, in accent. It used to be an ink
   chip in the photo's top-left corner — on a dark band that chip is the same
   colour as the background, so it read as a bite taken out of the photograph
   rather than as an ornament. Do not put it back on the image.
   -------------------------------------------------------------------------- */
.steps {
  display: grid;
  grid-template-columns: repeat(var(--step-cols, 4), 1fr);
  gap: var(--grid-gap);
  align-items: stretch;
}

.steps--3 { --step-cols: 3; }
.steps--4 { --step-cols: 4; }

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  min-inline-size: 0;
  text-align: center;
}

.step__media {
  position: relative;
  inline-size: 100%;
  aspect-ratio: var(--aspect-photo);
  overflow: hidden;
  border-radius: var(--radius-card);
  background-color: var(--c-sand-300);
  margin-block-end: var(--space-2xs);
}

.step__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-slowest) var(--ease);
}

.step:hover .step__img { transform: scale(var(--img-zoom)); }

/* The numeral: below the photo, on the axis, in accent, with a short rule under
   it. Never overlaid on the image — see the note in the block comment above. */
.step__num {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1;
  color: var(--accent-ink-on-light);
  font-variant-numeric: tabular-nums lining-nums;
}

.step__num::after {
  content: "";
  inline-size: 1.75rem;
  block-size: 2px;
  margin-block-start: var(--space-2xs);
  background-color: var(--accent);
}

.step__title {
  font-size: var(--step-2);
  line-height: var(--lh-heading);
  color: var(--ink-strong);
  text-wrap: balance;
}

.step__text {
  font-size: var(--step-0);
  line-height: var(--lh-body);
  color: var(--ink-muted);
  text-wrap: pretty;
}

@media (max-width: 61.99rem) {
  .steps--4 { --step-cols: 2; }
}

@media (max-width: 47.99rem) {
  .steps--3,
  .steps--4 { --step-cols: 1; }
  .step { max-inline-size: 28rem; margin-inline: auto; }
}


/* ==========================================================================
   10. QUOTE — a large centred testimonial.
   ==========================================================================
   <figure class="quote" data-reveal>
     <img class="quote__portrait" src="…" alt="" loading="lazy">   (optional)
     <blockquote class="quote__text">
       <p>“Eric walked our lot before he would quote a number.”</p>
     </blockquote>
     <figcaption class="quote__cite">
       Sarah &amp; Tom Whitcomb
       <span class="quote__meta">Lake Anna · 4,200 sq ft</span>
     </figcaption>
   </figure>

   Use inside a .section--dark. If that section has NO other photography, use
   .bleed-image with a __quote instead — a testimonial band with nothing in it
   but type is exactly the "section with no photos" the client rejected.
   -------------------------------------------------------------------------- */
.quote {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-inline-size: 46rem;
  margin-inline: auto;
}

.quote__portrait {
  inline-size: 5.5rem;
  block-size: 5.5rem;
  object-fit: cover;
  border-radius: var(--radius-pill);
  border: 2px solid var(--accent);
  margin-block-end: var(--space-lg);
}

.quote__text {
  font-family: var(--font-display);
  font-size: var(--step-4);
  line-height: var(--lh-snug);
  color: var(--ink-strong);
  text-wrap: balance;
}

.quote__cite {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin-block-start: var(--space-xl);
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-strong);
}

.quote__cite::before {
  content: "";
  inline-size: 2.5rem;
  block-size: 2px;
  margin: 0 auto var(--space-md);
  background-color: var(--accent);
}

.quote__meta {
  font-weight: var(--wt-regular);
  letter-spacing: var(--ls-caps);
  color: var(--ink-subtle);
}


/* ==========================================================================
   11. FAQ — centred, narrow accordion. (motion.js: initAccordion)
   ==========================================================================
   <div class="faq" data-accordion="single">
     <div class="faq__item">
       <h3>
         <button class="faq__q" type="button" data-accordion-trigger
                 aria-expanded="false" aria-controls="faq-1" id="faq-q-1">
           What does a custom home cost?
           <span class="faq__icon" aria-hidden="true"></span>
         </button>
       </h3>
       <div class="faq__panel" id="faq-1" role="region" aria-labelledby="faq-q-1">
         <div class="faq__panel-inner">
           <div class="faq__a"><p>Answer.</p></div>
         </div>
       </div>
     </div>
     …
   </div>

   grid-template-rows 0fr → 1fr animates the height with no JS measuring.
   -------------------------------------------------------------------------- */
.faq {
  max-inline-size: var(--container-narrow);
  margin-inline: auto;
  border-block-start: 1px solid var(--line);
}

.faq__item { border-block-end: 1px solid var(--line); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  inline-size: 100%;
  min-block-size: 4.5rem;
  padding-block: var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: var(--wt-regular);
  line-height: var(--lh-heading);
  text-align: start;
  color: var(--ink-strong);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}

.faq__q:hover { color: var(--accent-ink-on-light); }

/* Plus → minus. Two bars; the vertical one rotates flat when open. */
.faq__icon {
  position: relative;
  flex: none;
  inline-size: 1rem;
  block-size: 1rem;
}

.faq__icon::before,
.faq__icon::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: 2px;
  margin-block-start: -1px;
  background-color: var(--accent);
  transition: transform var(--dur-base) var(--ease);
}

.faq__icon::after { transform: rotate(90deg); }
.faq__item.is-open .faq__icon::after { transform: rotate(0deg); }

.faq__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-base) var(--ease);
}

.faq__item.is-open .faq__panel { grid-template-rows: 1fr; }

.faq__panel-inner {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear var(--dur-base);
}

.faq__item.is-open .faq__panel-inner {
  visibility: visible;
  transition-delay: 0s;
}

.faq__a {
  padding-block-end: var(--space-lg);
  padding-inline-end: var(--space-2xl);
  max-inline-size: var(--measure);
  font-size: var(--step-0);
  line-height: var(--lh-loose);
  color: var(--ink-muted);
}

@media (max-width: 47.99rem) {
  .faq__q { font-size: var(--step-1); }
  .faq__a { padding-inline-end: 0; }
}


/* ==========================================================================
   12. FORM — calm, centred lead form. Lives on a dark or photo section.
   ==========================================================================
   <section class="section section--dark section--photo" id="lead"
            data-callbar-stop>
     <img class="section__bg" src="…" alt="" aria-hidden="true">
     <div class="container">
       <header class="section-head" data-reveal> … </header>
       <form class="form" action="…" method="post" data-reveal>
         <div class="form__grid">
           <div class="field">
             <label class="field__label" for="name">Name <span class="req">*</span></label>
             <input class="field__input" id="name" name="name" required>
           </div>
           <div class="field"> … </div>              ← half width
           <div class="field form__row"> … </div>    ← full width
         </div>
         <input class="hp" name="_gotcha" tabindex="-1" autocomplete="off">
         <div class="form__actions">
           <button class="btn btn--primary btn--lg btn--block" type="submit">
             Request your consultation
           </button>
         </div>
         <p class="form__fine">No obligation. We reply within one business day.</p>
       </form>
     </div>
   </section>

   ONE step. The old two-step form (#step-1 / #step-2 / .formprogress) is gone —
   it was three extra components in service of hiding one extra field.
   -------------------------------------------------------------------------- */
.form {
  max-inline-size: var(--container-narrow);
  margin-inline: auto;
}

/* On a photo section the form is the conversion moment a $1M buyer scrutinises
   most. Floating bare fields on the house photo read as a lead-gen template, so
   the form sits on its own near-opaque ink PANEL — the photo becomes texture at
   the edges, the fields read as crisp, premium inputs on a solid card. */
.section--photo .form {
  padding: clamp(1.75rem, 4vw, 2.75rem);
  background-color: rgba(20, 14, 6, 0.95);
  border: 1px solid var(--line-inverse);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
}

.form__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
}

/* Full-width field inside the 2-up grid */
.form__row { grid-column: 1 / -1; }

.form__actions { margin-block-start: var(--space-xl); }

.form__fine {
  margin-block-start: var(--space-md);
  text-align: center;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-subtle);
}

/* Success / error message swapped in on submit */
.form__note {
  padding: var(--space-md);
  border: 1px solid var(--line-strong);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background-color: var(--surface-raised);
  font-size: var(--step-0);
  color: var(--ink);
}

@media (max-width: 47.99rem) {
  .form__grid { grid-template-columns: 1fr; }
}


/* ==========================================================================
   13. BADGES — an even, centred row of trust marks (NGBS, veteran-owned, …).
   ==========================================================================
   <ul class="badges" data-reveal-group>
     <li><img src="assets/logo/ngbs-logo.png" alt="NGBS Green Certified"></li>
     …
   </ul>
   -------------------------------------------------------------------------- */
.badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
}

.badges img {
  block-size: clamp(2.75rem, 5vw, 4rem);
  inline-size: auto;
  object-fit: contain;
}

/* Trust marks are artwork printed for light stock. On ink they need a real
   cream plate — not an opacity trick that turns a green certification mark
   into grey mud. */
.section--dark .badges img,
.site-footer .badges img {
  block-size: clamp(3.25rem, 5.5vw, 4.5rem);
  padding: var(--space-2xs) var(--space-sm);
  background-color: var(--c-sand-100);
  border-radius: var(--radius-sm);
}


/* ==========================================================================
   14. SITE HEADER — sticky, solid cream. (motion.js: initHeader → .is-scrolled)
   ==========================================================================
   The header is SOLID, never a transparent overlay. The Spartan logo is black
   type on transparent: over a hero photograph it vanishes, and both ways out —
   inverting it to a white silhouette, or crossfading two logo files — trade a
   real brand asset for a trick. A calm cream bar above a full-bleed photograph
   is also just what the good brochures do.

   <header class="site-header" data-header>
     <div class="site-header__inner container">
       <a class="brand" href="#top">
         <img class="brand__logo" src="assets/logo/spartan-homes-new-logo.png"
              alt="Spartan Homes" width="200" height="76">
       </a>
       <nav class="nav" aria-label="Primary">
         <a class="nav__link" href="#work">Our Work</a> …
       </nav>
       <div class="site-header__actions">
         <a class="site-header__phone" href="tel:…">(540) 000-0000</a>
         <a class="btn btn--primary btn--sm" href="#lead">Get Started</a>
         <button class="nav-toggle" type="button" data-nav-toggle
                 aria-expanded="false" aria-controls="nav-drawer"
                 aria-label="Open menu">
           <span class="nav-toggle__bar"></span>
         </button>
       </div>
     </div>
   </header>
   -------------------------------------------------------------------------- */
/* STICKY, not fixed. Sticky keeps the bar in the flow, so the hero simply starts
   below it and no component has to pad itself for a header height it cannot
   see. (This is why base.css uses overflow-x:clip on body, not hidden — hidden
   would make body a scroll container and sticky would silently stop working.) */
.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-header);
  background-color: var(--c-sand-100);
  border-block-end: 1px solid transparent;
  transition: box-shadow var(--dur-base) var(--ease),
              border-color var(--dur-base) var(--ease);
}

.site-header.is-scrolled {
  border-block-end-color: var(--line);
  box-shadow: var(--shadow-sm);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  block-size: var(--header-h);
  transition: block-size var(--dur-base) var(--ease);
}

.site-header.is-scrolled .site-header__inner { block-size: var(--header-h-slim); }

.brand { display: flex; align-items: center; flex: none; }

.brand__logo {
  block-size: clamp(2rem, 3.4vw, 2.75rem);
  inline-size: auto;
  transition: block-size var(--dur-base) var(--ease);
}

.site-header.is-scrolled .brand__logo { block-size: clamp(1.75rem, 2.8vw, 2.25rem); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 2.4vw, 2.25rem);
}

.nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}

.nav__link::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0.85rem;
  block-size: 2px;
  background-color: var(--accent);
  transform: scaleX(0);
  transition: transform var(--dur-base) var(--ease);
}

.nav__link:hover { color: var(--ink-strong); }
.nav__link:hover::after,
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex: none;
}

.site-header__phone {
  font-family: var(--font-sans);
  font-size: var(--step-0);
  font-weight: var(--wt-semibold);
  color: var(--ink-strong);
  white-space: nowrap;
}

.site-header__phone:hover { color: var(--accent-ink-on-light); }

/* Hamburger — three bars; the middle one is the element itself */
.nav-toggle {
  display: none;
  position: relative;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  cursor: pointer;
}

.nav-toggle__bar,
.nav-toggle__bar::before,
.nav-toggle__bar::after {
  position: absolute;
  inline-size: 22px;
  block-size: 2px;
  background-color: var(--ink-strong);
  transition: transform var(--dur-base) var(--ease),
              background-color var(--dur-fast) var(--ease);
}

.nav-toggle__bar::before { content: ""; transform: translateY(-7px); }
.nav-toggle__bar::after  { content: ""; transform: translateY(7px); }

/* The X STAYS INK. The drawer sits BELOW the header (--z-nav 150 < --z-header
   200), so when it is open the header is still its own cream bar sitting on top
   of the ink drawer — the close button is on cream, not on ink. Flipping these
   bars to cream makes the X cream-on-cream: invisible, and the drawer becomes a
   trap you can only escape with Esc. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bar { background-color: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::before { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::after  { transform: rotate(-45deg); }

@media (max-width: 61.99rem) {
  .nav { display: none; }
  .nav-toggle { display: flex; }
  .site-header__phone { display: none; }
}

@media (max-width: 47.99rem) {
  .site-header__actions .btn { display: none; }
}


/* ==========================================================================
   15. NAV DRAWER — mobile. (motion.js: initNav → .is-open)
   ==========================================================================
   <nav class="nav-drawer" id="nav-drawer" data-nav aria-label="Primary">
     <a class="nav-drawer__link" href="#work">Our Work</a> …
     <a class="btn btn--primary btn--lg btn--block" href="#lead">Get Started</a>
   </nav>

   The drawer sits BELOW the header in z-order on purpose (tokens: --z-nav <
   --z-header): the header carries the close X, and the drawer's top padding
   clears it. Raise --z-nav above --z-header and the drawer becomes a trap.
   -------------------------------------------------------------------------- */
.nav-drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-nav);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: calc(var(--header-h) + var(--space-2xl)) var(--gutter) var(--space-2xl);
  background-color: var(--c-ink-900);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-1.5rem);
  transition: opacity var(--dur-base) var(--ease),
              transform var(--dur-base) var(--ease),
              visibility 0s linear var(--dur-base);
}

.nav-drawer.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

.nav-drawer__link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 48px;
  font-family: var(--font-display);
  font-size: var(--step-3);
  color: var(--c-sand-100);
}

.nav-drawer.is-open .nav-drawer__link {
  animation: navItemIn var(--dur-slow) var(--ease) both;
  animation-delay: calc(120ms + (var(--i, 0) * 60ms));
}

.nav-drawer__link:hover { color: var(--accent); }

.nav-drawer .btn { margin-block-start: var(--space-lg); inline-size: min(100%, 22rem); }


/* ==========================================================================
   16. CALLBAR — sticky mobile call bar. (motion.js: initCallbar → .is-shown)
   ==========================================================================
   <div class="callbar" data-callbar>
     <a class="callbar__btn callbar__btn--call" href="tel:…">Call Now</a>
     <a class="callbar__btn callbar__btn--cta" href="#lead">Get Started</a>
   </div>
   Put [data-callbar-stop] on the lead-form section so the bar retracts and can
   never cover the submit button.
   -------------------------------------------------------------------------- */
.callbar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: var(--z-callbar);
  display: none;
  grid-template-columns: 1fr 1fr;   /* even halves. always. */
  block-size: var(--callbar-h);
  background-color: var(--c-ink-900);
  border-block-start: 1px solid rgba(252, 245, 234, 0.16);
  transform: translateY(100%);
  transition: transform var(--dur-base) var(--ease);
}

.callbar.is-shown { transform: none; }

.callbar__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  font-size: var(--step--1);
  font-weight: var(--wt-bold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  text-align: center;
}

.callbar__btn--call { color: var(--c-sand-100); }
.callbar__btn--cta  { color: var(--accent-ink); background-color: var(--accent); }

@media (max-width: 47.99rem) {
  .callbar { display: grid; }
}

/* The callbar outranks the drawer (--z-callbar 250 > --z-nav 150), so with the
   drawer open it floated a second "Get Started" over the drawer's own CTA.
   motion.js locks the body whenever the drawer or lightbox is open — use that. */
body.is-locked .callbar { display: none; }


/* ==========================================================================
   17. FOOTER — dark, centred, symmetrical. Everything on the axis.
   ==========================================================================
   <footer class="site-footer">
     <div class="container">
       <img class="footer__logo" src="…" alt="Spartan Homes">
       <nav class="footer__nav" aria-label="Footer">
         <a href="#work">Our Work</a> …
       </nav>
       <div class="footer__contact">
         <a href="tel:…">(540) 000-0000</a>
         <a href="mailto:…">build@spartanhomes.com</a>
       </div>
       <ul class="badges"> … </ul>
       <p class="footer__legal">© 2026 Spartan Homes · Class A Contractor …</p>
     </div>
   </footer>
   -------------------------------------------------------------------------- */
.site-footer {
  padding-block: var(--section-y);
  background-color: var(--c-ink-950);
  text-align: center;
}

.site-footer > .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
}

/* The logo is black type — on ink it needs the cream plate, same as the badges */
.footer__logo {
  block-size: clamp(2.5rem, 4vw, 3.25rem);
  inline-size: auto;
  padding: var(--space-2xs) var(--space-md);
  background-color: var(--c-sand-100);
  border-radius: var(--radius-sm);
}

.footer__nav,
.footer__contact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-md) var(--space-xl);
}

.footer__nav a {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--ink-inverse-muted);
}

.footer__nav a:hover { color: var(--accent); }

.footer__contact a {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  font-family: var(--font-display);
  font-size: var(--step-2);
  color: var(--c-sand-100);
}

.footer__contact a:hover { color: var(--accent); }

.footer__legal {
  inline-size: 100%;
  max-inline-size: var(--measure);
  padding-block-start: var(--space-xl);
  border-block-start: 1px solid var(--line-inverse);
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-inverse-subtle);
}

.footer__legal a { text-decoration: underline; text-underline-offset: 0.2em; }
.footer__legal a:hover { color: var(--accent); }


/* ==========================================================================
   18. LIGHTBOX — built by motion.js (initLightbox). The markup is JS-owned.
   ==========================================================================
   .lightbox > .lightbox__bar   (.lightbox__count, .lightbox__close)
             > .lightbox__stage (.lightbox__nav--prev, .lightbox__img,
                                 .lightbox__nav--next)
             > .lightbox__foot
   -------------------------------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: none;
  grid-template-rows: auto 1fr auto;
  gap: var(--space-md);
  padding: var(--space-md);
  /* 0.96 left the cream header bar faintly legible through the top of the
     overlay — the dialog is correctly on top (z 400 > 200), it was simply not
     opaque enough to bury a near-white surface. Blur + 0.985 buries it. */
  background-color: rgba(15, 11, 3, 0.985);
  backdrop-filter: blur(8px);
  color: var(--c-sand-100);
}

.lightbox.is-open {
  display: grid;
  animation: fadeIn var(--dur-base) var(--ease) both;
}

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

.lightbox__count {
  font-size: var(--step--1);
  font-weight: var(--wt-semibold);
  letter-spacing: var(--ls-caps);
  color: var(--ink-inverse-muted);
  font-variant-numeric: tabular-nums;
}

.lightbox__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  inline-size: 48px;
  block-size: 48px;
  color: var(--c-sand-100);
  border: 1px solid rgba(252, 245, 234, 0.24);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}

.lightbox__btn:hover {
  background-color: rgba(252, 245, 234, 0.1);
  border-color: var(--accent);
}

.lightbox__btn svg { inline-size: 22px; block-size: 22px; }

.lightbox__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  min-block-size: 0;
}

.lightbox__img {
  max-inline-size: 100%;
  max-block-size: 100%;
  object-fit: contain;
  border-radius: var(--radius-sm);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease);
}

.lightbox__img.is-loaded { opacity: 1; }

.lightbox__foot {
  min-block-size: 1.5em;
  text-align: center;
  font-size: var(--step--1);
  color: var(--ink-inverse-muted);
}

@media (max-width: 47.99rem) {
  .lightbox__stage { gap: var(--space-2xs); }
  .lightbox__nav {
    position: absolute;
    inset-block-end: var(--space-md);
    background-color: rgba(15, 11, 3, 0.7);
  }
  .lightbox__nav--prev { inset-inline-start: 0; }
  .lightbox__nav--next { inset-inline-end: 0; }
}


/* The header is sticky and therefore IN THE FLOW. Nothing needs to pad for it.
   There is no .page-top. There is no header-height offset anywhere in this file
   except --header-h itself, and only the header reads that. */


/* ==================================================================
   AUTO-SLIDER — a horizontal track that drifts on its own AND can be
   dragged / swiped / trackpad-scrolled by hand. No arrows, no dots.
   Behaviour: JS hook [data-autoslide] (js/motion.js). Respects
   prefers-reduced-motion (no auto-drift; still hand-scrollable).
   ================================================================== */
.autoslider {
  display: flex;
  gap: var(--space-md);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;                 /* Firefox */
  scroll-padding-inline-start: 2px;
  padding-block: 4px;
  cursor: grab;
}
.autoslider::-webkit-scrollbar { display: none; }   /* WebKit — hidden, drag/swipe still works */
.autoslider > * { flex: 0 0 auto; scroll-snap-align: start; }
.autoslider.is-grabbing { cursor: grabbing; scroll-snap-type: none; user-select: none; }
.autoslider.is-grabbing a,
.autoslider.is-grabbing img { pointer-events: none; }

/* Gallery laid out as a slider (reuses .gallery__item / __img / __cap looks). */
.gallery-slider > li {
  inline-size: clamp(16rem, 74vw, 27rem);   /* ~260 → 432px */
}
.gallery-slider .gallery__item {
  display: block;
  inline-size: 100%;
  aspect-ratio: 4 / 3;
  block-size: auto;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-xl);
}
@media (max-width: 47.99rem) {
  .gallery-slider .gallery__cap { display: block; }   /* keep captions on mobile slider */
}

/* ==================================================================
   GOOGLE-STYLE REVIEW CARD — for the reviews auto-slider. Deliberately
   uses a Google/Roboto look (distinct from the site's Cinzel/Open Sans)
   so it reads as a real Google review. Visual only — NO review schema.
   ================================================================== */
.greviews { padding-block: 6px; }
.greview {
  inline-size: clamp(17.5rem, 84vw, 22.5rem);    /* ~280 → 360px */
  background: #fff;
  border: 1px solid #dadce0;
  border-radius: 8px;
  padding: 20px;
  font-family: "Roboto", "Helvetica Neue", "Segoe UI", Arial, sans-serif;
  color: #3c4043;
  box-shadow: 0 1px 3px rgba(60, 64, 67, 0.12);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.greview__head { display: flex; align-items: center; gap: 12px; }
.greview__avatar {
  inline-size: 40px; block-size: 40px; border-radius: 50%;
  display: grid; place-items: center;
  color: #fff; font: 500 20px/1 "Roboto", Arial, sans-serif;
  text-transform: uppercase; flex: 0 0 auto;
}
.greview__meta { display: flex; flex-direction: column; min-inline-size: 0; }
.greview__name { font-size: 14px; font-weight: 500; color: #202124; letter-spacing: 0; }
.greview__sub { font-size: 12px; color: #70757a; }
.greview__g { margin-inline-start: auto; inline-size: 20px; block-size: 20px; flex: 0 0 auto; }
.greview__stars { display: flex; gap: 2px; }
.greview__stars svg { inline-size: 15px; block-size: 15px; display: block; }
.greview__text {
  font-size: 14px; line-height: 1.5; color: #3c4043; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 9; line-clamp: 9;
  -webkit-box-orient: vertical; overflow: hidden;
}


/* ==================================================================
   HERO BACKGROUND VIDEO — via YouTube embed. Muted, looping, NO controls,
   NO chrome, NON-INTERACTIVE (pointer-events:none so there's no pause / no
   click-through / no YouTube link). The .hero__img underneath is the poster /
   reduced-motion / blocked-autoplay fallback so the hero is never blank.
   NOTE: this loads from youtube-nocookie.com (an external request).
   ================================================================== */
.hero__ytbg {
  position: absolute;
  inset: 0;
  z-index: 0;               /* above .hero__img, below the .hero::before scrim */
  overflow: hidden;
  pointer-events: none;
}
.hero__ytbg iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(1.06);   /* slight overscan crops any chrome at the edges */
  /* Cover: whichever of these wins keeps the 16:9 frame filling the box. */
  inline-size: 100vw;
  block-size: 56.25vw;          /* 100vw × 9/16 */
  min-inline-size: 177.78vh;    /* 100vh × 16/9 */
  min-block-size: 100vh;
  border: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1s ease;
}
.hero__ytbg.is-ready iframe { opacity: 1; }
/* Transparent shield over the whole iframe — no hover, no clicks, no controls. */
.hero__ytblock { position: absolute; inset: 0; z-index: 1; background: transparent; }


/* Reviews section: a comfortable ~96px of TOP padding so it reads as its own
   band with breathing room under the full-bleed FAQ (id specificity guarantees
   this wins over any .section--flush-top left on the element). */
#reviews.section { padding-block-start: 6rem; }   /* 96px */

/* Freshman is a heavy SINGLE-WEIGHT display face (no lighter cut exists), so its
   stroke weight can't be thinned in CSS. To make the headings read lighter we
   trim their size a touch and open the tracking slightly — this takes ~20-25%
   of the visual heaviness off without changing the logo-matching letterforms. */
.hero__title,
.section-head__title,
.feature-row__title,
.bleed-image__title h1,
.bleed-image__title h2,
.photo-tile__label { font-weight: 400; letter-spacing: 0.012em; }
.hero__title        { font-size: calc(var(--step-6) * 0.88); }
.section-head__title{ font-size: calc(var(--step-5) * 0.9); }
.feature-row__title { font-size: calc(var(--step-4) * 0.92); }
.bleed-image__title > h1,
.bleed-image__title > h2 { font-size: calc(var(--step-6) * 0.88); }
@media (prefers-reduced-motion: reduce) {
  .hero__ytbg { display: none; }   /* honour reduced motion — the still shows */
}


/* ==================================================================
   FEATURE-SPLIT — 50/50 block where the IMAGE bleeds to the page edge and the
   text column is aligned to the site rail (Arzuman style). Full-bleed section
   (a direct child of the page, NOT inside .container). Reuses .feature-row__*
   type classes inside .feature-split__text.
   ================================================================== */
.feature-split { display: grid; grid-template-columns: 1fr; }
.feature-split__media { position: relative; min-block-size: clamp(20rem, 42vw, 40rem); }
.feature-split__media img { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
/* Text column: LEFT-aligned content, with EQUAL padding on all four sides so the
   island is symmetrically framed. Content fills the padded width (stretch) so the
   left and right gutters match; vertically centred so top and bottom match too. */
.feature-split__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  text-align: left;
  padding: clamp(2.5rem, 5.5vw, 5rem);   /* one value → same top/right/bottom/left */
}
.feature-split__text > * { max-inline-size: none; }
/* Comfortable, uncramped vertical rhythm between eyebrow → title → text → list. */
.feature-split__text .feature-row__title { margin-block-start: var(--space-md); }
.feature-split__text .feature-row__text { margin-block-start: var(--space-lg); }
.feature-split__text .feature-row__list { margin-block-start: var(--space-xl); text-align: left; }
.feature-split__text .feature-row__list > li + li { margin-block-start: var(--space-md); }
@media (min-width: 48rem) {
  .feature-split { grid-template-columns: 1fr 1fr; align-items: stretch; }
  .feature-split--reverse .feature-split__media { order: -1; }
}


/* ==================================================================
   FAQ-SPLIT — dark accordion panel + a full-height image with a CTA overlay
   (Arzuman FAQ, in Spartan ink instead of blue). Full-bleed section.
   ================================================================== */
.faq-split { display: grid; grid-template-columns: 1fr; }
.faq-split__panel {
  background-color: var(--c-ink-900);
  color: var(--ink-inverse);
  padding: clamp(3rem, 5.5vw, 5rem);   /* one value → equal on all four sides */
}
.faq-split__panel .section-head { align-items: flex-start; text-align: start; margin-inline: 0; max-inline-size: none; margin-block-end: var(--space-2xl); }
.faq-split__panel .section-head__title { color: var(--c-white); }
/* Accordion, flipped for the dark panel — with roomier, uncramped spacing. */
.faq-split__panel .faq__item { border-color: var(--line-inverse); }
.faq-split__panel .faq__q {
  color: var(--ink-inverse);
  padding-block: var(--space-lg);   /* taller rows, less cramped */
}
.faq-split__panel .faq__icon { color: var(--accent); }
.faq-split__panel .faq__a,
.faq-split__panel .faq__a p { color: var(--ink-inverse-muted); }
.faq-split__panel .faq__panel-inner { padding-block-end: var(--space-lg); }
.faq-split__media { position: relative; min-block-size: clamp(24rem, 46vw, 44rem); }
.faq-split__media img { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; }
.faq-split__cta {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
  padding: clamp(1.5rem, 4vw, 3rem);
  background: linear-gradient(to top, rgba(15, 11, 3, 0.72), rgba(15, 11, 3, 0) 80%);
  color: var(--c-white);
}
.faq-split__cta p {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.1;
  color: var(--c-white);
  text-shadow: 0 2px 18px rgba(15, 11, 3, 0.5);
}
@media (min-width: 48rem) {
  .faq-split { grid-template-columns: 1.05fr 0.95fr; align-items: stretch; }
}
