/* ==========================================================================
   Novo Real Estate Media — Shared Design System ("Direction F — Editorial
   Masthead"). Palette pulled from the real logo files (novo. wordmark, ink
   + gold). Keep in sync with the calculator's own :root block
   (Pricing Calculators/novo-pricing-calculator-v67.html) if either changes —
   they're two files by necessity (the calculator is a standalone embeddable
   tool) but should always look like one property.
   ========================================================================== */

:root{
  /* ---- ESPRESSO ----
     The dark tone is warm walnut rather than near-black, so nothing on the
     site is pure black. Reads like printed ink on paper. */
  --ink:#3B2F26;
  --ink-soft:#6A5A4A;
  --paper:#FBF8F2;
  --paper-deep:#F0EBE0;
  /* TWO golds, two jobs — do not collapse them. `--gold` sits on DARK grounds
     (hero photo, footer, closing CTA) and must stay bright enough to read
     there; `--gold-deep` is the accent on light paper.
     `--gold-deep` is pinned by the WORSE of its two grounds: it carries body-
     sized text (eyebrows, tags, table cells, inline links), so it needs 4.5:1
     on --paper-deep, not just on --paper. It was #8A6A43, which cleared paper
     at 4.69 but failed the tint sections at 4.18. Now 5.15 / 4.60. Don't
     lighten it back without re-checking against --paper-deep. */
  --gold:#C9A272;
  /* Label colour for anything sitting ON --gold. Darker than --ink on purpose:
     ink gives 5.49:1 there, which passes AA but was reported as looking washed
     out on a phone in daylight (2026-09-01). This is 7.10:1 — AAA — and the
     difference against the gold is what the eye actually reads. */
  --on-gold:#241C16;
  --gold-deep:#82643F;
  --line:rgba(59,47,38,.16);
  /* Dusk: a deliberately SHALLOW warm gradient for the dark bands — sky
     falling to last light. Kept close together on purpose; a wide spread
     read as a decorative gradient rather than as dusk. */
  --dusk-1:#2A211A;
  --dusk-2:#33281F;
  --dusk-3:#40301F;
  /* The one saturated room: the packages band, deeper than the other dark
     sections so it reads as its own space. */
  --room:#2E241C;
  /* Type roles. Mono is for MICRO-LABELS ONLY (eyebrows, tags, buttons, nav).
     It used to be set on every paragraph on the site, which is what made the
     whole thing read "digital" rather than premium — monospace is a code
     typeface and signals engineering no matter what surrounds it. */
  /* TWO faces, two jobs. --fd is the display face and carries headline scale;
     --ff is the body face. They were a single Plus Jakarta Sans token until
     Aug 2026 — Jakarta is on impeccable's overused-font list, and using one
     family for both is what made the pages read as templated. */
  --fd:'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --ff:'Be Vietnam Pro', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --fm:'IBM Plex Mono', ui-monospace, monospace;
  --radius:14px;
  --radius-sm:9px;
  --radius-lg:20px;
  --maxw:1180px;
}

*{box-sizing:border-box;}
html{
  /* In-app browsers — Messenger, Instagram, the Facebook webview — apply
     "font boosting": they decide a block is readable text and inflate it,
     which is why the hero read three lines in Chrome and four from a
     Messenger link, with the review chip wrapping. Desktop Chrome and
     Safari never do it, so it is invisible unless you open the site the way
     a lot of people actually will — from a social post. (Justin, 2026-09-01)
     100% means "use the size I specified", not "disable zoom": pinch-zoom
     still works, which is why this is not an accessibility regression. */
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
  scroll-behavior:smooth;}
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--ff);
  font-size:16px;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block;}
a{color:inherit;}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 32px;}
@media (max-width:640px){.wrap{padding:0 20px;}}

section{padding:100px 0;}
section.tight{padding:64px 0;}
@media (max-width:700px){section{padding:64px 0;}section.tight{padding:44px 0;}}

.eyebrow{
  font-family:var(--fm);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  display:flex;align-items:center;gap:12px;margin-bottom:20px;color:var(--gold-deep);
}
.eyebrow::before{content:'';width:26px;height:1px;background:currentColor;opacity:.5;}
.eyebrow.center{justify-content:center;}

h1,h2,h3{font-family:var(--fd);font-weight:800;font-style:normal;letter-spacing:-.035em;margin:0 0 20px;}
h1{font-size:clamp(32px, 5.2vw, 60px);line-height:1.04;}
h2{font-size:clamp(27px, 3.4vw, 40px);line-height:1.1;}
h3{font-size:clamp(19px, 2.2vw, 22px);line-height:1.3;font-weight:600;}
/* No italics anywhere — the italic serif was the most feminine element in the
   old system. Emphasis is carried by color alone now. */
em{font-style:normal;color:var(--gold-deep);}
p{line-height:1.7;color:var(--ink-soft);}
.lede{font-family:var(--ff);font-size:17px;line-height:1.7;color:var(--ink-soft);max-width:620px;margin:0 0 40px;}
.mono{font-family:var(--fm);}

/* ---- buttons: pill-shaped, gold — the one recurring CTA shape site-wide ---- */
.pill{
  font-family:var(--fm);font-size:12px;letter-spacing:.04em;text-transform:uppercase;
  display:inline-flex;align-items:center;gap:8px;padding:14px 24px;border-radius:999px;text-decoration:none;
  background:var(--ink);color:var(--paper);border:1px solid var(--ink);cursor:pointer;
  transition:background .15s ease, color .15s ease, border-color .15s ease, transform .15s ease;
}
.pill:hover{background:var(--gold-deep);border-color:var(--gold-deep);color:var(--paper);transform:translateY(-1px);}
/* On a dark ground an ink-filled pill is invisible until hover — that was the
   reported bug. Anywhere dark, the primary CTA flips to gold fill so it always
   has a visible shape. */
/* .v2-cta was missing from this list while .pill.dark below DID list it — so the
   primary pill fell through to the base ink fill on an ink ground and had a
   contrast ratio against its own section of exactly 1.00:1. The words were
   readable, the BUTTON was not, so it only appeared on hover. It also made the
   secondary .pill.outline (border 11.49:1) more visible than the primary, which
   is backwards. (Justin, 2026-08-28, /commercial/ closing CTA.) */
.on-dark .pill, .sec-dark .pill, .site-footer .pill, .v2-cta .pill, .v2-hero .pill,
.serve-panel.primary .pill, .tier-card.feat .pill{
  background:var(--gold);color:var(--on-gold);border-color:var(--gold);
}
.on-dark .pill:hover, .sec-dark .pill:hover, .site-footer .pill:hover, .v2-cta .pill:hover,
.v2-hero .pill:hover,
.serve-panel.primary .pill:hover, .tier-card.feat .pill:hover{
  background:var(--paper);border-color:var(--paper);color:var(--ink);
}

/* The "crisp pill" bevel — settled 2026-08-20 for the pricing page's dark block,
   promoted site-wide 2026-08-28 at Justin's ask. FILLED pills only: the inset
   highlight simulates a lit top edge on a solid surface, so on the transparent
   .outline and .dark variants it would draw a bright line across nothing. */
.pill:not(.outline):not(.dark){
  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);
}
.pill:not(.outline):not(.dark):active{
  transform:translateY(1px);
  box-shadow:inset 0 2px 5px rgba(21,19,15,.45), 0 1px 3px -1px rgba(21,19,15,.4);
}
/* Secondary CTA on dark — a real, visible outline at rest, not just on hover. */
.pill.outline{background:transparent;border:1px solid rgba(245,241,231,.55);color:var(--paper);}
.pill.outline:hover{background:rgba(245,241,231,.14);border-color:var(--paper);color:var(--paper);}
/* .pill.dark is the PAPER-ground outline variant: ink border, ink text. Dropped
   onto a dark section it is invisible until hover — it happened once on the
   architecture page. These two rules make it self-correct on any dark ground
   rather than relying on whoever writes the markup to remember. */
.pill.dark{background:transparent;border:1px solid var(--ink);color:var(--ink);}
.pill.dark:hover{background:var(--ink);color:var(--paper);}
.sec-dark .pill.dark, .v2-dark .pill.dark, .v2-cta .pill.dark, .on-dark .pill.dark{
  border-color:rgba(245,241,231,.55);color:var(--paper);}
.sec-dark .pill.dark:hover, .v2-dark .pill.dark:hover, .v2-cta .pill.dark:hover, .on-dark .pill.dark:hover{
  background:rgba(245,241,231,.14);border-color:var(--paper);color:var(--paper);}
.pill.sm{padding:10px 16px;font-size:11px;}
/* A long label — "Request a commercial quote" is the worst of them — ran out
   of room on a narrow phone and dropped its arrow onto a line of its own. The
   arrow is now glued to the last word in the markup, and the padding gives
   back the 12px that made the difference between one line and two. */
@media (max-width:430px){
  .pill{padding-left:18px;padding-right:18px;}
}

/* ---- header / nav ---- */
.site-header{
  /* 0.55 opacity read as glass over the dark hero and as mid gray over every
     interior page's paper body — where light nav text landed at 2.98:1,
     failing AA on all 32 pages. 0.80 keeps the blur and the translucency,
     and measures 6.03:1 on paper and 9.44:1 on the hero. */
  position:sticky;top:0;z-index:50;background:rgba(21,19,15,.80);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid rgba(245,241,231,.1);
}
.site-header .wrap{display:flex;align-items:center;justify-content:space-between;height:84px;}
/* The wordmark is 2:1, so 26px tall rendered only 52px wide in an 84px header —
   the brand was the smallest thing in its own masthead. 34px gives 68px of
   wordmark with 25px of air above and below. */
.site-header img.logo{height:34px;filter:brightness(0) invert(1);}
@media (max-width:700px){.site-header img.logo{height:30px;}}
.nav-links{display:flex;gap:30px;list-style:none;margin:0;padding:0;}
.nav-links a{
  font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  /* 0.94, not 0.82. Over a paper-bodied page the header resolves to #61574f and
     0.82 measured 4.82:1 — over the AA line but thin for 11px uppercase mono
     with letter-spacing, and reported as hard to read. 0.94 is 5.75:1 and the
     hover to full paper is still visible. */
  color:rgba(245,241,231,.94);text-decoration:none;transition:color .15s ease;
}
.nav-links a:hover,.nav-links a.active{color:var(--paper);}
.nav-right{display:flex;align-items:center;gap:18px;}
/* ---- nav dropdowns ----
   Two menus only: Services and Service Area. They exist because 26 of 33
   pages had no route from the header at all — every service page, every town
   page. The Service Area menu also puts 11 internal links to the local pages
   on every page of the site, which is most of what those pages are for.
   Hover alone is not enough: it is unusable on touch and unreachable by
   keyboard, so the trigger is a real <button> that toggles on click, and
   hover is a convenience layered on top for pointers. */
.has-menu{position:relative;}
.nav-top{font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  color:rgba(245,241,231,.94);background:none;border:none;padding:0;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;transition:color .15s ease;}
.nav-top:hover,.has-menu.open .nav-top{color:var(--paper);}
.caret{width:0;height:0;border-left:3.5px solid transparent;border-right:3.5px solid transparent;
  border-top:4px solid currentColor;opacity:.7;transition:transform .18s ease;}
.has-menu.open .caret{transform:rotate(180deg);}
.nav-menu{position:absolute;top:calc(100% + 14px);left:-18px;min-width:230px;z-index:60;
  background:var(--ink);border:1px solid rgba(245,241,231,.14);border-radius:8px;
  padding:12px;opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s;
  box-shadow:0 18px 40px rgba(0,0,0,.35);}
.has-menu.open .nav-menu{opacity:1;visibility:visible;transform:translateY(0);
  transition:opacity .16s ease,transform .16s ease,visibility 0s;}
.nav-menu ul{list-style:none;margin:0;padding:0;}
.nav-menu.two-col ul{display:grid;grid-template-columns:1fr 1fr;gap:0 8px;min-width:380px;}
.nav-menu a{display:block;font-family:var(--ff);font-size:14px;letter-spacing:0;text-transform:none;
  color:rgba(245,241,231,.94);padding:8px 12px;border-radius:5px;text-decoration:none;}
.nav-menu a:hover{background:rgba(245,241,231,.08);color:var(--paper);}
/* Region-grouped area menu: the region is the heading AND a link, its towns
   sit beneath it. A nested flyout would be worse — a second hover target is
   awkward on a trackpad and unusable on touch. (Justin, 2026-08-21) */
/* CSS columns, not a grid: the groups are wildly different lengths (one town
   vs eight), and a grid makes every row as tall as its tallest cell — which
   left craters under North Shore and Chicago. Columns pack by content.
   (Justin, 2026-08-21) */
.nav-menu.grouped > ul{display:block;columns:2;column-gap:34px;min-width:420px;}
.nav-menu.grouped > ul > li{break-inside:avoid;-webkit-column-break-inside:avoid;
  display:block;margin-bottom:10px;}
.nav-menu.grouped li.grp{display:block;}
/* .grp-head > a, NOT .grp > a — the caret button meant wrapping the region
   link in a .grp-head row, which silently broke this selector and dropped the
   regions back to plain paper text, indistinguishable from the towns under
   them. That is what flattened the menu. (Justin, 2026-08-21) */
.nav-menu.grouped .grp-head > a{font-family:var(--fm);font-size:11px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--gold);padding:8px 10px 6px;}
.nav-menu.grouped .grp-head > a:hover{color:var(--paper);}
.nav-menu.grouped .grp ul{display:block;min-width:0;margin:0 0 4px 12px;
  border-left:1px solid rgba(245,241,231,.16);padding-left:0;}
/* the region row: name on the left, expander on the right (mobile only) */
.nav-menu.grouped .grp-head{display:flex;align-items:center;justify-content:space-between;gap:6px;}
.nav-menu.grouped .grp-toggle{display:none;}
.nav-menu.grouped .grp ul a{padding-left:12px;font-size:13.5px;}
/* Nav collapses to the hamburger at 1080px, not 900px. The eight top-level
   items are 733px wide on their own; with the wordmark, the CTA and the wrap
   padding the bar needs about 1012px to stay on one line — so between 900 and
   1012 the old breakpoint left it wrapping onto two rows with "Service Area"
   and "Why Novo" stacked. Measured, not guessed (Justin, 2026-09-01). */
@media (max-width:1080px){
  /* A phone gets real nested dropdowns: each region collapsed behind its own
     expander, so the whole area menu fits on screen instead of running off the
     bottom. The region name stays a link to its hub page; the caret beside it
     is a separate control, so tapping the name never traps you. */
  .nav-menu.grouped > ul{columns:1;min-width:0;}
  .nav-menu.grouped > ul > li{margin-bottom:0;}
  .nav-menu.grouped .grp{border-bottom:1px solid rgba(245,241,231,.07);}
  .nav-menu.grouped .grp-head > a{padding:13px 0;font-size:11px;flex:1;}
  .nav-menu.grouped .grp-toggle{display:inline-flex;align-items:center;justify-content:center;
    width:38px;height:38px;flex:none;background:none;border:0;cursor:pointer;color:var(--gold);
    padding:0;}
  .nav-menu.grouped .grp-toggle::after{content:'';width:7px;height:7px;
    border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
    transform:rotate(45deg) translate(-2px,-2px);transition:transform .2s ease;}
  .nav-menu.grouped .grp.open .grp-toggle::after{transform:rotate(-135deg) translate(-2px,-2px);}
  /* No transition on max-height. A transition declared on the very property
     the class change flips is the failure this codebase has hit three times
     (background, grid-template-rows, max-height) — the property sticks at
     its from-value and the panel never opens. Flip it instantly and let a
     keyframe carry the polish; the last frame IS the resting state. */
  .nav-menu.grouped .grp ul{max-height:0;overflow:hidden;
    margin:0 0 0 4px;padding-left:12px;border-left:1px solid rgba(201,162,114,.38);}
  .nav-menu.grouped .grp.open ul{max-height:520px;animation:grpOpen .2s ease both;}
  @keyframes grpOpen{from{opacity:0;transform:translateY(-5px);}to{opacity:1;transform:none;}}
  .nav-menu.grouped .grp ul a{padding-top:9px;padding-bottom:9px;font-size:13.5px;
    color:rgba(251,248,242,.86);}
  .nav-menu.grouped .grp ul li:last-child a{padding-bottom:12px;}
}
.nav-menu .all{grid-column:1 / -1;margin-top:6px;padding-top:8px;
  border-top:1px solid rgba(245,241,231,.12);}
.nav-menu .all a{font-family:var(--fm);font-size:11px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--gold);}
/* Hover has to agree with the caret, or the arrow says "closed" while the panel
   is open. And once someone clicks to close, hover must stop re-opening it under
   the pointer — .hover-off does that until the pointer leaves. */
@media (hover:hover){
  .has-menu:not(.hover-off):hover .nav-menu{opacity:1;visibility:visible;transform:translateY(0);
    transition:opacity .16s ease,transform .16s ease,visibility 0s;}
  .has-menu:not(.hover-off):hover .caret{transform:rotate(180deg);}
}

@media (max-width:1080px){
  /* On a phone the panel is already a stacked list, so a floating dropdown has
     nowhere to float. The menus become inline accordions instead. */
  .has-menu{position:static;}
  .nav-top{width:100%;justify-content:space-between;padding:14px 0;
    border-bottom:1px solid rgba(245,241,231,.1);}
  .nav-menu{position:static;opacity:1;visibility:visible;transform:none;box-shadow:none;
    border:none;border-radius:0;background:transparent;padding:0 0 0 12px;
    max-height:0;overflow:hidden;transition:max-height .22s ease;}
  /* The cap is a ceiling, not a height — anything taller than it gets CLIPPED
     with no scrollbar and no warning. The Service Area menu measured 421px at
     15 towns (~28px each), so 640px was roughly 7 towns from silently hiding
     links. 900px holds ~30. Raise this before adding a fifteenth service or a
     thirty-first town, or switch to grid-template-rows 0fr/1fr and be rid of
     the magic number. */
  .has-menu.open .nav-menu{max-height:900px;}
  .nav-menu.two-col ul{grid-template-columns:1fr 1fr;min-width:0;}
  .nav-menu a{padding:10px 0;font-size:13.5px;}
  @media (hover:hover){.has-menu:hover .nav-menu{max-height:0;}
    .has-menu.open:hover .nav-menu{max-height:900px;}}
}

.nav-toggle{display:none;width:38px;height:38px;border:1px solid rgba(245,241,231,.3);border-radius:8px;background:transparent;cursor:pointer;align-items:center;justify-content:center;flex:none;}
.nav-toggle span,.nav-toggle span::before,.nav-toggle span::after{content:'';display:block;width:16px;height:1.5px;background:var(--paper);position:relative;}
.nav-toggle span::before{position:absolute;top:-5px;}
.nav-toggle span::after{position:absolute;top:5px;}
@media (max-width:1080px){
  .nav-links{
    /* bottom:auto with no max-height meant a menu taller than the screen simply
       ran off the bottom — unreachable, unscrollable, links silently lost.
       (Justin, 2026-08-21) */
    /* --navh is measured from the real header in shared.js. It was hard-coded
       at 84px while the header actually renders 106px, so the panel hung 22px
       below the viewport and its last link sat under the fold. */
    position:fixed;inset:var(--navh,84px) 0 auto 0;flex-direction:column;gap:0;background:var(--ink);
    max-height:var(--navmax,calc(100svh - 84px));overflow-y:auto;overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;
    border-bottom:1px solid rgba(245,241,231,.14);padding:8px 24px 18px;
    /* visibility:hidden takes the closed menu out of the tab order too —
       opacity alone left six invisible links keyboard-focusable, so tabbing
       from the logo went nowhere visible for six presses. It still animates,
       because visibility is deferred until the fade finishes. */
    transform:translateY(-8px);opacity:0;pointer-events:none;visibility:hidden;
    transition:opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
  }
  .nav-links.open{transform:translateY(0);opacity:1;pointer-events:auto;visibility:visible;
    transition:opacity .18s ease, transform .18s ease, visibility 0s;}
  .nav-links a{display:block;padding:14px 0;border-bottom:1px solid rgba(245,241,231,.1);}
  .nav-toggle{display:inline-flex;}
}

/* ---- photo grids ----
   Rows of photographs of different shapes read as an accident, so a grid
   crops them to one shape and they read as a set. height:auto matters: the
   image tags carry width/height attributes to reserve space before they load,
   and aspect-ratio is ignored on a box whose height is already definite. */
.shot-grid{display:grid;gap:16px;}
.shot-grid img{display:block;width:100%;height:auto;object-fit:cover;border-radius:var(--radius-lg);}
.shot-grid.cols-4{grid-template-columns:repeat(4,1fr);}
.shot-grid.cols-3{grid-template-columns:repeat(3,1fr);}
.shot-grid.cols-2{grid-template-columns:repeat(2,1fr);}
.shot-grid.square img{aspect-ratio:1/1;}
.shot-grid.tall img{aspect-ratio:4/5;}
.shot-grid.wide img{aspect-ratio:3/2;}
.shot-grid figure{margin:0;}
.shot-grid figcaption{font-family:var(--fm);font-size:11px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-soft);margin-top:10px;}
.on-dark .shot-grid figcaption, .sec-dark .shot-grid figcaption{color:rgba(251,248,242,.66);}
@media (max-width:900px){.shot-grid.cols-4,.shot-grid.cols-3{grid-template-columns:1fr 1fr;}}
/* One column means there is no row to line up, so the reason for cropping is
   gone — and a full-width crop on a phone is where it costs the most, taking a
   third off a photograph nobody asked to have reframed. Below this width every
   image is simply itself. The selectors carry the modifier class because a
   media query adds no specificity and .shot-grid.square img would otherwise
   still win. */
@media (max-width:560px){
  .shot-grid.cols-4,.shot-grid.cols-3,.shot-grid.cols-2{grid-template-columns:1fr;}
  .shot-grid.square img,.shot-grid.tall img,.shot-grid.wide img{aspect-ratio:auto;}
}

/* ---- service cards ----
   The row list reads as a table of contents: a name, a line of description, a
   rule between them. That is right for an index and wrong for a menu of things
   you can buy, which is what the commercial hub is — each one had to look like
   an object you can pick up. Cards are lifted off the ground rather than
   painted onto it, so the same rules work on the ink section here and on paper
   if this is rolled to the other hubs.

   The whole card is the link. An arrow you have to aim at is a worse target
   than a 300px box, and the arrow stays only as the affordance that says so. */
.svc-cards{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:38px;}
@media (max-width:820px){.svc-cards{grid-template-columns:1fr;}}
.svc-card{
  display:flex;flex-direction:column;position:relative;
  background:var(--paper);border:1px solid var(--line);border-radius:10px;
  padding:26px 24px 22px;text-decoration:none;color:inherit;
  transition:border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.svc-card h3{font-family:var(--fd);font-weight:700;font-size:19px;letter-spacing:-.02em;
  margin:0 0 10px;padding-right:28px;line-height:1.25;}
.svc-card p{font-family:var(--ff);font-size:14.5px;line-height:1.65;color:var(--ink-soft);margin:0;}
.svc-card .go{position:absolute;top:24px;right:22px;font-size:18px;color:var(--gold-deep);
  transition:transform .18s ease;}
a.svc-card:hover{border-color:var(--gold-deep);transform:translateY(-2px);
  box-shadow:0 16px 34px -20px rgba(21,19,15,.35);}
a.svc-card:hover .go{transform:translateX(3px);}
/* The one that has no page of its own: still a card, but nothing to aim at. */
.svc-card.here .tagline{font-family:var(--fm);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--gold-deep);margin-top:14px;}

/* On an ink ground the cards lift off it rather than sitting on it as cream
   slabs — four of those would shout louder than the heading above them. */
.on-dark .svc-card, .sec-dark .svc-card{
  background:rgba(251,248,242,.055);border-color:rgba(251,248,242,.17);color:var(--paper);
}
.on-dark .svc-card p, .sec-dark .svc-card p{color:rgba(251,248,242,.68);}
.on-dark .svc-card .go, .sec-dark .svc-card .go{color:var(--gold);}
.on-dark a.svc-card:hover, .sec-dark a.svc-card:hover{
  border-color:var(--gold);background:rgba(251,248,242,.085);box-shadow:none;
}
.on-dark .svc-card.here .tagline, .sec-dark .svc-card.here .tagline{color:var(--gold);}

/* ---- editorial row list (services / faq) ---- */
.row-list .row{
  display:grid;grid-template-columns:280px 1fr auto;gap:28px;align-items:center;
  padding:28px 0;border-top:1px solid var(--line);
}
.row-list .row:last-child{border-bottom:1px solid var(--line);}
/* Light-on-dark variants, kept for any section that is still ink-grounded. */
.on-dark .row-list .row, .sec-dark .row-list .row{border-top-color:rgba(245,241,231,.16);}
.on-dark .row-list .row:last-child, .sec-dark .row-list .row:last-child{border-bottom-color:rgba(245,241,231,.16);}
.row-list .row .name{font-family:var(--fd);font-weight:600;font-size:22px;letter-spacing:-.015em;}
.row-list .row .desc{font-family:var(--ff);font-size:15px;line-height:1.65;color:var(--ink-soft);max-width:520px;}
.on-dark .row-list .row .desc{color:rgba(245,241,231,.68);}
.row-list .row .arrow{font-size:20px;color:var(--gold-deep);}
.on-dark .row-list .row .arrow, .sec-dark .row-list .row .arrow{color:var(--gold);}
@media (max-width:760px){.row-list .row{grid-template-columns:1fr auto;}.row-list .row .desc{grid-column:1/-1;}}

/* ---- split panel (who we serve) ---- */
.serve-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:0;border:1px solid var(--line);}
@media (max-width:820px){.serve-grid{grid-template-columns:1fr;}}
.serve-panel{padding:48px 44px;}
.serve-panel + .serve-panel{border-left:1px solid var(--line);}
@media (max-width:820px){.serve-panel + .serve-panel{border-left:none;border-top:1px solid var(--line);}}
/* 44px of side padding is generous on a desktop column and absurd on a phone:
   it was eating 88 of 375px, which is what pushed the "request a commercial
   quote" button onto two lines with the arrow stranded on the second. */
@media (max-width:560px){.serve-panel{padding:34px 24px;}}
.serve-panel.primary{background:var(--ink);color:var(--paper);}
.serve-panel .tag{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold-deep);margin-bottom:14px;}
.serve-panel.primary .tag{color:var(--gold);}
.serve-panel h3{margin:0 0 16px;}
.serve-panel p.desc{font-family:var(--ff);font-size:15px;line-height:1.7;color:var(--ink-soft);max-width:440px;margin:0 0 26px;}
.serve-panel.primary p.desc{color:rgba(245,241,231,.65);}
.on-dark .lede{color:rgba(245,241,231,.65);}
.serve-list{list-style:none;margin:0 0 30px;padding:0;}
.serve-list li{font-family:var(--ff);font-size:15px;padding:11px 0;border-top:1px solid var(--line);}
.serve-panel.primary .serve-list li{border-top:1px solid rgba(245,241,231,.14);}
.serve-list li:last-child{border-bottom:1px solid var(--line);}
.serve-panel.primary .serve-list li:last-child{border-bottom:1px solid rgba(245,241,231,.14);}

/* ---- pinned portfolio scroll-reveal ---- */
.pin-wrap{position:relative;height:240vh;}
.pin-inner{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;background:var(--ink);}
.pin-grid{position:absolute;inset:0;display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);gap:4px;padding:4px;}
.pin-grid div{background-size:cover;background-position:center;}
.pin-scrim{position:absolute;inset:0;background:rgba(21,19,15,0);transition:background .05s linear;}
.pin-copy{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
  color:var(--paper);opacity:0;transform:translateY(24px);transition:opacity .1s linear, transform .1s linear;padding:0 24px;
}
.pin-copy h2{font-style:normal;max-width:820px;}
@media (max-width:760px){
  .pin-wrap{height:auto;}
  .pin-inner{position:relative;height:72vh;min-height:460px;}
  .pin-grid{grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;}
  .pin-grid div:nth-child(1){grid-column:1/3;grid-row:1/2;}
  .pin-grid div:nth-child(2){grid-row:2/3;}
  .pin-grid div:nth-child(3){grid-row:2/3;}
  .pin-grid div:nth-child(4),.pin-grid div:nth-child(5){display:none;}
  .pin-scrim{background:rgba(21,19,15,.74) !important;}
  .pin-copy{opacity:1 !important;transform:none !important;}
}

/* ---- tier cards overlapping the seam ---- */
.tiers-wrap{position:relative;background:var(--paper);padding:0 0 100px;}
.tiers{position:relative;z-index:3;margin-top:-96px;display:grid;grid-template-columns:repeat(3,1fr);gap:20px;}
@media (max-width:820px){.tiers{grid-template-columns:1fr;margin-top:-60px;}.tiers-wrap{padding-bottom:56px;}}
.tier-card{background:var(--paper);border:1px solid var(--line);padding:34px 30px;box-shadow:0 20px 50px -20px rgba(21,19,15,.25);}
.tier-card.feat{background:var(--ink);color:var(--paper);border-color:var(--ink);}
.tier-card .tag{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold-deep);margin-bottom:14px;}
.tier-card.feat .tag{color:var(--gold);}
.tier-card h3{margin:0 0 14px;}
.tier-card p{font-family:var(--ff);font-size:15px;line-height:1.7;color:var(--ink-soft);margin:0;}
.tier-card.feat p{color:rgba(245,241,231,.7);}
.tiers-cta{text-align:center;margin-top:44px;}

/* ---- numbered "why" columns ---- */
.why-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:44px;margin-top:20px;}
@media (max-width:760px){.why-grid{grid-template-columns:1fr;gap:36px;}}
.why-item .num{font-family:var(--fm);font-style:normal;font-size:20px;letter-spacing:.12em;color:var(--gold-deep);margin:0 0 14px;}
.why-item h3{margin:0 0 12px;}
.why-item p{font-family:var(--ff);font-size:15px;line-height:1.7;color:var(--ink-soft);margin:0;}

/* ---- simple tile photo grid (Home "Recent Work", etc.) ---- */
.tile-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;}
.tile-grid img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;}
@media (max-width:640px){.tile-grid{grid-template-columns:repeat(2,1fr);}}

/* ---- structural image placeholder ---- */
.ph-photo{
  background:repeating-linear-gradient(115deg, var(--paper-deep), var(--paper-deep) 10px, var(--paper) 10px, var(--paper) 20px);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;text-align:center;padding:16px;
  font-family:var(--fm);font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-soft);
}

/* ---- quote / credibility block ---- */
.quote-grid{display:grid;grid-template-columns:.8fr 1fr;gap:60px;align-items:center;}
@media (max-width:760px){.quote-grid{grid-template-columns:1fr;gap:28px;}}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:24px;}
@media (max-width:640px){.grid-2{grid-template-columns:1fr;}}
.about-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:56px;align-items:center;}
@media (max-width:760px){.about-grid{grid-template-columns:1fr;gap:28px;}}
/* ---- video embed ----
   Aspect-ratio rather than a fixed height, unlike .tour-embed: a video HAS a
   shape and letterboxing it is a visible mistake, where a tour viewer is just
   a window whose size is a judgement call. */
.video-embed{position:relative;aspect-ratio:16/9;border-radius:var(--radius-lg);
  overflow:hidden;background:var(--ink);box-shadow:0 20px 50px -20px rgba(21,19,15,.25);}
.video-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.video-cap{font-family:var(--fm);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-soft);margin-top:14px;text-align:center;}

/* ---- live 3D tour viewer ----
   Height rather than aspect-ratio: this is something you look around inside,
   not a picture, so what matters is that it is big enough to navigate without
   swallowing a laptop screen. */
.tour-embed{position:relative;border:1px solid var(--line);border-radius:var(--radius-lg);
  overflow:hidden;box-shadow:0 20px 50px -20px rgba(21,19,15,.2);background:var(--ink);}
.tour-embed iframe{display:block;width:100%;height:min(70vh,620px);border:0;}
@media (max-width:700px){.tour-embed iframe{height:min(64vh,460px);}}
.tour-cap{font-family:var(--fm);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-soft);margin-top:14px;text-align:center;}

.photo-trio{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;}
@media (max-width:640px){.photo-trio{grid-template-columns:1fr;}}

/* Hero background photo — mobile gets its own crop anchor rather than
   reusing the desktop one. A wide landscape photo covering a much taller
   mobile box zooms in far more than it does on a wide desktop box, so the
   same anchor point frames a noticeably different (and often worse) slice
   of the image on each. */
.hero-bg{background-size:cover;background-position:center 42%;}
@media (max-width:640px){.hero-bg{background-position:center 30%;}}

/* Stats strip under the hero — a grid instead of flex-wrap so items
   never break into a lopsided row when space runs short. */
.stats-strip{display:grid;grid-template-columns:repeat(4,1fr);}
.stats-strip .stat{text-align:center;padding:6px 14px;border-left:1px solid rgba(245,241,231,.14);}
.stats-strip .stat:first-child{border-left:none;}
@media (max-width:700px){
  .stats-strip{grid-template-columns:repeat(2,1fr);row-gap:22px;}
  .stats-strip .stat:nth-child(2n+1){border-left:none;}
  .stats-strip .stat:nth-child(n+3){border-top:1px solid rgba(245,241,231,.14);padding-top:20px;}
}
.quote-photo{position:relative;}
.quote-photo img{border-radius:2px;width:100%;aspect-ratio:4/3;object-fit:cover;}
.quote-photo .diag{position:absolute;bottom:-18px;left:34px;width:70px;height:70px;
  background-image:repeating-linear-gradient(115deg, var(--gold) 0 2px, transparent 2px 10px);}
.quote-mark{font-family:var(--fd);font-weight:700;font-size:56px;color:var(--gold-deep);line-height:1;margin:0 0 4px;}
blockquote{margin:0 0 26px;font-family:var(--fd);font-style:normal;font-weight:400;font-size:23px;line-height:1.45;letter-spacing:-.015em;color:var(--ink);}
.quote-attr{font-family:var(--fm);font-size:11px;letter-spacing:.04em;color:var(--ink-soft);}
.quote-flag{font-family:var(--fm);font-size:11px;color:var(--gold-deep);letter-spacing:.06em;text-transform:uppercase;margin-top:10px;}

/* ---- FAQ rows ---- */
.faq-list{max-width:820px;margin:0 auto;}
.faq-row{padding:26px 0;border-top:1px solid var(--line);}
.faq-list .faq-row:last-child{border-bottom:1px solid var(--line);}
.faq-row h3{margin:0 0 10px;}
.faq-row p{font-family:var(--ff);font-size:15px;line-height:1.7;color:var(--ink-soft);margin:0;max-width:680px;}

/* ---- forms (contact + commercial quote) ----
   Netlify Forms handles the submission, so there is no JS here and nothing to
   configure: the data-netlify attribute plus the hidden form-name input is the
   whole integration. The honeypot field is visually hidden rather than
   display:none, because bots skip hidden inputs but fill visible ones. */
.nform{margin-top:32px;}
.nform-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px;}
@media (max-width:620px){.nform-row{grid-template-columns:1fr;}}
.nfield{display:block;margin-bottom:14px;}
.nfield > span{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft);margin-bottom:6px;}
.nfield .req{color:var(--gold-deep);}
.nform input[type=text],.nform input[type=email],.nform input[type=tel],
.nform select,.nform textarea{
  width:100%;font-family:var(--ff);font-size:15px;color:var(--ink);background:var(--paper);
  border:1px solid var(--line);border-radius:5px;padding:12px 14px;outline:none;
  transition:border-color .16s ease;-webkit-appearance:none;appearance:none;
}
.nform select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23806040' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 14px center;background-size:12px;padding-right:38px;}
.nform textarea{min-height:130px;resize:vertical;line-height:1.6;}
.nform input:focus,.nform select:focus,.nform textarea:focus{border-color:var(--gold-deep);}
.nform button[type=submit]{margin-top:6px;}
.nform-note{font-family:var(--fm);font-size:11px;color:var(--ink-soft);margin-top:14px;line-height:1.6;}
.nform-hp{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}

/* ---- skip link ----
   Keyboard users otherwise tab the logo, the menu button, six nav links and a
   CTA before reaching any content, on every page. Off-screen until focused. */
/* fixed, not absolute: absolute anchors it to the document, so anyone tabbing
   after scrolling would focus a link sitting thousands of pixels above the
   viewport — visible to the accessibility tree, useless on screen. */
.skip-link{position:fixed;left:-9999px;top:0;z-index:100;
  font-family:var(--fm);font-size:12px;letter-spacing:.06em;text-transform:uppercase;
  background:var(--gold);color:var(--ink);padding:12px 18px;border-radius:0 0 6px 0;
  text-decoration:none;}
.skip-link:focus{left:0;}

/* ---- homepage package block: live sliders ----
   The three prices above are real HTML at the $650K / 2,400 default, which is
   what crawlers read. These sliders only move them. */
.glance-live{margin:28px auto 0;max-width:760px;}
.glance-fields{display:grid;grid-template-columns:1fr 1fr;gap:28px;}
@media (max-width:620px){.glance-fields{grid-template-columns:1fr;gap:20px;}}
.glance-lab{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.12em;
  text-transform:uppercase;color:rgba(245,241,231,.66);margin-bottom:4px;}
.glance-val{display:block;font-family:var(--fd);font-weight:800;font-size:26px;
  letter-spacing:-.03em;color:var(--paper);line-height:1.1;}
.glance-live input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:3px;
  border-radius:2px;margin-top:12px;background:rgba(245,241,231,.22);outline:none;cursor:pointer;}
.glance-live input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:20px;height:20px;border-radius:50%;background:var(--gold);border:3px solid var(--ink);
  box-shadow:0 0 0 1px var(--gold);cursor:pointer;}
.glance-live input[type=range]::-moz-range-thumb{width:20px;height:20px;border-radius:50%;
  background:var(--gold);border:3px solid var(--ink);cursor:pointer;}
.glance-live input[type=range]:focus-visible{outline:2px solid var(--gold);outline-offset:4px;}

/* This used to reorder the packages band on a phone, because the sliders sat
   after the cards in the markup and landed a screen below the prices they
   controlled. The express panel owns its own sliders now and puts them above
   its cards, so there is nothing left to reorder — and the rule had turned
   harmful: .glance-live, .tiers, .tier-foot and .tiers-cta no longer exist on
   the page, while the flex container it created gave the panel the default
   order:0 and floated the whole calculator ABOVE its own heading. */

/* ---- footer ---- */
.site-footer{background:var(--ink);color:rgba(245,241,231,.7);padding:64px 0 32px;border-top:1px solid rgba(245,241,231,.1);}
.footer-grid{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:40px;margin-bottom:48px;}
@media (max-width:760px){.footer-grid{grid-template-columns:1fr 1fr;}}
@media (max-width:480px){.footer-grid{grid-template-columns:1fr;}}
.footer-col img.logo{height:24px;filter:brightness(0) invert(1);margin-bottom:14px;}
.footer-col p{font-family:var(--ff);font-size:14.5px;line-height:1.7;color:rgba(245,241,231,.58);max-width:260px;}
/* Footer text alphas below are the minimum that clear 4.5:1 on the --ink
   ground (#3B2F26), measured rather than guessed: .40 was 3.19:1, .36 was
   2.86:1 — the worst contrast on the site. */
/* h3, not h4: these follow the page's closing <h2>, and skipping a level breaks
   the document outline screen readers navigate by. The label styling is
   unchanged — only the tag is. */
.footer-col h4,.footer-col .footer-h{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:rgba(245,241,231,.66);margin:0 0 16px;font-weight:400;}
.footer-col ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px;}
.footer-col a{color:rgba(245,241,231,.72);text-decoration:none;font-family:var(--ff);font-size:14.5px;}
.footer-col a:hover{color:var(--gold);}
.footer-bottom{display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px;padding-top:24px;border-top:1px solid rgba(245,241,231,.1);font-family:var(--fm);font-size:11px;color:rgba(245,241,231,.45);}

/* ---- lightweight stub-page notice (removed once a page gets real content) ---- */
.stub-notice{
  background:rgba(201,164,126,.12);border:1px dashed var(--gold-deep);border-radius:var(--radius-sm);
  padding:18px 22px;margin:0 0 40px;font-family:var(--fm);font-size:12px;color:var(--gold-deep);
  max-width:640px;
}

/* ---- light stats band (replaces the old near-black strip) ----
   Dark sections are now used sparingly — the closing CTA and the footer — so
   this band carries its rhythm with a tint and hairlines instead of ink. */
.lightband{background:var(--paper-deep);border-top:1px solid var(--line);border-bottom:1px solid var(--line);}
.lightband .stats-strip .stat{border-left:1px solid var(--line);}
.lightband .stats-strip .stat:first-child{border-left:none;}
.stat-k{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-soft);opacity:.75;margin-bottom:6px;}
.stats-strip .stat strong{display:block;font-family:var(--fd);font-weight:600;
  font-size:19px;letter-spacing:-.015em;color:var(--ink);}
.stat-sub{font-weight:400;color:var(--ink-soft);font-size:14px;}
/* Real stars, not just a numeral — visual impact at a glance. */
.stars{color:var(--gold-deep);letter-spacing:.06em;}
.plus{color:var(--gold-deep);}
@media (max-width:700px){
  .lightband .stats-strip .stat:nth-child(2n+1){border-left:none;}
  .lightband .stats-strip .stat:nth-child(n+3){border-top:1px solid var(--line);padding-top:20px;}
}

/* ---- review stars in testimonial blocks ---- */
.review-stars{font-size:15px;color:var(--gold-deep);letter-spacing:.1em;margin:0 0 10px;}

/* ---- coverage / service-area band ---- */
.coverage-grid{display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:center;}
@media (max-width:820px){.coverage-grid{grid-template-columns:1fr;gap:32px;}}
/* Leaflet map in the coverage band. aspect-ratio rather than a fixed height
   so it scales with the column instead of overflowing narrow screens — the
   failure mode of the vendor's own 550px iframe. */
.coverage-map{aspect-ratio:4/3;width:100%;background:var(--paper-deep);}
@media (max-width:820px){.coverage-map{aspect-ratio:1/1;}}
/* OSM's default palette is green-and-tan against a bright blue circle, which
   fights the espresso ground. Desaturating and warming the tiles in CSS keeps
   OpenStreetMap (and its attribution) while letting it sit with the brand. */
.coverage-map .leaflet-tile-pane{filter:saturate(.5) sepia(.18) brightness(1.02) contrast(.95);}
.coverage-map .leaflet-container{background:var(--paper-deep);font-family:var(--ff);}
.coverage-map .leaflet-control-attribution{
  background:rgba(251,248,242,.82);font-size:11px;font-family:var(--fm);
}
.coverage-map .leaflet-control-zoom a{
  color:var(--ink);background:var(--paper);border-color:var(--line);
}
.county-list{
  font-family:var(--fm);font-size:12px;letter-spacing:.06em;line-height:2.1;
  color:rgba(245,241,231,.65);margin:0;
}
.county-list .st{color:rgba(245,241,231,.66);}

/* ---- footer: counties served ---- */
/* This block used to hold a dense county list, which is why it was 11px mono.
   It now holds a sentence, and a sentence at 11px in a monospace face is the
   hardest thing on the page to read. Body face, body size; only the label above
   it stays a mono micro-label. */
.footer-counties{
  font-family:var(--ff);font-size:13.5px;line-height:1.7;letter-spacing:normal;
  color:rgba(245,241,231,.72);padding-top:22px;border-top:1px solid rgba(245,241,231,.1);
  margin-bottom:4px;
}
.footer-counties a{color:var(--gold);text-decoration:none;}
.footer-counties a:hover{text-decoration:underline;}
.footer-counties .fc-k{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(245,241,231,.66);margin-bottom:6px;}
.footer-bottom{border-top:none !important;padding-top:14px !important;}

/* The sticky header is ink-tinted glass, so an ink-filled pill sits invisible
   on it until hover — same bug as the dark sections. Gold fill here too. */
.site-header .pill{background:var(--gold);color:var(--on-gold);border-color:var(--gold);}
.site-header .pill:hover{background:var(--paper);border-color:var(--paper);color:var(--ink);}

/* ============================================================================
   DUSK — the dark bands stop being flat ink and become a shallow warm sky:
   deeper overhead, falling to last light at the bottom, with a thin lit
   horizon where sky meets land. Novo sells twilight photography, so the
   color is borrowed from a service rather than invented as decoration.
   Kept deliberately shallow — a wide gradient reads as UI ornament.
   ========================================================================= */
.sec-dark:not([style*="100vh"]),
.site-footer{
  background:linear-gradient(178deg,var(--dusk-1) 0%,var(--dusk-2) 62%,var(--dusk-3) 100%) !important;
  position:relative;
}
.site-footer{border-top:none;}
.sec-dark:not([style*="100vh"])::after,
.site-footer::before{
  content:'';position:absolute;left:0;right:0;height:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,var(--gold) 26%,var(--gold) 74%,transparent);
  opacity:.4;
}
.sec-dark:not([style*="100vh"])::after{bottom:0;}
.site-footer::before{top:0;}

/* ============================================================================
   THE PACKAGES BAND — this was a saturated dark room. The booking flow that
   now lives in it was designed on paper, and re-coloring every piece of it
   for a second ground cost more than the room was worth: each control that
   sat directly on the dark (labels, sliders, back links, buttons) needed its
   own exception, and any new one would need another. So the band is a light
   tint instead — still its own space, but the flow inside it renders exactly
   as designed with no per-element overrides at all.
   ========================================================================= */
.tiers-wrap{background:var(--paper-deep) !important;}
/* The cards originally carried a -96px margin so they straddled the seam
   between two sections. Against a colored room that reads as the cards
   colliding with the section above, so inside the room they sit properly
   within it instead. */
.tiers-wrap .tiers{margin-top:0;}
.tiers-wrap{padding-top:clamp(56px,7vw,84px);}
/* The static cards below are the no-JS fallback inside the mount, so they are
   what crawlers read. On the light band they need no inversion — the plain
   .tier-card / .tier-card.feat treatment above is already correct. */
@media (max-width:820px){.tiers-wrap .tiers{margin-top:0;}}

/* ============================================================================
   MARQUEE — the recent-work gallery scrolls rather than sitting in a grid.

   How the seamless loop works: each row holds the SAME image list twice, and
   the row translates by exactly -50% before resetting. Because the second
   half is identical to the first, the reset lands on a pixel-identical frame
   and there is no visible jump. The duplicate carries aria-hidden so screen
   readers and crawlers still see each photograph once.

   The two rows run in opposite directions — that contrast is what makes it
   read as a moving wall of work rather than a slideshow.
   ========================================================================= */
.marquee{
  --row-h:clamp(150px,19vw,250px);
  --speed:64s;
  display:flex;flex-direction:column;gap:10px;
  margin:40px 0 0;overflow:hidden;
}
.marquee-row{display:flex;width:max-content;animation:marquee var(--speed) linear infinite;}
.marquee-row.reverse{animation-direction:reverse;--speed:78s;}
.marquee-track{display:flex;gap:10px;padding-right:10px;flex:none;}
.marquee img{
  height:var(--row-h);width:auto;max-width:none;
  object-fit:cover;display:block;background:var(--paper-deep);
}
@keyframes marquee{from{transform:translateX(0);}to{transform:translateX(-50%);}}
/* Let people stop it to actually look at a photo. */
.marquee:hover .marquee-row,
.marquee:focus-within .marquee-row{animation-play-state:paused;}
/* Continuous motion is exactly what reduced-motion is asking us not to do.
   The duplicate track exists only to make the loop seamless, so it is hidden
   here and the strip becomes an ordinary horizontally-scrollable row. */
@media (prefers-reduced-motion:reduce){
  .marquee{overflow-x:auto;}
  .marquee-row{animation:none;}
  .marquee-row .marquee-track[aria-hidden="true"]{display:none;}
}
/* Motion is decorative here, so honor the OS setting: the rows stop and the
   gallery becomes a plain scrollable strip. */
@media (prefers-reduced-motion:reduce){
  .marquee-row{animation:none;}
  .marquee{overflow-x:auto;}
  .marquee-track[aria-hidden="true"]{display:none;}
}

/* Header for the packages band — light ground, so ordinary section colors. */
.tiers-head{max-width:640px;margin:0 0 clamp(34px,4vw,52px);}
.tiers-head .eyebrow{color:var(--gold-deep);}
.tiers-head h2{color:var(--ink);margin-bottom:14px;}
.tiers-head .lede{color:var(--ink-soft);margin-bottom:0;}

/* ============================================================================
   PINNED HERO — the photograph is fixed to the viewport and the rest of the
   page scrolls UP OVER it. The hero text scrolls away normally; the image
   stays put and is progressively covered by the section that follows.

   Deliberately a fixed ELEMENT, not `background-attachment:fixed` — the CSS
   property is the obvious route and is unreliable on iOS Safari.

   `!important` is required because both hero layers carry position:absolute
   as an inline style, and inline beats a stylesheet rule of any specificity.
   ========================================================================= */
#heroPhoto{background:transparent !important;}
#heroPhoto .hero-bg,
#heroPhoto .hero-scrim{position:fixed !important;inset:0;z-index:0;}
#heroPhoto > div:last-child{position:relative;z-index:1;}
/* Everything after the hero rides over the pinned image, so each following
   band needs its own opaque ground and a higher stacking order. */
main > *:not(#heroPhoto), .site-footer{position:relative;z-index:2;}
/* .v2-cta is excluded because it paints itself --ink and then lays a photo over
   the top. Without the exclusion this rule (0,4,1) beat it, the section stayed
   paper, and the paper-coloured heading was invisible for as long as the lazy
   background image took to arrive — or forever, if it failed. */
main > section:not(#heroPhoto):not(.sec-dark):not(.tiers-wrap):not(.sec-tint):not(.v2-cta):not(.v2-hero-person){background:var(--paper);}

/* Pinned backgrounds are the effect most likely to stutter on mobile Safari,
   and the payoff is small on a short screen — fall back to a normal scrolling
   hero on phones. */
@media (max-width:760px){
  #heroPhoto .hero-bg,
  #heroPhoto .hero-scrim{position:absolute !important;}
}

/* ============================================================================
   HOME — sections added in the content build
   ========================================================================= */

/* ---- package cards: items + example price ---- */
.tier-list{list-style:none;margin:0 0 20px;padding:0;}
.tier-list li{
  font-family:var(--ff);font-size:14.5px;line-height:1.5;color:var(--ink-soft);
  padding:7px 0;border-top:1px solid var(--line);
}
.tier-list li:first-child{border-top:none;padding-top:0;}
/* .tier-card.feat is the dark card, so its list has to opt out of --ink-soft:
   that is a light-background color, 1.96:1 on near-black, i.e. unreadable. */
.tier-card.feat .tier-list li{color:rgba(251,248,242,.78);border-top-color:rgba(251,248,242,.18);}
.tier-card.feat .tier-price{border-top-color:rgba(251,248,242,.26);}
.tier-price{border-top:1px solid var(--line);padding-top:14px;}
.tier-price strong{display:block;font-family:var(--fd);font-weight:700;font-size:30px;
  letter-spacing:-.03em;line-height:1;}
.tier-price span{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.06em;
  text-transform:uppercase;opacity:.62;margin-top:7px;}
.tier-foot{font-family:var(--ff);font-size:14px;color:var(--ink-soft);
  text-align:center;margin:26px auto 0;max-width:560px;}
.tier-foot a{color:var(--gold-deep);}

/* ---- before / after ---- */
/* gap widened from 14px: the hairline sits 6px outside each stage, so at 14px
   the two frames’ outlines met in the middle. */
.ba-grid{display:grid;grid-template-columns:1fr 1fr;gap:34px;margin-top:34px;}
@media (max-width:700px){.ba-grid{grid-template-columns:1fr;}}
.ba-item{margin:0;}
.ba-item figcaption{font-family:var(--fm);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-soft);opacity:.75;margin-top:10px;}
.ba-note{font-size:15px;color:var(--ink-soft);margin-top:22px;max-width:620px;}

/* ---- what every shoot includes ---- */
.incl-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:34px 40px;margin-top:38px;}
@media (max-width:900px){.incl-grid{grid-template-columns:1fr 1fr;gap:28px;}}
@media (max-width:600px){.incl-grid{grid-template-columns:1fr;}}
.incl h3{font-size:17px;margin:0 0 8px;color:var(--paper);}
.incl p{font-size:14.5px;line-height:1.65;color:rgba(251,248,242,.68);margin:0;}

/* ---- how booking works ---- */
.steps{list-style:none;counter-reset:none;margin:38px 0 0;padding:0;
  display:grid;grid-template-columns:repeat(4,1fr);gap:34px;}
@media (max-width:900px){.steps{grid-template-columns:1fr 1fr;gap:30px;}}
@media (max-width:600px){.steps{grid-template-columns:1fr;}}
.steps .n{font-family:var(--fm);font-size:11px;letter-spacing:.18em;color:var(--gold-deep);
  display:block;margin-bottom:12px;}
.steps h3{font-size:17px;margin:0 0 8px;}
.steps p{font-size:14.5px;line-height:1.65;color:var(--ink-soft);margin:0;}

/* ---- who this is built for ---- */
.fit-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;margin-top:36px;border:1px solid var(--line);}
@media (max-width:820px){.fit-grid{grid-template-columns:1fr;}}
.fit{padding:30px 32px;}
.fit + .fit{border-left:1px solid var(--line);}
@media (max-width:820px){.fit + .fit{border-left:none;border-top:1px solid var(--line);}}
.fit.alt{background:var(--paper-deep);}
.fit h3{font-size:16px;margin:0 0 16px;}
.fit ul{list-style:none;margin:0;padding:0;}
.fit li{font-size:14.5px;line-height:1.6;color:var(--ink-soft);
  padding:10px 0;border-top:1px solid var(--line);}
.fit li:first-child{border-top:none;}
.fit-note{font-size:14px;color:var(--ink-soft);margin-top:22px;max-width:640px;}

/* ---- About portrait: a background-removed PNG, so it needs a ground rather
   than a frame — a soft tint panel behind it reads deliberate instead of
   looking like a cut-out floating on the page. ---- */
/* Background-removed portrait, flattened onto the exact --paper value at
   export so it compresses as a photograph (209KB) rather than a 1.5MB PNG.
   No panel or frame behind it — it should read as standing on the page.
   NOTE: the paper color is baked into the file, so if --paper ever changes
   this image has to be re-exported to match. */
.about-portrait{
  width:100%;height:auto;display:block;background:none;
  /* The cutout stops mid-torso, leaving a hard horizontal edge. Masking the
     bottom to transparent dissolves it into the page instead. Works because
     the image's baked-in background and the section behind it are the same
     --paper value, so the fade has nothing to reveal. */
  -webkit-mask-image:linear-gradient(to bottom, #000 88%, rgba(0,0,0,.55) 96%, transparent 100%);
          mask-image:linear-gradient(to bottom, #000 88%, rgba(0,0,0,.55) 96%, transparent 100%);
}

/* ---- Who we work with: equal panels. Was 1.1fr/.9fr to weight residential;
   Justin preferred them matched. ---- */
.serve-grid{grid-template-columns:1fr 1fr;}
@media (max-width:820px){.serve-grid{grid-template-columns:1fr;}}

/* ---- What we shoot: two columns from tablet up. Seven single-file rows ran
   too long once the page grew; paired up they scan in half the height. ---- */
@media (min-width:900px){
  .row-list{display:grid;grid-template-columns:1fr 1fr;column-gap:56px;}
  .row-list .row{grid-template-columns:1fr auto;align-items:start;padding:22px 0;}
  .row-list .row:nth-child(2){border-top:1px solid var(--line);}
  .row-list .row .name{grid-column:1;grid-row:1;}
  .row-list .row .desc{grid-column:1;grid-row:2;max-width:none;margin-top:6px;}
  .row-list .row .arrow{grid-column:2;grid-row:1 / span 2;align-self:center;}
  .row-list .row:last-child{border-bottom:none;}
  .row-list .row:nth-last-child(-n+2){border-bottom:1px solid var(--line);}
}

/* ---- Tier cards: the package contents should out-weigh the section blurb,
   since that's what people actually compare. ---- */
.tier-list li{font-size:15.5px;padding:9px 0;}
.tier-card h3{font-size:20px;}
.tier-price strong{font-size:34px;}
/* Sentence-case here, not the uppercase label treatment of the base rule — so it
   needs real body sizing. At 11px/.62 the line that QUALIFIES the price ("2,800
   sq ft home listed at $700K") was the least readable thing on the card. */
.tier-price span{font-size:12.5px;line-height:1.55;text-transform:none;letter-spacing:.02em;opacity:.74;}
.cta-alt{font-family:var(--ff);font-size:14.5px;color:rgba(251,248,242,.66);margin:22px 0 0;}
.cta-alt a{color:var(--gold);}

/* ============================================================================
   BEFORE / AFTER COMPARISON SLIDER
   The "after" sits underneath at full size; the "before" is layered on top and
   clipped to --pos. Dragging changes --pos only — no reflow, no JS layout work.
   The control is a real range input (keyboard-operable, works without pointer
   events); the visible line and labels are decorative.
   ========================================================================= */
.ba-compare{margin:0;}
/* Only the images live in the stage — see the note in index.html. */
/* Floating-frame decoration, matching the galleries (Justin, 2026-08-28): a
   hairline held off the edge, plus the rounded corners every other image on the
   site now carries. The hairline is INK rather than gold — gold on these reads as
   a highlight, and the slider is a demonstration, not a call to action. The old
   1px border is gone: kept alongside the outline it drew two lines a few pixels
   apart. overflow:hidden already clips the images, so the radius applies to both
   halves and to the clip-path wipe. */
.ba-stage{position:relative;overflow:hidden;line-height:0;
  background:var(--paper-deep);border:0;border-radius:12px;touch-action:pan-y;
  outline:1px solid rgba(59,47,38,.26);outline-offset:6px;
  box-shadow:0 18px 38px -22px rgba(40,28,18,.42);}
.sec-dark .ba-stage,.v2-dark .ba-stage,.v2-cta .ba-stage{
  outline-color:rgba(251,248,242,.30);box-shadow:0 26px 48px -22px rgba(0,0,0,.7);}
.ba-img{display:block;width:100%;height:auto;}
.ba-img--before{position:absolute;top:0;left:0;width:100%;height:100%;
  object-fit:cover;clip-path:inset(0 calc(100% - var(--pos)) 0 0);}
.ba-line{position:absolute;top:0;bottom:0;left:var(--pos);width:2px;
  background:var(--paper);box-shadow:0 0 0 1px rgba(0,0,0,.25);pointer-events:none;}
.ba-line::after{content:'';position:absolute;top:50%;left:50%;width:38px;height:38px;
  transform:translate(-50%,-50%);border-radius:50%;
  background:var(--paper);box-shadow:0 2px 10px rgba(0,0,0,.3);}
/* Tags sit at the TOP now. At the bottom they collided with the burned-in
   "Virtually Staged" disclosure on the staged render and got clipped by the
   stage edge. */
.ba-tag{position:absolute;top:12px;font-family:var(--fm);font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--paper);
  background:rgba(22,18,14,.66);padding:6px 10px;pointer-events:none;
  line-height:1;white-space:nowrap;z-index:2;}
.ba-tag--l{left:12px;} .ba-tag--r{right:12px;}
.ba-range{position:absolute;inset:0;width:100%;height:100%;margin:0;
  -webkit-appearance:none;appearance:none;background:transparent;cursor:ew-resize;}
.ba-range:focus-visible{outline:2px solid var(--gold);outline-offset:2px;}
.ba-range::-webkit-slider-thumb{-webkit-appearance:none;width:44px;height:100%;
  background:transparent;cursor:ew-resize;}
.ba-range::-moz-range-thumb{width:44px;height:100%;border:0;background:transparent;cursor:ew-resize;}
.ba-compare.is-empty .ba-stage{border-style:dashed;}
/* These captions are sentences, not labels — "Interior — color corrected, and
   virtually staged" set in caps loses the word shapes we read by. Uppercase
   stays on the short labels elsewhere. */
.ba-compare figcaption{font-family:var(--fm);font-size:11px;letter-spacing:.04em;
  color:var(--ink-soft);opacity:.75;padding:18px 2px 0;line-height:1.5;}

/* ---- Section rhythm: a light tint so runs of plain-paper sections don't
   flatten into one another, and so no two adjacent bands share a ground.
   The page alternates paper / tint / dark, with the packages room as the one
   saturated moment. ---- */
.sec-tint{background:var(--paper-deep);}
.ba-note strong{font-weight:600;color:var(--ink);}

/* ============================================================================
   PRICING — comparison table and add-on price list
   ========================================================================= */
.tier-table{margin-top:36px;border-top:1px solid var(--line);}
.tt-row{display:grid;grid-template-columns:1.6fr repeat(3,1fr);gap:16px;
  padding:13px 0;border-bottom:1px solid var(--line);align-items:center;
  font-family:var(--ff);font-size:15px;color:var(--ink-soft);}
.tt-row > div + div{text-align:center;}
.tt-head{font-family:var(--fm);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink);opacity:.75;}
.tt-row .yes{color:var(--gold-deep);font-size:20px;line-height:1;}
.tt-foot{font-weight:600;color:var(--ink);border-bottom:none;
  border-top:1px solid var(--ink);margin-top:2px;}
.tt-foot > div + div{font-family:var(--fd);font-size:19px;letter-spacing:-.02em;}
@media (max-width:760px){
  /* No room for four columns. Rather than repeating every tier per row with
     dashes for the ones that don't apply — which read as noise — each row
     lists ONLY the tiers that include it.
     Two things this needs: the row must stop being a grid (grid items ignore
     inline-block, so the tiers would stack one per line), and the
     "not included" cells need enough specificity to beat the :nth-child
     rule below them. */
  .tt-row{display:block;padding:14px 0;}
  .tt-head{display:none;}
  .tt-row > div:first-child{
    font-family:var(--ff);font-size:15px;color:var(--ink);margin-bottom:6px;}
  .tt-row > div:nth-child(n+2){
    display:inline-block;margin:0 14px 0 0;
    font-family:var(--fm);font-size:11px;letter-spacing:.08em;
    text-transform:uppercase;color:var(--gold-deep);}
  .tt-row > div.yes{font-size:0;}
  .tt-row > div.yes::after{content:attr(data-t);font-size:11px;}
  .tt-row > div.txt::after{content:' — ' attr(data-t);}
  .tt-foot > div:nth-child(n+2){
    font-family:var(--ff);font-size:16px;text-transform:none;
    letter-spacing:-.01em;color:var(--ink);margin-right:18px;}
  .tt-foot > div:nth-child(n+2)::after{
    content:' ' attr(data-t);font-family:var(--fm);font-size:11px;
    letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft);}
  /* Must come LAST: this and the :nth-child rule above have identical
     specificity (0,2,1), so source order is what decides. */
  .tt-row > div.no{display:none;}
}


.addon-table{margin-top:32px;border-top:1px solid var(--line);}
.ar{display:flex;justify-content:space-between;align-items:baseline;gap:20px;
  padding:12px 0;border-bottom:1px solid var(--line);}
.ar span:first-child{font-family:var(--ff);font-size:15px;color:var(--ink);}
.ar span:last-child{font-family:var(--fm);font-size:12.5px;color:var(--gold-deep);
  white-space:nowrap;}

/* ---- service cross-links: a heading that happens to be a link should read as
   a heading first. Underline arrives on hover instead of sitting there. ---- */
/* The underline is a pseudo-element scaled on the X axis, NOT a gradient
   background sized to 1px. The gradient version painted --gold-deep as this
   link's background-image, so anything reading computed style (contrast
   checkers, reader modes) saw ink text on a solid gold-deep field and scored
   it 2.4:1. transform also composites on the GPU; background-size does not. */
.why-item h3 a{text-decoration:none;color:inherit;position:relative;
  transition:color .2s ease;}
.why-item h3 a::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:1px;
  background:var(--gold-deep);transform:scaleX(0);transform-origin:0 50%;
  transition:transform .25s ease;}
.why-item h3 a:hover{color:var(--gold-deep);}
.why-item h3 a:hover::after{transform:scaleX(1);}
@media (prefers-reduced-motion:reduce){.why-item h3 a::after{transition:none;}}

/* Reading measure inside a full-width .wrap. The site's convention is a CONSTANT
   left edge — sections keep the default .wrap and constrain the text instead.
   Overriding .wrap max-width per section made the column jump 43px -> 223px ->
   193px as you scrolled the Naperville page. */
.prose{max-width:760px;}
.prose .lede{max-width:660px;}

/* ==========================================================================
   GALLERY (.gal) — shared. This used to live only inside portfolio/index.html,
   so any other page using .portfolio-grid got NO styling and the photographs
   stacked at full page width.
   Cells are 3:2 because the delivered files are 3:2 (900x600) — matching the
   grid to the source means object-fit:cover crops exactly nothing. A square
   or 4:3 cell would silently trim every frame.
   ========================================================================== */
.gal{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;}
.gal a,.gal figure{margin:0;display:block;}
/* height:auto is load-bearing. An image's height attribute lands as a
   presentational hint, and aspect-ratio only fills in a dimension that is NOT
   otherwise specified — so with width:100% and that height in play,
   aspect-ratio is silently ignored and every frame renders 365x600 portrait. */
.gal img{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;border-radius:3px;background:var(--paper-deep);}
/* One frame may lead at double width — it stays 3:2, so it simply reads larger
   rather than being cropped into a different shape. */
.gal .lead{grid-column:span 2;}
.gal .lead img{aspect-ratio:3/2;}
@media (max-width:900px){
  .gal{grid-template-columns:repeat(2,1fr);}
  .gal .lead{grid-column:span 2;}
}
@media (max-width:560px){
  .gal{grid-template-columns:1fr;gap:8px;}
  .gal .lead{grid-column:span 1;}
}
.gal-cap{font-family:var(--fm);font-size:11.5px;letter-spacing:.04em;color:var(--ink-soft);
  margin:10px 0 0;line-height:1.55;}

/* ---- current page in the nav ----
   Applied from shared.js via aria-current, never by hand. The dropdown variant
   keeps the hover background permanently so the current item reads as a chip
   rather than only differing by color, which alone would be too subtle at
   14px on a dark bar. */
.nav-links a[aria-current="page"]{color:var(--gold);}
.nav-menu a[aria-current="page"]{
  background:rgba(201,162,114,.16);color:var(--gold);
  box-shadow:inset 0 0 0 1px rgba(201,162,114,.34);
}
.nav-menu a[aria-current="page"]:hover{background:rgba(201,162,114,.24);color:var(--paper);}
.nav-top[data-current="true"]{color:var(--gold);}

/* ---- reveal panel (booking embeds) ----
   A keyframe, not a transition: a declared transition on a property changed by
   a class has failed repeatedly in this codebase (background, grid-template-rows,
   max-height all documented). A keyframe's final frame IS the resting state, so
   there is nothing to lose a race with. */
.reveal-panel{display:none;}
.reveal-panel.on{display:block;animation:revealIn .5s cubic-bezier(.2,.6,.3,1) both;}
@keyframes revealIn{
  from{opacity:0;transform:translateY(14px);}
  to  {opacity:1;transform:translateY(0);}
}
.reveal-panel .cal-frame{min-width:320px;height:700px;border:1px solid var(--line);border-radius:3px;
  background:var(--paper-deep);}
.reveal-loading{font-family:var(--fm);font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);padding:26px 0;}
@media (prefers-reduced-motion:reduce){.reveal-panel.on{animation:none;}}

/* ---- homepage hero copy ----
   The bottom padding was clamp(96px,15vh,168px) inline. The 15vh is fine on a
   tall desktop hero and wrong on a phone: at a 715px viewport it resolves to
   107px inside a 631px hero, and since the block is bottom-anchored
   (justify-content:flex-end) that padding pushes the text UP, leaving it tight
   under the header with a dead strip of photograph beneath the buttons.
   Smaller bottom padding here moves the copy DOWN, which also seats it on the
   darkest part of the scrim where it reads best. */
.hero-copy{position:relative;z-index:2;padding:0 32px clamp(96px,15vh,168px);color:var(--paper);}
@media (max-width:700px){
  /* padding-top is a guard, not spacing: on a short phone the copy is taller
     than the viewport allows and would otherwise sit flush against the header.
     Safe because the section is min-height, so it grows rather than clipping. */
  .hero-copy{padding:28px 24px 60px;}
}

/* ---- homepage hero ----
   min-height, NOT height. With a fixed height and justify-content:flex-end,
   content taller than the box overflows out of the TOP and overflow:hidden
   clips it — measured at 360x600 the H1 was cut off 56px behind the header.
   max() keeps the full-viewport look on normal screens and a 520px floor on
   very short ones, while letting the section grow when the copy needs it. */
.hero-photo{
  position:relative;min-height:max(520px, calc(100vh - 84px));
  min-height:max(520px, calc(100svh - 84px));
  display:flex;flex-direction:column;justify-content:flex-end;
  background:#000;overflow:hidden;padding:0;
}

/* ---- tier badge ----
   The recommendation reads as a highlighted pill BESIDE the tier name, never on
   a line above it. Above the name it changes the card's height whenever the
   recommendation moves, and the calculator's own tier rows already did it this
   way — this makes the static cards match. */
.tier-card .tag .badge{
  /* 11px, not 10: functional text below that is a legibility failure on
     high-DPI and small viewports, and the calculator's own .tier-badge is
     already 11px — so this matches it rather than inventing a smaller one. */
  display:inline-block;font-family:var(--fm);font-size:11px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink);background:var(--gold);
  padding:3px 9px;border-radius:999px;margin-left:9px;vertical-align:middle;
  position:relative;top:-1px;white-space:nowrap;
}

/* The tile already chose house or condo; the panel does not ask again. */
.x-type-locked{display:none!important;}

/* Area sits with the price inputs. Two wide labels, so it wraps on a phone
   rather than squeezing the county list to nothing. */
.x-area .lab{display:block;margin-bottom:8px;}
/* Layout only — the appearance lives in express.css beside .x-type, the control
   this one has to match. Kept joined rather than gapped so it reads as one
   segmented switch. */
.x-areaseg button{flex:1 1 auto;min-width:0;white-space:nowrap;}
@media(max-width:520px){
  .x-areaseg{flex-direction:column;border-radius:8px;}
  .x-areaseg button{flex:1 1 100%;white-space:normal;text-align:center;}
  .x-areaseg button + button{border-left:0;border-top:1px solid var(--x-line,var(--line));}
}

.x-quote{margin:22px 0 0;padding:22px 20px;background:var(--paper-deep);
  border:1px solid var(--line);border-radius:var(--radius);text-align:center;}
.x-quote.is-done{opacity:.72;}
.xq-copy{font-family:var(--ff);font-size:14.5px;line-height:1.6;color:var(--ink-soft);
  margin:0 auto 14px;max-width:44ch;}
.xq-copy strong{color:var(--ink);}
.xq-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;
  max-width:460px;margin:0 auto;}
.xq-row input{flex:1 1 220px;min-width:0;font:inherit;font-size:15px;padding:12px 18px;
  border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--ink);
  text-align:center;}
.xq-row input:focus{outline:2px solid var(--gold);outline-offset:1px;border-color:var(--gold-deep);}
.xq-row button{flex:none;}
.xq-row button[disabled]{opacity:.55;cursor:default;}
.xq-msg{font-family:var(--fm);font-size:11.5px;letter-spacing:.04em;margin-top:10px;min-height:1em;
  color:var(--ink-soft);}
.xq-msg.ok{color:var(--gold-deep);}
.xq-msg.err{color:#8d3b2f;}
@media(max-width:520px){.xq-row button{width:100%;}}

/* The express summary card sits on a light panel inside a dark step. */
.x-sum .nq-open,.x-sum 
.x-sum

/* ===== email-this-quote =========================================== v2
   PAIRED BLOCK — an identical copy lives in the other file. calculator.html is
   iframed, so it cannot inherit shared.css; both copies must be changed
   together. Generated by tools/sync-nq.py — edit there, not here.
   Canonical copy strings:
     trigger  Email this exact quote &rarr;
     lede     Not booking today? Send yourself this quote so you have the number in writing.
     button   Email it
================================================================== */
.nq-open{display:block;width:100%;font-family:var(--fm);font-size:12px;letter-spacing:.04em;
  color:var(--gold-deep);background:none;border:none;border-top:1px solid var(--line);
  padding:15px 0 18px;margin-top:14px;cursor:pointer;text-align:left;}
.nq-open:hover{color:var(--ink);}
.nq{margin-top:14px;padding:15px 0 18px;border-top:1px solid var(--line);}
.nq-copy{font-family:var(--fm);font-size:11px;letter-spacing:.04em;color:var(--ink-soft);
  margin-bottom:10px;line-height:1.5;}
.nq-row{display:flex;gap:8px;flex-wrap:wrap;}
.nq-row input{flex:1 1 150px;min-width:0;font:inherit;font-size:14px;padding:10px 14px;
  border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--ink);}
.nq-row input:focus{outline:2px solid var(--gold);outline-offset:1px;border-color:var(--gold-deep);}
.nq-btn{flex:none;font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  padding:11px 16px;border-radius:999px;border:1px solid var(--ink);background:none;
  color:var(--ink);cursor:pointer;}
.nq-btn:hover{background:var(--ink);color:var(--paper);}
.nq-btn[disabled]{opacity:.5;cursor:default;}
.nq-msg{font-family:var(--fm);font-size:10.5px;letter-spacing:.03em;margin-top:10px;color:var(--ink-soft);}
.nq-msg.ok{color:var(--gold-deep);} .nq-msg.err{color:#8d3b2f;}
/* ===== end email-this-quote ======================================= */

/* ---- horizontal-overflow guards -------------------------------------------
   A single unbreakable string (a code token, a URL, a long slug) is enough to
   widen the whole layout and let a phone pan sideways — and because a fixed
   bar resolves left:0/right:0 against that widened box, the visible symptom
   shows up somewhere else entirely. Break long strings at the source.
   (2026-08-25: a `?product=land|tour|aerial|twilight|video` token in the canon
   did exactly this.) */
code,pre,kbd,samp{overflow-wrap:anywhere;}
pre{overflow-x:auto;max-width:100%;}
img,svg,video,canvas,iframe{max-width:100%;}

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