/* ==========================================================================
   NOVO V2 — the mockup layer. Loads AFTER shared.css and only adds; nothing
   here restyles an existing component, so the live site cannot be affected
   by accident. Espresso palette and the three faces stay — v2 changes the
   SHAPES and the MOTION, which is where the flatness actually lived.

   Design position vs HomeJab: they are a dark-UI marketplace — slick, anonymous,
   national. We are a premium local studio heading national. So v2 keeps the
   warm paper ground (nobody in this market owns it) and takes their moves:
   full-bleed media, evidence-bento, one gold phrase per headline, metric chips,
   and a single door into pricing.
   ========================================================================== */

/* ---- motion system --------------------------------------------------------
   One reveal pattern for the whole site. JS adds .v2-seen when a [data-v2r]
   element enters the viewport; children with [data-v2r-child] stagger. The
   resting state is ALWAYS the keyframe's final frame — transitions on
   class-changed properties have failed too many times in this codebase. */
@keyframes v2Rise{from{opacity:0;transform:translateY(22px);}to{opacity:1;transform:none;}}
@keyframes v2Fade{from{opacity:0;}to{opacity:1;}}
@keyframes v2Grow{from{transform:scaleX(0);}to{transform:scaleX(1);}}
/* PROGRESSIVE, as of the bare-pages incident: content is visible by DEFAULT.
   v2.js adds .v2-anim to <html> only when it has actually started and the
   observer exists — so a cached stylesheet, a blocked script, a JS error or
   any browser quirk costs the animation, never the page. The old way hid
   everything at parse time and trusted JS to un-hide it; Justin's phone saw
   pages that were "nothing but pictures" because that trust was misplaced. */
.v2-anim [data-v2r]{opacity:0;}
.v2-anim [data-v2r].v2-seen{opacity:1;animation:v2Rise .7s cubic-bezier(.16,.6,.24,1) both;}
.v2-anim [data-v2r].v2-seen [data-v2r-child]{animation:v2Rise .6s cubic-bezier(.16,.6,.24,1) both;
  animation-delay:calc(var(--v2i,0)*90ms);}
@media (prefers-reduced-motion:reduce){
  .v2-anim [data-v2r],.v2-anim [data-v2r].v2-seen,.v2-anim [data-v2r].v2-seen [data-v2r-child]{opacity:1;animation:none;}
}

/* ---- the gold phrase ------------------------------------------------------
   HomeJab's one reliably premium move: a single accent phrase inside a big
   serif headline. Ours is gold, and gets an underline that draws itself. */
/* nowrap keeps a SHORT highlight from stranding a word on its own line, but a
   multi-word phrase like "sell for more." cannot fit a 375px screen and ran
   straight off the edge. text-decoration underlines each line correctly on its
   own, so wrapping costs nothing below the breakpoint. */
/* Colour only, no underline. An underline is the web's universal signal for
   "this is a link", and a reader kept trying to click the highlighted words in
   headings (2026-09-01). The gold still lifts the phrase, and nothing here
   carries meaning that colour alone must convey — the sentence reads the same
   without it — so dropping the line costs nobody anything. */
.v2-hl{color:var(--gold-deep);white-space:nowrap;text-decoration:none;}
/* The hero sits on a dark photograph, so its highlight needs the BRIGHT gold —
   gold-deep on a dusk exterior is invisible, which is exactly how the "IL." in
   "…Hinsdale, IL." disappeared on every town page. */
.on-dark .v2-hl,.sec-dark .v2-hl,.v2-dark .v2-hl,
.v2-hero .v2-hl,.v2-cta .v2-hl{color:var(--gold);}

/* ---- hero -----------------------------------------------------------------
   Full-viewport media hero with a video slot: the <video> sits behind the
   poster image and fades in only once it can actually play, so the page never
   opens on a black rectangle waiting for a file that may not exist yet. */
/* Height is solved BACKWARDS from the seam card, not picked. The hero sits below
   an 85px sticky header, so the old flat 92svh put its bottom edge at 85 + 92% of
   the viewport -- already past the fold. The card then hangs 58px above that edge
   and stands 103px tall (197px stacked on a phone), so it landed 80-200px below
   the fold on every screen tested. Solving for "the whole card stays in frame"
   also pulls the headline up, which was the other half of the complaint
   (Justin, 2026-08-31). */
.v2-hero{position:relative;display:flex;align-items:flex-end;
  /* section{padding:100px 0} applied here too, so the box was min-height OR
     200px+content, whichever was larger -- which is why min-height alone did
     not shorten it. The wrap carries the only padding the hero needs. */
  /* Content is bottom-anchored, so the space above it is whatever happens to be
     left over -- with no floor. On pages where the copy nearly fills the hero
     that collapsed to 22px and the chip sat almost against the sticky nav
     (/commercial/, /contact/); on the home page it ballooned to 202px. A
     padding-top gives the gap a MINIMUM without changing pages that already
     have slack, since it only bites when content would otherwise reach the top.
     (Justin, 2026-08-31.) */
  padding-top:clamp(54px,8vh,96px);
  padding-bottom:0;
  --hdr-h:85px; --seam-h:103px; --seam-lift:58px;
  min-height:clamp(400px,
    calc(100svh - var(--hdr-h) - var(--seam-h) + var(--seam-lift) - 30px), 880px);
  background:var(--ink);color:var(--paper);overflow:hidden;}
@media(max-width:680px){.v2-hero{--seam-h:197px; --seam-lift:46px;}}
.v2-hero .media,.v2-hero .media img,.v2-hero .media video{position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;}
.v2-hero .media video{opacity:0;transition:opacity 1.2s ease;}
.v2-hero .media video.v2-playing{opacity:1;}
.v2-hero .media::after{content:'';position:absolute;inset:0;
  background:linear-gradient(to top,rgba(24,18,13,.82) 0%,rgba(24,18,13,.28) 46%,rgba(24,18,13,.34) 100%);}
/* The seam card cuts --seam-lift into the bottom of the hero, so a padding
   that ignores it puts the CTA right against the card (Justin, 2026-08-31).
   Clear the overlap first, then add real breathing room on top of it. */
.v2-hero .wrap{position:relative;z-index:2;width:100%;
  padding-bottom:calc(var(--seam-lift,58px) + clamp(54px,7.4vh,104px));}
.v2-hero h1{color:var(--paper);max-width:820px;font-size:clamp(40px,6vw,74px);
  letter-spacing:-.035em;line-height:1.02;margin:14px 0 18px;}
.v2-hero .lede{color:rgba(251,248,242,.82);max-width:560px;font-size:clamp(16px,1.6vw,19px);margin:0 0 26px;}
.v2-chip{display:inline-flex;align-items:center;gap:9px;background:rgba(24,18,13,.55);
  border:1px solid rgba(251,248,242,.28);border-radius:999px;padding:8px 16px;
  font-family:var(--fm);font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--paper);backdrop-filter:blur(6px);}
.v2-chip .stars{color:var(--gold);letter-spacing:.12em;}
.v2-hero .ctas{display:flex;gap:14px;flex-wrap:wrap;align-items:center;}
.v2-scrollcue{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);z-index:2;
  width:22px;height:34px;border:1.5px solid rgba(251,248,242,.5);border-radius:12px;}
.v2-scrollcue::after{content:'';position:absolute;left:50%;top:7px;width:3px;height:7px;margin-left:-1.5px;
  border-radius:2px;background:var(--gold);animation:v2Cue 1.8s ease-in-out infinite;}
@keyframes v2Cue{0%,100%{transform:translateY(0);opacity:1;}70%{transform:translateY(10px);opacity:0;}}
@media (prefers-reduced-motion:reduce){.v2-scrollcue{display:none;}}

/* ---- promise strip --------------------------------------------------------
   Four claims, five words each, straight under the hero. Nothing in it is
   aspirational — every line is enforceable today. */
.v2-strip{background:var(--ink);border-top:1px solid rgba(251,248,242,.1);padding:15px 0;}
.v2-strip .wrap{display:flex;gap:clamp(18px,4vw,44px);flex-wrap:wrap;justify-content:space-between;}
.v2-strip span{font-family:var(--fm);font-size:11px;letter-spacing:.11em;text-transform:uppercase;
  color:rgba(251,248,242,.78);display:inline-flex;align-items:center;gap:9px;}
.v2-strip span::before{content:'';width:5px;height:5px;border-radius:50%;background:var(--gold);}

/* ---- the one door ---------------------------------------------------------
   Property type first. Six tiles; picking one swaps the panel beneath it.
   This is the top of the mega-calculator and the homepage router both. */
.v2-door{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;}
@media(max-width:960px){.v2-door{grid-template-columns:repeat(3,1fr);}}
@media(max-width:560px){.v2-door{grid-template-columns:repeat(2,1fr);}}
.v2-door button{position:relative;background:var(--paper);border:1px solid var(--line);
  border-radius:14px;padding:18px 12px 15px;text-align:center;cursor:pointer;font:inherit;color:inherit;
  transition:border-color .16s ease,transform .16s ease,box-shadow .16s ease;}
.v2-door button:hover{border-color:var(--gold-deep);transform:translateY(-2px);
  box-shadow:0 14px 30px -18px rgba(21,19,15,.35);}
.v2-door button[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper);}
.v2-door .glyph{display:block;font-size:22px;line-height:1;margin-bottom:9px;}
.v2-door .nm{font-family:var(--fd);font-weight:700;font-size:14px;letter-spacing:-.01em;display:block;}
.v2-door .sub{font-family:var(--fm);font-size:11px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-soft);display:block;margin-top:4px;}
.v2-door button[aria-pressed="true"] .sub{color:rgba(251,248,242,.66);}
.v2-door-panel{display:none;margin-top:22px;}
.v2-door-panel.on{display:block;animation:v2Rise .5s cubic-bezier(.16,.6,.24,1) both;}

/* ---- full-bleed media boxes -----------------------------------------------
   The section Justin pointed at on HomeJab. Edge to edge — the grid escapes
   .wrap on purpose; the bleed is the energy. Each box is a photo (video slot
   behind it, same fade-when-playing contract as the hero) with an icon chip
   and a THREE-WORD line. No paragraphs in here, ever. */
.v2-media-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:3px;background:var(--ink);}
@media(max-width:1000px){.v2-media-grid{grid-template-columns:repeat(2,1fr);}}
@media(max-width:520px){.v2-media-grid{grid-template-columns:1fr;}}
.v2-media-box{position:relative;overflow:hidden;aspect-ratio:4/3;display:block;
  color:#fff;text-decoration:none;background:var(--ink);}
.v2-media-box img,.v2-media-box video{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;transition:transform .8s cubic-bezier(.16,.6,.24,1);}
.v2-media-box video{opacity:0;}
.v2-media-box video.v2-playing{opacity:1;transition:opacity .8s ease;}
.v2-media-box::after{content:'';position:absolute;inset:0;
  background:linear-gradient(to top,rgba(20,15,10,.88) 0%,rgba(20,15,10,.52) 22%,rgba(20,15,10,.05) 55%);
  transition:background .3s ease;}
.v2-media-box:hover img{transform:scale(1.05);}
.v2-media-box .tag{position:absolute;left:18px;bottom:16px;right:18px;z-index:2;}
.v2-media-box .tag .nm{font-family:var(--fd);font-weight:700;font-size:18px;letter-spacing:-.015em;
  display:flex;align-items:center;gap:8px;}
.v2-media-box .tag .nm .arr{font-size:15px;color:var(--gold);opacity:0;transform:translateX(-6px);
  transition:opacity .25s ease,transform .25s ease;}
.v2-media-box:hover .tag .nm .arr{opacity:1;transform:none;}
.v2-media-box .tag .sub{font-family:var(--fm);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.82);margin-top:4px;display:block;}

/* ---- evidence bento -------------------------------------------------------
   Numbers with receipts. Asymmetric two-column rows; each card must carry a
   proof element (star bar, mini timeline, chips, quote) — a bare number is
   just a claim, which is the CREP mistake. */
.v2-bento{display:grid;grid-template-columns:1fr 1.35fr;gap:16px;}
@media(max-width:880px){.v2-bento{grid-template-columns:1fr;}}
.v2-bcard{background:rgba(251,248,242,.05);border:1px solid rgba(251,248,242,.14);
  border-radius:16px;padding:28px 26px;overflow:hidden;}
.v2-bcard .v{font-family:var(--fd);font-weight:800;font-size:clamp(38px,4.6vw,54px);
  letter-spacing:-.04em;line-height:1;color:var(--paper);}
.v2-bcard .v small{font-size:.45em;font-weight:700;color:rgba(251,248,242,.6);letter-spacing:0;}
.v2-bcard .k{font-family:var(--fm);font-size:11px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--gold);margin-top:9px;}
.v2-bcard .d{font-size:14px;line-height:1.65;color:rgba(251,248,242,.66);margin-top:9px;max-width:420px;}
.v2-starbar{height:7px;border-radius:4px;background:rgba(251,248,242,.14);margin:18px 0 10px;overflow:hidden;}
.v2-starbar i{display:block;height:100%;width:100%;border-radius:4px;background:var(--gold);
  transform-origin:left;}
.v2-anim .v2-seen .v2-starbar i{animation:v2Grow 1.1s .3s cubic-bezier(.2,.7,.2,1) both;}
.v2-bquote{font-size:14px;line-height:1.6;color:rgba(251,248,242,.82);font-style:italic;}
.v2-bquote b{display:block;font-style:normal;font-family:var(--fm);font-size:11px;
  letter-spacing:.09em;text-transform:uppercase;color:rgba(251,248,242,.55);margin-top:7px;}
.v2-minitl{display:flex;align-items:center;gap:0;margin-top:20px;}
.v2-minitl .pt{flex:1;position:relative;text-align:center;}
.v2-minitl .pt::before{content:'';position:absolute;top:5px;left:0;right:0;height:2px;
  background:rgba(251,248,242,.16);}
.v2-minitl .pt:first-child::before{left:50%;} .v2-minitl .pt:last-child::before{right:50%;}
.v2-minitl .pt i{position:relative;z-index:1;display:inline-block;width:12px;height:12px;
  border-radius:50%;background:var(--gold);border:3px solid var(--room);}
.v2-minitl .pt .l1{font-family:var(--fm);font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:rgba(251,248,242,.6);margin-top:8px;}
.v2-minitl .pt .l2{font-family:var(--fm);font-size:11.5px;color:var(--gold);margin-top:2px;}
.v2-townchips{display:flex;flex-wrap:wrap;gap:7px;margin-top:18px;}
.v2-townchips a{text-decoration:none;transition:color .15s ease,border-color .15s ease,background .15s ease;}
.v2-townchips a:hover{color:var(--ink);background:var(--gold);border-color:var(--gold);}
.v2-townchips span,.v2-townchips a{font-family:var(--fm);font-size:11px;letter-spacing:.05em;text-transform:uppercase;
  color:rgba(251,248,242,.78);border:1px solid rgba(251,248,242,.2);border-radius:999px;padding:5px 11px;}
.v2-townchips span.hot{color:var(--ink);background:var(--gold);border-color:var(--gold);}

/* ---- masonry work wall ----------------------------------------------------
   CSS columns: no row grid, so no forced crops and no ragged gaps — every
   image keeps its own ratio and the columns pack themselves. This is also the
   portfolio fix. */
.v2-masonry{columns:3 300px;column-gap:12px;}
/* Phones keep two columns rather than collapsing to one: a single stack of
   full-width photos turns each section into a very long scroll. */
@media (max-width:620px){
  .v2-masonry{columns:2;column-gap:8px;}
  .v2-masonry figure{margin-bottom:8px;}
}
.v2-masonry figure{margin:0 0 12px;position:relative;overflow:hidden;border-radius:10px;
  break-inside:avoid;}
.v2-masonry img{display:block;width:100%;height:auto;transition:transform .7s cubic-bezier(.16,.6,.24,1);}
.v2-masonry figure:hover img{transform:scale(1.04);}
/* captions sit under the frame, so the figure cannot clip them */
.v2-masonry figure:has(figcaption){overflow:visible;border-radius:0;}
.v2-masonry figure:has(figcaption) img{border-radius:10px;}
.v2-masonry figcaption{font-family:var(--fm);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:7px;}

/* ---- process: metric cards ------------------------------------------------ */
.v2-proc{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;}
@media(max-width:820px){.v2-proc{grid-template-columns:1fr;}}
/* Four-item variant. The base grid is 3-across, which is right for the 3-step
   sections but leaves a lone orphan card on any set of four. This runs 4 across
   on wide screens and folds to a clean 2x2 before it collapses to one column,
   so a set of four is never ragged at any width. */
.v2-proc.four{grid-template-columns:repeat(4,1fr);}
@media(max-width:1080px){.v2-proc.four{grid-template-columns:repeat(2,1fr);}}
@media(max-width:820px){.v2-proc.four{grid-template-columns:1fr;}}
.v2-proc .step{background:var(--paper);border:1px solid var(--line);border-radius:16px;
  padding:26px 24px;position:relative;}
/* medallion numbers — the faint corner mono read as a watermark, not a marker */
.v2-proc .n{position:absolute;top:18px;right:18px;width:34px;height:34px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;background:var(--gold);color:var(--ink);
  font-family:var(--fm);font-size:12px;font-weight:600;letter-spacing:.02em;
  box-shadow:inset 0 1px 0 rgba(251,248,242,.5), inset 0 -2px 3px rgba(90,62,32,.35);}
/* The number is absolutely positioned in the top-right corner but nothing ever
   reserved room for it, so any heading long enough to reach the corner ran
   underneath it -- worst in the four-column variant, where the cards are
   narrowest (Justin, 2026-08-31). Reserve the corner; .no-n opts a section out
   of the numbers entirely, and then the heading gets its full width back. */
.v2-proc h3{margin:0 0 8px;font-size:18px;padding-right:44px;}
.v2-proc.no-n .n{display:none;}
.v2-proc.no-n h3{padding-right:0;}
.v2-proc p{margin:0 0 18px;font-size:14px;line-height:1.65;color:var(--ink-soft);}
.v2-metric{display:inline-block;font-family:var(--fm);font-size:13px;color:var(--gold-deep);
  background:var(--paper-deep);border:1px solid var(--line);border-radius:10px;padding:9px 14px;}
.v2-metric small{display:block;font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-soft);margin-top:2px;}

/* ---- review cards on photos ---------------------------------------------- */
.v2-reviews{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;}
@media(max-width:880px){.v2-reviews{grid-template-columns:1fr;}}
.v2-review{position:relative;border-radius:14px;overflow:hidden;min-height:330px;
  display:flex;align-items:flex-end;color:#fff;}
.v2-review img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.v2-review::after{content:'';position:absolute;inset:0;
  background:linear-gradient(to top,rgba(20,15,10,.9) 24%,rgba(20,15,10,.18) 70%);}
.v2-review .body{position:relative;z-index:2;padding:20px;}
.v2-review .stars{color:var(--gold);letter-spacing:.16em;font-size:13px;margin-bottom:9px;}
.v2-review p{margin:0 0 10px;font-size:14.5px;line-height:1.6;color:rgba(255,255,255,.94);}
.v2-review .who{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(255,255,255,.72);}

/* ---- coverage checker ----------------------------------------------------- */
.v2-cov{display:flex;gap:10px;flex-wrap:wrap;}
.v2-cov input{flex:1;min-width:220px;font-family:var(--ff);font-size:16px;color:var(--ink);
  background:var(--paper);border:1px solid var(--line);border-radius:999px;padding:15px 22px;}
.v2-cov input:focus{outline:2px solid var(--gold);outline-offset:2px;}
.v2-cov-out{margin-top:16px;min-height:24px;font-size:15.5px;line-height:1.6;}
.v2-cov-out .verdict{color:var(--gold-deep);font-family:var(--fm);font-size:11px;
  letter-spacing:.1em;text-transform:uppercase;display:block;margin-bottom:3px;}

/* ---- section headers, tightened ------------------------------------------- */
.v2-head{max-width:720px;margin:0 0 clamp(26px,3.4vw,40px);}
.v2-head.center{margin-left:auto;margin-right:auto;text-align:center;}
.v2-head h2{margin:10px 0 10px;font-size:clamp(28px,3.6vw,44px);letter-spacing:-.03em;}
.v2-head .lede{margin:0;font-size:16.5px;}
/* .lede carries max-width:620px from shared.css with margin:0, so inside a
   720px centered head its BOX sat flush left — text centered inside a box that
   was itself 50px off center. Every centered head on the site had this. */
.v2-head.center .lede{margin-left:auto;margin-right:auto;}
.v2-kick{font-family:var(--fm);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold-deep);display:inline-flex;align-items:center;gap:10px;}
/* The mark is a short VERTICAL bar, not the horizontal rule this used to be.
   Two reasons it changed: the long left dash had become a signature, and it was
   26px wide, which threw a centered kicker ~18px off true centre before the
   matching ::after was added. */
.v2-kick::before{content:'';width:26px;height:1px;background:currentColor;}

/* A centred kicker gets a matching bar on the right so the TEXT centres, not
   just the box. Both centring paths must be listed here: most kickers centre
   via a .v2-head.center wrapper, but 29 of them centre themselves and used to
   carry an inline justify-content, which this rule could not see — so those sat
   off-centre. They now use .v2-kick.center. If you add a third way to centre
   one, add it here too or it will drift right. */
.v2-head.center .v2-kick,.v2-kick.center{justify-content:center;}
.v2-head.center .v2-kick::after,.v2-kick.center::after{content:'';width:26px;height:1px;
  background:currentColor;}
.on-dark .v2-kick,.sec-dark .v2-kick,.v2-dark .v2-kick{color:var(--gold);}

/* ---- big closing CTA ------------------------------------------------------ */
.v2-cta{position:relative;overflow:hidden;background:var(--ink);color:var(--paper);
  text-align:center;padding:clamp(72px,10vw,130px) 0;}
.v2-cta .media,.v2-cta .media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.v2-cta .media::after{content:'';position:absolute;inset:0;background:rgba(24,18,13,.78);}
.v2-cta .wrap{position:relative;z-index:2;}
.v2-cta h2{color:var(--paper);font-size:clamp(30px,4.4vw,54px);max-width:760px;margin:0 auto 14px;}
.v2-cta .lede{color:rgba(251,248,242,.78);max-width:520px;margin:0 auto 30px;}

/* Solid closing CTA — commercial pages. The photo-backed version was reusing an
   image already shown higher up the same page, and a darkened photo behind a
   price reads as decoration rather than an offer. This is a plain espresso
   band: the price, what it buys, and the two things you can do. Keeps .v2-cta
   in the markup so shared.css's paper-painter exclusion still applies.
   (Justin, 2026-08-22) */
.v2-cta.solid{padding:clamp(56px,7vw,92px) 0;}
.v2-cta.solid .wrap{max-width:820px;}
.v2-cta.solid h2{font-size:clamp(26px,3.6vw,42px);margin-bottom:12px;}
.v2-cta.solid .cta-price{display:inline-block;font-family:var(--fm);font-size:11px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink);background:var(--gold);
  border-radius:999px;padding:6px 14px;margin-bottom:18px;}
.v2-cta.solid .lede{max-width:600px;margin-bottom:28px;}
.v2-cta.solid .cta-acts{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;}
.v2-cta.solid .cta-fine{font-family:var(--fm);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:rgba(251,248,242,.5);margin-top:22px;}

/* dark room helper for v2 sections. ALWAYS pair it with .sec-dark in markup:
   shared.css paints every main > section paper with a selector that carries an
   ID inside :not() — specificity (1,3,2) — which no plain class can outrank.
   .sec-dark is on that rule's exclusion list, so it is the key that lets this
   background actually apply. */
.v2-dark{background:var(--room);color:var(--paper);}
.v2-dark h2{color:var(--paper);}
.v2-dark .lede{color:rgba(251,248,242,.72);}
/* paper cards inside a dark room keep ink text — without this the .v2-dark
   color:paper inheritance made card h3s paper-on-paper, i.e. invisible */
.v2-dark .v2-proc .step,.sec-dark .v2-proc .step{color:var(--ink);}

/* the v2 mockup banner — so nobody mistakes the mockup for the live site */
.v2-banner{position:sticky;top:0;z-index:200;background:var(--gold);color:var(--ink);
  font-family:var(--fm);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  text-align:center;padding:6px 12px;}

/* ==========================================================================
   DEPTH PASS — the "more 3D" Justin felt on HomeJab, decomposed into five
   mechanical ingredients: (1) media that is never perfectly still, (2) large
   rounded panels floating on soft glows, (3) elements that respond to the
   cursor in space, (4) content that moves without being scrolled, and
   (5) layers moving at different speeds. Each below, all reduced-motion safe.
   ========================================================================== */

/* (1) Ken Burns: the hero poster breathes even before any video file exists.
   scale-only, 26s, alternating — slow enough to feel like weather. */
/* Ken Burns range. This used to run to 1.17, which magnified every hero by
   17% on top of the object-fit:cover crop — that is what read as "too zoomed
   in", and it also forced a 900px source to paint at ~1685px on a 1440px
   viewport, so it made the softness worse at the same time. 1.08 keeps the
   drift visible without eating the frame. */
@keyframes v2Kb{from{transform:scale(1.01);}to{transform:scale(1.08);}}
/* Ken Burns lives on the wrapper so both exposures push together — animating
   them separately lets the layers drift apart mid-dissolve. */
.v2-hero .media{animation:v2Kb 30s ease-in-out infinite alternate;transform-origin:52% 42%;}
.v2-hero .media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
/* Two animations, on purpose. The intro fires almost immediately so the
   day-to-dusk trick is seen rather than missed; the loop then takes over on a
   long, slow swing so it never reads as a flickering slideshow. */
@keyframes v2DuskIn{from{opacity:0;}to{opacity:1;}}
@keyframes v2DuskLoop{
  0%,28%{opacity:1;}
  38%,66%{opacity:0;}
  76%,100%{opacity:1;}
}
.v2-hero .media .hero-dusk{opacity:0;
  animation:v2DuskIn 2.2s ease-in-out .9s both,
            v2DuskLoop 48s ease-in-out 5.5s infinite;}
@media (prefers-reduced-motion:reduce){
  .v2-hero .media,.v2-hero .media .hero-dusk{animation:none;}
}
/* The day frame is bright behind white type, so the copy carries its own
   shadow rather than relying on the scrim alone. */
.v2-hero h1,.v2-hero .lede,.v2-hero .v2-hero-stats,.v2-hero .v2-chip{
  text-shadow:0 1px 2px rgba(20,14,9,.45),0 2px 22px rgba(20,14,9,.55);}
.v2-hero .media::after{background:linear-gradient(to top,
  rgba(24,18,13,.88) 0%,rgba(24,18,13,.55) 32%,rgba(24,18,13,.3) 62%,rgba(24,18,13,.4) 100%);}
.v2-hero .media video.v2-playing ~ img,
.v2-hero .media video.v2-playing + img{animation:none;}
@media (prefers-reduced-motion:reduce){.v2-hero .media img{animation:none;}}

/* (2) glow: a warm radial behind the evidence bento, the HomeJab teal-glow
   move in our temperature. Pure decoration, so it lives on a ::before. */
.v2-glow{position:relative;overflow:hidden;}
.v2-glow > .wrap{position:relative;z-index:1;}
.v2-glow::before{content:'';position:absolute;top:-220px;right:-160px;width:720px;height:720px;
  border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(201,162,114,.16) 0%,rgba(201,162,114,.05) 42%,transparent 68%);}
.v2-glow::after{content:'';position:absolute;bottom:-260px;left:-200px;width:640px;height:640px;
  border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(201,162,114,.09) 0%,transparent 62%);}

/* (3) cursor tilt: media boxes pivot up to 3.5deg toward the pointer. JS sets
   --tx/--ty; everything else is CSS, so touch devices simply never tilt. */
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  /* Perspective belongs on the BOX, not the grid. On the grid all eight boxes
     share one vanishing point at the grid's center, so the further a box sits
     from that center the more its rotation reads as a shear — the "twirling"
     that made the photographs look broken. perspective() inside each box's own
     transform gives every one its own vanishing point, straight through itself.

     The rotation is also halved, and the image scales a hair so that a tilted
     box never reveals the paper behind its own corner. */
  .v2-tilt .v2-media-box{
    transform:perspective(1100px) rotateX(var(--ty,0deg)) rotateY(var(--tx,0deg));
    transition:transform .22s ease-out;will-change:transform;}
  .v2-tilt .v2-media-box img{transform:scale(1.03);}
  .v2-tilt .v2-media-box:hover img{transform:scale(1.06);}
  .v2-tilt .v2-media-box:hover{z-index:2;}
}

/* (4) film strips: two auto-scrolling rows of frames, opposite directions.
   The track holds its content TWICE and translates -50%, so the loop is
   seamless at any width. Hover pauses; reduced motion degrades to a plain
   scrollable row rather than a frozen half-offscreen one. */
.v2-strip-gal{overflow:hidden;display:grid;gap:10px;padding-top:clamp(18px,3vw,34px);}
/* Film rows are a real scroll container, not a transform animation: that buys
   native touch swipe and pointer drag for free, and lets JS drive the drift by
   nudging scrollLeft. The track is doubled in the markup, so wrapping the
   scroll position by half the width loops seamlessly. */
.v2-filmrow{overflow-x:auto;overflow-y:hidden;position:relative;cursor:grab;
  scrollbar-width:none;-ms-overflow-style:none;overscroll-behavior-x:contain;}
.v2-filmrow::-webkit-scrollbar{display:none;}
.v2-filmrow.v2-grab{cursor:grabbing;user-select:none;}
.v2-filmrow.v2-grab img{pointer-events:none;}
.v2-filmrow .track{display:flex;gap:10px;width:max-content;}
.v2-filmrow figure{margin:0;position:relative;flex:none;border-radius:10px;overflow:hidden;}
.v2-filmrow img{display:block;height:clamp(180px,24vw,300px);width:auto;
  -webkit-user-drag:none;user-select:none;}

/* (5) parallax: elements with data-v2px drift a few percent slower than the
   page. JS writes --v2px in px; nothing here moves without it. */
[data-v2px]{transform:translateY(var(--v2px,0));will-change:transform;}

/* wipe tabs: one stage, two comparisons (twilight / staging). */
.v2-ba-tabs{display:flex;gap:9px;justify-content:center;margin:0 0 22px;}
.v2-ba-tabs button{font-family:var(--fm);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  background:var(--paper);border:1px solid var(--line);border-radius:999px;padding:10px 20px;
  color:var(--ink);cursor:pointer;transition:border-color .15s ease,background .15s ease,color .15s ease;}
.v2-ba-tabs button:hover{border-color:var(--gold-deep);}
.v2-ba-tabs button[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper);}
.v2-ba-pane{display:none;}
.v2-ba-pane.on{display:block;animation:v2Fade .4s ease both;}

/* ==========================================================================
   FRESH PASS — sections that are not HomeJab's. Sources of the moves:
   - Apple: scrollytelling (sticky media that changes as copy scrolls past),
     one idea per screen, short declarative sentences.
   - Tesla: stat-first hero — the numbers ARE the hero copy.
   - Anthropic: restraint as luxury; the editorial ledger over icon grids.
   - Fashion houses: the outlined word marquee as a section divider.
   ========================================================================== */

/* ---- Tesla move: hero stats. Three numbers anchored at the hero's foot —
   read before the first scroll, no section spent on them. */
.v2-hero-stats{display:flex;gap:clamp(28px,5vw,64px);margin-top:30px;flex-wrap:wrap;}
.v2-hero-stats .hs .n{font-family:var(--fd);font-weight:800;font-size:clamp(22px,2.6vw,32px);
  letter-spacing:-.03em;color:var(--paper);line-height:1;}
.v2-hero-stats .hs .l{font-family:var(--fm);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(251,248,242,.66);margin-top:6px;}

/* ---- fashion-house divider: an outlined word marquee. Decorative, so it is
   aria-hidden and cheap; the outline keeps it quiet enough to sit between
   sections without shouting. */
.v2-words{overflow:hidden;padding:clamp(18px,3vw,30px) 0;border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);background:var(--paper);}
/* The film rows and this marquee used to share @keyframes v2Film. When the
   rows became JS-driven scroll containers those keyframes were deleted with
   them, and this quietly stopped moving — an animation naming a keyframe that
   does not exist fails silently. Its own keyframes now. */
@keyframes v2Marquee{from{transform:translateX(0);}to{transform:translateX(-50%);}}
.v2-words .track{display:flex;gap:0;width:max-content;
  animation:v2Marquee 40s linear infinite;will-change:transform;}
.v2-words span{font-family:var(--fd);font-weight:800;font-size:clamp(34px,5vw,64px);
  letter-spacing:-.02em;white-space:nowrap;padding-right:.55em;
  color:transparent;-webkit-text-stroke:1.5px rgba(59,47,38,.34);}
.v2-words span.solid{color:var(--gold-deep);-webkit-text-stroke:0;}
@media (prefers-reduced-motion:reduce){.v2-words .track{animation:none;}}

/* ---- Apple move: the case study. Copy steps scroll on the left; the media
   pane on the right is sticky and crossfades to match the active step. On
   narrow screens the pane hides and each step shows its own image inline. */
.v2-story{display:grid;grid-template-columns:1fr 1.15fr;gap:clamp(28px,4vw,64px);align-items:start;}
.v2-story-steps .step{min-height:52vh;display:flex;flex-direction:column;justify-content:center;
  padding:6vh 0;}
.v2-story-steps .k{font-family:var(--fm);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--gold-deep);margin-bottom:10px;}
.v2-story-steps h3{font-size:clamp(22px,2.6vw,32px);letter-spacing:-.025em;margin:0 0 10px;}
.v2-story-steps p{margin:0;font-size:15.5px;line-height:1.7;color:var(--ink-soft);max-width:400px;}
.v2-story-steps .step img{display:none;}
/* Centered in the viewport, not pinned under the header — the pane should sit
   level with whichever step you are reading. max() keeps it clear of the bar
   on short screens. */
.v2-story-media{position:sticky;top:max(84px, calc(50vh - min(36vh, 320px)));height:min(72vh,640px);border-radius:16px;overflow:hidden;
  box-shadow:0 30px 70px -30px rgba(21,19,15,.4);}
.v2-story-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .6s ease;}
.v2-story-media img.on{opacity:1;}
.v2-story-media .cap{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:26px 18px 14px;
  font-family:var(--fm);font-size:11px;letter-spacing:.11em;text-transform:uppercase;color:#fff;
  background:linear-gradient(to top,rgba(20,15,10,.72),transparent);}
@media (max-width:880px){
  .v2-story{grid-template-columns:1fr;}
  .v2-story-media{display:none;}
  .v2-story-steps .step{min-height:0;padding:26px 0;}
  .v2-story-steps .step img{display:block;width:100%;height:auto;border-radius:12px;margin:0 0 16px;}
}

/* ---- THE DOOR, three ways. One data-door-style attribute on the wrapper
   decides which variant renders; the switcher chip row (mockup chrome) sets
   it and remembers the choice, so Justin can flip between them live. */
.v2-doorwrap > [data-doorvariant]{display:none;}
.v2-doorwrap[data-door-style="tiles"]  > [data-doorvariant="tiles"],
.v2-doorwrap[data-door-style="panels"] > [data-doorvariant="panels"],
.v2-doorwrap[data-door-style="ledger"] > [data-doorvariant="ledger"]{display:block;
  animation:v2Fade .35s ease both;}

/* Variant B — image panels: the services treatment, two columns, real work
   behind every property type. */
.v2-door-panels{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
@media (max-width:640px){.v2-door-panels{grid-template-columns:1fr;}}
.v2-door-panels .v2-media-box{aspect-ratio:5/2;border-radius:12px;}
@media (max-width:640px){.v2-door-panels .v2-media-box{aspect-ratio:3/1.2;}}

/* Variant C — the ledger: oversized editorial rows, a floating preview that
   swaps with the hovered row. The Anthropic answer to an icon grid. */
.v2-ledger{position:relative;}
.v2-ledger .rows{border-top:1px solid var(--line);}
.v2-ledger a{display:grid;grid-template-columns:auto 1fr auto auto;gap:clamp(14px,2.6vw,30px);
  align-items:baseline;padding:clamp(16px,2.4vw,24px) 0;border-bottom:1px solid var(--line);
  text-decoration:none;color:inherit;transition:padding-left .25s ease;}
.v2-ledger a:hover{padding-left:14px;}
.v2-ledger .idx{font-family:var(--fm);font-size:12px;color:var(--gold-deep);}
.v2-ledger .nm{font-family:var(--fd);font-weight:800;letter-spacing:-.03em;
  font-size:clamp(24px,3.6vw,44px);line-height:1.05;}
.v2-ledger a:hover .nm{color:var(--gold-deep);}
.v2-ledger .sub{font-family:var(--fm);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-soft);}
.v2-ledger .arr{font-size:20px;color:var(--gold-deep);opacity:0;transform:translateX(-8px);
  transition:opacity .22s ease,transform .22s ease;}
.v2-ledger a:hover .arr{opacity:1;transform:none;}
.v2-ledger-preview{position:absolute;right:0;top:-40px;width:min(330px,30%);aspect-ratio:3/2;
  border-radius:12px;overflow:hidden;pointer-events:none;opacity:0;transform:rotate(2deg) scale(.97);
  transition:opacity .28s ease,transform .28s ease;box-shadow:0 26px 60px -24px rgba(21,19,15,.45);z-index:3;}
.v2-ledger-preview img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .3s ease;}
.v2-ledger-preview img.on{opacity:1;}
.v2-ledger.previewing .v2-ledger-preview{opacity:1;transform:rotate(0) scale(1);}
@media (max-width:880px),(hover:none){.v2-ledger-preview{display:none;}}

/* The mobile book bar (fixed bottom "See your price" + "Get in touch") was
   removed 2026-08-31: the sticky header already carries the same CTA, so the
   bar showed a second copy of the primary action and took 74px off every phone
   screen for it. .v2-bar-off and the body padding went with it. */

/* ---- door variant D: the ledger, with a thumbnail per row ----------------
   Justin's "in-between": the ledger's editorial calm, but photographic —
   without becoming a third wall of images on a page that already has two.
   Each row carries one small square frame; the hover preview is retired
   because the thumbnail is already the preview. */
.v2-ledger.with-thumbs a{grid-template-columns:auto auto 1fr auto auto;}
.v2-ledger .thumb{width:clamp(52px,6vw,72px);aspect-ratio:1/1;border-radius:10px;overflow:hidden;
  flex:none;align-self:center;background:var(--paper-deep);}
.v2-ledger .thumb img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .5s cubic-bezier(.16,.6,.24,1);}
.v2-ledger a:hover .thumb img{transform:scale(1.08);}
@media (max-width:560px){
  .v2-ledger.with-thumbs a{grid-template-columns:auto auto 1fr auto;}
  .v2-ledger.with-thumbs .sub{display:none;}
}
/* The row is [idx][name][sub][arrow] with the NAME on 1fr and the sub on auto,
   so a long description takes its full width and the name gets the remainder.
   On a phone that squeezed "Floor Plans & CAD" into four lines next to the
   longest sub on the page (Justin, 2026-08-31). Below 640px the sub drops to
   its own line under the name, which is the only column that needs the width. */
@media (max-width:640px){
  .v2-ledger a{grid-template-columns:auto 1fr auto;row-gap:7px;align-items:center;}
  .v2-ledger .idx,.v2-ledger .nm,.v2-ledger .arr{grid-row:1;}
  .v2-ledger .sub{grid-row:2;grid-column:2 / -1;}
  .v2-ledger.with-thumbs a{grid-template-columns:auto auto 1fr auto;}
}

/* The secondary path is still there, but it stops looking like a second offer.
   One button on the page, and it goes to the calculator. */
.v2-quiet{font-family:var(--fm);font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);text-decoration:underline;text-underline-offset:4px;
  text-decoration-color:var(--line);align-self:center;}
.v2-quiet:hover{color:var(--ink);text-decoration-color:var(--gold-deep);}
.sec-dark .v2-quiet,.v2-dark .v2-quiet,.v2-cta .v2-quiet{color:rgba(251,248,242,.72);
  text-decoration-color:rgba(251,248,242,.32);}
.sec-dark .v2-quiet:hover,.v2-dark .v2-quiet:hover,.v2-cta .v2-quiet:hover{color:var(--paper);
  text-decoration-color:var(--gold);}

/* ---- pricing page: static package table, cards and add-ons ----------------
   The table exists so prices are in the HTML itself. Crawlers do not run the
   calculator, so anything only the calculator knows is invisible to them. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;}
.pr-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:26px 0 0;
  border:1px solid var(--line);border-radius:var(--radius-lg);background:var(--paper);}
.pr-table{width:100%;border-collapse:collapse;min-width:560px;}
.pr-table th,.pr-table td{padding:13px 16px;text-align:right;border-bottom:1px solid var(--line);
  font-size:15px;white-space:nowrap;}
.pr-table thead th{font-family:var(--fm);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-soft);font-weight:500;background:var(--paper-deep,#f2ede2);}
.pr-table tbody th{text-align:left;font-weight:600;font-size:14.5px;}
.pr-table tbody tr:last-child th,.pr-table tbody tr:last-child td{border-bottom:0;}
.pr-table tbody tr:hover td,.pr-table tbody tr:hover th{background:var(--paper-deep,#f2ede2);}
.pr-foot{font-family:var(--fm);font-size:11.5px;letter-spacing:.04em;color:var(--ink-soft);
  margin:12px 0 0;text-align:center;}

/* The site wrapper is 1180px, which gives four cards ~280px each and breaks
   bullets mid-phrase. The packages section gets its own wider rail. */
.pr-wide{max-width:1400px;}
.pr-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin:30px 0 0;
  align-items:stretch;}
@media(max-width:1000px){.pr-cards{grid-template-columns:repeat(2,1fr);}}
@media(max-width:600px){.pr-cards{grid-template-columns:1fr;}}
.pr-card{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-lg);
  padding:26px 24px;position:relative;display:flex;flex-direction:column;}
/* Premium is the featured card — the homepage tier-card espresso treatment.
   Paper cards keep ink text; this one flips to the dark room with gold. */
.pr-card.pr-best{background:var(--ink);border-color:var(--ink);color:var(--paper);
  box-shadow:0 26px 54px -24px rgba(21,19,15,.55);}
.pr-card.pr-best h3{color:var(--paper);}
.pr-card.pr-best .pr-price{color:var(--gold);}
.pr-card.pr-best .pr-desc{color:rgba(251,248,242,.78);}
.pr-card.pr-best .v2-inc li{color:rgba(251,248,242,.85);border-top-color:rgba(251,248,242,.14);}
.pr-card.pr-best .v2-inc li::before{color:var(--gold);}
/* every card's price carries the accent */
.pr-card .pr-price{color:var(--gold-deep);}
/* the Premium COLUMN in the worked-examples table gets a warm tint + its flag */
.pr-table th.col-best,.pr-table td.col-best{background:rgba(201,162,114,.14);}
.pr-table thead th.col-best{color:var(--gold-deep);}
.pr-flag{position:absolute;top:-10px;left:20px;background:var(--gold);color:var(--ink);
  font-family:var(--fm);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  padding:4px 10px;border-radius:999px;}
.pr-card h3{font-size:17px;letter-spacing:-.015em;margin:0 0 4px;}
.pr-price{font-family:var(--fd);font-weight:800;font-size:26px;letter-spacing:-.03em;margin:0 0 8px;}
.pr-price small{font-family:var(--fm);font-size:11px;font-weight:400;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-soft);}
.pr-desc{font-size:14px;line-height:1.55;color:var(--ink-soft);margin:0;}
.pr-card .v2-inc{grid-template-columns:1fr;margin:14px 0 0;}
.pr-card .v2-inc li{font-size:14px;line-height:1.5;padding:8px 0 8px 20px;}
.pr-card .v2-inc li::before{left:0;}

.pr-addons{display:grid;grid-template-columns:repeat(2,1fr);gap:0 34px;margin:26px 0 0;}
@media(max-width:640px){.pr-addons{grid-template-columns:1fr;}}
/* Every row is the same height whether or not it carries a qualifier, so the
   price column reads as a clean column of numbers rather than a ragged list.
   The qualifier sits under the NAME — putting it under the price was what made
   staging, declutter and virtual twilight look broken next to the plain rows. */
.pr-addon{display:flex;align-items:center;justify-content:space-between;gap:18px;
  min-height:68px;padding:12px 0;border-bottom:1px solid var(--line);}
.pr-addon .nm{font-size:15.5px;line-height:1.35;}
.pr-addon .nm small{display:block;font-family:var(--fm);font-size:10.5px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:4px;}
.pr-addon b{font-family:var(--fd);font-weight:700;font-size:18px;white-space:nowrap;
  font-variant-numeric:tabular-nums;}
.pr-note{font-family:var(--fm);font-size:11px;line-height:1.6;letter-spacing:.03em;
  color:var(--ink-soft);margin:14px 0 0;padding-top:12px;border-top:1px solid var(--line);}

/* A worked two-column example: same list price, different size, different
   number. Concrete beats explanation for a pricing argument. */
/* the worked-example panel lives on the dark method band now — pin its ink
   so nothing inherits the room's paper color (same trap as the step cards) */
.v2-dark .pr-worked,.sec-dark .pr-worked{color:var(--ink);}
.pr-worked{margin:30px 0 0;padding:24px 26px;background:var(--paper);
  border:1px solid var(--line);border-radius:var(--radius-lg);}
/* Town-page worked example: a three-row ledger, name / what you get / price.
   The note column is the one that must be allowed to wrap, so the price stays
   pinned right and the rows never go ragged. Below 620px the note drops under
   the name rather than squeezing into a third of a phone screen. */
.wk-row{display:grid;grid-template-columns:auto 1fr auto;gap:6px 18px;align-items:baseline;
  padding:15px 0;border-top:1px solid var(--line);}
.wk-row:first-child{border-top:0;padding-top:4px;}
.wk-name{font-family:var(--fd);font-weight:700;font-size:17px;letter-spacing:-.01em;}
.wk-note{font-size:13.5px;color:var(--ink-soft);line-height:1.5;}
.wk-price{font-family:var(--fd);font-weight:800;font-size:21px;letter-spacing:-.02em;
  white-space:nowrap;}
@media(max-width:620px){
  .wk-row{grid-template-columns:1fr auto;}
  .wk-note{grid-column:1 / -1;order:3;}
}
.wk-foot{margin:20px 0 0;font-size:14px;line-height:1.65;color:var(--ink-soft);text-align:center;}
.pr-worked-lead{margin:0 0 18px;font-size:15.5px;color:var(--ink-soft);text-align:center;}
.pr-worked-grid{display:grid;grid-template-columns:1fr auto 1fr;gap:18px;align-items:center;}
@media(max-width:600px){.pr-worked-grid{grid-template-columns:1fr;gap:10px;}}
.pr-worked-col{text-align:center;padding:16px 12px;background:var(--paper-deep,#f2ede2);
  border-radius:12px;}
.pr-worked-col .k{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-soft);}
.pr-worked-col .t{display:block;font-size:14px;color:var(--ink-soft);margin-top:3px;}
.pr-worked-col b{display:block;font-family:var(--fd);font-weight:800;font-size:30px;
  letter-spacing:-.03em;margin-top:8px;font-variant-numeric:tabular-nums;}
.pr-worked-vs{font-family:var(--fm);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold-deep);}
.pr-worked-note{margin:18px 0 0;font-size:14.5px;line-height:1.6;color:var(--ink-soft);
  text-align:center;max-width:62ch;margin-left:auto;margin-right:auto;}

/* ---- pricing: shoot-type breakdowns --------------------------------------
   Native <details> so every price sits in the raw HTML. Crawlers do not run
   the calculator, and they do not click tabs either. */
.pt-list{margin:26px 0 0;border-top:1px solid var(--line);}
.pt-item{border-bottom:1px solid var(--line);}
.pt-item summary{list-style:none;cursor:pointer;padding:20px 40px 20px 0;position:relative;
  display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 16px;}
.pt-item summary::-webkit-details-marker{display:none;}
.pt-item summary::after{content:'';position:absolute;right:8px;top:50%;width:9px;height:9px;
  margin-top:-6px;border-right:2px solid var(--gold-deep);border-bottom:2px solid var(--gold-deep);
  transform:rotate(45deg);transition:transform .2s ease;}
.pt-item[open] summary::after{transform:rotate(-135deg);margin-top:-2px;}
.pt-item summary:hover .pt-name{color:var(--gold-deep);}
.pt-name{font-family:var(--fd);font-weight:700;font-size:18px;letter-spacing:-.02em;
  transition:color .15s ease;}
.pt-meta{font-family:var(--fm);font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);}
.pt-body{padding:0 0 26px;max-width:64ch;}
.pt-body p{font-size:15px;line-height:1.7;color:var(--ink-soft);margin:0 0 14px;}
.pt-body .v2-inc{grid-template-columns:1fr;margin:0 0 14px;}
.pt-note{font-size:14px!important;padding:11px 15px;background:var(--paper-deep);
  border-left:2px solid var(--gold-deep);margin:0 0 16px!important;}
.pt-table{width:100%;border-collapse:collapse;margin:0 0 16px;font-size:14.5px;}
.pt-table th{font-family:var(--fm);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold-deep);font-weight:500;text-align:left;padding:8px 10px;
  border-bottom:1px solid var(--line);}
.pt-table td{padding:10px;border-bottom:1px solid var(--line);}
.pt-table td:not(:first-child){text-align:right;font-family:var(--fd);font-weight:700;
  font-variant-numeric:tabular-nums;white-space:nowrap;}
.pt-table th:not(:first-child){text-align:right;}
@media(max-width:560px){
  .pt-item summary{padding-right:32px;}
  .pt-name{font-size:16.5px;}
}

/* ---- pricing door, thumbnail variant -------------------------------------
   The emoji tiles read as a toy next to real photography. Same control, same
   behavior — a picture of the thing instead of a pictogram of it. Three across
   on desktop (two rows), two across on phones (three rows), as asked. */
.v2-door.door-thumbs{grid-template-columns:repeat(3,1fr);gap:14px;}
.v2-door.door-thumbs button{display:flex;align-items:center;gap:14px;text-align:left;
  padding:12px 16px 12px 12px;}
.door-thumbs .th{flex:none;width:62px;height:62px;border-radius:10px;overflow:hidden;
  background:var(--paper-deep);}
.door-thumbs .th img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .5s cubic-bezier(.16,.6,.24,1);}
.door-thumbs button:hover .th img{transform:scale(1.07);}
.door-thumbs .txt{min-width:0;}
/* Every tile sub reserves two lines. A one-word-longer label (e.g. "Residential
   · Commercial" on the virtual-tour tile) wraps at these widths, and without
   the reservation only THAT tile's row grows — measured 176px against 160px for
   the rows above it, which reads as the grid stepping. Reserving the space on
   all six keeps the rows identical whatever the labels say later. */
@media(max-width:900px){.door-thumbs .sub{min-height:33px;}
  /* 9 tiles in the 2-col mobile grid leaves an orphan — a lone last tile
     spans the full row so the grid closes clean at any count */
  .v2-door.door-thumbs > button:last-child:nth-child(odd){grid-column:1 / -1;}}
.door-thumbs .nm{display:block;margin:0;}
.door-thumbs .sub{display:block;margin-top:3px;}
@media(max-width:960px){
  .v2-door.door-thumbs{grid-template-columns:repeat(2,1fr);}
}
@media(max-width:560px){
  .v2-door.door-thumbs{grid-template-columns:repeat(2,1fr);gap:10px;}
  .v2-door.door-thumbs button{flex-direction:column;align-items:flex-start;gap:10px;padding:12px;}
  .door-thumbs .th{width:100%;height:82px;}
  .door-thumbs .nm{font-size:13.5px;}
  .door-thumbs .sub{font-size:10px;}
}

/* First-booking offer. Sits with the guarantee because it does the same job —
   removing the reason to hesitate — and it is a gift, not a discount, so it
   never appears as a struck-through price. */
.v2-firsttime{display:flex;gap:20px;align-items:flex-start;margin:26px 0 0;padding:22px 26px;
  background:var(--ink);color:var(--paper);border-radius:var(--radius-lg);}
.v2-firsttime .ft-tag{flex:none;font-family:var(--fm);font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;background:var(--gold);color:var(--ink);border-radius:999px;
  padding:5px 12px;margin-top:3px;white-space:nowrap;}
.v2-firsttime h3{color:var(--paper);margin:0 0 7px;font-size:19px;letter-spacing:-.02em;line-height:1.25;}
.v2-firsttime p{margin:0;color:rgba(251,248,242,.8);font-size:14.5px;line-height:1.65;max-width:70ch;}
@media(max-width:600px){
  .v2-firsttime{flex-direction:column;gap:12px;padding:20px;}
  .v2-firsttime h3{font-size:17px;}
}

/* Placeholder that stands in for the calculator until a property type is
   chosen. Says what will happen rather than sitting there blank. */
.door-empty{display:flex;gap:16px;align-items:center;justify-content:center;text-align:left;
  padding:30px 28px;border:1px dashed #c3b7a0;border-radius:var(--radius-lg);
  background:var(--paper);}
.door-empty[hidden]{display:none;}
.door-empty .de-mark{font-size:22px;color:var(--gold-deep);line-height:1;flex:none;}
.door-empty p{margin:0;font-size:15.5px;line-height:1.6;color:var(--ink-soft);max-width:52ch;}
.door-empty strong{color:var(--ink);}
@media(max-width:600px){
  .door-empty{padding:22px 20px;gap:12px;align-items:flex-start;}
  .door-empty p{font-size:14.5px;}
}

/* The pricing intro sits directly under 115px of sticky chrome on a phone, so
   the section's own 64px top padding read as a hole above the eyebrow. Desktop
   has room for it; phones do not. */
@media(max-width:640px){
  section[data-express-intro]{padding-top:26px;}
}

@media(max-width:700px){
  .v2-hl{white-space:normal;}
  /* 40px was the clamp floor, which is a lot of type for a 375px screen once
     the highlight is allowed to wrap. */
  .v2-hero h1{font-size:clamp(31px,8.2vw,40px);}
}

/* ---- transplants from the layering study (Justin-approved 2026-08-20) ------ */

/* 14 · seam card: straddles the hero -> body boundary. In flow, pulled up over
   the hero's bottom edge. Numbers live in raw HTML for crawlers. */
.v2-seamwrap{position:relative;z-index:5;margin:-58px 0 0;padding:0 20px;}
.v2-seamcard{max-width:880px;margin:0 auto;background:var(--paper);border:1px solid var(--line);
  border-radius:14px;box-shadow:0 30px 70px -26px rgba(21,19,15,.4);
  display:grid;grid-template-columns:repeat(4,1fr);}
.v2-seamcard .st{padding:20px 16px;text-align:center;border-left:1px solid var(--line);}
.v2-seamcard .st:first-child{border-left:0;}
.v2-seamcard b{font-family:var(--fd);font-weight:900;font-size:clamp(18px,2.2vw,25px);
  letter-spacing:-.02em;display:block;}
.v2-seamcard span{font-family:var(--fm);font-size:10px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-soft);display:block;margin-top:5px;line-height:1.5;}
@media(max-width:680px){
  .v2-seamwrap{margin-top:-46px;padding:0 14px;}
  .v2-seamcard{grid-template-columns:1fr 1fr;}
  .v2-seamcard .st:nth-child(3){border-left:0;}
  .v2-seamcard .st:nth-child(n+3){border-top:1px solid var(--line);}
}

/* 13 · photo pile: two overlapping frames on a gold plate. Layered shadows do
   the depth; the second frame gets a paper border like a print. */
.v2-pile{position:relative;padding:30px 30px 30px 0;}
.v2-pile .plate{position:absolute;right:0;top:0;width:72%;height:82%;
  background:var(--gold);border-radius:12px;opacity:.9;}
.v2-pile img{position:relative;border-radius:12px;width:82%;z-index:2;
  box-shadow:0 42px 80px -28px rgba(21,19,15,.55), 0 12px 24px -10px rgba(21,19,15,.35),
    0 2px 5px rgba(21,19,15,.18);}
.v2-pile img + img{width:52%;margin:-26% 0 0 52%;z-index:3;border:6px solid var(--paper);
  box-shadow:0 48px 90px -30px rgba(21,19,15,.6), 0 16px 30px -12px rgba(21,19,15,.4),
    0 3px 7px rgba(21,19,15,.2);}
/* Role swap: hovering the small frame promotes it — it grows toward the pile's
   centre while the big frame recedes and dims. Hover-capable devices only;
   touch gets the same swap via a tap toggle (.swap) from v2.js. */
.v2-pile img{transition:transform .5s cubic-bezier(.2,.7,.2,1), filter .5s ease;}
.v2-pile img + img{cursor:pointer;transform-origin:82% 88%;}
@media(hover:hover){
  .v2-pile img + img:hover{transform:scale(1.55);z-index:4;}
  .v2-pile:has(img + img:hover) img:first-of-type{transform:scale(.9);
    filter:saturate(.75) brightness(.94);}
}
.v2-pile img.zoom{transform:scale(1.5);z-index:5;}
.v2-pile.swap img:first-of-type{transform:scale(.9);filter:saturate(.75) brightness(.94);}

/* 18 · review tiles: three listing tiles switch the big quote. Every quote is
   in the DOM (crawlers read all three); JS only toggles which one shows. */
/* The star glyphs carry a 7px letter-spacing and the label is nowrap, so this row
   measured 414px inside a 335px column at 375px wide — it widened the whole layout,
   which is what let the page pan sideways on a phone. The fixed .v2-bookbar then
   resolved its left:0/right:0 against that widened box and looked like the culprit.
   Allow the row to wrap, and let the label break on the narrowest screens. */
.v2-revstars{font-size:28px;letter-spacing:7px;color:var(--gold);display:flex;
  align-items:center;gap:20px;line-height:1;margin:4px 0 18px;
  flex-wrap:wrap;row-gap:9px;max-width:100%;}
.v2-revstars small{font-family:var(--fm);font-size:11.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-soft);white-space:nowrap;min-width:0;}
@media(max-width:520px){
  .v2-revstars{font-size:23px;letter-spacing:5px;gap:12px;}
  .v2-revstars small{white-space:normal;letter-spacing:.09em;}
}
.sec-dark .v2-revstars small,.v2-dark .v2-revstars small{color:rgba(251,248,242,.65);}
.v2-revlay{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  gap:clamp(24px,4vw,56px);align-items:center;}
.v2-revtiles{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;}
.v2-rt{position:relative;border:0;padding:0;border-radius:14px;overflow:hidden;cursor:pointer;
  aspect-ratio:4/5;background:var(--paper-deep);
  box-shadow:0 22px 48px -20px rgba(21,19,15,.4);transition:transform .3s ease, box-shadow .3s ease;}
.v2-rt img{width:100%;height:100%;object-fit:cover;display:block;
  filter:saturate(.55) brightness(.94);transition:filter .3s ease;}
.v2-rt span{position:absolute;left:8px;bottom:8px;font-family:var(--fm);font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;color:#fff;background:rgba(15,11,7,.72);
  border-radius:5px;padding:4px 8px;}
.v2-rt:nth-child(1){margin-top:24px;}
.v2-rt:nth-child(3){margin-top:48px;}
@keyframes v2RtBob1{0%,100%{transform:translateY(0);}50%{transform:translateY(-9px);}}
@keyframes v2RtBob2{0%,100%{transform:translateY(0);}50%{transform:translateY(7px);}}
.v2-rt:nth-child(odd){animation:v2RtBob1 6s ease-in-out infinite;}
.v2-rt:nth-child(2){animation:v2RtBob2 7s ease-in-out infinite;}
.v2-rt[aria-pressed="true"]{animation:none;transform:translateY(-6px);
  box-shadow:0 34px 64px -22px rgba(21,19,15,.55);}
.v2-rt[aria-pressed="true"] img{filter:none;}
.v2-rt[aria-pressed="true"]::after{content:'';position:absolute;inset:-10px;
  border:2px solid var(--gold);border-radius:20px;pointer-events:none;}
@media(prefers-reduced-motion:reduce){.v2-rt{animation:none;}}
.v2-revq{display:none;}
.v2-revq.on{display:block;}
.v2-revq blockquote{margin:0;font-family:var(--fd);font-weight:800;letter-spacing:-.025em;
  font-size:clamp(19px,2.6vw,28px);line-height:1.25;}
.v2-revq blockquote::before{content:'\201C';display:block;font-size:60px;line-height:.5;
  color:var(--gold);margin:0 0 14px;}
.v2-revq cite{display:block;font-style:normal;font-family:var(--fm);font-size:11.5px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink-soft);margin-top:16px;}
@media(max-width:780px){.v2-revlay{grid-template-columns:1fr;}
  .v2-revtiles{max-width:420px;}}
/* no-JS: every quote shows stacked, tiles still render */
.no-js .v2-revq{display:block;margin-bottom:18px;}

/* ---- pricing-page variation pass (2026-08-20) ---- */
.sec-dark .v2-head h2,.v2-dark .v2-head h2{color:var(--paper);}
.sec-dark .v2-head .lede,.v2-dark .v2-head .lede{color:rgba(251,248,242,.75);}

/* 19 · the dusk satin ground, as a section treatment. The texture is base1's
   sculpt remapped to our palette; 100% 100% because the pleats live at the
   file's edges — cover crops them away (learned the hard way). Elevation
   shadows lift the band proud of its neighbours. */
.v2-ground-dusk{position:relative;background:var(--dusk-1);
  box-shadow:0 -26px 48px -30px rgba(21,19,15,.35), 0 26px 48px -30px rgba(21,19,15,.35);}
.v2-ground-dusk::before{content:'';position:absolute;inset:0;pointer-events:none;
  background:url('../assets/textures/novo-satin-dusk.jpg') center/100% 100% no-repeat;}
.v2-ground-dusk > *{position:relative;z-index:1;}

/* 16 · the underlap: espresso panel biting into a large photo. v3 proportions
   from the study — the photo is the landscape, the card is a compact panel. */
.v2-under{position:relative;display:grid;grid-template-columns:minmax(0,.36fr) minmax(0,.64fr);
  align-items:center;gap:0;padding:26px 0;}
.v2-under .block{position:relative;z-index:2;background:var(--ink);border-radius:var(--radius-lg);
  padding:clamp(22px,3vw,36px);margin-right:clamp(-90px,-10vw,-150px);color:var(--paper);
  max-width:430px;justify-self:end;
  box-shadow:0 46px 90px -28px rgba(21,19,15,.62), 0 16px 32px -14px rgba(21,19,15,.4);}
.v2-under .block .v2-kick{color:var(--gold);}
.v2-under .block h3{font-family:var(--fd);font-weight:900;font-size:clamp(19px,2.1vw,25px);
  letter-spacing:-.025em;margin:10px 0 8px;color:var(--paper);}
.v2-under .block p{margin:0 0 16px;max-width:34ch;font-size:13.5px;color:rgba(251,248,242,.8);}
/* crisp pill: inner bevel + tight neutral shadow, no glow (settled 2026-08-20) */
.v2-under .block .pill{box-shadow:
    inset 0 1px 0 rgba(251,248,242,.55), inset 0 -2px 4px rgba(90,62,32,.45),
    0 3px 8px -2px rgba(21,19,15,.5);}
.v2-under figure{margin:0;position:relative;z-index:1;border-radius:var(--radius-lg);
  overflow:hidden;box-shadow:0 30px 64px -26px rgba(21,19,15,.45);}
.v2-under img{width:100%;height:100%;object-fit:cover;aspect-ratio:16/10;}
@media(max-width:780px){
  .v2-under{grid-template-columns:1fr;padding:12px 0;}
  .v2-under .block{margin:0 16px -72px 16px;z-index:2;max-width:none;justify-self:stretch;}
}

/* ---- person-hero (why-novo): the page argues "you're hiring a person", so
   the hero IS the person — a true cutout standing on the band, cropped by its
   bottom edge (Justin's reference: his coaching-site hero, light-themed here).
   Ground is the satin-paper texture from the layering study. Desktop: text
   left, cutout right. Mobile: text first, cutout below, bottom-anchored. ---- */
/* The satin-paper texture read as a smudgy overlay behind the portrait rather
   than as paper — the mobile rule below already disabled it for the same reason.
   Plain paper with a soft warm falloff behind the figure instead.
   (Justin, 2026-08-28.) */
.v2-hero-person{position:relative;overflow:hidden;color:var(--ink);padding:0;
  background:radial-gradient(120% 90% at 78% 62%,rgba(201,162,114,.13),transparent 62%),
             var(--paper);
  box-shadow:0 26px 48px -30px rgba(21,19,15,.25);}
.v2-hero-person .wrap{--hp:clamp(56px,9vw,116px);position:relative;}
/* the offset was pulling the figure up past the section edge, so the top of the
   head sat hard against the top of the page — dropped so there is real headroom */
.v2-hero-person .media{position:absolute;right:0;bottom:calc(-1*var(--hp));top:calc(-1*var(--hp) + clamp(84px,9vw,136px));
  width:min(42%,500px);pointer-events:none;}
.v2-hero-person .media img{position:absolute;bottom:0;right:0;width:100%;max-height:100%;
  object-fit:contain;object-position:bottom center;
  filter:drop-shadow(0 34px 44px rgba(21,19,15,.32));}
.v2-hero-person .wrap{z-index:2;padding-top:var(--hp);padding-bottom:var(--hp);}
.v2-hero-person h1{max-width:13ch;text-wrap:balance;}
.v2-hero-person .lede{max-width:42ch;}
@media(max-width:820px){
  .v2-hero-person{padding-bottom:0;}
  .v2-hero-person .wrap{--hp:clamp(40px,7vw,56px);}
  .v2-hero-person h1{max-width:none;margin-top:8px;}
  .v2-hero-person .lede{margin-bottom:4px;}
  .v2-hero-person .media{position:relative;inset:auto;width:100%;height:min(100vw,440px);
    margin:2px 0 0;display:block;}
  .v2-hero-person .media img{right:auto;left:50%;transform:translateX(-50%);width:auto;
    height:100%;}
  .v2-hero-person .wrap{padding-bottom:0;}
}

/* seam-card duo: proof left, one CTA right — the why-novo hero hand-off */
.v2-seamcard.duo{grid-template-columns:1fr auto;align-items:center;
  padding:16px clamp(16px,3vw,28px);gap:16px;}
.v2-seamcard.duo .st{border:0;padding:0;text-align:left;display:flex;align-items:center;gap:14px;}
.v2-seamcard.duo .stars{color:var(--gold-deep);letter-spacing:3px;font-size:15px;white-space:nowrap;}
.v2-seamcard.duo .st span{margin:0;}
@media(max-width:680px){
  .v2-seamcard.duo{grid-template-columns:1fr;text-align:center;}
  .v2-seamcard.duo .st{justify-content:center;border:0;}
  .v2-seamcard.duo .st:nth-child(n+2){border:0;}
}

/* landscape-lead pile variant (why-novo: family photo + work shot). The base
   geometry assumes a tall lead image; a landscape lead needs its own numbers,
   composed so the group's OPTICAL mass is centered — the plate's right
   overhang balances the lead's left edge. */
.v2-pile.wide{padding:16px 0 30px;}
.v2-pile.wide .plate{left:auto;right:0;top:-10px;width:74%;height:86%;}
.v2-pile.wide img{width:96%;}
.v2-pile.wide img + img{width:42%;margin:-30% 2% 0 auto;display:block;}
.v2-pile.wide img + img + img{width:46%;margin:-12% auto 0 3%;}


/* ---- split hero -------------------------------------------------------------
   For pages whose best available frame is not big enough to go full-bleed —
   the vacant-lot aerial is 1024px wide, which reads soft across a 2560px hero
   but is sharp at half width. Copy on the paper side, photo on the other.
   Also the right shape for subjects that ARE a rectangle (a parcel, a plan)
   rather than a space you stand inside. (Justin, 2026-08-21) */
/* padding:0 is load-bearing — this is a <section>, and shared.css gives every
   section 100px of vertical padding, which put paper bands above and below the
   photo instead of letting it bleed to the edges. (Justin, 2026-08-21) */
/* min-width:0 on the tracks: a grid column is min-content-sized by default, so a
   wide child (the mosaic images here) pushed the column past 1fr and the whole
   hero ran ~1.4px past the viewport on a phone — enough to let the page drift
   sideways, which is the bug class fixed site-wide on 2026-08-25. */
.v2-hero-split{position:relative;background:var(--paper);color:var(--ink);padding:0;
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:stretch;
  min-height:min(78svh,660px);max-width:100%;overflow-x:clip;}
/* The copy block is CENTRED in its own column, not aligned to the site gutter.
   Gutter alignment tracked (100vw - maxw)/2, which grows with the screen while
   the right pad did not -- at 4K that put 442px of dead paper on the left and
   67px on the right, with the text crowding the photo (Justin, 2026-08-31).
   Centring a fixed measure keeps the 1280 look almost unchanged (70/70 vs the
   old 82/67) and stays balanced at any width. The floor keeps small screens
   from losing their margins entirely.
   NB: measure the RENDERED text when checking the gap, never the h1's box --
   an unbreakable run (a stray &nbsp;) paints outside the box and the box lies. */
.v2-hero-split .hs-copy{display:flex;flex-direction:column;justify-content:center;
  --hs-measure:560px;
  padding-block:clamp(38px,6vw,80px);
  padding-inline:max(clamp(22px,4.4vw,68px), calc((100% - var(--hs-measure)) / 2));
  max-width:none;width:100%;}
.v2-hero-split .hs-copy > *{max-width:var(--hs-measure);}
.v2-hero-split .hs-copy h1{margin:0 0 16px;}
.v2-hero-split .hs-copy .lede{color:var(--ink-soft);max-width:52ch;}
.v2-hero-split .hs-media{position:relative;overflow:hidden;background:var(--ink);}
.v2-hero-split .hs-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
/* a hairline of paper down the seam keeps the photo from butting the text */
.v2-hero-split .hs-media::before{content:'';position:absolute;left:0;top:0;bottom:0;width:1px;
  background:var(--line);z-index:2;}
.v2-hero-split .v2-chip{margin-bottom:18px;align-self:flex-start;}
/* mosaic variant: for a portfolio, the hero IS the work — one borrowed frame
   says less than four that show the range (Justin, 2026-08-21) */
.v2-hero-split .hs-media.mosaic{display:grid;grid-template-columns:1fr 1fr;
  grid-template-rows:1fr 1fr;gap:3px;background:var(--line);}
.v2-hero-split .hs-media.mosaic img{position:relative;inset:auto;width:100%;height:100%;
  object-fit:cover;}
/* copy-led: for towns with no local photography of their own. The photo is
   explicitly supporting rather than a claim about where the work was done. */
.v2-hero-split.copy-led{grid-template-columns:1.5fr .95fr;min-height:min(66svh,560px);}
.v2-hero-split.copy-led .hs-copy{--hs-measure:680px;}
/* hub pages: the towns a region covers. Links where the town has its own
   page, plain text where it does not — a pill that goes nowhere reads as broken. */
.hub-towns{display:flex;flex-wrap:wrap;gap:9px;justify-content:center;margin-top:24px;}
.hub-town{font-family:var(--fm);font-size:11.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink);border:1px solid var(--line);border-radius:999px;padding:9px 15px;
  text-decoration:none;transition:border-color .18s ease,background .18s ease;}
a.hub-town:hover{border-color:var(--gold-deep);background:rgba(201,162,114,.10);}
.hub-town.is-flat{color:var(--ink-soft);border-style:dashed;}
@media(max-width:860px){.v2-hero-split.copy-led{grid-template-columns:minmax(0,1fr);}}
.v2-hero-split .ctas{margin-top:26px;}
@media(max-width:860px){
  .v2-hero-split{grid-template-columns:minmax(0,1fr);min-height:0;}
  .v2-hero-split .hs-copy{order:2;max-width:none;padding:32px 22px 30px;}
  .v2-hero-split .hs-media{order:1;height:46svh;min-height:260px;}
}


/* ---- town local-knowledge block ---------------------------------------------
   The section that makes a town page a town page rather than a find-and-replace
   of the last one: prose about the housing stock on the left, what it changes
   about the shoot on the right. Duplicate-thin town pages are the classic
   doorway-page pattern; this is the block that has to be genuinely different
   on all fourteen. (Justin, 2026-08-21) */
.town-local{display:grid;grid-template-columns:1.15fr .85fr;gap:44px;align-items:start;}
.town-local .tl-copy p{color:var(--ink-soft);margin:0 0 14px;}
.town-local .tl-copy p:last-child{margin-bottom:0;}
.town-local .tl-side{border-left:2px solid var(--gold);padding-left:22px;}
.town-local .tl-side h3{font-size:15px;margin:0 0 14px;letter-spacing:-.01em;}
.town-local .tl-side ul{list-style:none;margin:0;padding:0;}
.town-local .tl-side li{position:relative;padding-left:16px;margin-bottom:12px;
  font-size:14px;line-height:1.55;color:var(--ink-soft);}
.town-local .tl-side li::before{content:'';position:absolute;left:0;top:8px;width:5px;height:5px;
  border-radius:50%;background:var(--gold-deep);}
.town-local .tl-side li b{color:var(--ink);font-weight:600;}
@media(max-width:860px){
  .town-local{grid-template-columns:1fr;gap:26px;}
  .town-local .tl-side{border-left:0;border-top:2px solid var(--gold);padding-left:0;padding-top:20px;}
}


/* ---- service-area region tiles ---------------------------------------------
   Replaces a 14-row ledger the visitor had to scroll to find their own town.
   Four region tiles, each carrying its towns as direct links — the region is
   the headline click, the towns are one click too, and nothing is buried.
   (Justin, 2026-08-21) */
.area-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:18px;margin-top:28px;}
.area-tile{position:relative;overflow:hidden;border-radius:var(--radius-lg,14px);
  border:1px solid var(--line);background:var(--ink);color:var(--paper);
  display:flex;flex-direction:column;justify-content:flex-end;min-height:270px;
  text-decoration:none;isolation:isolate;}
.area-tile > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2;
  transition:transform .8s cubic-bezier(.16,.6,.24,1);}
.area-tile::after{content:'';position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to top,rgba(24,18,13,.92) 8%,rgba(24,18,13,.55) 52%,rgba(24,18,13,.30) 100%);}
.area-tile:hover > img{transform:scale(1.045);}
.area-tile .at-body{padding:22px 24px 20px;}
.area-tile .at-count{font-family:var(--fm);font-size:10px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--gold);margin-bottom:7px;display:block;}
.area-tile h3{font-size:clamp(21px,2.3vw,27px);margin:0 0 12px;color:var(--paper);letter-spacing:-.02em;}
.area-tile .at-towns{display:flex;flex-wrap:wrap;gap:6px;}
.area-tile .at-towns a{font-family:var(--fm);font-size:11px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--paper);background:rgba(251,248,242,.13);border:1px solid rgba(251,248,242,.22);
  border-radius:999px;padding:5px 11px;text-decoration:none;
  transition:background .18s ease,border-color .18s ease;}
.area-tile .at-towns a:hover{background:var(--gold);border-color:var(--gold);color:var(--ink);}
.area-tile .at-go{position:absolute;top:18px;right:20px;font-family:var(--fm);font-size:11px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--paper);opacity:.82;}
@media(max-width:760px){
  .area-tiles{grid-template-columns:1fr;gap:14px;}
  .area-tile{min-height:230px;}
}


/* ---- bracket set ------------------------------------------------------------
   The three exposures that go into one HDR frame, side by side, with the
   delivered image full width beneath. Real frames from a Chicago kitchen
   (Justin, 2026-08-21). */
.v2-brackets{display:grid;gap:14px;}
.v2-brackets .bset{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;}
.v2-brackets .bset figure{margin:0;}
/* height:auto is load-bearing — the width/height attributes on the markup are
   presentational hints, and height:600px from the attribute beats aspect-ratio
   (which only applies when one axis is auto). Without it these render 284x600. */
.v2-brackets .bset img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;
  border-radius:8px;border:1px solid var(--line);}
.v2-brackets .bset figcaption{font-family:var(--fm);font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:7px;text-align:center;}
.v2-brackets .bfinal{margin:6px 0 0;}
.v2-brackets .bfinal img{display:block;width:100%;height:auto;border-radius:12px;
  box-shadow:0 18px 40px -22px rgba(21,19,15,.4);}
.v2-brackets .bfinal figcaption{font-family:var(--fm);font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--gold-deep);margin-top:10px;text-align:center;}
@media(max-width:620px){
  /* three across still reads at phone width — they are a comparison, and
     stacking them loses the whole point of showing them together */
  .v2-brackets .bset{gap:7px;}
  /* was 8.5px — unreadable on a phone. 10px is the floor for static labels;
     the captions wrap instead of shrinking. */
  .v2-brackets .bset figcaption{font-size:10px;letter-spacing:.02em;}
}


/* ---- justified gallery ------------------------------------------------------
   Replaces the CSS-columns masonry, which balanced COLUMNS and therefore always
   left the last column short — every gallery on the site read as ragged
   (Justin, 2026-08-22, having asked more than once).

   This is the Flickr/Google-Photos "justified rows" layout, done in pure CSS:
     · each figure carries its true aspect ratio as --ar (written at build time
       from the real file by tools/gallery.py, so it can never disagree with the
       image);
     · flex-basis is proportional to --ar and flex-grow IS --ar, which makes the
       flex algorithm scale every item in a row to the SAME height while the row
       fills the container exactly;
     · ::after is a zero-height flex item with an enormous grow factor. It lands
       on the last row and eats the leftover space, so a short final row keeps
       its natural size instead of stretching into a distorted banner.
   Adding or removing an image needs no other change — the rows re-justify. */
.v2-gal{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;}
.v2-gal > figure{margin:0;min-width:0;
  flex:var(--ar,1.5) 1 calc(var(--ar,1.5) * 230px);}
.v2-gal > figure > img{display:block;width:100%;aspect-ratio:var(--ar,1.5);
  object-fit:cover;border-radius:10px;background:var(--paper-deep,#EFEAE0);}
.v2-gal figcaption{font-family:var(--fm);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:7px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* the slack-eater: keeps the final row honest */
.v2-gal::after{content:'';flex-grow:1000000;flex-basis:0;height:0;}
/* one row of very wide images should not become a letterbox strip */
.v2-gal.tall > figure{flex-basis:calc(var(--ar,1.5) * 300px);}
@media(max-width:720px){
  .v2-gal{gap:8px;}
  .v2-gal > figure{flex-basis:calc(var(--ar,1.5) * 150px);}
}
@media(max-width:460px){
  /* phones: one per row, uncropped — a justified row of three on a 375px screen
     is three thumbnails nobody can read */
  .v2-gal > figure{flex:1 1 100%;}
  .v2-gal::after{display:none;}
}


/* ---- portfolio galleries ---------------------------------------------------
   Chosen from the gallery lab (Justin, 2026-08-28): 03 spotlight, 09 floating
   frames, 11 hero over rail, 22 lightbox grid.

   THE ORPHAN-ROW RULE. A CSS grid leaves a half-empty last row hanging on the
   left, which is what made these sections read as lopsided. Flex-wrap with
   justify-content:center fixes it without any per-count special casing: a full
   row still spans the container exactly, and a short last row centres itself.
   Set --cols per section; the basis maths keeps full rows edge to edge. */
.pf-grid{--gap:16px;--cols:3;display:flex;flex-wrap:wrap;gap:var(--gap);justify-content:center;}
/* Column count belongs in a CLASS, not an inline style. Pages used to set
   style="--cols:4" directly on the element, and an inline custom property beats
   every media query -- so a four-across gallery stayed four across on a 390px
   phone. Same failure the .co-grid inline grid-template-columns had.
   (Justin, 2026-08-31.) */
.pf-grid.pf-c2{--cols:2;}
.pf-grid.pf-c3{--cols:3;}
.pf-grid.pf-c4{--cols:4;}
.pf-grid > figure{margin:0;flex:0 1 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));}

/* 22 + 09 — lightbox grid wearing the floating-frame decoration.
   outline-offset needs breathing room, so the gap is larger than it looks. */
.pf-lb{--gap:30px;}
.pf-lb img{width:100%;aspect-ratio:3/2;object-fit:cover;border-radius:3px;display:block;
  outline:1px solid rgba(201,162,114,.45);outline-offset:6px;cursor:zoom-in;
  box-shadow:0 18px 38px -20px rgba(40,28,18,.45);
  transition:transform .45s cubic-bezier(.16,.6,.24,1),box-shadow .45s,outline-color .3s;}
.pf-lb figure:hover img{transform:translateY(-6px);outline-color:var(--gold);
  box-shadow:0 30px 54px -22px rgba(40,28,18,.6);}
/* on an ink ground the same frame reads as a gallery wall */
.sec-dark .pf-lb img,.v2-dark .pf-lb img{outline-color:rgba(201,162,114,.5);
  box-shadow:0 34px 60px -22px rgba(0,0,0,.85);}

/* Service-page galleries carry captions that do real work — the floor-plan pair
   is a basic-vs-enhanced comparison, so losing the labels loses the argument.
   The 6px outline-offset means the caption needs clearance or it collides. */
.pf-grid figcaption{font-family:var(--fm);font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:15px;line-height:1.5;}
.sec-dark .pf-grid figcaption,.v2-dark .pf-grid figcaption{color:rgba(251,248,242,.62);}

/* 03 — spotlight: one large frame with smaller ones breaking over its edge */
.pf-spot{position:relative;padding:0 0 64px;}
.pf-spot .sp-main img{width:100%;aspect-ratio:16/10;object-fit:cover;border-radius:16px;
  display:block;cursor:zoom-in;box-shadow:0 30px 60px -20px rgba(40,28,18,.5);}
.pf-spot .sp-chip{position:absolute;width:min(230px,26%);border-radius:12px;overflow:hidden;
  border:4px solid var(--paper);box-shadow:0 20px 44px -14px rgba(40,28,18,.62);
  transition:transform .35s cubic-bezier(.16,.6,.24,1);margin:0;}
.pf-spot .sp-chip img{width:100%;aspect-ratio:3/2;object-fit:cover;display:block;cursor:zoom-in;}
.pf-spot .sp-chip:nth-of-type(2){right:0;bottom:0;}
.pf-spot .sp-chip:nth-of-type(3){left:3%;bottom:-14px;}
.pf-spot .sp-chip:hover{transform:translateY(-8px) scale(1.03);}

/* 11 — hero over rail. Hovering or focusing a rail frame swaps the hero
   (Justin's ask). No lightbox here by his instruction: the rail IS the
   enlarge mechanism, so a zoom cursor would promise a second one. */
.pf-rail .pr-main{margin:0;border-radius:14px;overflow:hidden;position:relative;}
.pf-rail .pr-main img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block;
  transition:opacity .32s ease;}
.pf-rail .pr-main.is-swapping img{opacity:.25;}
.pf-rail .pr-strip{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:8px;}
/* was hard-sized for exactly six thumbs, so a seventh wrapped to a second row.
   Sharing the space works at any count. */
.pf-rail .pr-strip button{flex:1 1 0;min-width:0;padding:0;border:0;background:none;
  border-radius:8px;overflow:hidden;cursor:pointer;line-height:0;
  outline:2px solid transparent;outline-offset:2px;transition:outline-color .25s;}
.pf-rail .pr-strip img{width:100%;aspect-ratio:1;object-fit:cover;display:block;
  filter:saturate(.72) brightness(.92);transition:filter .3s ease,transform .3s ease;}
.pf-rail .pr-strip button:hover img,.pf-rail .pr-strip button:focus-visible img,
.pf-rail .pr-strip button[aria-current="true"] img{filter:none;transform:scale(1.05);}
.pf-rail .pr-strip button[aria-current="true"]{outline-color:var(--gold);}
.pf-rail .pr-strip button:focus-visible{outline-color:var(--gold-deep);}

/* video row — was .v2-masonry (CSS columns), which let the caption break away
   from its iframe into the next column. A grid keeps each item whole. */
.pf-video{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;align-items:start;}
.pf-video > *{min-width:0;}
.pf-video .video-embed{aspect-ratio:16/9;border-radius:12px;overflow:hidden;background:var(--ink);}
.pf-video .video-embed iframe{width:100%;height:100%;border:0;display:block;}
.pf-video .video-cap{font-family:var(--fm);font-size:10.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);margin:9px 0 0;}
.pf-soon{aspect-ratio:16/9;border:2px dashed var(--gold-deep);border-radius:12px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
  background:var(--paper-deep);color:var(--ink-soft);text-align:center;padding:12px;}
.pf-soon b{font-family:var(--fm);font-size:12px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold-deep);font-weight:500;}
.pf-soon span{font-size:13px;}

/* ---- lightbox -------------------------------------------------------------- */
.v2-lb{position:fixed;inset:0;z-index:400;display:none;place-items:center;
  background:rgba(24,18,13,.95);padding:clamp(16px,4vw,48px);}
.v2-lb.on{display:grid;}
.v2-lb img{max-width:min(96vw,1500px);max-height:84vh;object-fit:contain;border-radius:4px;
  box-shadow:0 40px 90px -30px rgba(0,0,0,.9);}
.v2-lb figcaption{font-family:var(--fm);font-size:11px;letter-spacing:.09em;
  color:rgba(251,248,242,.78);text-align:center;margin-top:16px;}
.v2-lb figure{margin:0;display:flex;flex-direction:column;align-items:center;}
.v2-lb button{position:absolute;background:rgba(251,248,242,.1);color:var(--paper);
  border:1px solid rgba(251,248,242,.28);border-radius:999px;cursor:pointer;
  width:46px;height:46px;font-size:19px;line-height:1;display:grid;place-items:center;
  transition:background .2s,border-color .2s;}
.v2-lb button:hover{background:var(--gold);border-color:var(--gold);color:var(--ink);}
.v2-lb .lb-x{top:clamp(12px,3vw,26px);right:clamp(12px,3vw,26px);}
.v2-lb .lb-p{left:clamp(8px,2vw,26px);top:50%;transform:translateY(-50%);}
.v2-lb .lb-n{right:clamp(8px,2vw,26px);top:50%;transform:translateY(-50%);}
.v2-lb .lb-count{position:absolute;bottom:clamp(12px,3vw,26px);left:50%;transform:translateX(-50%);
  font-family:var(--fm);font-size:11px;letter-spacing:.12em;color:rgba(251,248,242,.6);}
body.v2-lb-open{overflow:hidden;}

@media(max-width:820px){
  .pf-grid,.pf-grid.pf-c2,.pf-grid.pf-c3,.pf-grid.pf-c4{--cols:2;--gap:12px;}
  .pf-lb{--gap:22px;}
  .pf-rail .pr-strip button{flex:0 1 calc((100% - 2 * 8px) / 3);}
  .pf-video{grid-template-columns:1fr;}
  .pf-spot{padding-bottom:0;}
  .pf-spot .sp-chip{position:static;width:auto;margin-top:10px;display:inline-block;
    width:calc(50% - 6px);}
  .v2-lb button{width:40px;height:40px;}
}
/* Two columns holds down to a phone; one column made the thumbnails enormous
   and turned a six-image grid into six screens of scrolling. */
@media(max-width:380px){ .pf-grid,.pf-grid.pf-c2,.pf-grid.pf-c3,.pf-grid.pf-c4{--cols:1;} }


/* ---- hub town rail --------------------------------------------------------
   Gallery-lab #15 (hover expand) with no photograph behind it — there are no
   town-specific images, and captioning a Winnetka interior "Oak Brook" would be
   a false claim. So the PRICE is the visual.
   The text is never display:none — it stays in the DOM for crawlers and answer
   engines, which is the whole reason these sections exist. (Justin, 2026-08-28.) */
/* The shell hugs the rail so three cards do not sit in an eight-card box. Safe
   only because the cards no longer shrink — a shrink-to-fit shell around
   shrinkable children sizes off itself and collapses them. */
.tw-shell{border:1px solid var(--line);border-radius:16px;padding:10px;margin-top:26px;
  background:var(--paper-deep);width:max-content;max-width:100%;margin-left:auto;margin-right:auto;}
.tw-rail{display:flex;gap:6px;height:214px;justify-content:center;}
/* --tw-open is the ONE place the open width is set. Measured, not guessed: the
   widest line any card carries is the "Premium package - what we would book"
   label at 259px, so 259 + 2x20 padding = 299px is the real requirement. It was
   420px, which left 121px of dead space inside every card on every hub -- most
   obvious on the rails with only three or four towns (Justin, 2026-08-31). */
.tw-rail{--tw-open:324px;}
.tw{position:relative;flex:0 0 84px;min-width:0;overflow:hidden;cursor:pointer;text-align:left;
  display:block;text-decoration:none;
  border:1px solid rgba(201,162,114,.26);border-radius:11px;padding:0;font-family:inherit;
  color:var(--paper);
  background:linear-gradient(165deg,#4A3B2F 0%,var(--ink) 58%);
  transition:flex-basis .45s cubic-bezier(.16,.6,.24,1),border-color .3s ease,box-shadow .3s ease;}
.tw[aria-expanded="true"]{flex:0 0 var(--tw-open);border-color:var(--gold);
  box-shadow:0 22px 44px -24px rgba(0,0,0,.75);}
.tw:focus-visible{outline:2px solid var(--gold);outline-offset:2px;}
/* Hover only where there IS a pointer — on a touch screen it sticks after a tap
   and fights the accordion below. */
@media(hover:hover){
  .tw:hover{flex:0 0 var(--tw-open);border-color:var(--gold);box-shadow:0 22px 44px -24px rgba(0,0,0,.75);}
  .tw:hover .tw-name{transform:rotate(0) translateY(-116px);}
  .tw:hover .tw-body{opacity:1;transform:none;}
  .tw:hover .tw-eg{opacity:1;}
}
/* the name pivots from running up the edge to sitting above the figure */
.tw-name{position:absolute;left:20px;bottom:22px;transform-origin:left bottom;
  transform:rotate(-90deg);white-space:nowrap;
  font-family:var(--fd);font-weight:800;font-size:17px;letter-spacing:-.01em;color:var(--paper);
  transition:transform .45s cubic-bezier(.16,.6,.24,1);}
.tw[aria-expanded="true"] .tw-name{transform:rotate(0) translateY(-116px);}
.tw-body{position:absolute;left:20px;right:20px;bottom:22px;display:flex;flex-direction:column;
  gap:6px;opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity .35s ease .08s,transform .35s ease .08s;}
.tw[aria-expanded="true"] .tw-body{opacity:1;transform:none;}
/* Sits in the space above the town name — a figure with square footage and a
   list price reads as a quote unless something frames it as a worked example. */
.tw-eg{position:absolute;left:20px;top:18px;font-family:var(--fm);font-size:10px;
  letter-spacing:.13em;text-transform:uppercase;color:rgba(251,248,242,.5);
  opacity:0;transition:opacity .3s ease .1s;white-space:nowrap;}
.tw[aria-expanded="true"] .tw-eg{opacity:1;}
.tw-lab{font-family:var(--fm);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold);}
.tw-price{font-family:var(--fd);font-weight:800;font-size:clamp(28px,2.6vw,36px);letter-spacing:-.03em;
  line-height:1;color:var(--paper);font-variant-numeric:tabular-nums;margin:1px 0 3px;}
/* these used to be nowrap, which clipped inside the panel — they wrap now */
.tw-meta{font-size:12.5px;line-height:1.45;color:rgba(251,248,242,.82);margin-bottom:2px;}
.tw-range{font-family:var(--fm);font-size:10.5px;letter-spacing:.04em;line-height:1.5;
  color:rgba(251,248,242,.52);}
.tw-lead{margin:0 0 14px;}
.tw-foot{font-size:13px;color:var(--ink-soft);text-align:center;margin:18px 0 0;}
.tw-foot a{color:var(--gold-deep);}
@media(prefers-reduced-motion:reduce){.tw,.tw-name,.tw-body{transition-duration:.001ms;}}
/* Narrower desktops: fewer pixels per panel, so the open one needs more of them. */
/* Below this the eight-card rail no longer fits, so it goes back to sharing the
   available width rather than hugging. */
/* Between the accordion and full width the rail used to switch to proportional
   flex (1fr / 4fr), which re-created the exact problem --tw-open solves: with
   three towns the open card stretched to 615px around 299px of text. Keep the
   measured width here too and let the cards SHRINK (not grow) if the rail runs
   out of room; the shell hugs so a short rail stays centred. */
@media(max-width:1100px){
  .tw-rail{height:230px;}
  .tw-shell{width:max-content;max-width:100%;}
  /* The cards must NOT shrink. A shrink-to-fit shell around shrinkable children
     sizes itself off them and they collapse -- tried it, every card went to 2px.
     Non-shrinking cards + a scrollable rail is the combination that holds: an
     eight-town rail scrolls on a narrow laptop instead of squashing. */
  .tw{flex:0 0 84px;}
  .tw[aria-expanded="true"]{flex:0 0 var(--tw-open);}
  .tw-rail{overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;justify-content:flex-start;}
}
/* Phone: a real accordion. Tap a row, it opens downward. */
@media(max-width:820px){
  .tw-shell{padding:8px;}
  .tw-rail{flex-direction:column;height:auto;gap:8px;}
  .tw,.tw[aria-expanded="true"]{flex:none;}
  .tw-eg{position:static;opacity:1;padding:14px 18px 0;display:block;}
  .tw-name{position:static;transform:none;display:block;padding:4px 18px 15px;font-size:16px;}
  .tw[aria-expanded="true"] .tw-name{transform:none;}
  .tw-body{position:static;opacity:0;transform:none;max-height:0;overflow:hidden;
    padding:0 18px;gap:4px;
    transition:max-height .35s ease,opacity .25s ease,padding .35s ease;}
  .tw[aria-expanded="true"] .tw-body{opacity:1;max-height:260px;padding:0 18px 18px;}
}

/* ---- two more gallery shapes, from the lab -------------------------------
   Hubs run different picks so five pages do not show one sample five times.
   06 brick wall (Chicago) and 02 feature band (Barrington). */
.pf-brick{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;}
.pf-brick figure{margin:0;overflow:hidden;}
.pf-brick img{width:100%;aspect-ratio:3/2;object-fit:cover;display:block;cursor:zoom-in;
  transition:opacity .3s ease,transform .6s ease;}
.pf-brick:hover img{opacity:.5;}
.pf-brick figure:hover img{opacity:1;transform:scale(1.04);}
.pf-band{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
.pf-band figure{margin:0;position:relative;border-radius:14px;overflow:hidden;}
.pf-band img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;cursor:zoom-in;
  transition:transform .8s cubic-bezier(.16,.6,.24,1);}
.pf-band figure:hover img{transform:scale(1.04);}
.pf-band figcaption{position:absolute;left:0;right:0;bottom:0;padding:26px 18px 14px;
  font-family:var(--fm);font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--paper);background:linear-gradient(to top,rgba(24,18,13,.88),transparent);}
@media(max-width:760px){
  .pf-brick{grid-template-columns:repeat(2,1fr);}
  .pf-band{grid-template-columns:1fr;}
}

/* two more lab shapes for the commercial pages: 07 boxed pair (architecture,
   where there are only ever a couple of frames worth showing) and 20 contact
   sheet (construction, where the point is that the record is thorough). */
.pf-pair{display:grid;grid-template-columns:1fr 1fr;gap:18px;background:var(--paper-deep);
  border:1px solid var(--line);border-radius:16px;padding:18px;}
.pf-pair figure{margin:0;}
.pf-pair img{width:100%;aspect-ratio:3/2;object-fit:cover;border-radius:9px;display:block;
  cursor:zoom-in;transition:transform .5s cubic-bezier(.16,.6,.24,1);}
.pf-pair figure:hover img{transform:scale(1.03);}
.pf-pair figcaption{font-family:var(--fm);font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:11px;}
.pf-sheet{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;}
.pf-sheet figure{margin:0;position:relative;}
.pf-sheet img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:7px;display:block;
  cursor:zoom-in;filter:saturate(.9);transition:filter .3s ease,transform .4s ease;}
.pf-sheet figure:hover img{filter:none;transform:translateY(-3px);}
.pf-sheet figcaption{font-family:var(--fm);font-size:10px;letter-spacing:.07em;
  color:var(--ink-soft);margin-top:7px;line-height:1.4;}
@media(max-width:820px){
  .pf-pair{grid-template-columns:1fr;}
  .pf-sheet{grid-template-columns:repeat(2,1fr);}
}

/* ---- FAQ: two-column accordion (gallery-lab FAQ #03) ----------------------
   TWO INDEPENDENT COLUMNS, not a two-column grid. A grid shares row heights, so
   opening an answer on the right stretched the row on the left and left a hole
   under the closed question beside it — which is the bug in Justin's screenshot.
   Each column is its own stack; opening one cannot move the other.
   <details>/<summary> means no JavaScript, and the answers stay in the DOM for
   crawlers and answer engines whether open or shut. (Justin, 2026-08-28.) */
.faq-two{display:grid;grid-template-columns:1fr 1fr;gap:0 48px;align-items:start;}
.faq-two .faq-col{display:flex;flex-direction:column;}
.faq-two details{border-top:1px solid var(--line);}
.faq-two .faq-col > details:last-child{border-bottom:1px solid var(--line);}
.faq-two summary{list-style:none;cursor:pointer;display:flex;gap:16px;align-items:flex-start;
  padding:17px 0;margin:0;}
.faq-two summary::-webkit-details-marker{display:none;}
.faq-two summary h3{margin:0;font-family:var(--fd);font-weight:700;font-size:16px;
  letter-spacing:-.01em;line-height:1.35;color:var(--ink);flex:1;}
.faq-two summary::after{content:"+";margin-left:auto;flex:0 0 auto;line-height:1;
  font-family:var(--fm);font-size:19px;color:var(--gold-deep);
  transition:transform .2s ease,color .2s ease;}
.faq-two details[open] summary::after{content:"\2013";}
.faq-two summary:hover h3{color:var(--gold-deep);}
.faq-two summary:focus-visible{outline:2px solid var(--gold);outline-offset:-2px;border-radius:4px;}
.faq-two details p{margin:0;padding:0 34px 20px 0;font-family:var(--ff);font-size:14.5px;
  line-height:1.7;color:var(--ink-soft);}
.faq-two details[open]{animation:faqIn .28s ease both;}
@keyframes faqIn{from{opacity:.4;}to{opacity:1;}}

/* Colour variants so 32 pages do not all look the same. The palette does not
   change — only which part of it carries the rules and the marker. */
.faq-two.faq-gold details{border-color:rgba(201,162,114,.42);}
.faq-two.faq-gold .faq-col > details:last-child{border-color:rgba(201,162,114,.42);}
.faq-two.faq-gold summary::after{color:var(--gold);}
.faq-two.faq-gold details[open] summary h3{color:var(--gold-deep);}

.faq-two.faq-ink details{border-color:rgba(59,47,38,.22);}
.faq-two.faq-ink .faq-col > details:last-child{border-color:rgba(59,47,38,.22);}
.faq-two.faq-ink summary::after{color:var(--ink);}
.faq-two.faq-ink details[open] summary{background:linear-gradient(90deg,rgba(59,47,38,.05),transparent 70%);}

.faq-two.faq-card details{border:1px solid var(--line);border-radius:12px;margin-bottom:10px;
  background:var(--paper);}
.faq-two.faq-card .faq-col > details:last-child{border:1px solid var(--line);}
.faq-two.faq-card summary{padding:16px 18px;}
.faq-two.faq-card details p{padding:0 18px 16px;}
.faq-two.faq-card details[open]{border-color:var(--gold);}

/* dark grounds */
.sec-dark .faq-two summary h3,.v2-dark .faq-two summary h3{color:var(--paper);}
.sec-dark .faq-two details p,.v2-dark .faq-two details p{color:rgba(251,248,242,.76);}
.sec-dark .faq-two details,.v2-dark .faq-two details{border-color:rgba(251,248,242,.18);}
.sec-dark .faq-two summary::after,.v2-dark .faq-two summary::after{color:var(--gold);}

@media(max-width:820px){
  .faq-two{grid-template-columns:1fr;gap:0;}
  /* one column on a phone: the second column's top border would double up */
  .faq-two .faq-col:first-child > details:last-child{border-bottom:0;}
}
@media(prefers-reduced-motion:reduce){.faq-two details[open]{animation:none;}}

/* ---- not-yet-shot placeholder --------------------------------------------
   Stands where a real example will go. Says plainly that the thing is being
   worked on rather than pretending to be a broken image -- an empty frame with
   no explanation reads as a bug, and "coming soon" with no voice reads as a
   template (Justin, 2026-08-31). */
.v2-ph{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:9px;text-align:center;padding:22px 20px;border-radius:12px;
  background:var(--paper-deep);border:1px dashed rgba(130,100,63,.42);color:var(--ink-soft);}
.v2-ph .v2-ph-k{font-family:var(--fm);font-size:11px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--gold-deep);}
.v2-ph .v2-ph-t{font-size:14px;line-height:1.5;max-width:30ch;color:var(--ink-soft);}
.v2-ph.tall{aspect-ratio:9/16;}
.v2-ph.wide{aspect-ratio:16/9;}
.v2-ph.band{aspect-ratio:5/2;}
@media(max-width:560px){.v2-ph.band{aspect-ratio:auto;min-height:150px;}}

/* ---- contact form ---------------------------------------------------------
   Posts to /api/contact (netlify/functions/contact.mjs), which sends through
   Resend -- the same sender the quote email already uses. Not Netlify Forms:
   that would be the one piece of the site that could not move to Hostinger. */
.cf{display:grid;gap:14px;margin-top:26px;}
.cf-row{display:grid;gap:14px;grid-template-columns:1fr 1fr;}
@media(max-width:640px){.cf-row{grid-template-columns:1fr;}}
.cf label{display:flex;flex-direction:column;gap:7px;font-family:var(--fm);font-size:11px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft);}
.cf input,.cf textarea,.cf select{font-family:var(--ff);font-size:15.5px;color:var(--ink);
  background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:13px 14px;
  width:100%;transition:border-color .18s ease,box-shadow .18s ease;}
.cf textarea{min-height:150px;resize:vertical;line-height:1.6;}
.cf input:focus,.cf textarea:focus,.cf select:focus{outline:none;border-color:var(--gold-deep);
  box-shadow:0 0 0 3px rgba(201,162,114,.22);}
.cf input:focus-visible,.cf textarea:focus-visible,.cf select:focus-visible{outline:none;}
/* The honeypot must be unreachable, not merely invisible: display:none is
   skipped by some bots, so take it out of the layout AND the tab order. */
.cf-pot{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}
.cf-foot{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-top:4px;}
.cf-note{font-size:13.5px;color:var(--ink-soft);margin:0;}
.cf-msg{margin:0;font-size:14.5px;line-height:1.6;border-radius:10px;padding:13px 15px;}
.cf-msg[data-state="ok"]{background:rgba(122,150,110,.14);border:1px solid rgba(90,120,80,.42);color:#3D5233;}
.cf-msg[data-state="err"]{background:rgba(176,92,74,.12);border:1px solid rgba(150,70,55,.42);color:#7E3A2C;}
.cf-msg[hidden]{display:none;}
.cf button[disabled]{opacity:.6;cursor:progress;}

/* ---- legal pages -------------------------------------------------------
   Plain by design. These are read to answer a question, not admired, so the
   measure is narrow, the headings are quiet and nothing competes with the
   text. */
.legal{max-width:720px;margin:0 auto;}
.legal h2{font-size:clamp(20px,2.4vw,26px);margin:44px 0 12px;}
.legal h2:first-of-type{margin-top:8px;}
.legal h3{font-size:16px;margin:26px 0 8px;}
.legal p,.legal li{font-size:15.5px;line-height:1.75;color:var(--ink-soft);}
.legal li{margin:0 0 8px;}
.legal ul{padding-left:20px;margin:0 0 16px;}
.legal strong{color:var(--ink);font-weight:600;}
.legal a{color:var(--gold-deep);}
.legal .legal-date{font-family:var(--fm);font-size:11.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-soft);margin-bottom:28px;}
.legal table{width:100%;border-collapse:collapse;margin:6px 0 18px;font-size:14.5px;}
.legal th,.legal td{text-align:left;padding:10px 12px 10px 0;border-bottom:1px solid var(--line);
  vertical-align:top;color:var(--ink-soft);}
.legal th{font-family:var(--fm);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink);}
/* A three-column table cannot fit a phone. Below 640px each row becomes a small
   stacked block with its column name as a label, which reads better than either
   a squeezed table or a sideways scroll. */
@media(max-width:640px){
  .legal table,.legal tbody,.legal tr,.legal td,.legal th{display:block;width:100%;}
  .legal thead,.legal tr:first-child{display:none;}
  .legal tr{border-bottom:1px solid var(--line);padding:14px 0;}
  .legal tr:last-child{border-bottom:0;}
  .legal td{border:0;padding:0 0 6px;}
  .legal td:first-child{color:var(--ink);font-weight:600;}
  .legal td:last-child{padding-bottom:0;}
}

/* ---- address autocomplete ------------------------------------------------
   Body-level so it escapes the calculator iframe's clipped panels; positioned
   against the input on every scroll and resize. */
.addr-ac{position:absolute;z-index:9999;background:var(--paper,#FBF8F2);
  border:1px solid var(--line,rgba(59,47,38,.16));border-radius:10px;overflow:hidden;
  box-shadow:0 18px 40px -16px rgba(21,19,15,.34);}
.addr-ac[hidden]{display:none;}
.addr-ac-item{display:block;width:100%;text-align:left;background:none;border:0;
  font-family:var(--ff,system-ui);font-size:14.5px;line-height:1.45;color:var(--ink,#3B2F26);
  padding:11px 13px;cursor:pointer;border-bottom:1px solid var(--line,rgba(59,47,38,.10));}
.addr-ac-item:last-child{border-bottom:0;}
.addr-ac-item:hover,.addr-ac-item.on{background:rgba(201,162,114,.16);}

/* Floor plans are documents, not photographs. The shared .pf-lb rule crops to
   3/2 with object-fit:cover, and the plans are 1.76 wide — so the left and
   right edges of every plan were being cut off, which on a floor plan means
   missing rooms. Show them whole. */
.pf-grid.pf-plans img{aspect-ratio:auto;height:auto;object-fit:contain;background:var(--paper);}

/* Opt-in heavier scrim for a hero whose photograph is busy where the text
   sits. The shared gradient is deliberately light in the middle band so most
   heroes keep their photograph; this one needs more. */
.v2-hero.hero-scrim .media::after{
  background:linear-gradient(to top,rgba(24,18,13,.88) 0%,rgba(24,18,13,.58) 46%,rgba(24,18,13,.52) 100%);}
